pax_global_header00006660000000000000000000000064145033254260014516gustar00rootroot0000000000000052 comment=dd495e233a1574ee0a1c61231e6e3bf71b5e49cf OSCAR-code-v1.5.1/000077500000000000000000000000001450332542600134675ustar00rootroot00000000000000OSCAR-code-v1.5.1/.gitignore000066400000000000000000000007251450332542600154630ustar00rootroot00000000000000# 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.5.1/Building/000077500000000000000000000000001450332542600152245ustar00rootroot00000000000000OSCAR-code-v1.5.1/Building/Icons/000077500000000000000000000000001450332542600162775ustar00rootroot00000000000000OSCAR-code-v1.5.1/Building/Icons/CopyIcons.bat000066400000000000000000000004271450332542600207000ustar00rootroot00000000000000@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.5.1/Building/Icons/Empty-1079.png000066400000000000000000002071341450332542600205100ustar00rootroot00000000000000PNG  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.5.1/Building/Icons/Empty-16.png000066400000000000000000000064771450332542600203450ustar00rootroot00000000000000PNG  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.5.1/Building/Icons/Empty-24.png000066400000000000000000000072661450332542600203410ustar00rootroot00000000000000PNG  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.5.1/Building/Icons/Full-100.png000066400000000000000000000162171450332542600202140ustar00rootroot00000000000000PNG  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.5.1/Building/Icons/Full-1024.png000066400000000000000000002731451450332542600203070ustar00rootroot00000000000000PNG  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.5.1/Building/Icons/Full-1079.png000066400000000000000000003715401450332542600203170ustar00rootroot00000000000000PNG  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.5.1/Building/Icons/Full-150.png000066400000000000000000000254251450332542600202220ustar00rootroot00000000000000PNG  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.5.1/Building/Icons/Full-256.png000066400000000000000000000517101450332542600202250ustar00rootroot00000000000000PNG  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.5.1/Building/Icons/Full-32.png000066400000000000000000000103701450332542600201320ustar00rootroot00000000000000PNG  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.5.1/Building/Icons/Full-512.png000066400000000000000000001252271450332542600202250ustar00rootroot00000000000000PNG  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.5.1/Building/Icons/Full-64.png000066400000000000000000000154501450332542600201430ustar00rootroot00000000000000PNG  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.5.1/Building/Icons/OSCAR.icns000066400000000000000000010351611450332542600200330ustar00rootroot00000000000000icns: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.5.1/Building/Icons/Oscar-100.png000066400000000000000000000230641450332542600203570ustar00rootroot00000000000000PNG  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.5.1/Building/Icons/Oscar-1079.png000066400000000000000000002720101450332542600204540ustar00rootroot00000000000000PNG  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.5.1/Building/Icons/Photoshop Notes.xlsm000066400000000000000000000270451450332542600222500ustar00rootroot00000000000000PK!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.5.1/Building/Icons/README000066400000000000000000000035771450332542600171730ustar00rootroot00000000000000All 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.5.1/Building/Icons/Wave-100.png000066400000000000000000000227451450332542600202170ustar00rootroot00000000000000PNG  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.5.1/Building/Icons/Wave-1079.png000066400000000000000000003330371450332542600203160ustar00rootroot00000000000000PNG  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.5.1/Building/Icons/Wave-24.png000066400000000000000000000071231450332542600201350ustar00rootroot00000000000000PNG  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.5.1/Building/Icons/Wave-32.png000066400000000000000000000104361450332542600201350ustar00rootroot00000000000000PNG  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.5.1/Building/Icons/Wave-48.png000066400000000000000000000134051450332542600201430ustar00rootroot00000000000000PNG  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.5.1/Building/Icons/Wave-64.png000066400000000000000000000155531450332542600201470ustar00rootroot00000000000000PNG  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.5.1/Building/Icons/logo.ico000066400000000000000000001062411450332542600177370ustar00rootroot00000000000000 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.5.1/Building/Linux/000077500000000000000000000000001450332542600163235ustar00rootroot00000000000000OSCAR-code-v1.5.1/Building/Linux/BUILD_Linux.md000066400000000000000000000023531450332542600206660ustar00rootroot00000000000000You 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.5.1/Building/Linux/OSCAR-test.desktop000066400000000000000000000004501450332542600215410ustar00rootroot00000000000000[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.5.1/Building/Linux/OSCAR-test.png000066400000000000000000000064501450332542600206620ustar00rootroot00000000000000PNG  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.5.1/Building/Linux/OSCAR.desktop000066400000000000000000000004211450332542600205620ustar00rootroot00000000000000[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.5.1/Building/Linux/OSCAR.png000066400000000000000000000064501450332542600177050ustar00rootroot00000000000000PNG  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.5.1/Building/Linux/README.first000066400000000000000000000030271450332542600203330ustar00rootroot00000000000000The 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.5.1/Building/Linux/clean_rm-NN-test1.sh000077500000000000000000000002331450332542600220070ustar00rootroot00000000000000#! /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.5.1/Building/Linux/clean_rm-NN-test2.sh000077500000000000000000000001561450332542600220140ustar00rootroot00000000000000 # # delete all the folder not deleted by the purge command # now, application name appli_name="OSCAR-test" OSCAR-code-v1.5.1/Building/Linux/clean_rm-NN.sh000077500000000000000000000001671450332542600207570ustar00rootroot00000000000000#! /bin/bash set -e # # delete all the folder not deleted by the purge command # application name appli_name="OSCAR" OSCAR-code-v1.5.1/Building/Linux/clean_rm-common-NN.old000077500000000000000000000004241450332542600224050ustar00rootroot00000000000000 # 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.5.1/Building/Linux/clean_rm-common-NN.sh000077500000000000000000000005261450332542600222440ustar00rootroot00000000000000 # begin common script shell # delete all the folder not deleted by the purge command echo "appli_name=${appli_name}" list=$(find /usr/share -name ${appli_name} 2>/dev/null) for folder in $list do if [ $folder != '/usr/share/OSCAR' ] then echo "to delete : '$folder'" rm -r $folder fi done # end common script shell OSCAR-code-v1.5.1/Building/Linux/clean_rm-test.xx000077500000000000000000000010251450332542600214420ustar00rootroot00000000000000#! /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.5.1/Building/Linux/clean_rm.xx000077500000000000000000000004301450332542600204640ustar00rootroot00000000000000#! /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.5.1/Building/Linux/copyright000066400000000000000000000020071450332542600202550ustar00rootroot00000000000000Format: 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.5.1/Building/Linux/ln_usrbin-NN-test.sh000077500000000000000000000002451450332542600221440ustar00rootroot00000000000000#! /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.5.1/Building/Linux/ln_usrbin-NN.sh000077500000000000000000000002401450332542600211620ustar00rootroot00000000000000#! /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.5.1/Building/Linux/ln_usrbin-common-NN.sh000077500000000000000000000024771450332542600224660ustar00rootroot00000000000000# 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.5.1/Building/Linux/ln_usrbin-test.xx000077500000000000000000000046101450332542600216600ustar00rootroot00000000000000#! /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.5.1/Building/Linux/ln_usrbin.xx000077500000000000000000000046601450332542600207100ustar00rootroot00000000000000#! /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.5.1/Building/Linux/mkDistDeb.xx000077500000000000000000000145231450332542600205620ustar00rootroot00000000000000#! /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.5.1/Building/Linux/mkOSDistDeb-NN.sh000077500000000000000000000203671450332542600213130ustar00rootroot00000000000000#! /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.5.1/Building/Linux/mkOSDistDeb.xx000077500000000000000000000154671450332542600210340ustar00rootroot00000000000000#! /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.5.1/Building/Linux/mkRedHat.sh000077500000000000000000000077101450332542600203660ustar00rootroot00000000000000#! /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.5.1/Building/Linux/rm_usrbin-NN-test.sh000077500000000000000000000001341450332542600221460ustar00rootroot00000000000000#! /bin/bash # # no error is permitted set -e # application name appli_name="OSCAR-test" OSCAR-code-v1.5.1/Building/Linux/rm_usrbin-NN.sh000077500000000000000000000001311450332542600211660ustar00rootroot00000000000000#! /bin/bash # # no error is permitted set -e # application name appli_name="OSCAR" OSCAR-code-v1.5.1/Building/Linux/rm_usrbin-common-NN.sh000077500000000000000000000043701450332542600224650ustar00rootroot00000000000000 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.5.1/Building/Linux/rm_usrbin-test.xx000077500000000000000000000020231450332542600216610ustar00rootroot00000000000000#! /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.5.1/Building/Linux/rm_usrbin.xx000077500000000000000000000051421450332542600207110ustar00rootroot00000000000000#! /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.5.1/Building/Linux/rpm/000077500000000000000000000000001450332542600171215ustar00rootroot00000000000000OSCAR-code-v1.5.1/Building/Linux/rpm/.gitignore000066400000000000000000000001001450332542600211000ustar00rootroot00000000000000.ccache # This is ignored in a parent directory !Makefile* *~ OSCAR-code-v1.5.1/Building/Linux/rpm/Makefile000066400000000000000000000011531450332542600205610ustar00rootroot00000000000000# 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.5.1/Building/Linux/rpm/Makefile-stage2000066400000000000000000000007061450332542600217470ustar00rootroot00000000000000# # 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.5.1/Building/Linux/rpm/Makefile.common000066400000000000000000000002721450332542600220510ustar00rootroot00000000000000# # 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.5.1/Building/Linux/rpm/OSCAR.spec.raw000066400000000000000000000036061450332542600214410ustar00rootroot00000000000000# # 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.5.1/Building/Linux/rpm/README.md000066400000000000000000000006001450332542600203740ustar00rootroot00000000000000# 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.5.1/Building/Linux/rpm/generic-rpm.make000066400000000000000000000112741450332542600221750ustar00rootroot00000000000000# # 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.5.1/Building/Linux/tst_user.sh000077500000000000000000000004501450332542600205310ustar00rootroot00000000000000#! /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.5.1/Building/MacOS/000077500000000000000000000000001450332542600161665ustar00rootroot00000000000000OSCAR-code-v1.5.1/Building/MacOS/BUILD-mac.md000066400000000000000000000104561450332542600201130ustar00rootroot00000000000000# 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.5.1/Building/MacOS/Info.plist.in000066400000000000000000000017171450332542600205510ustar00rootroot00000000000000 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.5.1/Building/MacOS/README.rtfd/000077500000000000000000000000001450332542600200615ustar00rootroot00000000000000OSCAR-code-v1.5.1/Building/MacOS/README.rtfd/Oscar-mac-10-catalina-fix-permissions.jpg000066400000000000000000001757521450332542600275360ustar00rootroot00000000000000JFIFHHExifMM*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.5.1/Building/MacOS/README.rtfd/Oscar-mac-4-click.jpg000066400000000000000000001175411450332542600236250ustar00rootroot00000000000000JFIFHHExifMM*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.5.1/Building/MacOS/README.rtfd/Oscar-mac-5-drag.jpg000066400000000000000000001322311450332542600234470ustar00rootroot00000000000000JFIFHHExifMM*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.5.1/Building/MacOS/README.rtfd/Oscar-mac-6-right-click-open.jpg000066400000000000000000002777161450332542600257140ustar00rootroot00000000000000JFIFHHExifMM*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.5.1/Building/MacOS/README.rtfd/Oscar-mac-7b-catalina-permissions.jpg000066400000000000000000000420351450332542600270250ustar00rootroot00000000000000JFIFHHExifMM*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.5.1/Building/MacOS/README.rtfd/Oscar-mac-9-catalina-open-1.jpg000066400000000000000000000612361450332542600254150ustar00rootroot00000000000000JFIFHHExifMM*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.5.1/Building/MacOS/README.rtfd/TXT.rtf000066400000000000000000000172611450332542600212640ustar00rootroot00000000000000{\rtf1\ansi\ansicpg1252\cocoartf2639 \cocoatextscaling0\cocoaplatform0{\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.13 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.5.1/Building/MacOS/background.png000066400000000000000000000526611450332542600210250ustar00rootroot00000000000000PNG  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.5.1/Building/MacOS/create_dmg000077500000000000000000000076671450332542600202260ustar00rootroot00000000000000#!/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.5.1/Building/MacOS/finalize_plist000077500000000000000000000017351450332542600211360ustar00rootroot00000000000000#!/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.5.1/Building/Windows/000077500000000000000000000000001450332542600166565ustar00rootroot00000000000000OSCAR-code-v1.5.1/Building/Windows/BUILD-WIN.md000066400000000000000000000303361450332542600204770ustar00rootroot00000000000000Creating 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. The deploy.bat file is required to build release and install versions of Oscar using QtCreator or with buildall.bat. The buildall.bat compiles , builds Oscar and calls deploy.bat to create release and install version of Oscar. There are three sections to this documentation. 1) Required Programs 2) Start Developing using batch files 3) Start Developing using QtCreator ## 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. - Gawk is required. You can use the version included with Git for Windows or install Gawk for Windows from . The deployment batch file will use the Git for Windows version if gawk.exe is not in 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 Gawk (if Git for Windows’ gawk is not used)** From , download setup for “Complete package, except sources”. When downloaded, run the setup program. Accept default options and location. The deployment batch file assumes that gawk.exe is in your PATH, so either add c:\\Program Files (x86)\\gnuwin32\\bin to your PATH or copy the executables to some other directory already in your PATH. **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). ## Start Developing using batch files - Requires Qt , Git and Inno setup described in above section. - Batch files buildall.bat and deploy.bat are used. - buildall.bat creates a build folder, compiles and executes deploy.bat - Supports both 32 and 64 bit version with an option to build brokenGl - Supports Qt 5.9.x 5.12.x 5.15.x - Auto detection for which compiler to use. - buildall.bat supports command Line options - deploy.bat creates a release version and an install version. - deploy.bat is also used by QtCreator - The release folder contains OSCAR.exe and all other files necessary to run OSCAR - The install folder contains the installable version of OSCAR...exe - Lists the release and install versions of OSCAR in the build folder. #### Validate the installed software. - Verify Qt - \ will be in the form N.N.N example 5.15.2 - For example: if Qt is installed at D:\\Qt then - The \ must contain the following folders: \ Tools - - \ is D:\\Qt - Verify Git and Inno are installed - Note: Inno is used to create the Install version of OSCAR - Git for windows provides GAWK. #### Examples use the following assumptions Except for Inno, Git, Qt, and OSCAR may be located in different locations. - Inno installed: - "C:\\Program Files (x86)\\Inno Setup 6" - Git installed: - "C:\\Program Files\\Git" - Qtinstalled: - "D:\\Qt" - OSCAR installed: - "D:\\OSCAR" #### Building Commands - Build install version for OSCAR for 32 and 64 bit versions - D:\\OSCAR\OSCAR-code\\Building\\Windows\buildall.bat D:\\Qt - Build install version for OSCAR for 64 bit version - D:\\OSCAR\OSCAR-code\\Building\\Windows\buildall.bat D:\\Qt 64 - Build Just release version for OSCAR for 64 bit version - D:\\OSCAR\OSCAR-code\\Building\\Windows\buildall.bat D:\\Qt 64 skipInstall - Build release version for OSCAR for 64 bit version - without deleting build folder first - D:\\OSCAR\OSCAR-code\\Building\\Windows\buildall.bat D:\\Qt 64 skipInstall remake - The current folder is not used by the buildall.bat - There is a pause when the build completes. - This insure that the user has a chance to read the build results. - Allows using windows shortcuts to build OSCAR and see results. Note: The default folder of Qt is C:\\Qt If the Qt is located at the default folder then the \ is not required as a command line option. #### Windows Shortcuts - Windows shortcuts can be used to execute the build commands or run OSCAR. - Create shortcut to buildall.bat - rename shortcut to indicate its function - edit the short cut property and add options to the Target - Create a shortcut to release version of OSCAR.exe - For offical OSCAR release should not use remake or skipInsall options - Should add skipInstall options for developement, testing, verification - Suggestion is to create the following shortcut example. - use options \ 64 skipInstall - - shortcut name: "OSCAR Fresh build" - use options \ 64 skipInstall remake - - shortcut name: "OSCAR quick rebuild" - Create Shortcut to release version of OSCAR.exe (not the install version) - - shortcut name: "RUN OSCAR" #### Buildall.bat options. - A full list of options can be displayed using the help option. **32** Build 32 bit versions **64** Build 64 bit versions 32 and 64 bit version are built if both 32 and 64 are used or both not used **brokenGL** (special option) to build brokenGL version **make** The default option. removes and re-creates the build folder. **remake** Execute Make in the existing build folder. Does not re-create the Makefile. Should not be used for Offical OSCAR release **skipInstall** skips creating the Install version saving both time and disk space **skipDeploy** skips executing the deploy.bat script. just compiles oscar. There is a pause when the build completes. This insure that the user has a chance to read the build results. This also allows building using windows shortcuts. 1) create a shortcut to buildall.bat edit shortcut's property add the necessary options: D:\\Qt 64 remake skipInstall 2) create and shortcut for the make option. 3) create a shortcut to the **release** version of OSCAR.exe ## Start 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.5.1/Building/Windows/BuildInstall.iss000066400000000000000000000130111450332542600217600ustar00rootroot00000000000000; 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.5.1/Building/Windows/Setup.ico000066400000000000000000000236261450332542600204630ustar00rootroot00000000000000(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.5.1/Building/Windows/Use Legacy Graphics.reg000066400000000000000000000003441450332542600230200ustar00rootroot00000000000000Windows Registry Editor Version 5.00 [HKEY_CURRENT_USER\Software\OSCAR_Team\OSCAR] "GFXEngine"=dword:00000002OSCAR-code-v1.5.1/Building/Windows/buildall-515.bat000066400000000000000000000032411450332542600214460ustar00rootroot00000000000000setlocal :::@echo off ::: You must set these paths to your QT configuration set qtpath=C:\Qt set qtVersion=5.15.2 ::: This file has been updated to work with Qt 5.15.2 and mingw 8.1.0 ::: ::: 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%\mingw81_%1 echo QTDIR is %qtdir% set PATH=%qtpath%\Tools\mingw810_%1\bin;%qtpath%\%qtVersion%\mingw81_%1\bin;%PATH% ::: echo %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%\mingw81_%1\bin\qmake.exe ..\oscar\oscar.pro -spec win32-g++ %extraparams% >qmake.log 2>&1 && %qtpath%\Tools\mingw810_%1\bin\mingw32-make.exe qmake_all >>qmake.log 2>&1 %qtpath%\Tools\mingw810_%1\bin\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.5.1/Building/Windows/buildall.bat000066400000000000000000000260071450332542600211430ustar00rootroot00000000000000@echo off setlocal :: todo add help / descriptions ::: This script assumes that it resides OSCAR-data/Building/Windows ::: You must defined the path to QT configuration or passit as a parameter ::: The version is detected based on the path ::: The compiler to use is based on the qt verson. ::: @echo off ::: CONFIGURATION SECTION set QtPath=C:\Qt ::: END CONFIGURATION SECTION :::============================================ ::: ::: START INITIALIZATIO SECTION echo Command line: %0 %* call :parse %* if NOT %ERRORLEVEL%==0 goto :endProgram set buildErrorLevel=0 ::: basedir is OSCAR-code folder - contains folders Building , oscar , ... ::: parentdir is the folder that contains OSCAR-code. ::: change relative paths into absolute paths ::: Get path to this script. then cd parent folder. %cd% retunrs absolute pathname. cd %~dp0 & cd ../../../ set parentdir=%cd% set basedir=%parentdir%\OSCAR-code IF NOT "%basedir%"=="%basedir: =%" ( call :configError absolute path of OSCAR-code contains a space - not handled by Qt. echo %basedir% goto :endProgram) IF NOT exist "%basedir%\Building" ( call :notfound2 OSCAR-code: "%basedir%" goto :endProgram) set baseBuildName=%basedir%\build-oscar-win set OSCARPRO=%basedir%\oscar\oscar.pro set requiredOutSideOscar= IF %doOutSide%==1 ( set baseBuildName=%parentdir%\build-oscar-win set OSCARPRO=%basedir%\OSCAR_QT.pro set requiredOutSideOscar=oscar\ ) ::: Construct name of our build directory ::: Build Directory can be based on the directories the QtCreator uses for either OSCAR_QT.pro or oscar.pro ::: For OSCAR_QT.pro is at parentDir\OSCAR-code\OCASR_QT.pro the build will be at parentDir\BuildDir ::: For oscar.pro is at OSCAR-code\oscar\oscar.pro the build will be at OSCAR-code\BuildDir :: check if QtPath from parameters list set defaultQtPath=%QtPath% if NOT "%requestQtPath%"=="" (set QtPath=%requestQtPath%) :: check valid qt folder to find version and tool folders IF NOT exist %QtPath%\nul ( call :configError InvalidPath to Qt. QtPath:%QtPath% echo The QtPath can be added as a parameter to %~nx0 call :helpInfo goto :endProgram ) set foundQtVersion= set foundQtToolsDir= set foundQtVersionDir= for /d %%i in (%QtPath%\*) do (call :parseQtVersion %%i) if "%foundQtVersion%"=="" ( echo :configError %QtPath% is not a valid Qt folder. Must contain version 5.xx.xx or 6.xx.xx folder echo Please enter a valid Qt Path as a parameter call :helpInfo goto :endProgram ) if "%foundQtToolsDir%"=="" ( . echo :configError %QtPath% is not a valid Qt folder. Must contain a Tools folder echo Please enter a valid Qt Path as a parameter call :helpInfo goto :endProgram ) echo Using QtVersion %foundQtVersion% set qtVersion=%foundQtVersion% set QtVersionDir=%QtPath%\%qtVersion% set QtToolsDir=%QtPath%\Tools ::: Compiler Tools for creating Makefile (linux qmake features) if "5.15."=="%qtVersion:~0,5%" ( :: For qt release 5.15 set mingwQmakeVersion=mingw81 set mingwQmakeTools=mingw810 ) else ( :: For qt previous releases set mingwQmakeVersion=mingw73 set mingwQmakeTools=mingw730 ) ::: END INITIALIZATIO SECTION :::============================================ :main ::: Start BUILDING ON WINDOWS call :startTimer "Start Time is" if %do32%==1 ( call :buildone 32 || goto :finished if %doBrokenGl%==1 ( call :elapseTime "Elapse Time is" call :buildone 32 brokengl || goto :finished ) ) if %do64%==1 ( if %do32%==1 (call :elapseTime "Elapse Time is") call :buildone 64 || goto :finished if %doBrokenGl%==1 ( call :elapseTime "Elapse Time is" call :buildone 64 brokengl || goto :finished ) ) :finished :: this does work. dir %baseBuildName%*_bit\Installer\OSCAR*.exe echo Start Time is %saveStartTime% call :endTimer "End Time is" FOR /D %%j in ("%baseBuildName%*") do (call :sub1 %%j) goto :endProgram ::: return to caller :endProgram pause exit /b :::============================================ :sub1 FOR /F %%i in ("%1\%requiredOutSideOscar%Installer\") do (call :sub2 %%i ) FOR /F %%i in ("%1\%requiredOutSideOscar%Release\") do (call :sub2 %%i ) goto :eof :sub2 :: echo PLACE 2 %1 FOR %%k in ("%1*.exe") do (call :sub3 %%k ) goto :eof :sub3 echo %~t1 %1 goto :eof :parseQtVersion set tmpf=%~nx1 if "5."=="%tmpf:~0,2%" call :gotVersion %1 %tmpf% & goto :eof if "6."=="%tmpf:~0,2%" call :gotVersion %1 %tmpf% & goto :eof if "Tools"=="%tmpf%" call :gotTools %1 %tmpf% & goto :eof goto :eof :gotTools :: if NOT "%foundQtToolsDir%"=="" (echo Duplicate Qt Versions %1 and %foundQtToolsDir% Error & exit /b 101) set foundQtToolsDir=%1 :: echo Found QtTools %1 goto :eof :gotVersion :: if NOT "%foundQtVersion%"=="" (echo Duplicate Qt Versions %1 and %foundQtVersion% Error & exit /b 101) set foundQtVersion=%2 set foundQtVersionDir=%1 :: echo Found QtVersion %foundQtVersion% goto :eof :::============================================ :parse set do32=0 set do64=0 set doBrokenGl=0 set doOutSide=1 set useExistingBuild=0 set skipCompile=0 set skipDeploy=0 set requestQtPath= set toggleEcho="off" set skipInstallCommand= :parseLoop IF "%1"=="" GOTO :exitParseLoop set arg=%1 ::: echo ^<%arg%^> IF "%arg%"=="32" (set do32=1 & GOTO :endLoop ) IF "%arg%"=="64" (set do64=1 & GOTO :endLoop ) IF /I "%arg:~0,2%"=="br" (set doBrokenGl=1 & GOTO :endLoop ) IF /I "%arg:~0,5%"=="SKIPD" (set skipDeploy=1 & GOTO :endLoop ) IF /I "%arg:~0,5%"=="SKIPC" ( set useExistingBuild=1 set skipCompile=1 GOTO :endLoop ) IF /I "%arg:~0,2%"=="OU" (set doOutSide=1 & GOTO :endLoop ) echo windows cmd.exe workaround 1>nul IF /I "%arg:~0,5%"=="SKIPI" ( set skipInstallCommand=skipInstall GOTO :endLoop ) IF /I "%arg:~0,2%"=="re" (set useExistingBuild=1 & GOTO :endLoop ) IF /I "%arg:~0,2%"=="ve" ( echo on & GOTO :endLoop ) IF exist %arg%\Tools\nul IF exist %arg%\Licenses\nul ( set requestQtPath=%arg% GOTO :endLoop) IF NOT "%arg:~0,2%"=="he" ( echo _ echo Invalid argument '%arg%' ) else ( echo _ ) call :helpInfo exit /b 123 :endLoop SHIFT GOTO :parseLoop :exitParseLoop :: echo requestQtPath ^<%requestQtPath%^> set /a sum=%do32%+%do64% if %sum% == 0 (set do32=1 & set do64=1 ) goto :eof :::============================================ ::: Timer functions ::: Allows personalization of timer functions. ::: defaults displaying the curr ent time ::: Could Use Timer 4.0 - Command Line Timer - www.Gammadyne.com - to show time it takes to compile. :startTimer set saveStartTime=%TIME% :: timer.exe /nologo echo %~1 %saveStartTime% goto :eof :elapseTime :: timer.exe /r /nologo echo %~1 %TIME% goto :eof :endTimer :: timer.exe /s /et /nologo echo %~1 %TIME% goto :eof :::=============================================================================== :configError set buildErrorLevel=24 echo *** CONFIGURATION ERROR *** echo %* goto :eof :notfound echo *** CONFIGURATION ERROR *** set buildErrorLevel=25 echo NotFound %* goto :eof :::=============================================================================== :: Subroutine to "build one" version :buildOne setLocal set bitSize=%1 set brokenGL=%2 echo Building %1 %2 ::: echo do not remove this line - batch bug? 1>nul::: set QtVersionCompilerDir=%qtVersionDir%\%mingwQmakeVersion%_%bitSize% if NOT exist %QtVersionCompilerDir%\nul ( call :buildOneError 21 notfound QtCompiler: %QtVersionCompilerDir% goto :endBuildOne ) set QtToolsCompilerDir=%QtToolsDir%\%mingwQmakeTools%_%bitSize% if NOT exist %QtToolsCompilerDir% ( call :buildOneError 22 notfound QTToolCompiler: %QtToolsCompilerDir% goto :endBuildOne ) set path=%QtToolsCompilerDir%\bin;%QtVersionCompilerDir%\bin;%PATH% :: echo =================================== :: echo %path% :: echo =================================== :::goto :eof ::: Verify all configuration parameters set savedir=%cd% set dirname=%baseBuildName% if "%brokenGL%"=="brokengl" ( set dirname=%dirname%-LegacyGFX set extraparams=DEFINES+=BrokenGL ) set buildDir=%dirname%_%bitSize%_bit :: allow rebuild without removing build when a parameter is "SKIP" if NOT exist %buildDir%\nul goto :makeBuildDir if %useExistingBuild%==1 goto :skipBuildDir rmdir /s /q %buildDir% IF NOT %ERRORLEVEL%==0 ( call :buildOneError %ERRORLEVEL% error removing Build Folder: %buildDir% GOTO :endBuildOne ) :makeBuildDir mkdir %buildDir% || ( call :buildOneError 23 mkdir %buildDir% failed %ERRORLEVEL% goto :endBuildOne ) echo created %buildDir% :skipBuildDir cd %buildDir% if %skipCompile%==1 goto :doDeploy if NOT exist %buildDir%\Makefile goto :createMakefile if %useExistingBuild%==1 goto :skipMakefile if NOT exist %OSCARPRO% { call :buildOneError 24 notfound oscar.pro is not found. goto :endBuildOne) :createMakefile echo Creating Oscar's Makefile %QtVersionCompilerDir%\bin\qmake.exe %OSCARPRO% -spec win32-g++ %extraparams% >qmake.log 2>&1 || ( call :buildOneError 25 Failed to create Makefile part1 type qmake.log goto :endBuildOne) %QtToolsCompilerDir%/bin/mingw32-make.exe qmake_all >>qmake.log 2>&1 || ( call :buildOneError 26 Failed to create Makefile part2 type qmake.log goto :endBuildOne) :skipMakefile :: Always compile build echo Compiling Oscar mingw32-make.exe -j8 >make.log 2>&1 || ( call :buildOneError 27 Make Failed type qmake.log goto :endbuildOne ) ::: echo skipDeploy: %skipDeploy% :doDeploy if %skipDeploy%==1 goto :endBuildOne echo Deploying and Creating Installation Exec in %cd% call "%~dp0\deploy.bat" %skipInstallCommand% set buildErrorLevel=%ERRORLEVEL% if %buildErrorLevel% == 0 ( echo === MAKE %bitSize% %brokenGL% SUCCESSFUL === goto :endBuildOne ) else ( call :buildOneError %buildErrorLevel% DEPLOY FAILED goto :endBuildOne ) :buildOneError echo *** MAKE %bitSize% %brokenGL% FAILED *** echo %* exit /b %1 :endBuildOne cd %savedir% 1>nul 2>nul endLocal exit /b %buildErrorLevel% :::============================================ :helpInfo echo _ echo The %~nx0 script file creates Release and Install 32 and 64 bit versions of Oscar. echo The %~nx0 has parameters to configure the default options. echo %~nx0 can be executed from the File Manager echo Parameter can be added using a short cut and adding parameters to the shortcut's target. echo _ echo OPTIONS DESCRIPTION echo br[okenGl] Builds the brokenGL versions of OSCAR echo 32 Builds just 32 bit versions echo 64 Builds just 64 bit versions echo ^ Overrides the default path (C:\Qt) to QT's installation - contains Tools, License, ... folders echo ou[tside] Default: Build is located outside of git repository. Same as building with OSCAR_QT.pro echo in[side] Build is located inside of git repository. Same as building with oscar.pro echo he[lp] Display this help message echo _ echo The offical builds of OSCAR should not use the following options. These facilate development and testing echo re[make] Skips recreating Makefile, but executes make in existing build folder echo skipC[ompile] Skips all compiling. Leaves build folder untouched. echo skipD[eploy] Skip calling deploy.bat. Does not create Release or install versions. echo ve[rbose] Start verbose logging echo _ echo ^ Displays invalid parameter message and help message then exits. goto :eof OSCAR-code-v1.5.1/Building/Windows/deploy-515.bat000066400000000000000000000101611450332542600211510ustar00rootroot00000000000000::: ::: 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 --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 endlocalOSCAR-code-v1.5.1/Building/Windows/deploy.bat000066400000000000000000000207641450332542600206530ustar00rootroot00000000000000::: ::: 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:%ProgramFiles(x86)% ::: gawk - somewhere in the PATH or in Git for Windows installed with Git\cmd in the path ::: ::: Deploy.bat resides in ...\OSCAR-code\Building\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 echo Executing %~nx0 %* setlocal set doSkipInstall=0 set suffixParam=%1 if "%1"=="skipInstall" ( set doSkipInstall=1 set suffixParam=%2 ) else ( if "%2"=="skipInstall" set doSkipInstall=1 ) echo doSkipInstall = %doSkipInstall% set errDescription= set errvalue=0 ::: Current Directory is the shadowBuildDir set shadowBuildDir=%cd% ::: toolDir is where the Window install\deploy tools are located cd %~dp0 && cd ..\..\.. set parentDir=%cd% if exist %shadowBuildDir%\oscar\nul set shadowBuildDir=%shadowBuildDir%\oscar cd %shadowBuildDir% set toolDir=%parentDir%\OSCAR-code\Building\Windows ::: Calculate directory where OSCAR-code is located. set sourceDir=%parentDir%\OSCAR-code\oscar echo tooldir is %toolDir% ::echo parentDir is %parentDir% :: echo sourceDir is %sourceDir% echo shadowBuildDir is %shadowBuildDir% if NOT exist %shadowBuildDir%\OSCAR.exe ( call :err 41 %shadowBuildDir%\OSCAR.exe does not exist goto :endProgram ) call :cleanFolder if exist %shadowBuildDir%\buildInfo.iss del /q %shadowBuildDir%\buildInfo.iss || (call :err 42 & goto :endProgram) if exist %shadowBuildDir%\buildInstall.iss del /q %shadowBuildDir%\buildInstall.iss || (call :err 43 & goto :endProgram) if exist %shadowBuildDir%\setup.ico del /q %shadowBuildDir%\setup.ico || (call :err 44 & goto :endProgram) where gawk > temp.txt 2>nul set er=%errorlevel% set gawk= temp.txt 2>nul set er=%errorlevel% set /p git=nul if NOT %ERRORLEVEL%==0 ( call :err 45 %ERRORLEVEL% failure to copy %toolDir%\buildInstall.iss to %shadowBuildDir% goto :endProgram ) copy %toolDir%\setup.ico %shadowBuildDir% 1>nul if NOT %ERRORLEVEL%==0 ( call :err failure to copy %toolDir%\setup.ico to %shadowBuildDir% goto :endProgram ) :::copy %toolDir%\use*.reg %shadowBuildDir% || call :err 47 & goto :endProgram ::: 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. echo ; This script auto-generated by DEPLOY.BAT >%shadowBuildDir%\buildinfo.iss gawk -f %toolDir%\getBuildInfo.awk %sourcedir%\VERSION >>%shadowBuildDir%\buildInfo.iss if NOT %ERRORLEVEL%==0 ( call :err 60 & goto :endProgram) gawk -f %toolDir%\getBuildInfo.awk %sourcedir%\git_info.h >>%shadowBuildDir%\buildInfo.iss if NOT %ERRORLEVEL%==0 ( call :err 62 & goto :endProgram) echo %shadowBuildDir% | gawk -f %toolDir%\getBuildInfo.awk >>%shadowBuildDir%\buildInfo.iss if NOT %ERRORLEVEL%==0 ( call :err 63 & goto :endProgram) echo #define MySuffix "%suffixParam%" >>%shadowBuildDir%\buildinfo.iss if NOT %ERRORLEVEL%==0 ( call :err 64 & goto :endProgram) :::echo #define MySourceDir "%sourcedir%" >>%shadowBuildDir%\buildinfo.iss set releaseDir=%shadowBuildDir%\Release ::: Create Release directory and subdirectories ::: if exist %shadowBuildDir%\Release\*.* rmdir /s /q %shadowBuildDir%\Release mkdir %releaseDir% cd %shadowBuildDir%\Release copy %shadowBuildDir%\oscar.exe . 1>nul if NOT %ERRORLEVEL%==0 ( call :err 71 & goto :endProgram) :::copy %shadowBuildDir%\use*.reg . || call :err 72 & goto :endProgram ::: if exist %shadowBuildDir%\Installer\*.* rmdir /s /q %shadowBuildDir%\Installer (call :err 73 & goto :endProgram) ::: starting to copy files ::: 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 %shadowBuildDir%\help\*.qch copy %shadowBuildDir%\help %releaseDir%\Help 1>nul || (call :err 80 & goto :endProgram) ::: if exist Html\*.* rmdir /s /q Html mkdir Html copy %shadowBuildDir%\html %releaseDir%\html 1>nul || (call :err 81 & goto :endProgram) ::: if exist Translations\*.* rmdir /s /q Translations mkdir Translations copy %shadowBuildDir%\translations %releaseDir%\Translations 1>nul || (call :err 84 & goto :endProgram) ::: Run deployment tool echo Running Qt Deployment tool windeployqt.exe --release --force --compiler-runtime --no-translations --verbose 1 OSCAR.exe 1>nul 2>nul if %errorlevel% == 0 goto :cleanup ::: echo windeployqt error = %errorlevel% executing next version ::: Post mingw 8.0 --release Must not be used. windeployqt.exe --force --compiler-runtime --no-translations --verbose 1 OSCAR.exe 1>nul || (call :err 87 & goto :endProgram) :cleanup ::: Clean up unwanted translation files ::::For unknown reasons, Win64 build copies the .ts files into the release dir/ectory, while Win32 does not if exist Translations\*.ts del /q Translations\*.ts ::: Create installer cd .. IF %doSkipInstall%==1 goto :endProgram echo Creating OSCAR Installation Exec "%ProgramFiles(x86)%\Inno Setup 6\iscc" /Q BuildInstall.iss || (call :err 88 & goto :endProgram) if NOT exist %shadowBuildDir%\Installer\OSCAR*.exe (call :err 89 & goto :endProgram) :endProgram echo Finished %~nx0 %errDescription% exit /b %errvalue% :::: ======================= :err set errvalue=%1 set errDescription=%* echo %~nx0 detected error: %* exit /b %errvalue% ::: ===================================================================================== :getGawk :: check if gawk is already available. where gawk > temp.txt 2>nul set er=%errorlevel% set gawk= temp.txt 2>nul set er=%errorlevel% set /p git= # 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.5.1/Htmldocs/000077500000000000000000000000001450332542600152445ustar00rootroot00000000000000OSCAR-code-v1.5.1/Htmldocs/about-af.html000066400000000000000000000057351450332542600176420ustar00rootroot00000000000000 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.5.1/Htmldocs/about-ar.html000066400000000000000000000066621450332542600176560ustar00rootroot00000000000000 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.5.1/Htmldocs/about-bg.html000066400000000000000000000077371450332542600176500ustar00rootroot00000000000000 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.5.1/Htmldocs/about-da.html000066400000000000000000000055221450332542600176320ustar00rootroot00000000000000 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.5.1/Htmldocs/about-de.html000066400000000000000000000062161450332542600176370ustar00rootroot00000000000000 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.5.1/Htmldocs/about-el.html000066400000000000000000000106321450332542600176440ustar00rootroot00000000000000 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.5.1/Htmldocs/about-es.html000066400000000000000000000061671450332542600176630ustar00rootroot00000000000000 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.5.1/Htmldocs/about-fi.html000066400000000000000000000054641450332542600176510ustar00rootroot00000000000000 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.5.1/Htmldocs/about-fil.html000066400000000000000000000062371450332542600200240ustar00rootroot00000000000000 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.5.1/Htmldocs/about-fr.html000066400000000000000000000060701450332542600176540ustar00rootroot00000000000000 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.5.1/Htmldocs/about-he.html000066400000000000000000000063001450332542600176350ustar00rootroot00000000000000 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.5.1/Htmldocs/about-hu.html000066400000000000000000000060661450332542600176660ustar00rootroot00000000000000 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.5.1/Htmldocs/about-it.html000066400000000000000000000057341450332542600176670ustar00rootroot00000000000000 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.5.1/Htmldocs/about-nl.html000066400000000000000000000067701450332542600176650ustar00rootroot00000000000000 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.5.1/Htmldocs/about-no.html000066400000000000000000000061321450332542600176600ustar00rootroot00000000000000 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.5.1/Htmldocs/about-pl.html000066400000000000000000000074671450332542600176730ustar00rootroot00000000000000 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.5.1/Htmldocs/about-pt.html000066400000000000000000000056771450332542600177040ustar00rootroot00000000000000 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.5.1/Htmldocs/about-pt_BR.html000066400000000000000000000062151450332542600202540ustar00rootroot00000000000000 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.5.1/Htmldocs/about-ro.html000066400000000000000000000056331450332542600176710ustar00rootroot00000000000000 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.5.1/Htmldocs/about-sv.html000066400000000000000000000063261450332542600177010ustar00rootroot00000000000000 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.5.1/Htmldocs/about-th.html000066400000000000000000000113211450332542600176530ustar00rootroot00000000000000 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.5.1/Htmldocs/about-tr.html000066400000000000000000000064071450332542600176760ustar00rootroot00000000000000 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.5.1/Htmldocs/about-zh.html000066400000000000000000000047721450332542600176750ustar00rootroot00000000000000 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.5.1/Htmldocs/about.html000066400000000000000000000052701450332542600172500ustar00rootroot00000000000000 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.5.1/Htmldocs/credits.html000066400000000000000000000153541450332542600175770ustar00rootroot00000000000000 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 https://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, Ray Elliott (LoudSnorer), untoutseul05

Reporters
Crimson Nape, palerider

Testers
Fred Bonjour (Gideon) (Lead Tester), GuyScharf, Jeff8356, bollar, c137jayde, cberistain, coldfeet7, geraldbergeron, Homerec130, johnnyb00, jr3586, minderbinder, ScottZZZ, Shnorky, SleepyHenry, stbrown3rd, unidee

Advisors
SkepticDoc, Sleeprider, srlevine1, harre, rhashimoto

Translators
Arie Klerk (A KLERK) (Translations Team Coordinator, Dutch), 1st.qwerty (Polish), ApneaHero (Czech), C Chan (Chinese Trad), delta (Romanian), dolceitalia (Italian), dprenerov (Bulgarian), drmaestro (Turkish), drol (French), eddyDee (Hebrew), FaureCourtet (French), GregK (Russian/Hebrew), hearsay73 (Filipino), Heyns (Afrikaans), hisaotsu (Japanese), Hypoxic (Greek), k2boys (Korean), Lazer1234 (Swedish), 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), zellem (Chinese Simplified)
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.5.1/Htmldocs/release_notes.html000066400000000000000000001001001450332542600207520ustar00rootroot00000000000000 release_notes

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

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

  • [new] Added DS Go Auto 500G150 model
  • [fix] Re-enabled biometrics
  • [fix] Disabled pixmap caching
  • [fix] High Resolution display issue
    • Setting added to Preferences > Appearance to enable/disable High Resolution mode
    • Setting is disabled by default
  • [fix] Checkupdates fixed

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

  • [new] Added Support for Resmed AirSense 11 models 39483 and 39517
  • [new] Added Clinical and Permissive mode for compliance
  • [new] Added Clinical tab to preferences
  • [new] New CPAP session bar (experimental)
  • [new] Additional settings reported for ResMed bilevel machines
  • [new] Red zero line for flow rate on by default
  • [new] Added dynamic resizing of daily graphs by double clicking graph name
  • [new] Hoffrichter Point 3 machines now supported
  • [new] Resvent machines now supported
  • [new] F&P Sleepstyle now reports Obstructive and Central separately
  • [new] Added Shift+click to zoom into 3 minute window
  • [new] Search function added to Daily Screen left panel
  • [new] Event Flags graph automatically resizes to event types selected
  • [new] Added warning dialog when a session is disabled
  • [new] Added Backup feature for daily/overview graph settings
  • [fix] Fix high resolution display issues
  • [fix] Improved support for PrismaLine bilevel modes
  • [fix] Selected event not showing in PRS1 devicess
  • [fix] Updated wording for View > Reset Graphs
  • [fix] Crash on View > Reset Graphs
  • [fix] Viatom now imports files with .dat extension
  • [fix] Graph combo box updated
  • [fix] Allow Graph and Event combo boxes to remain open after changes.
  • [fix] Overview display of ResMed Oximeter events now works.

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

  • [new] Support for Philips Respironics DreamStation 2:
    • DreamStation 2 CPAP (410X150C)
    • DreamStation 2 Advanced CPAP (420X150C)
    • DreamStation 2 Auto CPAP Advanced (520X110C, 520X130C, 520X150C)
    • DreamStation 2 Auto CPAP Advanced with P-Flex (521X120C, 521X140C)
  • [new] Additional Philips Respironics devices tested and fully supported:
    • BiPAP Auto (System One 60 Series) (761P)
    • BiPAP autoSV Advanced 30 (System One 60 Series) (961TCA)
    • REMstar Auto (System One) (552P)
  • [new] For AutoSet 11, two models were added to the tested list: 39421 and 39485.
  • [fix] Added support for pressure pulse, CA, and VS on BiPAP autoSV Advanced 30 (System One 60 Series) (960T).
  • [fix] Fixed pressure settings scale on BiPAP autoSV Advanced 30 (System One 60 Series) (960T).
  • [fix] For all ResMed devices, square plot (no smoothing) has been forced on all 2 second pressure graphs.
  • [fix] File Export Sessions now exports statistics session data properly.
  • [fix] Fixed a rare crash on import when encountering corrupted Philips Respironics directories.
  • [fix] Fixed an incorrect warning message when importing some CheckMe O2 Max data.

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.5.1/OSCAR_QT.pro000066400000000000000000000006031450332542600154630ustar00rootroot00000000000000lessThan(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.5.1/README000066400000000000000000000040571450332542600143550ustar00rootroot00000000000000OpenSource 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.5.1/Translations/000077500000000000000000000000001450332542600161505ustar00rootroot00000000000000OSCAR-code-v1.5.1/Translations/Afrikaans.af.ts000066400000000000000000016552511450332542600210230ustar00rootroot00000000000000 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. 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 i i 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 Search Soek Layout Save and Restore Graph Layout Settings 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 Device Settings Instellings <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.) no data :( geen data :( Sorry, this device only provides compliance data. Jammer, die toestel verskaf slegs voldoenings inligting. 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.. CPAP Sessions CPAP Sessies Sleep Stage Sessions Slaapvlak Sessies Position Sensor Sessions Posisie Sensor Sessies Unknown Session Onbekende Sessie 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 "Nothing's here!" "Hier is niks!" Oximeter Information Oximeter Informasie Clinical Mode Disabling Sessions requires the Permissive Mode SpO2 Desaturations Baie slegte vertaling!!! SpO2 Desaturasies Pulse Change events Polsslag Verandering Gebeurtenisse SpO2 Baseline Used SpO2 Basislyn Gebruik Statistics Statistieke Total time in apnea Totale tyd in apneë Time over leak redline Tyd oor lek rooilyn Event Breakdown Gebeurtenis Afbraak This CPAP device does NOT record detailed data Hierdie CPAP toestel verskaf NIE detail inligting nie 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?? 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 Hide All Events Versteek Alle Gebeurtenisse Show All Events Vertoon Alle Gebeurtenisse Hide All Graphs Show All Graphs DailySearchTab Clear Bookmark Jumps to Date's Bookmark Start Search DATE Jumps to Date Match Notes Notas Notes containing Bookmarks Boekmerke Bookmarks containing AHI Daily Duration Session Duration Days Skipped Disabled Sessions Number of Sessions Click HERE to close Help Help Help No Data Jumps to Date's Details Number Disabled Session Jumps to Date's Details Note Jumps to Date's Notes AHI Jumps to Date's Details EventsPerHour Session Duration Jumps to Date's Details Minutes Number of Sessions Jumps to Date's Details Sessions Daily Duration Jumps to Date's Details Hours Number of events Jumps to Date's Events Events Automatic start Continue Search End of Search No Matches Skip:%1 %1/%2%3 days %1 dae {1/%2%3 ?} Found %1 Finds days that match specified criteria. Searches from last day to first day. First click on Match Button then select topic. Then click on the operation to modify it. or update the value Topics without operations will automatically start. Compare Operations: numberic or character. Numberic Operations: Character Operations: Summary Line Left:Summary - Number of Day searched Center:Number of Items Found Right:Minimum/Maximum for item searched Result Table Column One: Date of match. Click selects date. Column two: Information. Click selects date. Then Jumps the appropiate tab. Wildcard Pattern Matching: Wildcards use 3 characters: Asterisk Question Mark Backslash. Asterisk matches any number of characters. Question Mark matches a single character. Backslash matches next character. DateErrorDisplay ERROR The start date MUST be before the end date Die begindatum MOET voor die einddatum wees The entered start date %1 is after the end date %2 Die verskafde begindatum %1 is na die einddatum %2 Hint: Change the end date first Wenk: Verander die einddatum eerste The entered end date %1 Die verskafde einddatum %1 is before the start date %1 is voor die begindatum %1 Hint: Change the start date first Wenk: Verander die begindatum eerste 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 device Record cannot be imported in this profile. Hierdie toestel inligting kan nie ingelees word in hierdie profiel 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 Show Standard Report Standard Standaard Show Monthly Report Monthly Maandelikse Show Range Report Date Range Datum Bestek Select Report Date Report Date 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 Purge ALL Device Data Vee ALLE toestel data uit 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 Standard - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> Advanced - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> 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 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 &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 Couldn't find any valid Device Data at %1 Kon nie enige geldige toestel data opspoor by %1 nie 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 No supported data was found Importing Data Voer Data In The User's Guide will open in your default browser Die Gebruikersgids sal oopmaak in u bestek browser Are you sure you want to rebuild all CPAP data for the following device: Is u seker dat u alle CPAP data wil herskep vir die volgende toestel:: For some reason, OSCAR does not have any backups for the following device: Vir een of ander rede het OSCAR geen rugsteun vir die volgende toestel nie: Would you like to import from your own backups now? (you will have no data visible for this device until you do) Wil u invour vanaf u eie rugsteun? (u sal geen data beskikbaar hê vir die toestel totdat u dit doen nie) OSCAR does not have any backups for this device! OSCAR het geen rugsteun vir hierdie toestel nie! Unless you have made <i>your <b>own</b> backups for ALL of your data for this device</i>, <font size=+2>you will lose this device's data <b>permanently</b>!</font> Tensy u <i>u <b>eie</b> rugsteun het vir AL die data vir hierdie toestel</i>, <font size=+2>gaan u hierdie toestel se data <b>permanent verloor</b>!</font> You are about to <font size=+2>obliterate</font> OSCAR's device database for the following device:</p> U is op die punt om OSCAR se databasis <font size=+2>te vernietig</font> vir die volgende toestel:</p> A file permission error caused the purge process to fail; you will have to delete the following folder manually: 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 You must select and open the profile you wish to modify 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. 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. 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. 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. 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 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 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 This software is being designed to assist you in reviewing the data produced by your CPAP Devices and related equipment. Hierdie sagteware is ontwerp om u by te staan in die nagaan van data wat geproduseer is deur u CPAP toestelle en verwante toerusting. 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 Layout Save and Restore Graph Layout Settings 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) Hide All Graphs Show All Graphs 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> 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. <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 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 I started this oximeter recording at (or near) the same time as a session on my CPAP device. Ek het hierdie oximeter opname begin op (of naby) dieselfde tyd as 'n sessie op my CPAP masjien. <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 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) This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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 herinstalleer. (Hoogs aanbeveel, tensy u min skyfspasie het of nie omgee vir die grafiekdata nie) <html><head/><body><p>Provide an alert when importing data from any device model that has not yet been tested by OSCAR developers.</p></body></html> <html><head/><body><p>Gee '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 device Waarsku wanneer data ingevoer word vanaf 'n ongetoetsde maskien &CPAP &CPAP This calculation requires Total Leaks data to be provided by the CPAP device. (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 Totale Lek data verskaf word deur die CPAP masjien. (bv. PRS1, maar nie ResMed nie, wat reeds hierdie het) Die Onbedoelde Lek berekeninge 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. Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device missed. This option must be enabled before import, otherwise a purge is required. Aktiveer / deaktiveer gebeurtenis merkers vir eksperimentele gebeurtenisse. Dit laat opsporing van grensgebeurtenisse toe wat op sommige van die masjiene gemis word. Hierdie opsie moet geaktiveer word voor data invoer, anders word 'n opruiming van die databasis vereis. This experimental option attempts to use OSCAR's event flagging system to improve device detected event positioning. Hierdie eksperimentele opsie poog om OSCAR se gebeurtenismerkerstelsel te gebruik om masjien-waargenome gebeurtenis posisionering te verbeter. Resync Device Detected Events (Experimental) Hersinkroniseer Masjien-Bespeurde Gebeurtenisse (Eksperimenteel) Allow duplicates near device events. Laat herhaling in die omgewing van masjien gebeurtenisse toe. Show flags for device detected events that haven't been identified yet. Wys merkers vir masjien-opgespoorde gebeurtenisse wat nog nie geïdentifiseer is nie. 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 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ë. Duration of airflow restriction Tydsduur van lugvloei beperking s s Event Duration Geburtenis Tydsduur 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 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><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Sessies korter as hierdie tydperk sal nie vertoon word nie</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></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>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 <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Pasgemaakte gebeurtenismerking is 'n eksperimentele metode om gebeurtenisse op te spoor wat deur die toestel gemis word. Hulle is </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></p></body></html> 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 <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices 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> <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:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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><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:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Sinkroniseer Oximetrie en CPAP Data</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data ingevoer vanaf SpO2Review (van .spoR lêers) of die seriale invoermetode </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;"> het die korrekte inligting om te sinkroniseer.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Reële data mode (deur 'n seriale kabel te gebruik) is een manier om 'n akkurate sinkronisasie te verseker met CMS50 oximeters, maar kompenseer nie vir CPAPA klok dryf nie.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Indien u die Oximeter se opname mode 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 die CPAP toestel begin, kan u nou ook die data sinkroniseer. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Die seriale invoerproses neem die begintyd van laasnag se eerste CPAP sessie. (Onthou om u CPAP data eerste in te voer!)</span></p></body></html> 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) <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> 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 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 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. Clinical Clinical Settings Select Oscar Operating Mode Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. Clinical Mode permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. Permissive Mode Hours Oximetry Settings Oximetrie Instellings <!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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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 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 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> 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. Whether to include device serial number on device settings changes report Sluit masjien serienommer op die masjien instellings verslag in 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) For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. Enables SessionBar in Event Flags Graph 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 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? ResMed S9 devices 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). 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 Clinical Mode: Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. Basically replicates the reports and data stored on the devices data card. This includes pap devices, oximeters, etc. Compliance reports fall under this mode. Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. Permissive Mode: Allows user to select which data sets/ sessions to be used for calculations and display. Additional charts and calculations may be available that are not available from the vendor data. Changing the Oscar Operating Mode: Requires a reload of the user's profile. Data will be saved and restored. Minor Flag Klein Merker Span Mate Always Minor Altyd Klein Never Nooit Restart Required Herbegin Vereis No CPAP devices detected Geen CPAP toestelle bespeur nie Will you be using a ResMed brand device? Wil u 'n ResMed toestel gebruik? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, 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> 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 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 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 Length: %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 milliSeconds 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 EEPAP Min EEPAP Max EEPAP 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 Device Toestel 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: Compiler: Software Engine Sagteware Engine ANGLE / OpenGLES ANGLE / OpenGLES Desktop OpenGL Desktop OpenGL m m cm cm in inch kg kg l/min l/min 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 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 device'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 Device Toestel het Geen Data Vermoë Nie Your %1 CPAP Device (Model %2) is unfortunately not a data capable model. U %1 CPAP toestel (Model %2) is ongelukkig nie in staat om data te verskaf nie. I'm sorry to report that OSCAR can only track hours of use and very basic settings for this device. Ongelukkig kan OSCAR slegs gebruiksure en basiese instellings van hierdie toestel rapporteer. Device Untested Ongetoetste Toestel Your %1 CPAP Device (Model %2) has not been tested yet. U %1 CPAP toestel (Model %2) is nog nie getoets nie. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Dit lyk soortgelyk genoeg aan ander toestelle dat dit moontlik mag werk, maar die ontwikkelaars sal graag 'n zip weergawe van hierdie toestel se SD kaart wil kry om te verseker dat dit werk met OSCAR. Device Unsupported Toestel word nie ondersteun nie Sorry, your %1 CPAP Device (%2) is not supported yet. Jammer, u %1 CPAP Toestel (%2) is nog nie ondersteun nie. The developers need a .zip copy of this device's SD card and matching clinician .pdf reports to make it work with OSCAR. Die ontwikkelaars benodig 'n ZIP weergawe van hierdie toestel se SD kaart en ooreenstemmende doktersverslag of PDF analises om dit te laat werk met OSCAR. Getting Ready... Maak Gereed... Scanning Files... Skandeer Lêers... Importing Sessions... Sessie Invoer... UNKNOWN APAP (std) APAP (dyn) Auto S Auto S/T AcSV SoftPAP Mode Pressure relief during exhalation Slight Softstart pressure Pressure during soft start period PSoft Softstart minimum pressure Minimum pressure during soft start period PSoftMin Auto start Automatically turn on the device by breathing Softstart time Lenght of soft start period Soft start maximum time Maximum lenght of soft start period Soft start max. time Soft start pressure Higher End Expiratory Pressure Humidifier level Tube type Obstruction level Obstruction level in percentage rRMVFluctuation Relative respiratory minute volume fluctuation Relative respiratory minute volume Measured pressure Full flow Artefact Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) Epoch (2 mins) with Flow Limitation Deep Sleep Deep sleep, stable respiration Timed breath BiSoft Mode BiSoft 1 BiSoft 2 TriLevel PMaxOA EEPAPMin Lower End Expiratory Pressure EEPAPMax rRMV ART CriticalLeak Mask leakage is above a critical treshold CL eMO Epoch (2 mins) with Mild Obstruction eSO Epoch (2 mins) with Severe Obstruction eS Epoch (2 mins) with Snoring eFL DS Finishing up... Voltooiing... Untested Data Ongetoetste Data 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 A few breaths automatically starts device 'n Paar asemteue begin outomaties die toestel Device automatically switches off Toestel skakel outomaties af Whether or not device allows Mask checking. Of die toestel toelaat dat die Masker nagegaan word. 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 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 A period during a session where the device could not detect flow. 'n Periode gedurende 'n sessie waartydens die toestel die vloei kon waarneem nie. 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 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 Auto Off Outo af Mask Alert Masker Waarskuwing Show AHI Vertoon AHI Whether or not device shows AHI via built-in display. Of u toestel AHI vertoon op die ingeboude skerm. The number of days in the Auto-CPAP trial period, after which the device will revert to CPAP Die aantal dae in die Outo CPAP toets periode, waarna die toestel sal terugkeer na CPAP Breathing Not Detected Asemhaling Nie Waargeneem 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 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> <i>Your old device data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> <i>U ou toestel data moet hergenereer word gegewe dat die rugsteun funksie nie afgeskakel was tydens vorige data intrek sessies nie.</i> OSCAR does not yet have any automatic card backups stored for this device. OSCAR het nog geen outomatiese kaart rugsteun vir hierdie toestel nie. 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? Device Database Changes Toestel Databasis Veranderinge 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. The device data folder needs to be removed manually. The toestel se data vouer moet handmatig deur uself verwyder word. 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. 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 This means you will need to import this device data again afterwards from your own backups or data card. Dit beteken dat u die toestel se data agterna weer sal moet intrek van u eie rugsteun of data kaart af. 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. 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 End Expiratory Pressure 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 Cheyne Stokes Respiration (CSR) Cheyne Stokes Respirasie (CSR) Periodic Breathing (PB) Ongereëlde Asemhaling (PB) Clear Airway (CA) Oop Lugweg (CA) An apnea caused by airway obstruction 'n Apneë veroorsaak deur lugweg obstruksie A partially obstructed airway 'n Gedeeltelik-geblokte lugweg UA UA A vibratory snore 'n Vibrerende Snork 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 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. 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. 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 A sudden (user definable) change in heart rate 'n Skielike (gebruiker definieerbare) verandering in hartklop 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 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 An abnormal period of Cheyne Stokes Respiration 'n Abnormale periode van Cheyne Stokes Respirasie CSR CSR An abnormal period of Periodic Breathing 'n Abnormale tydperk van Ongereëlde Asemhaling An apnea that couldn't be determined as Central or Obstructive. 'n Apneë wat nie bepaal kon word as Sentraal of Obstruktief nie. An apnea reported by your CPAP device. A restriction in breathing from normal, causing a flattening of the flow waveform. 'n Beperking in asemhaling wat afplatting van die vloi golfvorm veroorsaak. A vibratory snore as detected by a System One device 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 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 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. Obstructive Apnea (OA) Obstruktiewe Apneë (OA) Hypopnea (H) Hypopneë (H) Unclassified Apnea (UA) Ongeklassifiseerde Apneë (UA) Apnea (A) Apneë (A) An apnea reportred by your CPAP device. 'n Apneë wat deur u CPAP toestel gerapporteer is. Flow Limitation (FL) Vloeibeperking (FL) RERA (RE) RERA (RE) Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. Vibratory Snore (VS) Vibrerende Snork (VS) Leak Flag (LF) Lek Merker (LF) A large mask leak affecting device performance. 'n Groot maskerlek wat toestelverrigting beinvloed. Large Leak (LL) Groot Lek (LL) Non Responding Event (NR) Nie-reaksie Gebeurtenis (NR) Expiratory Puff (EP) Uitgaande Teug (EP) SensAwake (SA) SensAwake (SA) User Flag #1 (UF1) Gebruikersmerker #1 (UF1) User Flag #2 (UF2) Gebruikersmerker #2 (UF2) User Flag #3 (UF3) Gebruikersmerker #3 (UF3) Pulse Change (PC) Polsverandering (PC) SpO2 Drop (SD) SpO2 Afname (SD) I/E Value Apnea Hypopnea Index (AHI) Apneë Hypopneë Indeks (AHI) Respiratory Disturbance Index (RDI) Respiratoriese Versteuring Indeks (RDI) 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? OSCAR Reminder OSCAR Herinnering Don't forget to place your datacard back in your CPAP device Moenie vergeet om u data kaart terug te sit in u CPAP toestel nie 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 oximetry settings to defaults? 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) 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) Min EEPAP %1 Max EEPAP %2 PDIFF %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 Selection Length 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 Device auto starts by breathing Toestel begin outomaties deur asemhaling Response Reaksie Device auto stops by breathing Toestel outo stop deur nie asem te haal nie Patient View Pasiënt Aansig RiseEnable RiseTime Cycle Trigger TiMax TiMin Your ResMed CPAP device (Model %1) has not been tested yet. U ResMed CPAP toestel (Model %1) is nog nie getoets nie. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card to make sure it works with OSCAR. Dit lyk soortgelyk genoeg aan ander toestelle dat dit moontlik mag werk, maar die ontwikkelaars sal graag 'n zip weergawe van hierdie toestel se SD kaart wil kry om te verseker dat dit werk met OSCAR. SmartStart SmartStart 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 Smart Stop Slim Stop Simple Eenvoudige Advanced Gevorderde 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... Permissive Mode Total disabled sessions: %1, found in %2 days Total disabled sessions: %1 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. Updating Statistics cache Opdateer Statistieke Usage Statistics Gebruiks Statistieke 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 Löwenstein Prisma Smart Resvent/Hoffrichter iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings Add Add Feature inhibited. The maximum number of Items has been exceeded. creates new copy of current settings. Restore Restores saved settings from selection. Rename Renames the selection. Must edit existing name then press enter. Update Updates the selection with current settings. Delete Deletes the selection. Expanded Help menu. Exits the Layout menu. <h4>Help Menu - Manage Layout Settings</h4> Exits the help menu. Exits the dialog menu. This feature manages the saving and restoring of Layout Settings. Layout Settings control the layout of a graph or chart. Different Layouts Settings can be saved and later restored. Button Description Creates a copy of the current Layout Settings. The default description is the current date. The description may be changed. The Add button will be greyed out when maximum number is reached. Other Buttons Greyed out when there are no selections Loads the Layout Settings from the selection. Automatically exits. io Modify the description of the selection. Same as a double click.io Saves the current Layout Settings to the selection. Prompts for confirmation. Deletes the selecton. Control Exit Gaan Uit (Red circle with a white "X".) Returns to OSCAR menu. Return Next to Exit icon. Only in Help Menu. Returns to Layout menu. Escape Key Exit the Help or Layout menu. Layout Settings * Name * Pinning * Plots Enabled * Height * Order * Event Flags * Dotted Lines * Height Options General Information Maximum description size = 80 characters. Maximum Saved Layout Settings = 30. Saved Layout Settings can be accessed by all profiles. Layout Settings only control the layout of a graph or chart. They do not contain any other data. They do not control if a graph is displayed or not. Layout Settings for daily and overview are managed independantly. Maximum number of Items exceeded. No Item Selected Ok to Update? Ok To Delete? SessionBar %1h %2m %1h %2m No Sessions Present Geen Sessies Teenwoordig nie SleepStyleLoader Import Error Intrek Fout This device Record cannot be imported in this profile. Hierdie toestel inligting kan nie ingelees word in hierdie profiel 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 Device Information Toestel Inligting Changes to Device Settings Veranderinge aan Toestel Instellings Oscar has no data to report :( OSCAR het geen data om te rapporteer nie :( Database has No %1 data available. Database has %1 day of %2 Data on %3 Database has %1 days of %2 Data, between %3 and %4 Total Days: %1 Days Not Used: %1 Days Used: %1 Dae Gebruik: %1 Days %1 %2 Hours: %3 Best Device Setting Worst Device Setting 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) 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 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 device.</span></p></body></html> <span style=" font-weight:600;">Waarskuwing: </span><span style=" color:#ff0000;">ResMed S9 SD Kaarte 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 toestel.</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 device is detected Let daarop dat sommige voorkeure afgedwing word wanneer 'n ResMed-toestel 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 today %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 <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 pressure was under %1 %2 for %3% of the time. U druk was onder %1 %2 vir %3% van die tyd. 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. 1 day ago 1 dag gelede Your device was on for %1. U toestel was aan vir %1. Your CPAP device used a constant %1 %2 of air U CPAP toestel het 'n konstante %1 %2 lug gebruik Your device used a constant %1-%2 %3 of air. U toestel het 'n konstante %1-%2 %3 lug gebruik. Your device was under %1-%2 %3 for %4% of the time. U toestel was onder %1-%2 %3 vir %4% van die tyd. Your EEPAP pressure was under %1 %2 for %3% of the time. 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 Double click Y-axis: Return to AUTO-FIT Scaling Dubbelklik Y-as: Keer terug na OUTO Skalering Double click Y-axis: Return to DEFAULT Scaling Dubbelklik Y-as: Keer terug na VERSTEK Skalering Double click Y-axis: Return to OVERRIDE Scaling Dubbelklik Y-as: Keer terug na GESPESIFISEERDE Skalering Double click Y-axis: For Dynamic Scaling Dubbelklik Y-as: Vir Dinamiese Skalering Double click Y-axis: Select DEFAULT Scaling Dubbelklik Y-as: Kies VERSTEK Skalering Double click Y-axis: Select AUTO-FIT Scaling Dubbelklik Y-as: Kies OUTO-PAS Skalering %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.5.1/Translations/Arabic.ar.ts000066400000000000000000016726431450332542600203250ustar00rootroot00000000000000 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. عفوا، لا يمكن تحديد موقع ملاحظات الإصدار. 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 مجلة i 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 إزالة المرجعية Search بحث Layout Save and Restore Graph Layout Settings Show/hide available graphs. إظهار / إخفاء الرسوم البيانية المتاحة. Breakdown انفصال events أحداث UF1 UF1 UF2 UF2 Time at Pressure الوقت في الضغط Clinical Mode Disabling Sessions requires the Permissive Mode 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.) no data :( Sorry, this device only provides compliance data. This bookmark is in a currently disabled area.. هذه الإشارة المرجعية موجودة في منطقة معطلة حاليًا .. CPAP Sessions جلسات CPAP Details تفاصيل Sleep Stage Sessions جلسات مرحلة النوم Position Sensor Sessions جلسات استشعار الموقف Unknown Session جلسة غير معروفة 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 الوقت خارج المنحدر Start بداية End النهاية Unable to display Pie Chart on this system غير قادر على عرض مخطط دائري على هذا النظام "Nothing's here!" "لا يوجد شيء هنا!" No data is available for this day. لا توجد بيانات متاحة لهذا اليوم. Oximeter Information معلومات مقياس التأكسج Click to %1 this session. انقر فوق %1 هذه الجلسة. disable تعطيل enable ممكن %1 Session #%2 %1 الجلسة #%2 %1h %2m %3s %1h %2m %3s Device Settings <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 انهيار الحدث This CPAP device does NOT record detailed data 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 إشارة مرجعية في %1 Hide All Events إخفاء جميع الأحداث Show All Events عرض جميع الأحداث Hide All Graphs Show All Graphs DailySearchTab Clear Bookmark Jumps to Date's Bookmark Start Search DATE Jumps to Date Match Notes Notes containing Bookmarks إشارات مرجعية Bookmarks containing AHI Daily Duration Session Duration Days Skipped Disabled Sessions Number of Sessions Click HERE to close Help Help مساعدة No Data Jumps to Date's Details Number Disabled Session Jumps to Date's Details Note Jumps to Date's Notes AHI Jumps to Date's Details EventsPerHour Session Duration Jumps to Date's Details Minutes Number of Sessions Jumps to Date's Details Sessions Daily Duration Jumps to Date's Details Hours Number of events Jumps to Date's Events Events Automatic start Continue Search End of Search No Matches Skip:%1 %1/%2%3 days %1 أيام {1/%2%3 ?} Found %1 Finds days that match specified criteria. Searches from last day to first day. First click on Match Button then select topic. Then click on the operation to modify it. or update the value Topics without operations will automatically start. Compare Operations: numberic or character. Numberic Operations: Character Operations: Summary Line Left:Summary - Number of Day searched Center:Number of Items Found Right:Minimum/Maximum for item searched Result Table Column One: Date of match. Click selects date. Column two: Information. Click selects date. Then Jumps the appropiate tab. Wildcard Pattern Matching: Wildcards use 3 characters: Asterisk Question Mark Backslash. Asterisk matches any number of characters. Question Mark matches a single character. Backslash matches next character. DateErrorDisplay ERROR The start date MUST be before the end date The entered start date %1 is after the end date %2 Hint: Change the end date first The entered end date %1 is before the start date %1 Hint: Change the start date first 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 device 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 وضع التقرير Show Standard Report Standard اساسي Show Monthly Report Monthly شهريا Show Range Report Date Range نطاق الموعد Select Report Date Report Date Statistics الإحصاء Daily اليومي Overview نظرة عامة Oximetry التأكسج Import استيراد Help مساعدة &File ملف &View رأي &Reset Graphs إعادة تعيين الرسوم البيانية &Help مساعدة Troubleshooting استكشاف الأخطاء وإصلاحها &Data البيانات &Advanced المتقدمة 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 استيراد بيانات دريم Standard - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> Advanced - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> Purge Current Selected Day &CPAP &Oximetry التأكسج &Sleep Stage &Position &All except Notes All including &Notes Show &Line Cursor عرض وخط المؤشرمشاهدة والخط المؤشر Purge ALL Device Data 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 إظهار مخطط دائري على الصفحة اليومية 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 مشكلة الاستيراد Couldn't find any valid Device Data at %1 Please insert your CPAP data card... الرجاء إدخال بطاقة بيانات CPAP ... Access to Import has been blocked while recalculations are in progress. تم حظر الوصول إلى الاستيراد أثناء إعادة الحساب. CPAP Data Located بيانات CPAP تقع Import Reminder استيراد تذكير No supported data was found Importing Data استيراد البيانات Are you sure you want to rebuild all CPAP data for the following device: Please note, that this could result in loss of data if OSCAR's backups have been disabled. يرجى ملاحظة أن هذا قد يؤدي إلى فقدان البيانات إذا تم تعطيل النسخ الاحتياطية لـ OSCAR. For some reason, OSCAR does not have any backups for the following device: Would you like to import from your own backups now? (you will have no data visible for this device until you do) OSCAR does not have any backups for this device! Unless you have made <i>your <b>own</b> backups for ALL of your data for this device</i>, <font size=+2>you will lose this device's data <b>permanently</b>!</font> You are about to <font size=+2>obliterate</font> OSCAR's device database for the following device:</p> A file permission error caused the purge process to fail; you will have to delete the following folder manually: 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 You must select and open the profile you wish to modify 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. إذا كنت تستطيع قراءة هذا ، فلن يعمل أمر إعادة التشغيل. سيكون عليك القيام بذلك بنفسك يدويًا. 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. نظرًا لعدم وجود نسخ احتياطية داخلية لإعادة الإنشاء منها ، سيتعين عليك الاستعادة من جهازك. Note as a precaution, the backup folder will be left in place. لاحظ كإجراء وقائي ، سيتم ترك مجلد النسخ الاحتياطي في مكانه. 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 حتى الآن 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) 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> ، ولا يأتي مع أي ضمان ، وبدون أي مطالبات بالرشاقة لأي غرض من الأغراض. This software is being designed to assist you in reviewing the data produced by your CPAP Devices and related equipment. 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 إعادة تعيين العرض إلى نطاق التاريخ المحدد Layout Save and Restore Graph Layout Settings 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) Hide All Graphs Show 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> 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. أرغب في استخدام الوقت الذي تم الإبلاغ عنه بواسطة مقياس التأكسج الخاص بي المدمج في الساعة. <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 صفحة المعلومات 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 I started this oximeter recording at (or near) the same time as a session on my CPAP device. <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 تجاهل الجلسات القصيرة 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 (الاستيراد الأول أبطأ ، ولكن يجعل النسخ الاحتياطية أصغر) This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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>Provide an alert when importing data from any device model that has not yet been tested by OSCAR developers.</p></body></html> Warn when importing data from an untested device &CPAP This calculation requires Total Leaks data to be provided by the CPAP device. (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. Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device missed. This option must be enabled before import, otherwise a purge is required. This experimental option attempts to use OSCAR's event flagging system to improve device detected event positioning. Resync Device Detected Events (Experimental) Allow duplicates near device events. Show flags for device detected events that haven't been identified yet. Clinical Clinical Settings Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. تعتبر الأيام مع تحت هذا الاستخدام بأنها "غير مكتملة". عادة ما تعتبر 4 ساعات متوافقة. hours ساعات Flow Restriction تقييد التدفق Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. النسبة المئوية للقيود المفروضة على تدفق الهواء من القيمة المتوسطة. قيمة 20 ٪ تعمل بشكل جيد للكشف عن انقطاع النفس. Duration of airflow restriction مدة تقييد تدفق الهواء s s Event Duration مدة الحدث 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 تظهر في مخطط انهيار مخطط الأحداث 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><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></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>ملاحظة: هذا غير مخصص للتصحيحات الزمنية! تأكد من ضبط ساعة ونظام التشغيل الخاصين بك بشكل صحيح.</p></body></html> Hours ساعات 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-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></p></body></html> 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 أصغر ، لكن تغيير اليوم يكون أبطأ.) <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> Select Oscar Operating Mode Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. Clinical Mode permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. Permissive Mode Hours <!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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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> 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 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 التحذير عند مواجهة بيانات لم يتم رؤيتها من قبل 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> Oximetry Settings إعدادات قياس التأكسج <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</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 السماح لتوسيع نطاق المحور ص Include Serial Number تشمل الرقم التسلسلي Graphics Engine (Requires Restart) محرك الرسومات (يتطلب إعادة التشغيل) <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices do not support changing these settings.</p></body></html> Whether to include device serial number on device settings changes report Print reports in black and white, which can be more legible on non-color printers Print reports in black and white (monochrome) For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. Enables SessionBar in Event Flags Graph 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 نظرة عامة No CPAP devices detected Will you be using a ResMed brand device? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, days will <b>split at noon</b> like in ResMed's commercial software.</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? يتطلب إجراء واحد أو أكثر من التغييرات التي أجريتها إعادة تشغيل هذا التطبيق ، حتى تدخل هذه التغييرات حيز التنفيذ. هل ترغب في القيام بذلك الآن؟ ResMed S9 devices routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). 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 علم Clinical Mode: Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. Basically replicates the reports and data stored on the devices data card. This includes pap devices, oximeters, etc. Compliance reports fall under this mode. Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. Permissive Mode: Allows user to select which data sets/ sessions to be used for calculations and display. Additional charts and calculations may be available that are not available from the vendor data. Changing the Oscar Operating Mode: Requires a reload of the user's profile. Data will be saved and restored. Minor Flag العلم الصغرى Span امتداد Always Minor دائما الصغرى Never أبدا This may not be a good idea قد لا تكون هذه فكرة جيدة 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 أوقية 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 Length: %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 ثواني milliSeconds 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 EEPAP Min EEPAP Max EEPAP 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 العلاقات العامة. ارتياح Device No Data Available لا تتوافر بيانات App key: مفتاح التطبيق: Operating system: نظام التشغيل: Built with Qt %1 on %2 تم إنشاؤه باستخدام Qt %1 على %2 Graphics Engine: محرك الرسومات: Graphics Engine type: نوع محرك الرسومات: Compiler: Software Engine محرك البرمجيات ANGLE / OpenGLES Desktop OpenGL m m cm cm in kg l/min Only Settings and Compliance Data Available Summary Data Only Bookmarks إشارات مرجعية Mode الوضع Model نموذج Brand علامة تجارية Serial مسلسل Series سلسلة 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 device's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Non Data Capable Device Your %1 CPAP Device (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 device. Device Untested Your %1 CPAP Device (Model %2) has not been tested yet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Device Unsupported Sorry, your %1 CPAP Device (%2) is not supported yet. The developers need a .zip copy of this device's SD card and matching clinician .pdf reports to make it work with OSCAR. Getting Ready... يستعد... Scanning Files... جارٍ فحص الملفات ... Importing Sessions... استيراد الجلسات ... UNKNOWN APAP (std) APAP (dyn) Auto S Auto S/T AcSV SoftPAP Mode Pressure relief during exhalation Slight Softstart pressure Pressure during soft start period PSoft Softstart minimum pressure Minimum pressure during soft start period PSoftMin Auto start Automatically turn on the device by breathing Softstart time Lenght of soft start period Soft start maximum time Maximum lenght of soft start period Soft start max. time Soft start pressure Higher End Expiratory Pressure Humidifier level Tube type Obstruction level Obstruction level in percentage rRMVFluctuation Relative respiratory minute volume fluctuation Relative respiratory minute volume Measured pressure Full flow Artefact Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) Epoch (2 mins) with Flow Limitation Deep Sleep Deep sleep, stable respiration Timed breath BiSoft Mode BiSoft 1 BiSoft 2 TriLevel PMaxOA EEPAPMin Lower End Expiratory Pressure EEPAPMax rRMV ART CriticalLeak Mask leakage is above a critical treshold CL eMO Epoch (2 mins) with Mild Obstruction eSO Epoch (2 mins) with Severe Obstruction eS Epoch (2 mins) with Snoring eFL DS Finishing up... الانتهاء من ... 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 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 A few breaths automatically starts device Device automatically switches off Whether or not device allows Mask checking. 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 مدة المحاكمة التلقائية 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 غير مؤكد: تنفس متغير محتمل ، وهي فترات انحراف كبير عن ذروة اتجاه التدفق الشهيق A period during a session where the device could not detect flow. 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 تشغيل تلقائي Auto Off إيقاف السيارات Mask Alert تنبيه قناع Show AHI عرض AHI Whether or not device shows AHI via built-in display. The number of days in the Auto-CPAP trial period, after which the device will revert to CPAP Breathing Not Detected التنفس لم يتم كشفه BND BND Timed Breath توقيت التنفس Machine Initiated Breath آلة بدأت التنفس TB TB Windows User مستخدم ويندوز Using استخدام , found SleepyHead - ، وجدت SleepyHead - You must run the OSCAR Migration Tool يجب عليك تشغيل أداة ترحيل OSCAR 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> <i>Your old device 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. لا يوجد لدى الأداة OSCAR أي نسخ احتياطية للبطاقات تلقائيًا مخزنة لهذا الجهاز. This means you will need to import this device 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؟ Device Database Changes Sorry, the purge operation failed, which means this version of OSCAR can't start. عذرًا ، فشلت عملية التطهير ، مما يعني أنه لا يمكن بدء تشغيل هذا الإصدار أو OSCAR. The device data folder needs to be removed manually. 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 وإكمال عملية الترقية. Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. بمجرد الترقية ، لا يمكنك <font size = + 1> </font> استخدام هذا الملف الشخصي مع الإصدار السابق بعد الآن. 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 ارتفاع ضغط الزفير End 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 vibratory snore as detected by a System One device 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 توقف التنفس أثناء فتح مجرى الهواء Cheyne Stokes Respiration (CSR) Periodic Breathing (PB) Clear Airway (CA) An apnea caused by airway obstruction انقطاع النفس الناجم عن انسداد مجرى الهواء A partially obstructed airway مجرى الهواء عرقلة جزئيا UA UA A vibratory snore شخير اهتزازي Pressure Pulse نبض الضغط A pulse of pressure 'pinged' to detect a closed airway. نبضة ضغط "تتعرض لضغوط" لاكتشاف مجرى الهواء المغلق. A type of respiratory event that won't respond to a pressure increase. وهناك نوع من الحدث التنفسي الذي لن يستجيب لزيادة الضغط. Intellipap event where you breathe out your mouth. حدث Intellipap حيث تتنفس فمك. SensAwake feature will reduce pressure when waking is detected. ميزة SensAwake ستقلل الضغط عند اكتشاف الاستيقاظ. Heart rate in beats per minute معدل ضربات القلب في يدق في الدقيقة الواحدة Blood-oxygen saturation percentage نسبة تشبع الدم بالأكسجين Plethysomogram تخطيط التحجم An optical Photo-plethysomogram showing heart rhythm رسم ضوئي بصري ضوئي يظهر إيقاع القلب A sudden (user definable) change in heart rate تغيير مفاجئ (يمكن تعريف المستخدم) في معدل ضربات القلب A sudden (user definable) drop in blood oxygen saturation انخفاض مفاجئ (يمكن تعريف المستخدم) في تشبع الأكسجين في الدم SD SD Breathing flow rate waveform معدل تدفق التنفس الموجي 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 An abnormal period of Cheyne Stokes Respiration فترة غير طبيعية من شايان ستوكس تنفس CSR CSR An abnormal period of Periodic Breathing فترة غير طبيعية من التنفس الدوري An apnea that couldn't be determined as Central or Obstructive. انقطاع النفس الذي لا يمكن تحديده على أنه مركزي أو معيق. An apnea reported by your CPAP device. A restriction in breathing from normal, causing a flattening of the flow waveform. وجود قيود في التنفس من المعتاد ، مما تسبب في تسطيح الموجي التدفق. 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 ماكس تسرب 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 تسرب متوسط 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. Obstructive Apnea (OA) Hypopnea (H) Unclassified Apnea (UA) Apnea (A) Flow Limitation (FL) RERA (RE) Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. Vibratory Snore (VS) Leak Flag (LF) A large mask leak affecting device performance. Large Leak (LL) Non Responding Event (NR) Expiratory Puff (EP) SensAwake (SA) User Flag #1 (UF1) User Flag #2 (UF2) User Flag #3 (UF3) Pulse Change (PC) SpO2 Drop (SD) I/E Value Apnea Hypopnea Index (AHI) Respiratory Disturbance Index (RDI) 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? هل أنت متأكد أنك تريد استخدام هذا المجلد؟ OSCAR Reminder أوسكار تذكير Don't forget to place your datacard back in your CPAP device 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 oximetry 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) 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) Min EEPAP %1 Max EEPAP %2 PDIFF %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 مدرب النوم الشخصي Selection Length 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 الزفير تخفيف الضغط المستوى Device auto starts by breathing Response Device auto stops by breathing Patient View RiseEnable RiseTime Cycle Trigger TiMax TiMin Your ResMed CPAP device (Model %1) has not been tested yet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card to make sure it works with OSCAR. SmartStart 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 Smart Stop Simple Advanced المتقدمة 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... ارجوك انتظر... Permissive Mode Total disabled sessions: %1, found in %2 days Total disabled sessions: %1 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. Updating Statistics cache تحديث الاحصائيات المخبأ Usage Statistics إحصائيات الاستخدام 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 Löwenstein Prisma Smart Resvent/Hoffrichter iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings Add Add Feature inhibited. The maximum number of Items has been exceeded. creates new copy of current settings. Restore Restores saved settings from selection. Rename Renames the selection. Must edit existing name then press enter. Update Updates the selection with current settings. Delete Deletes the selection. Expanded Help menu. Exits the Layout menu. <h4>Help Menu - Manage Layout Settings</h4> Exits the help menu. Exits the dialog menu. This feature manages the saving and restoring of Layout Settings. Layout Settings control the layout of a graph or chart. Different Layouts Settings can be saved and later restored. Button Description Creates a copy of the current Layout Settings. The default description is the current date. The description may be changed. The Add button will be greyed out when maximum number is reached. Other Buttons Greyed out when there are no selections Loads the Layout Settings from the selection. Automatically exits. io Modify the description of the selection. Same as a double click.io Saves the current Layout Settings to the selection. Prompts for confirmation. Deletes the selecton. Control Exit خروج (Red circle with a white "X".) Returns to OSCAR menu. Return Next to Exit icon. Only in Help Menu. Returns to Layout menu. Escape Key Exit the Help or Layout menu. Layout Settings * Name * Pinning * Plots Enabled * Height * Order * Event Flags * Dotted Lines * Height Options General Information Maximum description size = 80 characters. Maximum Saved Layout Settings = 30. Saved Layout Settings can be accessed by all profiles. Layout Settings only control the layout of a graph or chart. They do not contain any other data. They do not control if a graph is displayed or not. Layout Settings for daily and overview are managed independantly. Maximum number of Items exceeded. No Item Selected Ok to Update? Ok To Delete? SessionBar %1h %2m %1h %2m No Sessions Present لا توجد جلسات حالية SleepStyleLoader Import Error خطأ في الاستيراد This device 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 Device Information Changes to Device Settings Database has No %1 data available. Database has %1 day of %2 Data on %3 Database has %1 days of %2 Data, between %3 and %4 Total Days: %1 Days Not Used: %1 Days Used: %1 الأيام المستخدمة: %1 Days %1 %2 Hours: %3 Best Device Setting Worst Device Setting 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 مفتوح المصدر مجاني 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 إعدادات الضغط 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 device.</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 device is detected First import can take a few minutes. يمكن أن يستغرق الاستيراد الأول بضع دقائق. The last time you used your %1... آخر مرة استخدمت فيها %1 ... last night اخر مساء today %2 days ago %2 منذ أيام was %1 (on %2) كان %1 (في %2) %1 hours, %2 minutes and %3 seconds %1 ساعات ،%2 دقيقة و %3 ثواني <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 pressure was under %1 %2 for %3% of the time. كان ضغطك أقل من %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٪ من الوقت. 1 day ago Your device was on for %1. Your CPAP device used a constant %1 %2 of air Your device used a constant %1-%2 %3 of air. Your device was under %1-%2 %3 for %4% of the time. Your EEPAP pressure was under %1 %2 for %3% of the time. 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 Double click Y-axis: Return to AUTO-FIT Scaling Double click Y-axis: Return to DEFAULT Scaling Double click Y-axis: Return to OVERRIDE Scaling Double click Y-axis: For Dynamic Scaling Double click Y-axis: Select DEFAULT Scaling Double click Y-axis: Select AUTO-FIT Scaling %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.5.1/Translations/Bulgarian.bg.ts000066400000000000000000016765031450332542600210350ustar00rootroot00000000000000 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. За съжаление, Бележки по изданието не се намери. 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 Журнал i К 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 Ако в настройки ръст е над нула, задаване на тегло тук ще се покажи стойността на индекса на телесната маса (ИТМ) Search Търсене Layout Save and Restore Graph Layout Settings 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?? Нула часа?? 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 Device Settings Настройки за апарата <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.) (липсват настройките на Режима и Налягането; показват се вчерашните.) Start Начало End Край This bookmark is in a currently disabled area.. Тази отметка се намира в зона, която в момента е деактивирана.. Session Information Информация за сесия Clinical Mode Disabling Sessions requires the Permissive Mode 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 Време извън рампинг This CPAP device does NOT record detailed data Този CPAP апарат не записва подробни данни no data :( няма данни :( Sorry, this device only provides compliance data. За съжаление, апаратът предоставя само данни за съответствие. "Nothing's here!" "Няма нищо тук!" No data is available for this day. Няма налични данни за този ден. Pick a Colour Избери цвят Bookmark at %1 Отметка в %1 Hide All Events Скрий всички събития Show All Events Покажи всички събития Hide All Graphs Show All Graphs DailySearchTab Clear Bookmark Jumps to Date's Bookmark Start Search DATE Jumps to Date Match Notes Бележки Notes containing Bookmarks Отметки Bookmarks containing AHI Daily Duration Session Duration Days Skipped Disabled Sessions Number of Sessions Click HERE to close Help Help Помощ No Data Jumps to Date's Details Number Disabled Session Jumps to Date's Details Note Jumps to Date's Notes AHI Jumps to Date's Details EventsPerHour Session Duration Jumps to Date's Details Minutes Number of Sessions Jumps to Date's Details Sessions Daily Duration Jumps to Date's Details Hours Number of events Jumps to Date's Events Events Automatic start Continue Search End of Search No Matches Skip:%1 %1/%2%3 days Found %1 Finds days that match specified criteria. Searches from last day to first day. First click on Match Button then select topic. Then click on the operation to modify it. or update the value Topics without operations will automatically start. Compare Operations: numberic or character. Numberic Operations: Character Operations: Summary Line Left:Summary - Number of Day searched Center:Number of Items Found Right:Minimum/Maximum for item searched Result Table Column One: Date of match. Click selects date. Column two: Information. Click selects date. Then Jumps the appropiate tab. Wildcard Pattern Matching: Wildcards use 3 characters: Asterisk Question Mark Backslash. Asterisk matches any number of characters. Question Mark matches a single character. Backslash matches next character. DateErrorDisplay ERROR The start date MUST be before the end date ГРЕШКА Началната дата ТРЯБВА да бъде преди крайната The entered start date %1 is after the end date %2 Въведената начална дата %1 е след крайната %2 Hint: Change the end date first Подсказване: Първо промени крайната дата The entered end date %1 Въведената крайна дата %1 is before the start date %1 е преди началната дата %1 Hint: Change the start date first Подсказване: Първо промени началната дата 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 device 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 Режим на отчет Show Standard Report Standard Стандартен Show Monthly Report Monthly Месечен Show Range Report Date Range Период от време Select Report Date Report Date Statistics Статистики Daily Дневна Overview Общ преглед Oximetry Оксиметрия Import Импорт Help Помощ &File &Файл &View &Преглед &Reset Graphs &Възстановяване на графики &Help &Помощ Troubleshooting Отстраняване на неизправности &Data &Данни &Advanced &Операции за напреднали Purge ALL Device 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 Импортирай данни от &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 - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> Advanced - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> 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 &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 Импорт проблем Please insert your CPAP data card... Моля поставете вашата карта със CPAP данни... CPAP Data Located Данни за CPAP са открити Import Reminder Напомнянe при импорт Find your CPAP data card Намери картата с данни за CPAP No supported data was found The User's Guide will open in your default browser Ръководството на потребителя ще се отвори в браузъра по подразбиране Are you sure you want to rebuild all CPAP data for the following device: Сигурен ли си, че искаш да възстановиш всички данни за CPAP за следния апарат: For some reason, OSCAR does not have any backups for the following device: По някаква причина OSCAR не разполага с бекъп за следния апарат: 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? Сигурни ли сте че желаете да направите това? Would you like to import from your own backups now? (you will have no data visible for this device until you do) Желаеш ли да извършиш импорт от свой собствен бекъп? (няма да виждаш никакви данни за този апарат докато не го направиш) Note as a precaution, the backup folder will be left in place. За всеки случай бекъп папката ще бъде оставена. OSCAR does not have any backups for this device! OSCAR не разполага с никой бекъп за този апарат! Unless you have made <i>your <b>own</b> backups for ALL of your data for this device</i>, <font size=+2>you will lose this device'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 device database for the following device:</p> На път си да <font size=+2>изтриеш</font>базите данни на OSCAR за следното устройство:</p> Are you <b>absolutely sure</b> you want to proceed? <b>Абсолютно ли сте сигурни</b> че желаете да продължите? A file permission error caused the purge process to fail; you will have to delete the following folder manually: 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 Импортирането на данните приключи You must select and open the profile you wish to modify %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. Ако можеш да прочетеш това, означава, че командата за рестартиране не е сработила. Ще трябва да го направиш сам ръчно. No profile has been selected for Import. Не е избран профил за импортиране. %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. Моля, не забрави да избереш главната папка или буквата на устройството на картата с данни, а не папка в нея. Check for updates not implemented Проверка за обновления не е внедрена Choose where to save screenshot Избери къде да запишеш снимка на екрана Image files (*.png) Файлове с изображения (*.png) Please note, that this could result in loss of data if OSCAR's backups have been disabled. Моля, имай предвид, че това може да доведе до загуба на данни, ако бекъп на OSCAR са били деактивирани. 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) Компресирани файлове (*.zip) Creating zip... Създаване на компресирания файл... Calculating size... Изчисляване на размера... Reporting issues is not yet implemented Докладването на проблеми все още не е внедрено OSCAR Information Информацията за OSCAR 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 Device Data at %1 Не се намерят валидни данни за устройството на %1 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 Изберете държава 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 Марк Уаткинс и порциите са авторско право &copy;2019-2022 Екипа на OSCAR 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 Devices and related equipment. Този софтуер е създаден, за да ти помогне да прегледаш данните, получени от твоите устройства 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 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. Авторите няма да носят отговорност за <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 Анулиране на изгледа до избрания период от време Layout Save and Restore Graph Layout Settings 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) Hide All Graphs Show 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> 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. Желая да използвам времето подадено от вградения часовник на моя оксиметър. <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 &Информационна страница 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 I started this oximeter recording at (or near) the same time as a session on my CPAP device. <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 Игнориране на кратки сесии 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 часа Flow Restriction Дихателнo ограничение Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. Процент на дихателното ограничение от средната стойност. Стойност 20% е подходяща за откриване на апнеи. Duration of airflow restriction Продължителност на дихателното ограничение s сек Event Duration Продължителност на събитие 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 Показване графика с разбивка на събитията 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.) <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 събития 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 Маркиране при пулс под 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. Бележка: Използва се метод на линейни изчисления. Промяната на тези стойности изискват преизчисляване. Tooltip Timeout Време на показване на подсказка Graph Tooltips Подсказки в графики Top Markers Топ маркери <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> 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 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 <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></p></body></html> l/min <html><head/><body><p>Cumulative Indices</p></body></html> Oximetry Settings <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</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 Позволяване на мащабиране по ос/абциса Include Serial Number Graphics Engine (Requires Restart) This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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>Provide an alert when importing data from any device model that has not yet been tested by OSCAR developers.</p></body></html> Warn when importing data from an untested device This calculation requires Total Leaks data to be provided by the CPAP device. (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. Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device missed. This option must be enabled before import, otherwise a purge is required. This experimental option attempts to use OSCAR's event flagging system to improve device detected event positioning. Resync Device Detected Events (Experimental) Allow duplicates near device events. Show flags for device detected events that haven't been identified yet. <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices do not support changing these settings.</p></body></html> Clinical Clinical Settings Select Oscar Operating Mode Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. Clinical Mode permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. Permissive Mode Hours <!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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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 device serial number on device settings changes report Print reports in black and white, which can be more legible on non-color printers Print reports in black and white (monochrome) For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. Enables SessionBar in Event Flags Graph 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. Направете двойно кликане с мишката за да смените цвят за графика/маркер/данни в този канал. Overview Общ преглед No CPAP devices detected Will you be using a ResMed brand device? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, days will <b>split at noon</b> like in ResMed's commercial software.</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 Необходим е рестарт ResMed S9 devices routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). 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 Флаг Clinical Mode: Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. Basically replicates the reports and data stored on the devices data card. This includes pap devices, oximeters, etc. Compliance reports fall under this mode. Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. Permissive Mode: Allows user to select which data sets/ sessions to be used for calculations and display. Additional charts and calculations may be available that are not available from the vendor data. Changing the Oscar Operating Mode: Requires a reload of the user's profile. Data will be saved and restored. Minor Flag Минимален маркер Span Разделяне Always Minor Постоянен маркер 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 Това може би не е добра идея 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 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 Length: %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 EEPAP Min EEPAP Max EEPAP 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 Device Min IPAP Мин IPAP App key: Operating system: Операционна система: Built with Qt %1 on %2 Graphics Engine: Graphics Engine type: Compiler: Software Engine ANGLE / OpenGLES Desktop OpenGL m m cm cm in kg Minutes минути Seconds секунди milliSeconds h ч m м s с ms мс Events/hr събития/час Hz Hz l/min 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 Серия 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 device's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Non Data Capable Device Your %1 CPAP Device (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 device. Device Untested Your %1 CPAP Device (Model %2) has not been tested yet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Device Unsupported Sorry, your %1 CPAP Device (%2) is not supported yet. The developers need a .zip copy of this device's SD card and matching clinician .pdf reports to make it work with OSCAR. Getting Ready... Scanning Files... Importing Sessions... UNKNOWN APAP (std) APAP (dyn) Auto S Auto S/T AcSV SoftPAP Mode Pressure relief during exhalation Slight Softstart pressure Pressure during soft start period PSoft Softstart minimum pressure Minimum pressure during soft start period PSoftMin Auto start Automatically turn on the device by breathing Softstart time Lenght of soft start period Soft start maximum time Maximum lenght of soft start period Soft start max. time Soft start pressure Higher End Expiratory Pressure Humidifier level Tube type Obstruction level Obstruction level in percentage rRMVFluctuation Relative respiratory minute volume fluctuation Relative respiratory minute volume Measured pressure Full flow Artefact Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) Epoch (2 mins) with Flow Limitation Deep Sleep Deep sleep, stable respiration Timed breath BiSoft Mode BiSoft 1 BiSoft 2 TriLevel PMaxOA EEPAPMin Lower End Expiratory Pressure EEPAPMax rRMV ART CriticalLeak Mask leakage is above a critical treshold CL eMO Epoch (2 mins) with Mild Obstruction eSO Epoch (2 mins) with Severe Obstruction eS Epoch (2 mins) with Snoring eFL DS Finishing up... Untested Data 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 A few breaths automatically starts device Device automatically switches off Whether or not device allows Mask checking. 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 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 A period during a session where the device could not detect flow. Peak Flow Peak flow during a 2-minute interval 22mm 22мм Backing Up Files... model %1 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 Автоматично включване Auto Off Автоматично изключване Mask Alert Предупреждение за маска Show AHI Покажи AHI Whether or not device shows AHI via built-in display. The number of days in the Auto-CPAP trial period, after which the device will revert to CPAP Breathing Not Detected BND BND Timed Breath Периодично дишане Machine Initiated Breath Дишане инициирано от апарата TB TB Windows User Уиндоус потребител Using , found SleepyHead - You must run the OSCAR Migration Tool 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> <i>Your old device 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. 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? Device Database Changes Sorry, the purge operation failed, which means this version of OSCAR can't start. The device data folder needs to be removed manually. 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. OSCAR %1 needs to upgrade its database for %2 %3 %4 This means you will need to import this device data again afterwards from your own backups or data card. Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. 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? Сигурни ли сте, че желаете да използвате тази папка? OSCAR Reminder Don't forget to place your datacard back in your CPAP device 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 oximetry 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 Абнормален период на Чейн-Стоксово дишане Cheyne Stokes Respiration (CSR) Periodic Breathing (PB) Clear Airway (CA) Obstructive Apnea (OA) Hypopnea (H) An apnea that couldn't be determined as Central or Obstructive. Unclassified Apnea (UA) Apnea (A) A restriction in breathing from normal, causing a flattening of the flow waveform. Flow Limitation (FL) RERA (RE) Vibratory Snore (VS) Vibratory Snore (VS2) Вибраторно хъркане (VS2) Leak Flag (LF) A large mask leak affecting device performance. Large Leak (LL) Non Responding Event (NR) Expiratory Puff (EP) SensAwake (SA) User Flag #1 (UF1) User Flag #2 (UF2) User Flag #3 (UF3) Pulse Change (PC) SpO2 Drop (SD) A ResMed data item: Trigger Cycle Event Apnea Hypopnea Index (AHI) Respiratory Disturbance Index (RDI) 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 Апнея причинена поради обструкция на горните дихателни пътища A partially obstructed airway Частична обструкция на горните дихателни пътища UA UA A vibratory snore Вибраторно хъркане Pressure Pulse Пулсиращо налягане A pulse of pressure 'pinged' to detect a closed airway. Пулсиращо налягане, което 'ping-ва' за да засече обструкция в дихателния път. Large Leak Големи течове LL LL A type of respiratory event that won't respond to a pressure increase. Тип респираторно събитие, което не реагира на увеличение на налягането. Intellipap event where you breathe out your mouth. Събитието на Intellipap, когато се издишва през уста. SensAwake feature will reduce pressure when waking is detected. Функцията SensAwake ще намали налягането, когато засече събуждане. Heart rate in beats per minute Сърдечен пулс в удари за минута Blood-oxygen saturation percentage Процент кислородна сатурация Plethysomogram Плетизмограма An optical Photo-plethysomogram showing heart rhythm Оптична фото-плетизмограма показваща сърдечен ритъм A sudden (user definable) change in heart rate Внезапна (може да се укаже от потребител) промяна в сърдечния ритъм A sudden (user definable) drop in blood oxygen saturation Внезапен (може да се укаже от потребител) спад в кислородната сатурация SD SD Breathing flow rate waveform Вълна на дебита на дишане 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 CSR CSR An abnormal period of Periodic Breathing Абнормален период на периодично вдишване 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 Макс теч 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 Средни течове 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. End Expiratory Pressure An apnea reported by your CPAP device. Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. A vibratory snore as detected by a System One device I/E Value 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) 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) Min EEPAP %1 Max EEPAP %2 PDIFF %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 Степен на облекчение на налягането при издишане Device auto starts by breathing Response Device auto stops by breathing Patient View RiseEnable RiseTime Cycle Trigger TiMax TiMin Your ResMed CPAP device (Model %1) has not been tested yet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card to make sure it works with OSCAR. SmartStart SmartStart 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 Smart Stop Simple Advanced Разширено Parsing STR.edf records... Auto Автоматичен Mask Маска ResMed Mask Setting Настройки маска ResMed Pillows Възглавнички Full Face Фул фейс Nasal Назална Ramp Enable Включен рампинг Selection Length 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... Permissive Mode Total disabled sessions: %1, found in %2 days Total disabled sessions: %1 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. Updating Statistics cache Usage Statistics Статистики за употреба 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 Löwenstein Prisma Smart Resvent/Hoffrichter iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings Add Add Feature inhibited. The maximum number of Items has been exceeded. creates new copy of current settings. Restore Restores saved settings from selection. Rename Renames the selection. Must edit existing name then press enter. Update Updates the selection with current settings. Delete Deletes the selection. Expanded Help menu. Exits the Layout menu. <h4>Help Menu - Manage Layout Settings</h4> Exits the help menu. Exits the dialog menu. This feature manages the saving and restoring of Layout Settings. Layout Settings control the layout of a graph or chart. Different Layouts Settings can be saved and later restored. Button Description Creates a copy of the current Layout Settings. The default description is the current date. The description may be changed. The Add button will be greyed out when maximum number is reached. Other Buttons Greyed out when there are no selections Loads the Layout Settings from the selection. Automatically exits. io Modify the description of the selection. Same as a double click.io Saves the current Layout Settings to the selection. Prompts for confirmation. Deletes the selecton. Control Exit Изход (Red circle with a white "X".) Returns to OSCAR menu. Return Next to Exit icon. Only in Help Menu. Returns to Layout menu. Escape Key Exit the Help or Layout menu. Layout Settings * Name * Pinning * Plots Enabled * Height * Order * Event Flags * Dotted Lines * Height Options General Information Maximum description size = 80 characters. Maximum Saved Layout Settings = 30. Saved Layout Settings can be accessed by all profiles. Layout Settings only control the layout of a graph or chart. They do not contain any other data. They do not control if a graph is displayed or not. Layout Settings for daily and overview are managed independantly. Maximum number of Items exceeded. No Item Selected Ok to Update? Ok To Delete? SessionBar %1h %2m %1h %2m No Sessions Present Няма налични сесии SleepStyleLoader Import Error Грешка при импортиране This device 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 Device Information Changes to Device Settings Oscar has no data to report :( Database has No %1 data available. Database has %1 day of %2 Data on %3 Database has %1 days of %2 Data, between %3 and %4 Total Days: %1 Days Not Used: %1 Days Used: %1 Дни употреба: %1 Days %1 %2 Hours: %3 Best Device Setting Worst Device Setting 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 Последна седмица 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 Настройки налягане 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 device.</span></p></body></html> <span style=" font-weight:600;">Предупреждение: </span><span style=" color:#ff0000;">Карти памет за 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 device is detected Забележи, че някои настройки са принудени, когато се открие ResMed устройство First import can take a few minutes. Първият импорт може да отнеме няколко минути. The last time you used your %1... Последният път, когато сте използвали %1 апарата си... last night снощи today %2 days ago преди %2 дни was %1 (on %2) е било %1 (в %2) %1 hours, %2 minutes and %3 seconds %1 часа, %2 минути и %3 секунди <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 pressure was under %1 %2 for %3% of the time. Налягането ви е било под %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% от времето. 1 day ago преди един ден Your device was on for %1. Устройството ти работи за %1. Your CPAP device used a constant %1 %2 of air Твоето CPAP устройство използваше постоянно %1 %2 от въздух Your device used a constant %1-%2 %3 of air. Устройството ти използваше постоянно %1-%2 %3 от въздух. Your device was under %1-%2 %3 for %4% of the time. Устройството ти беше под %1-%2 %3 през %4% от времето. Your EEPAP pressure was under %1 %2 for %3% of the time. 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 Double click Y-axis: Return to AUTO-FIT Scaling Double click Y-axis: Return to DEFAULT Scaling Double click Y-axis: Return to OVERRIDE Scaling Double click Y-axis: For Dynamic Scaling Double click Y-axis: Select DEFAULT Scaling Double click Y-axis: Select AUTO-FIT Scaling %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.5.1/Translations/Chinese.zh_CN.ts000066400000000000000000015730501450332542600211110ustar00rootroot00000000000000 AboutDialog &About &关于 Release Notes 版本注释 Credits 信任 GPL License GPL许可证 Close 关闭 Show data folder 显示数据文件夹 Sorry, could not locate About file. 抱歉,无法找到相关文件. Sorry, could not locate Credits file. 抱歉,无法找到信用证书. 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 备注 i 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 跳转到最近一天的数据记录 B.M.I. 体重指数. Sleep Stage Sessions 睡眠阶段会话 Oximeter Information 血氧仪信息 Events 事件 CPAP Sessions CPAP会话 Medium Starts 开始 Weight 体重 Zombie 极差 Bookmarks 标记簇 %1 events %1 事件 events 事件 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 未知会话 This CPAP device does NOT record detailed data 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 Clinical Mode Disabling Sessions requires the Permissive Mode Total time in apnea 呼吸暂停总时间 Total ramp time 斜坡升压总时间 Time outside of ramp 斜坡升压之外的时间 "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 ... 我感觉... Search Layout Save and Restore Graph Layout Settings 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 Device Settings PAP Mode: %1 PAP模式: %1 Start 开始 End 结束 Unable to display Pie Chart on this system 无法在此系统上显示饼图 No data is available for this day. 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. no data :( Sorry, this device only provides compliance data. This bookmark is in a currently disabled area.. (Mode and Pressure settings missing; yesterday's shown.) Hide All Events 隐藏所有事件 Show All Events 显示所有事件 Hide All Graphs Show All Graphs DailySearchTab Clear Bookmark Jumps to Date's Bookmark Start Search DATE Jumps to Date Match Notes 备注 Notes containing Bookmarks 标记簇 Bookmarks containing AHI Daily Duration Session Duration Days Skipped Disabled Sessions Number of Sessions Click HERE to close Help Help 帮助 No Data Jumps to Date's Details Number Disabled Session Jumps to Date's Details Note Jumps to Date's Notes AHI Jumps to Date's Details EventsPerHour Session Duration Jumps to Date's Details Minutes Number of Sessions Jumps to Date's Details Sessions Daily Duration Jumps to Date's Details Hours Number of events Jumps to Date's Events Events Automatic start Continue Search End of Search No Matches Skip:%1 %1/%2%3 days %1天 {1/%2%3 ?} Found %1 Finds days that match specified criteria. Searches from last day to first day. First click on Match Button then select topic. Then click on the operation to modify it. or update the value Topics without operations will automatically start. Compare Operations: numberic or character. Numberic Operations: Character Operations: Summary Line Left:Summary - Number of Day searched Center:Number of Items Found Right:Minimum/Maximum for item searched Result Table Column One: Date of match. Click selects date. Column two: Information. Click selects date. Then Jumps the appropiate tab. Wildcard Pattern Matching: Wildcards use 3 characters: Asterisk Question Mark Backslash. Asterisk matches any number of characters. Question Mark matches a single character. Backslash matches next character. DateErrorDisplay ERROR The start date MUST be before the end date The entered start date %1 is after the end date %2 Hint: Change the end date first The entered end date %1 is before the start date %1 Hint: Change the start date first 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 device 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 导入错误 Couldn't find any valid Device Data at %1 Choose a folder 选择一个文件夹 No supported data was found Are you sure you want to rebuild all CPAP data for the following device: For some reason, OSCAR does not have any backups for the following device: Would you like to import from your own backups now? (you will have no data visible for this device until you do) OSCAR does not have any backups for this device! Unless you have made <i>your <b>own</b> backups for ALL of your data for this device</i>, <font size=+2>you will lose this device's data <b>permanently</b>!</font> You are about to <font size=+2>obliterate</font> OSCAR's device database for the following device:</p> A file permission error caused the purge process to fail; you will have to delete the following folder manually: 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. 由于没有可用的内部备份可供重建使用,请自行从备份中还原。 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 存档 Purge ALL Device Data 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 最新 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. 重启命令不起作用,需要手动重启。 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 Troubleshooting Show Standard Report Show Monthly Report Show Range Report Select Report Date Report Date &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) You must select and open the profile you wish to modify 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 Standard - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> Advanced - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> 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 选择国家 This software is being designed to assist you in reviewing the data produced by your CPAP Devices and related equipment. 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 用户名 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 呼吸 紊乱 指数 Reset view to selected date range 将视图重置为所选日期范围 Layout Save and Restore Graph Layout Settings 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 去年 Last Two Months 前两个月 Total Time in Apnea 呼吸暂停总时间 Total Time in Apnea (Minutes) 呼吸暂停总时间 (分钟) Snapshot Hide All Graphs Show 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> <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. 使用血氧仪的时间作为系统时钟. <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 &信息页 I started this oximeter recording at (or near) the same time as a session on my CPAP device. &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... 正在等待设备开始上传数据... 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> ) <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 图表高度 For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. Enables SessionBar in Event Flags Graph 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. 天. 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 归零 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 跳过无数据的日期 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 气流限制的持续时间 <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices do not support changing these settings.</p></body></html> 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> 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 在事件分类饼图中显示 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> 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. 双击改变这个区块/标记/数据的默认颜色. Clinical Mode: Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. Basically replicates the reports and data stored on the devices data card. This includes pap devices, oximeters, etc. Compliance reports fall under this mode. Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. Permissive Mode: Allows user to select which data sets/ sessions to be used for calculations and display. Additional charts and calculations may be available that are not available from the vendor data. Changing the Oscar Operating Mode: Requires a reload of the user's profile. Data will be saved and restored. No CPAP devices detected Will you be using a ResMed brand device? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, days will <b>split at noon</b> like in ResMed's commercial software.</p> 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> 阈值来进行某些计算 ResMed S9 devices routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). 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> This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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) 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 无需确认直接导入 <html><head/><body><p>Provide an alert when importing data from any device model that has not yet been tested by OSCAR developers.</p></body></html> Warn when importing data from an untested device This calculation requires Total Leaks data to be provided by the CPAP device. (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. Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device missed. This option must be enabled before import, otherwise a purge is required. This experimental option attempts to use OSCAR's event flagging system to improve device detected event positioning. Resync Device Detected Events (Experimental) Allow duplicates near device events. General CPAP and Related Settings 通用呼吸机以及相关设置 Show flags for device detected events that haven't been identified yet. 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. 是否显示此波形的细分概览。 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. 注意:默认选用线性计算法。如果更改数据需重新计算. 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> Clinical Clinical Settings Select Oscar Operating Mode Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. Clinical Mode permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. Permissive Mode Hours 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" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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数据量变小,但日期变化较慢。) <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> 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)更改此设置。 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 Include Serial Number <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><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> Warn when previously unseen data is encountered <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</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 Whether to include device serial number on device settings changes report 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 大量漏气 O2 氧气 OA 阻塞性 NR 未响应事件 PB 周期性呼吸 PC 混合面罩 Compiler: in kg EEPAP Min EEPAP Max EEPAP PP 最高压力 PS 压力 Device 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 压力上升 Hours 小时 Leaks 漏气率 Model 型式 Phone 电话号码 Ready 就绪 W-Avg W-Avg Snore 鼾声 Start 开始 Usage 使用 cmH2O 厘米水柱 Pressure Support 压力支持 ratio 比率 Tidal Volume 呼吸容量 Entire Day 整天 Heart rate in beats per minute 心脏每分钟的跳动次数 Pat. Trig. Breath 患者触发呼吸 Ramp Delay Period 斜坡升压期间 Sleep Stage 睡眠阶段 Minute Vent. 分钟通气率. 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 一次振动打鼾 Lower Inspiratory Pressure 更低的吸气压力 Resp. Rate 呼吸速率 Insp. Time 吸气时间 Exp. Time 呼气时间 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? 是否希望在报告中显示标记区域? Patient Triggered Breaths 患者出发的呼吸 Events 事件 (% %1 in events) (% %1 事件) No Data 无数据 Page %1 of %2 页码 %1 到 %2 Median 中值 PS Max 压力支持最大压力 PS Min 最小压力 End Expiratory Pressure Cheyne Stokes Respiration (CSR) Periodic Breathing (PB) Clear Airway (CA) Obstructive Apnea (OA) Hypopnea (H) Unclassified Apnea (UA) Apnea (A) Flow Limitation (FL) RERA (RE) Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. Vibratory Snore (VS) Leak Flag (LF) A large mask leak affecting device performance. Large Leak (LL) Non Responding Event (NR) Expiratory Puff (EP) SensAwake (SA) User Flag #1 (UF1) User Flag #2 (UF2) User Flag #3 (UF3) Pulse Change (PC) SpO2 Drop (SD) I/E Value Flow Limit. 气流限制. Apnea Hypopnea Index (AHI) 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. 通过压力脉冲'砰'可以侦测到气道关闭. 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 device's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Non Data Capable Device Your %1 CPAP Device (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 device. Device Untested Your %1 CPAP Device (Model %2) has not been tested yet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Device Unsupported Sorry, your %1 CPAP Device (%2) is not supported yet. The developers need a .zip copy of this device's SD card and matching clinician .pdf reports to make it work with OSCAR. Plethysomogram 体积描述术 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 最大漏气率 REI=%1 VSI=%2 FLI=%3 PB/CSR=%4%% 呼吸作用指数=%1 鼾声指数=%2 气流受限指数=%3 周期性呼吸/潮湿呼吸=%4%% Median rate of detected mask leakage 面罩漏气率的中间值 Mask Pressure 面罩压力 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 呼气时间 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 面罩的最大漏气率 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 milliSeconds Events/hr 事件/小时 Hz Hz l/min 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. 未找到视窗浏览器的可执行文件. Important: 重要提示: This folder currently resides at the following location: 本地文档位置: Rebuilding from %1 Backup 由%1备份重建中 Vibratory Snore (VS2) 呼吸 频率 (呼吸次数/分钟) A vibratory snore as detected by a System One device 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) Min EEPAP %1 Max EEPAP %2 PDIFF %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 自动打开 Auto Off 自动关闭 Mask Alert 面罩报警 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 最大压力 An apnea reported by your CPAP device. 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? 确定将所有通道颜色恢复默认设置吗? Are you sure you want to reset all your oximetry 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 % 灌注指数 % Respiratory Disturbance Index (RDI) APAP (Variable) APAP(自动) Zero 0 Upper Threshold 增加 Lower Threshold 降低 Snapshot %1 快照 %1 Selection Length (%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 Length: %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 CPAP Mode CPAP模式 VPAPauto VPAP全自动 ASVAuto ASV全自动 Auto for Her Auto for Her CSR CSR It is likely that doing this will cause data corruption, are you sure you want to do this? 此操作会损坏数据,是否继续? 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 潮式呼吸的不正常时期 An abnormal period of Periodic Breathing 周期性呼吸的不正常时期 SmartStart 自启动 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 手动 RiseEnable RiseTime Cycle Trigger TiMax TiMin Your ResMed CPAP device (Model %1) has not been tested yet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card to make sure it works with OSCAR. 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... 导入会话... UNKNOWN APAP (std) APAP (dyn) Auto S Auto S/T AcSV SoftPAP Mode Pressure relief during exhalation Slight Softstart pressure Pressure during soft start period PSoft Softstart minimum pressure Minimum pressure during soft start period PSoftMin Auto start Automatically turn on the device by breathing Softstart time Lenght of soft start period Soft start maximum time Maximum lenght of soft start period Soft start max. time Soft start pressure Higher End Expiratory Pressure Humidifier level Tube type Obstruction level Obstruction level in percentage rRMVFluctuation Relative respiratory minute volume fluctuation Relative respiratory minute volume Measured pressure Full flow Artefact Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) Epoch (2 mins) with Flow Limitation Deep Sleep Deep sleep, stable respiration Timed breath BiSoft Mode BiSoft 1 BiSoft 2 TriLevel PMaxOA EEPAPMin Lower End Expiratory Pressure EEPAPMax rRMV ART CriticalLeak Mask leakage is above a critical treshold CL eMO Epoch (2 mins) with Mild Obstruction eSO Epoch (2 mins) with Severe Obstruction eS Epoch (2 mins) with Snoring eFL DS Finishing up... 整理中... Breathing Not Detected 呼吸未被检测到 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将会使用其中的第一个: <b>OSCAR maintains a backup of your devices data card that it uses for this purpose.</b> <b>OSCAR保留了设备数据卡的备份</b> <i>Your old device 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. OSCAR尚未为此设备存储任何备份。 This means you will need to import this device data again afterwards from your own backups or data card. 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? Device Database Changes Sorry, the purge operation failed, which means this version of OSCAR can't start. 抱歉,清除操作失败,此版本的OSCAR无法启动。 The device data folder needs to be removed manually. 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提醒 Don't forget to place your datacard back in your CPAP device 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: Data directory: Permissive Mode Total disabled sessions: %1, found in %2 days Total disabled sessions: %1 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. 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 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 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 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 A few breaths automatically starts device Device automatically switches off Whether or not device allows Mask checking. Whether or not device shows AHI via built-in display. The number of days in the Auto-CPAP trial period, after which the device will revert to CPAP A period during a session where the device could not detect flow. 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 varies Backing up files... Reading data files... Snoring event. SN model %1 unknown model iVAPS Soft Standard 标准 BiPAP-T BiPAP-S BiPAP-S/T PAC Device auto starts by breathing SmartStop Smart Stop Device auto stops by breathing Simple Advanced Humidity SleepStyle 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 Löwenstein Prisma Smart Resvent/Hoffrichter iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings Add Add Feature inhibited. The maximum number of Items has been exceeded. creates new copy of current settings. Restore Restores saved settings from selection. Rename Renames the selection. Must edit existing name then press enter. Update Updates the selection with current settings. Delete Deletes the selection. Expanded Help menu. Exits the Layout menu. <h4>Help Menu - Manage Layout Settings</h4> Exits the help menu. Exits the dialog menu. This feature manages the saving and restoring of Layout Settings. Layout Settings control the layout of a graph or chart. Different Layouts Settings can be saved and later restored. Button Description Creates a copy of the current Layout Settings. The default description is the current date. The description may be changed. The Add button will be greyed out when maximum number is reached. Other Buttons Greyed out when there are no selections Loads the Layout Settings from the selection. Automatically exits. io Modify the description of the selection. Same as a double click.io Saves the current Layout Settings to the selection. Prompts for confirmation. Deletes the selecton. Control Exit 退出 (Red circle with a white "X".) Returns to OSCAR menu. Return Next to Exit icon. Only in Help Menu. Returns to Layout menu. Escape Key Exit the Help or Layout menu. Layout Settings * Name * Pinning * Plots Enabled * Height * Order * Event Flags * Dotted Lines * Height Options General Information Maximum description size = 80 characters. Maximum Saved Layout Settings = 30. Saved Layout Settings can be accessed by all profiles. Layout Settings only control the layout of a graph or chart. They do not contain any other data. They do not control if a graph is displayed or not. Layout Settings for daily and overview are managed independantly. Maximum number of Items exceeded. No Item Selected Ok to Update? Ok To Delete? SessionBar No Sessions Present 没有会话 %1h %2m %1% %2m SleepStyleLoader Import Error 导入出错 This device 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 上一个会话 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: 地址: Device Information Changes to Device Settings Database has No %1 data available. Database has %1 day of %2 Data on %3 Database has %1 days of %2 Data, between %3 and %4 Total Days: %1 Days Not Used: %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 最差的潮式呼吸 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. 请在属性选单中选中预调取汇总信息选项. Worst Device Setting 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) No data found?!? Days %1 %2 Hours: %3 Best Device Setting 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 统计 <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 device.</span></p></body></html> Note that some preferences are forced when a ResMed device is detected First import can take a few minutes. 第一次导入将会花费数分钟. The last time you used your %1... 上一次您使用您的%1... last night 上一晚 today %2 days ago %2 天以前 was %1 (on %2) 是 %1 (在 %2) %1 hours, %2 minutes and %3 seconds %1 小时, %2分钟 %3 秒 Your device was on for %1. Your CPAP device used a constant %1 %2 of air Your device used a constant %1-%2 %3 of air. Your device was under %1-%2 %3 for %4% of the time. <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 EEPAP pressure was under %1 %2 for %3% of the time. 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. 因为有些选项会影响导入. Welcome to the Open Source CPAP Analysis Reporter 欢迎使用开源CPAP分析报告 Your pressure was under %1 %2 for %3% of the time. 压力低于 %1 %2 ,持续时间%3%. 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%. 1 day ago gGraph Double click Y-axis: Return to AUTO-FIT Scaling Double click Y-axis: Return to DEFAULT Scaling Double click Y-axis: Return to OVERRIDE Scaling Double click Y-axis: For Dynamic Scaling Double click Y-axis: Select DEFAULT Scaling Double click Y-axis: Select AUTO-FIT Scaling %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.5.1/Translations/Chinese.zh_TW.ts000066400000000000000000016127241450332542600211450ustar00rootroot00000000000000 AboutDialog Sorry, could not locate About file. 歹勢,找不到相關檔案。 Close 關閉 &About &關於 GPL License GPL授權 About OSCAR %1 關於OSCAR %1 Sorry, could not locate Credits file. 歹勢,找不到此志工名單。 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 結束 Oximetry Sessions 血氧飽和度監測時段 Color 顏色 Search 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 移至最近一天的數據資料 B.M.I. 身體質量指數 Sleep Stage Sessions 睡眠狀態療程 Oximeter Information 血氧飽和濃度器資訊 Events 重點事件 CPAP Sessions PAP 療程 Medium Starts 開始 Weight 體重 i Zombie 遲鈍 If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value 如果之前你已輸入身高,在此輸入體重便會自動計算出身體質量指數 Bookmarks 書籤集 Layout Save and Restore Graph Layout Settings Session End Times 療程結束次數 enable 啟用 %1 events %1 重點事件 events 重點事件 Event Breakdown 重點事件解析 UF1 UF2 Click to %1 this session. 點擊以 %1 這個療程. %1 Session #%2 %1節%2 %1h %2m %3s %1小時%2分鐘%3秒 Device Settings 本機設定 <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.) "Nothing's here!" "這裡啥都沒有!" Awesome 美賣,讚喔 Pulse Change events 脈搏變化重點事件 SpO2 Baseline Used 血氧飽和度採用基準 Zero hours?? 零小時?? Go to the previous day 移至前一天 Details 詳細說明 Time over leak redline 漏氣超標的計時 disable 停用 Clinical Mode Disabling Sessions requires the Permissive Mode This CPAP device does NOT record detailed data 此設備無法記錄詳細數據 no data :( 無數據:( Sorry, this device only provides compliance data. 此設備只提供概括數據。 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 療程啟動次數 Hide All Events 隐藏所有事件 Show All Events 顯示所有事件 Hide All Graphs Show All Graphs DailySearchTab Clear Bookmark Jumps to Date's Bookmark Start Search DATE Jumps to Date Match Notes Notes containing Bookmarks Bookmarks containing AHI Daily Duration Session Duration Days Skipped Disabled Sessions Number of Sessions Click HERE to close Help Help 帮助 No Data Jumps to Date's Details Number Disabled Session Jumps to Date's Details Note Jumps to Date's Notes AHI Jumps to Date's Details EventsPerHour Session Duration Jumps to Date's Details Minutes Number of Sessions Jumps to Date's Details Sessions Daily Duration Jumps to Date's Details Hours Number of events Jumps to Date's Events Events Automatic start Continue Search End of Search No Matches Skip:%1 %1/%2%3 days %1天 {1/%2%3 ?} Found %1 Finds days that match specified criteria. Searches from last day to first day. First click on Match Button then select topic. Then click on the operation to modify it. or update the value Topics without operations will automatically start. Compare Operations: numberic or character. Numberic Operations: Character Operations: Summary Line Left:Summary - Number of Day searched Center:Number of Items Found Right:Minimum/Maximum for item searched Result Table Column One: Date of match. Click selects date. Column two: Information. Click selects date. Then Jumps the appropiate tab. Wildcard Pattern Matching: Wildcards use 3 characters: Asterisk Question Mark Backslash. Asterisk matches any number of characters. Question Mark matches a single character. Backslash matches next character. DateErrorDisplay ERROR The start date MUST be before the end date The entered start date %1 is after the end date %2 Hint: Change the end date first The entered end date %1 is before the start date %1 Hint: Change the start date first 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 Import Error 匯入出錯 The Day records overlap with already existing content. 本日的資料已覆蓋已儲存的内容. This device Record cannot be imported in this profile. 此設備的記錄無法匯入此個人檔案。 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) Couldn't find any valid Device Data at %1 無法找到任何可用數據%1 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 尋找你的CPAP數據卡 Importing Data 正在匯入資料 Online Users &Guide 在線&指南 View &Welcome 查看&欢迎 Show Right Sidebar 顯示右側邊欄 Show Statistics view 顯示統計 Import &Dreem Data 匯入&Dreem數據 Import &Viatom/Wellue Data 匯入&Viatom/Wellue數據 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資料 Daily Sidebar 每日侧边栏 Note as a precaution, the backup folder will be left in place. 請注意:請將備份資料夾保留在合适的位置。 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. 由於没有可用的内部備份可供重建使用,請自行從備份中还原。 Please wait, importing from backup folder(s)... 請稍等,正在由備份資料夾匯入... No supported data was found Check for updates not implemented 檢查是否有未公開更新 Choose where to save screenshot 選擇存放截取熒幕的路徑 Image files (*.png) 圖片檔案(*.png) The User's Guide will open in your default browser 用戶指南會在預設瀏覽器開啟 Are you sure you want to rebuild all CPAP data for the following device: 確定要重建下列所有CPAP數據: Please note, that this could result in loss of data if OSCAR's backups have been disabled. 請注意,如果停用了OSCAR's備份,這可能會導致資料丢失。 A file permission error caused the purge process to fail; you will have to delete the following folder manually: 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内的血氧儀資料吗 You must select and open the profile you wish to modify OSCAR Information OSCAR資訊 O&ximetry Wizard &血氧儀安裝小幫手 Bookmarks 標記簇 Right &Sidebar 右&侧边栏 Rebuild CPAP Data 重建資料 The FAQ is not yet implemented FAQ尚未施行 XML Files (*.xml) XML文件 (*.xml) Export review is not yet implemented 匯出检查不可用 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 將OSCAR診斷記錄添加至壓縮文件 Create zip of all OSCAR data 將所有OSCAR數據添加至壓縮 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 For some reason, OSCAR does not have any backups for the following device: 因為某些原因,OSCAR無法對以下設備提供任何支援: Would you like to import from your own backups now? (you will have no data visible for this device until you do) 您希望立即從備份匯入吗?(完成匯入,才能有資料顯示) OSCAR does not have any backups for this device! OSCAR尚未為此设备儲存任何備份! Unless you have made <i>your <b>own</b> backups for ALL of your data for this device</i>, <font size=+2>you will lose this device's data <b>permanently</b>!</font> 請備份!你的數據將永久刪除 You are about to <font size=+2>obliterate</font> OSCAR's device database for the following device:</p> 你將永久刪除OSCAR中以下設備的數據庫 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 報告问题不可用 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 列印&報告 Export for Review 匯出查看 Take &Screenshot &截圖 Show Standard Report Show Monthly Report Show Range Report Select Report Date Report Date Overview 總覽 &Reset Graphs &重設圖表 Troubleshooting 故障排除 &Import CPAP Card Data &匯入CPAP數據卡 Show Daily view 顯示每日觀察 Show Overview view 顯示概況總覽 &Maximize Toggle &最大化切換 Maximize window 最大化窗口 Show Debug Pane 顯示调试面板 Reset Graph &Heights 重設圖表&高度 Reset sizes of graphs 重設圖表大小 &Edit Profile &編輯個人檔案 Standard - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> Advanced - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> &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 &参数設定 Are you <b>absolutely sure</b> you want to proceed? 您 <b>確定</b> 要繼續吗? Import Success 匯入成功 Choose where to save journal 選取儲存日誌的位置 &Frequently Asked Questions &常见问题 Oximetry 血氧测定 Purge ALL Device Data 清除所有機內數據 System Information 系統資訊 Show &Pie Chart Show Pie Chart on Daily page 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 歡迎使用OSCAR(Open Source CPAP Analysis Reporter-開源CPAP解析彙整程序) This software is being designed to assist you in reviewing the data produced by your CPAP Devices 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. 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> 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 呼吸 紊乱 指數 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 快照 Layout Save and Restore Graph Layout Settings Last Two Months 前两個月 Hide All Graphs Show All Graphs 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' <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>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;">給CPAP 使用者的提示: </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-weight:600; font-style:italic;">請注意:</span><span style=" font-style:italic;">首先從下拉目錄中選擇正確的血氧儀種類。</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;">首先從此組中選取你的血氧儀。</span></p></body></html> Day recording (normally would have) started 日常記錄(一般而言)開始 I started this oximeter recording at (or near) the same time as a session on my CPAP device. 我的血氧儀開啟時間與CPAP療程開啟時間同步(或相近)。 &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 檢測到多重療程 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. 天. 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 療程儲存選項 Graph Titles 圖表标题 Zero Reset 归零 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? 应用這些變更需要資料重新/解压缩过程。此操作可能需要几分鐘才能完成。 确实要進行這些變更吗? 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 保持小 Unknown Events 未知事件 ResMed S9 devices routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). ResMed S9會週期地刪除數據卡內7天或30天前的數據(視乎數據解析度)。 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 重建資料索引 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%百分位数 This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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數據卡的備份資料, ResMed S9會刪除7天前的高解析數據和30天前的圖形數據。 OSCAR會保留一份數據複件以備你需要。 (強烈建議你保留,除非你的硬盤空間不足或者你不需要圖形數據) 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> <html><head/><body><p>Provide an alert when importing data from any device 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 device 警告匯入數據來源為未經測試型號 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 查询 Time Weighted average of Indice 時間加权平均值指數 Middle Calculations 中值計算 Skip over Empty Days 跳过無資料的日期 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 真极大值 Clinical Mode: Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. Basically replicates the reports and data stored on the devices data card. This includes pap devices, oximeters, etc. Compliance reports fall under this mode. Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. Permissive Mode: Allows user to select which data sets/ sessions to be used for calculations and display. Additional charts and calculations may be available that are not available from the vendor data. Changing the Oscar Operating Mode: Requires a reload of the user's profile. Data will be saved and restored. Minor Flag 次要標記 No CPAP devices detected 未有偵測到CPAP裝置 Will you be using a ResMed brand device? 你會使用ResMed品牌的裝置嗎? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, days will <b>split at noon</b> like in ResMed's commercial software.</p> <p><b>請注意:</b>OSCAR的進階療程分割功能將無法在 <b>ResMed</b>裝置上使用,由於設定和數據上的限制此功能會關閉。</p><p>如果你使用的是ResMed裝置,日期變更時間會在正午。</p> 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 無變更 Whether to include device serial number on device settings changes report 是否在裝置設定變更報告中包含裝置序列號 Graph Text 圖表文字 Double click to change the default color for this channel plot/flag/data. 双击變更這個區塊/標記/資料的預設颜色. AHI/Hour Graph Time Window AHI/小時 圖形時間窗 Changing SD Backup compression options doesn't automatically recompress backup data. 改變數據卡備份壓縮方案不會自動將你的備份數據重新壓縮。 <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">療程時長短於此將不會顯示</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> Import without asking for confirmation 無需确认直接匯入 <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 警告遭遇以往未見過的數據 &CPAP This calculation requires Total Leaks data to be provided by the CPAP device. (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已擁有此項計算功能) 這裡的意外漏氣計算方式為線性,並非使用不同面罩和通氣量曲線。 如果你使用了不同的面罩,請取平均值,所得結果應該足夠接近。 Your masks vent rate at 20 cmH2O pressure 在20 cmH2O下你的面罩通氣比 Your masks vent rate at 4 cmH2O pressure 在4 cmH2O下你的面罩通氣比 4 cmH2O 20 cmH2O Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device missed. This option must be enabled before import, otherwise a purge is required. 開啟/關閉實驗性窒息事件記錄增強功能。 此功能會偵測到一些介乎閾值和裝置選擇忽視的事件。 請在匯入數據前開啟此項,否則需要刪除已匯入數據再重新處理。 s This experimental option attempts to use OSCAR's event flagging system to improve device detected event positioning. 此項實驗性功能嘗試以OSCAR窒息事件系統來強化裝置偵測事件的定位。 Resync Device Detected Events (Experimental) 重新同步裝置偵測到的窒息事件(此為實驗性功能) Allow duplicates near device events. 允許複寫相近的窒息事件. Show flags for device detected events that haven't been identified yet. 顯示無法識別的窒息事件. l/min <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p>累計指數</p></body></html> AHI Apnea Hypopnea Index 睡眠窒息及低通氣指數 RDI Respiratory Disturbance Index 呼吸紊乱指數 <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices do not support changing these settings.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">請注意:</span>因為某些限制, ResMed裝置不支援更改以上設定。</p></body></html> Clinical Clinical Settings Select Oscar Operating Mode Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. Clinical Mode permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. Permissive Mode Hours bpm 次每分鐘 Discard segments under 删除偏低的資料 <!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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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> Allow use of multiple CPU cores where available to improve performance. Mainly affects the importer. 允许使用多核CPU以提高性能 提高匯入性能。 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。測試版本每7天會檢查是否有更新。你可以更改檢查更新的週期為小於7天。 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,或者將其翻譯成不同語言的用戶。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 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. 注意:預設选用線性計算法。如果變更資料需重新計算. 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 切换标签 &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><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">客制化窒息事件是一項嘗試偵測裝置選擇忽視的窒息事件的實驗性功能。這些事件 </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">並不</span><span style=" font-family:'Sans'; font-size:10pt;">計算在AHI中。</span></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>事件血氧飽和濃度降低</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轴缩放 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) 打印黑白報告(單色) For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. Enables SessionBar in Event Flags Graph 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 呼吸機品牌 Profile: None 個人檔案:無 Ventilator Model 呼吸機型號 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: 圖形引擎種類: Compiler: App key: ANGLE / OpenGLES m cm 厘米 kg 千克 milliSeconds m s Hz 赫茲 EEPAP Min EEPAP Max EEPAP AVAPS UF1 UF2 UF3 AI 呼吸中止 CA 中枢性 EP 呼氣壓力 FL 氣流受限 in HI 低通氣指數 CSR 陳施氏呼吸 IE 呼吸 LE 漏氣率 LF 漏氣标志 LL 大量漏氣 O2 氧氣 OA 阻塞性 NR 未影響事件 PB 周期性呼吸 PC 混合面罩 No PP 最高壓力 l/min 升/分鐘 Only Settings and Compliance Data Available 僅限設定及順應性數據可用 Summary Data Only 僅限概要數據 PS 壓力 Device 設備 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 OSCAR %1 需要為%2 %3 %4升級器數據庫 <i>Your old device 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 device data again afterwards from your own backups or data card. 這代表你需要重新匯入你的備份數據或數據卡。 Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. 一旦更新完成,你將<font size=+1>不能</font>在舊版本上使用此個人資料。 Device Database Changes 設備資料库變更 The device data folder needs to be removed manually. 數據資料夾需要手動移除。 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 &保存 varies 差異 n/a 不適用 Min EEPAP %1 Max EEPAP %2 PDIFF %3-%4 (%5) EPAP %1 PS %2-%3 (%4) EPAP %1-%2 IPAP %3-%4 (%5) Therapy Pressure 治療壓力 BiPAP 双水平氣道正压通氣 Brand 品牌 ResMed S9 EPR: 呼氣壓力释放: Daily 日常 Email 电子邮件 Error 錯誤 First 第一次 Ramp Pressure 壓力上升 Hours 小時 Leaks 漏氣率 Max: 最大: Min: 最小: Model 型式 Nasal 鼻罩 Notes 備註 Phone 电话号码 Ready 就緒 TTIA: 呼吸中止總時間: Snore 打鼾 Start 开始 Usage 使用 cmH2O 厘米水柱 Pressure Support 壓力支持 Bedtime: %1 睡眠時間:%1 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) (1天前) (%2 days ago) (%2天前) Scanning Files 正在扫描檔案 Heart rate in beats per minute 心臟每分鐘的跳動次数 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 選擇要移動的SleepHead或OSCAR數據文檔 or CANCEL to skip migration. 或者按取消跳過移動. The folder you chose does not contain valid SleepyHead or OSCAR data. 你選擇的文檔不包含任何可用的SleepHead或OSCA數據。 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嘗試使用兼容性更高但更慢的方法生成圖像。 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. OSCAR可以複製你的舊有文件到此文檔。 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時在諮詢. Data directory: 數據名錄: Migrate SleepyHead or OSCAR Data? 移動SleepHead或OSCAR數據? 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到下一步或No跳過. 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. 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. 無法寫入排錯記錄,你仍可使用debug pane但排錯記錄不會下入硬碟. 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? 此操作會损坏資料,是否繼續? Don't forget to place your datacard back in your CPAP device 溫馨提示:記得將數據卡插回呼吸機 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 晨起感觉 Disconnected 断开 Sleep Stage 睡眠階段 Minute Vent. 分鐘通氣率. Ramp Event 斜坡啟動事件 SensAwake feature will reduce pressure when waking is detected. 觉醒偵測功能會在偵測到醒来時降低PAP的壓力. Show All Events 顯示所有事件 Upright angle in degrees 垂直 Importing Sessions... 匯入療程... UNKNOWN APAP (std) APAP (dyn) Auto S Auto S/T AcSV SoftPAP Mode Pressure relief during exhalation Slight Softstart pressure Pressure during soft start period PSoft Softstart minimum pressure Minimum pressure during soft start period PSoftMin Auto start Automatically turn on the device by breathing Softstart time Lenght of soft start period Soft start maximum time Maximum lenght of soft start period Soft start max. time Soft start pressure Higher End Expiratory Pressure Humidifier level Tube type Obstruction level Obstruction level in percentage rRMVFluctuation Relative respiratory minute volume fluctuation Relative respiratory minute volume Measured pressure Full flow Artefact Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) Epoch (2 mins) with Flow Limitation Deep Sleep Deep sleep, stable respiration Timed breath BiSoft Mode BiSoft 1 BiSoft 2 TriLevel PMaxOA EEPAPMin Lower End Expiratory Pressure EEPAPMax rRMV ART CriticalLeak Mask leakage is above a critical treshold CL eMO Epoch (2 mins) with Mild Obstruction eSO Epoch (2 mins) with Severe Obstruction eS Epoch (2 mins) with Snoring eFL DS 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 一次振動打鼾 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. 你的%1 %2 (%3)生成的數據類型未知. The imported data may not be entirely accurate, so the developers would like a .zip copy of this device's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. 匯入的數據可能不精準,程式開發者希望閣下能提供一份醫療報告及相應的數據卡原始數據,以提高OSCAR的精準度。 Non Data Capable Device 没有使用機器的資料 Your %1 CPAP Device (Model %2) is unfortunately not a data capable model. 你的CPAP裝置%1(型號%2)並沒有生成和記錄數據功能。 I'm sorry to report that OSCAR can only track hours of use and very basic settings for this device. OSCAR只能追踪本裝置的使用時間和基本設定。 Device Untested 未測試裝置 Your %1 CPAP Device (Model %2) has not been tested yet. 你的CPAP%1(型號%2)並未被測試。 It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card and matching clinician .pdf reports to make sure it works with OSCAR. 找到可能與閣下裝置相容的機型,但程式開發者希望閣下能提供一份醫療報告及相應的數據卡原始數據,以完善OSCAR的相容性。 Device Unsupported 不支援裝置 Sorry, your %1 CPAP Device (%2) is not supported yet. 你的CPAP%1(%2)並未受支援。 The developers need a .zip copy of this device's SD card and matching clinician .pdf reports to make it work with OSCAR. 程式開發者希望閣下能提供一份醫療報告及相應的數據卡原始數據,以完善OSCAR的功能。 A relative assessment of the pulse strength at the monitoring site 脈搏的强度的相关评估 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 分鐘 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 呼氣壓力释放水平 Device auto starts by breathing 呼吸觸發自啟動 Response 反應 Soft prisma? 呼氣舒壓 SmartStop 智能關機 RiseEnable RiseTime Cycle Trigger TiMax TiMin Your ResMed CPAP device (Model %1) has not been tested yet. 你的ResMed裝置(型號%1)並未受測試. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card to make sure it works with OSCAR. 找到可能與閣下裝置相容的機型,但程式開發者希望閣下能提供一份醫療報告及相應的數據卡原始數據,以完善OSCAR的相容性。 Smart Stop 智能關機 Device auto stops by breathing Patient View 患者界面 Simple 基礎 Advanced 進階 BiPAP-T BiPAP-S BiPAP-S/T PAC Parsing STR.edf records... Unintentional Leaks 無意識漏氣量 Would you like to show bookmarked areas in this report? 是否希望在報告中顯示標記区域? VPAPauto VPAP全自動 Physical Height 身高 Pt. Access 患者通道 ASV (Fixed EPAP) ASV模式 (固定呼氣壓力) Patient Triggered Breaths 患者出发的呼吸 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. 未找到视窗浏览器的可执行檔案. Connected 连接 Low Usage Days: %1 低使用天数:%1 PS Max 壓力支持最大壓力 PS Min 最小壓力 Selection Length 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 呼吸事件 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: 重要提示: 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 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? 確定將所有通道颜色恢复預設設定吗? Are you sure you want to reset all your oximetry settings to defaults? BrainWave 脑波 Inspiratory Pressure 吸氣壓力 Number of Awakenings 觉醒次数 A pulse of pressure 'pinged' to detect a closed airway. 通过壓力脉冲'砰'可以偵測到氣道關閉. Intellipap pressure relief level. Intellipap 壓力释放水平. 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? Permissive Mode Total disabled sessions: %1, found in %2 days Total disabled sessions: %1 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. 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 患者出发的呼吸百分比 Days: %1 天数:%1 Plethysomogram 体积描述术 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 Length: %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 吸氣氣壓上升時間 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 深層睡眠時長 Pressure Max 最大壓力 Pressure Min 最小壓力 Diameter of primary CPAP hose PAP主管内径 Max Leaks 最大漏氣率 Time to Sleep 睡眠時長 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 &删除 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 面罩壓力 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. End Expiratory Pressure Pressure Set Pressure Setting IPAP Set IPAP Setting EPAP Set EPAP Setting An abnormal period of Cheyne Stokes Respiration 潮式呼吸的不正常時期 Cheyne Stokes Respiration (CSR) 潮式呼吸 Periodic Breathing (PB) 周期性呼吸 Clear Airway (CA) 開放性氣道窒息 Obstructive Apnea (OA) 阻塞性氣道窒息 Hypopnea (H) 低通氣 An apnea that couldn't be determined as Central or Obstructive. 未知類型睡眠窒息事件. An apnea reported by your CPAP device. A restriction in breathing from normal, causing a flattening of the flow waveform. 呼吸受限時會導致氣流波形變平坦。 Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. A vibratory snore as detected by a System One device A type of respiratory event that won't respond to a pressure increase. 未導致壓力上升的呼吸事件. I/E Value Debugging channel #1 排錯通道#1 For internal use only 只限內部使用 Test #1 測試#1 Debugging channel #2 排錯通道#2 Test #2 測試#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 更高的吸氣壓力 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... 扫描檔案... Flex Mode Flex模式 Sessions 療程 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 更低的呼氣壓力 Unclassified Apnea (UA) 未知睡眠窒息(UA) Apnea (A) 睡眠窒息(A) An apnea reportred by your CPAP device. 由裝置回報的睡眠窒息. Flow Limitation (FL) 氣流受限(FL) RERA (RE) 呼吸努力相關覺醒(RE) Vibratory Snore (VS) 震動式打鼾 (VS) Leak Flag (LF) 漏氣标志(LF) A large mask leak affecting device performance. 裝置表現收面罩大量漏氣影響. Large Leak (LL) 大量漏氣(LL) Non Responding Event (NR) 未回應事件(NR) Expiratory Puff (EP) 嘴部呼氣 SensAwake (SA) F&P, similar to RERA 呼吸努力相關覺醒 User Flag #1 (UF1) User Flag #2 (UF2) User Flag #3 (UF3) Pulse Change (PC) SD SpO2 Drop (SD) 血氧飽和度降低(SD) Mask Pressure (High frequency) A ResMed data item: Trigger Cycle Event Apnea Hypopnea Index (AHI) 睡眠窒息及低通氣指數 Respiratory Disturbance Index (RDI) 呼吸紊乱指數 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並完成升级过程。 %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 呼氣時間 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 機型%1 unknown model 未知機型 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水平 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 由呼氣轉換到吸氣所需時間,數值越高轉換速度越慢 Rise Time Lock Whether Rise Time settings are available to you. 是否能由你更改吸氣氣壓上升時間. Rise Lock 吸氣氣壓上升時間鎖定 Humidifier Status 加湿器状态 Humidification Mode 加濕模式 PRS1 Humidification Mode SystemOne加濕模式 Humid. Mode 加濕模式 Fixed (Classic) 固定加熱 Adaptive (System One) 自動加熱 Heated Tube 加熱喉管 Passover Tube Temperature 加熱管溫度 PRS1 Heated Tube Temperature SystemOne加熱管溫度 Tube Temp. 加熱管溫度 PRS1 Humidifier Setting SystemOne加濕器設定 Target Time 目標時間 PRS1 Humidifier Target Time SystemOne加濕器目標時間 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 面罩類型鎖定 A few breaths automatically starts device 呼吸可觸發裝置自啟動 Device automatically switches off 裝置自動關閉 Whether or not device allows Mask checking. 是否允許裝置進行面罩檢查. Whether or not device shows AHI via built-in display. 是否允許裝置顯示AHI. The number of days in the Auto-CPAP trial period, after which the device will revert to CPAP Auto-trail週期日數,結束後裝置會恢復定壓模式 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 後備呼吸模式:無(off),自動或固定數值 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 Auto-Trial持續時間 Auto-Trial Dur. Auto-Trial持續時間 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 A period during a session where the device could not detect flow. BND Machine Initiated Breath 呼吸触发機器开启 TB Peak Flow 氣流峰值 Peak flow during a 2-minute interval 2分鐘內氣流峰值間距 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 有更新版本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>. Information about more recent test version %1 is available at <a href='%2'>%2</a> Check for OSCAR Updates 檢查OSCAR可用更新 Unable to check for updates. Please try again later. 無法檢查更新,請稍後再試. Loading summaries 正在載入摘要 This page in other languages: 其他語言: %1 Graphs %1 of %2 Graphs %1 Event Types %1 of %2 Event Types Löwenstein Prisma Smart Resvent/Hoffrichter iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings Add Add Feature inhibited. The maximum number of Items has been exceeded. creates new copy of current settings. Restore Restores saved settings from selection. Rename Renames the selection. Must edit existing name then press enter. Update Updates the selection with current settings. Delete Deletes the selection. Expanded Help menu. Exits the Layout menu. <h4>Help Menu - Manage Layout Settings</h4> Exits the help menu. Exits the dialog menu. This feature manages the saving and restoring of Layout Settings. Layout Settings control the layout of a graph or chart. Different Layouts Settings can be saved and later restored. Button Description Creates a copy of the current Layout Settings. The default description is the current date. The description may be changed. The Add button will be greyed out when maximum number is reached. Other Buttons Greyed out when there are no selections Loads the Layout Settings from the selection. Automatically exits. io Modify the description of the selection. Same as a double click.io Saves the current Layout Settings to the selection. Prompts for confirmation. Deletes the selecton. Control Exit 退出 (Red circle with a white "X".) Returns to OSCAR menu. Return Next to Exit icon. Only in Help Menu. Returns to Layout menu. Escape Key Exit the Help or Layout menu. Layout Settings * Name * Pinning * Plots Enabled * Height * Order * Event Flags * Dotted Lines * Height Options General Information Maximum description size = 80 characters. Maximum Saved Layout Settings = 30. Saved Layout Settings can be accessed by all profiles. Layout Settings only control the layout of a graph or chart. They do not contain any other data. They do not control if a graph is displayed or not. Layout Settings for daily and overview are managed independantly. Maximum number of Items exceeded. No Item Selected Ok to Update? Ok To Delete? SessionBar %1h %2m %1% %2m No Sessions Present 没有療程 SleepStyleLoader Import Error 匯入出錯 The Day records overlap with already existing content. 本日的資料已覆蓋已儲存的内容。 This device Record cannot be imported in this profile. 此設備的記錄無法匯入此個人檔案。 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 指數 Device Information 裝置信息 Changes to Device Settings 更改至裝置設定 Best Device Setting Date: %1 - %2 日期:%1 - %2 AHI: %1 Total Hours: %1 總小時數:%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% %1 Median %1 中值 Min %1 最小 %1 This report was prepared on %1 by OSCAR %2 此報告由%1 OSCAR %2提供 OSCAR is free open-source CPAP report software OSCAR是一個免費的開源CPAP彙整程序 No data found?!? 沒有找到數據! Oscar has no data to report :( OSCAR找不到數據:( Most Recent 最近 Database has No %1 data available. Database has %1 day of %2 Data on %3 Database has %1 days of %2 Data, between %3 and %4 Total Days: %1 Days Not Used: %1 Days %1 %2 Hours: %3 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. 請在属性選單中選中預調取彙總資訊選項。 Worst Device Setting 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 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 EEPAP pressure was under %1 %2 for %3% of the time. Your average leaks were %1 %2, which is %3 your %4 day average of %5. 平均漏氣為 %1 %2,即 %3 您的 %5 天 %4 平均值。 Welcome to the Open Source CPAP Analysis Reporter 歡迎使用開放資源 CPAP 解析彙整程式 <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 device.</span></p></body></html> <span style=" font-weight:600;">警告:</span><span style=" color:#ff0000;">在插入電腦前,請打開你的ResMed S9數據卡防寫保護,否則有可能導致你的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. 有些至關重要的偏好選項會影響資料匯入. 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> Note that some preferences are forced when a ResMed device is detected 請注意,如果偵測到ResMed裝置某些偏好項目可能會被鎖定。 First import can take a few minutes. 首次記錄導入需耗時數分鐘。 today Your device was on for %1. 你的裝置啟動了%1. equal to 等於 It would be a good idea to check File->Preferences first, 開始的第一步,先檢查 檔案 --> 偏好選項, %2 days ago %2 天前 1 day ago 1 天前 Statistics 統計數據 CPAP Importer CPAP 導入器 Your pressure was under %1 %2 for %3% of the time. 壓力低於 %1 %2,持續時間%3%. Overview 綜合概況 reasonably close to 合理地近似 Your CPAP device used a constant %1 %2 of air Your device used a constant %1-%2 %3 of air. Your device was under %1-%2 %3 for %4% of the time. 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%。 %1 hours, %2 minutes and %3 seconds %1 小時,%2分 %3 秒 The last time you used your %1... 您上次使用 %1... gGraph Double click Y-axis: Return to AUTO-FIT Scaling Double click Y-axis: Return to DEFAULT Scaling Double click Y-axis: Return to OVERRIDE Scaling Double click Y-axis: For Dynamic Scaling Double click Y-axis: Select DEFAULT Scaling Double click Y-axis: Select AUTO-FIT Scaling %1 days %1天 gGraphView Clone %1 Graph 複製 %1 圖表 Oximeter Overlays 血氧儀 覆蓋 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%以查看所有每日數據。 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.5.1/Translations/Czech.cz.ts000066400000000000000000015464451450332542600202120ustar00rootroot00000000000000 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. 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 Ημερολόγιο διάφορων πράξεων i 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 Search Layout Save and Restore Graph Layout Settings Show/hide available graphs. Breakdown events UF1 UF1 UF2 UF2 Time at Pressure Clinical Mode Disabling Sessions requires the Permissive Mode 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 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 "Nothing's here!" No data is available for this day. Oximeter Information Details Click to %1 this session. disable enable %1 Session #%2 %1h %2m %3s Device Settings <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 This CPAP device does NOT record detailed data Sessions all off! Sessions exist for this day but are switched off. Impossibly short session Zero hours?? no data :( Sorry, this device only provides compliance data. Complain to your Equipment Provider! Pick a Colour Bookmark at %1 Hide All Events Show All Events Hide All Graphs Show All Graphs DailySearchTab Clear Bookmark Jumps to Date's Bookmark Start Search DATE Jumps to Date Match Notes Σημειώσεις Notes containing Bookmarks Σελιδοδείκτες Bookmarks containing AHI Daily Duration Session Duration Days Skipped Disabled Sessions Number of Sessions Click HERE to close Help Help No Data Jumps to Date's Details Number Disabled Session Jumps to Date's Details Note Jumps to Date's Notes AHI Jumps to Date's Details EventsPerHour Session Duration Jumps to Date's Details Minutes Number of Sessions Jumps to Date's Details Sessions Daily Duration Jumps to Date's Details Hours Number of events Jumps to Date's Events Events Automatic start Continue Search End of Search No Matches Skip:%1 %1/%2%3 days Found %1 Finds days that match specified criteria. Searches from last day to first day. First click on Match Button then select topic. Then click on the operation to modify it. or update the value Topics without operations will automatically start. Compare Operations: numberic or character. Numberic Operations: Character Operations: Summary Line Left:Summary - Number of Day searched Center:Number of Items Found Right:Minimum/Maximum for item searched Result Table Column One: Date of match. Click selects date. Column two: Information. Click selects date. Then Jumps the appropiate tab. Wildcard Pattern Matching: Wildcards use 3 characters: Asterisk Question Mark Backslash. Asterisk matches any number of characters. Question Mark matches a single character. Backslash matches next character. DateErrorDisplay ERROR The start date MUST be before the end date The entered start date %1 is after the end date %2 Hint: Change the end date first The entered end date %1 is before the start date %1 Hint: Change the start date first 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 device 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 Show Standard Report Standard Show Monthly Report Monthly Show Range Report Date Range Select Report Date Report Date Statistics Στατιστικά Daily Overview Oximetry Import Help &File &View &Reset Graphs &Help Troubleshooting &Data &Advanced 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 - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> Advanced - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> 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 Purge ALL Device 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 Couldn't find any valid Device Data at %1 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 No supported data was found 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. 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. Note as a precaution, the backup folder will be left in place. Are you <b>absolutely sure</b> you want to proceed? A file permission error caused the purge process to fail; you will have to delete the following folder manually: 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 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" Are you sure you want to rebuild all CPAP data for the following device: 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 device: Would you like to import from your own backups now? (you will have no data visible for this device until you do) OSCAR does not have any backups for this device! Unless you have made <i>your <b>own</b> backups for ALL of your data for this device</i>, <font size=+2>you will lose this device's data <b>permanently</b>!</font> You are about to <font size=+2>obliterate</font> OSCAR's device database for the following device:</p> There was a problem opening MSeries block File: MSeries Import complete You must select and open the profile you wish to modify 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 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. This software is being designed to assist you in reviewing the data produced by your CPAP Devices and related equipment. 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 Layout Save and Restore Graph Layout Settings 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) Hide All Graphs Show 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> 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. <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 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 I started this oximeter recording at (or near) the same time as a session on my CPAP device. <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 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. Flow Restriction Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. Duration of airflow restriction s Event Duration 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 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.) <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> Calculate Unintentional Leaks When Not Present 4 cmH2O 20 cmH2O Note: A linear calculation method is used. Changing these values requires a recalculation. 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 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 <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> 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 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 <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></p></body></html> l/min <html><head/><body><p>Cumulative Indices</p></body></html> Oximetry Settings <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</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) This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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>Provide an alert when importing data from any device model that has not yet been tested by OSCAR developers.</p></body></html> Warn when importing data from an untested device This calculation requires Total Leaks data to be provided by the CPAP device. (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. Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device missed. This option must be enabled before import, otherwise a purge is required. This experimental option attempts to use OSCAR's event flagging system to improve device detected event positioning. Resync Device Detected Events (Experimental) Allow duplicates near device events. Show flags for device detected events that haven't been identified yet. <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices do not support changing these settings.</p></body></html> Clinical Clinical Settings Select Oscar Operating Mode Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. Clinical Mode permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. Permissive Mode Hours <!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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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 device serial number on device settings changes report Print reports in black and white, which can be more legible on non-color printers Print reports in black and white (monochrome) For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. Enables SessionBar in Event Flags Graph 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 No CPAP devices detected Will you be using a ResMed brand device? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, 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? ResMed S9 devices routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). 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 Clinical Mode: Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. Basically replicates the reports and data stored on the devices data card. This includes pap devices, oximeters, etc. Compliance reports fall under this mode. Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. Permissive Mode: Allows user to select which data sets/ sessions to be used for calculations and display. Additional charts and calculations may be available that are not available from the vendor data. Changing the Oscar Operating Mode: Requires a reload of the user's profile. Data will be saved and restored. Minor Flag Span Always Minor Never This may not be a good idea 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 cmH2O Med. Min: %1 Min: Max: Max: %1 %1 (%2 days): %1 (%2 day): % in %1 Hours Min %1 Length: %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 milliSeconds 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 EEPAP Min EEPAP Max EEPAP 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 Device No Data Available App key: Operating system: Built with Qt %1 on %2 Graphics Engine: Graphics Engine type: Compiler: Software Engine ANGLE / OpenGLES Desktop OpenGL m cm in kg l/min Only Settings and Compliance Data Available Summary Data Only Bookmarks Σελιδοδείκτες Mode Model Brand Serial Series 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 device's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Non Data Capable Device Your %1 CPAP Device (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 device. Device Untested Your %1 CPAP Device (Model %2) has not been tested yet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Device Unsupported Sorry, your %1 CPAP Device (%2) is not supported yet. The developers need a .zip copy of this device's SD card and matching clinician .pdf reports to make it work with OSCAR. Getting Ready... Scanning Files... Importing Sessions... UNKNOWN APAP (std) APAP (dyn) Auto S Auto S/T AcSV SoftPAP Mode Pressure relief during exhalation Slight Softstart pressure Pressure during soft start period PSoft Softstart minimum pressure Minimum pressure during soft start period PSoftMin Auto start Automatically turn on the device by breathing Softstart time Lenght of soft start period Soft start maximum time Maximum lenght of soft start period Soft start max. time Soft start pressure Higher End Expiratory Pressure Humidifier level Tube type Obstruction level Obstruction level in percentage rRMVFluctuation Relative respiratory minute volume fluctuation Relative respiratory minute volume Measured pressure Full flow Artefact Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) Epoch (2 mins) with Flow Limitation Deep Sleep Deep sleep, stable respiration Timed breath BiSoft Mode BiSoft 1 BiSoft 2 TriLevel PMaxOA EEPAPMin Lower End Expiratory Pressure EEPAPMax rRMV ART CriticalLeak Mask leakage is above a critical treshold CL eMO Epoch (2 mins) with Mild Obstruction eSO Epoch (2 mins) with Severe Obstruction eS Epoch (2 mins) with Snoring eFL DS Finishing up... 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 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 A few breaths automatically starts device Device automatically switches off Whether or not device allows Mask checking. 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 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 A period during a session where the device could not detect flow. 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 Off Mask Alert Show AHI Whether or not device shows AHI via built-in display. The number of days in the Auto-CPAP trial period, after which the device will revert to CPAP Breathing Not Detected BND Timed Breath Machine Initiated Breath TB 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 device 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 device 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? Device Database Changes Sorry, the purge operation failed, which means this version of OSCAR can't start. The device data folder needs to be removed manually. 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. Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. 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 End 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 Cheyne Stokes Respiration (CSR) Periodic Breathing (PB) Clear Airway (CA) An apnea caused by airway obstruction A partially obstructed airway UA A vibratory snore Pressure Pulse A pulse of pressure 'pinged' to detect a closed airway. A type of respiratory event that won't respond to a pressure increase. Intellipap event where you breathe out your mouth. SensAwake feature will reduce pressure when waking is detected. Heart rate in beats per minute Blood-oxygen saturation percentage Plethysomogram An optical Photo-plethysomogram showing heart rhythm A sudden (user definable) change in heart rate A sudden (user definable) drop in blood oxygen saturation SD Breathing flow rate waveform 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 Ratio between Inspiratory and Expiratory time ratio Pressure Min Pressure Max Pressure Set Pressure Setting IPAP Set IPAP Setting EPAP Set EPAP Setting An abnormal period of Cheyne Stokes Respiration CSR An abnormal period of Periodic Breathing An apnea that couldn't be determined as Central or Obstructive. An apnea reported by your CPAP device. A restriction in breathing from normal, causing a flattening of the flow waveform. A vibratory snore as detected by a System One device 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 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 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. Obstructive Apnea (OA) Hypopnea (H) Unclassified Apnea (UA) Apnea (A) Flow Limitation (FL) RERA (RE) Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. Vibratory Snore (VS) Leak Flag (LF) A large mask leak affecting device performance. Large Leak (LL) Non Responding Event (NR) Expiratory Puff (EP) SensAwake (SA) User Flag #1 (UF1) User Flag #2 (UF2) User Flag #3 (UF3) Pulse Change (PC) SpO2 Drop (SD) I/E Value Apnea Hypopnea Index (AHI) Respiratory Disturbance Index (RDI) 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? OSCAR Reminder Don't forget to place your datacard back in your CPAP device 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 oximetry 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 (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 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) Min EEPAP %1 Max EEPAP %2 PDIFF %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 Selection Length 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 Device auto starts by breathing Response Device auto stops by breathing Patient View RiseEnable RiseTime Cycle Trigger TiMax TiMin Your ResMed CPAP device (Model %1) has not been tested yet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card to make sure it works with OSCAR. SmartStart 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 Smart Stop Simple Advanced 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... Loading summaries Permissive Mode Total disabled sessions: %1, found in %2 days Total disabled sessions: %1 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. 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 Löwenstein Prisma Smart Resvent/Hoffrichter iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings Add Add Feature inhibited. The maximum number of Items has been exceeded. creates new copy of current settings. Restore Restores saved settings from selection. Rename Renames the selection. Must edit existing name then press enter. Update Updates the selection with current settings. Delete Deletes the selection. Expanded Help menu. Exits the Layout menu. <h4>Help Menu - Manage Layout Settings</h4> Exits the help menu. Exits the dialog menu. This feature manages the saving and restoring of Layout Settings. Layout Settings control the layout of a graph or chart. Different Layouts Settings can be saved and later restored. Button Description Creates a copy of the current Layout Settings. The default description is the current date. The description may be changed. The Add button will be greyed out when maximum number is reached. Other Buttons Greyed out when there are no selections Loads the Layout Settings from the selection. Automatically exits. io Modify the description of the selection. Same as a double click.io Saves the current Layout Settings to the selection. Prompts for confirmation. Deletes the selecton. Control Exit (Red circle with a white "X".) Returns to OSCAR menu. Return Next to Exit icon. Only in Help Menu. Returns to Layout menu. Escape Key Exit the Help or Layout menu. Layout Settings * Name * Pinning * Plots Enabled * Height * Order * Event Flags * Dotted Lines * Height Options General Information Maximum description size = 80 characters. Maximum Saved Layout Settings = 30. Saved Layout Settings can be accessed by all profiles. Layout Settings only control the layout of a graph or chart. They do not contain any other data. They do not control if a graph is displayed or not. Layout Settings for daily and overview are managed independantly. Maximum number of Items exceeded. No Item Selected Ok to Update? Ok To Delete? SessionBar %1h %2m No Sessions Present SleepStyleLoader Import Error This device 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 Device Information Changes to Device Settings Database has No %1 data available. Database has %1 day of %2 Data on %3 Database has %1 days of %2 Data, between %3 and %4 Total Days: %1 Days Not Used: %1 Days Used: %1 Days %1 %2 Hours: %3 Best Device Setting Worst Device Setting 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. Date: %1 - %2 AHI: %1 Total Hours: %1 Most Recent Compliance (%1 hrs/day) OSCAR is free open-source CPAP report software No data found?!? Oscar has no data to report :( Last Week Last 30 Days Last 6 Months Last Year Last Session Details Days Pressure Relief Pressure Settings 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 device.</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 device is detected First import can take a few minutes. The last time you used your %1... last night today %2 days ago was %1 (on %2) %1 hours, %2 minutes and %3 seconds <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 pressure was under %1 %2 for %3% of the time. 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. 1 day ago Your device was on for %1. Your CPAP device used a constant %1 %2 of air Your device used a constant %1-%2 %3 of air. Your device was under %1-%2 %3 for %4% of the time. Your EEPAP pressure was under %1 %2 for %3% of the time. Your average leaks were %1 %2, which is %3 your %4 day average of %5. No CPAP data has been imported yet. gGraph Double click Y-axis: Return to AUTO-FIT Scaling Double click Y-axis: Return to DEFAULT Scaling Double click Y-axis: Return to OVERRIDE Scaling Double click Y-axis: For Dynamic Scaling Double click Y-axis: Select DEFAULT Scaling Double click Y-axis: Select AUTO-FIT Scaling %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.5.1/Translations/Dansk.da.ts000066400000000000000000015464451450332542600201660ustar00rootroot00000000000000 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. 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 Ημερολόγιο διάφορων πράξεων i 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 Search Layout Save and Restore Graph Layout Settings Show/hide available graphs. Breakdown events UF1 UF1 UF2 UF2 Time at Pressure Clinical Mode Disabling Sessions requires the Permissive Mode 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 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 "Nothing's here!" No data is available for this day. Oximeter Information Details Click to %1 this session. disable enable %1 Session #%2 %1h %2m %3s Device Settings <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 This CPAP device does NOT record detailed data Sessions all off! Sessions exist for this day but are switched off. Impossibly short session Zero hours?? no data :( Sorry, this device only provides compliance data. Complain to your Equipment Provider! Pick a Colour Bookmark at %1 Hide All Events Show All Events Hide All Graphs Show All Graphs DailySearchTab Clear Bookmark Jumps to Date's Bookmark Start Search DATE Jumps to Date Match Notes Σημειώσεις Notes containing Bookmarks Σελιδοδείκτες Bookmarks containing AHI Daily Duration Session Duration Days Skipped Disabled Sessions Number of Sessions Click HERE to close Help Help No Data Jumps to Date's Details Number Disabled Session Jumps to Date's Details Note Jumps to Date's Notes AHI Jumps to Date's Details EventsPerHour Session Duration Jumps to Date's Details Minutes Number of Sessions Jumps to Date's Details Sessions Daily Duration Jumps to Date's Details Hours Number of events Jumps to Date's Events Events Automatic start Continue Search End of Search No Matches Skip:%1 %1/%2%3 days Found %1 Finds days that match specified criteria. Searches from last day to first day. First click on Match Button then select topic. Then click on the operation to modify it. or update the value Topics without operations will automatically start. Compare Operations: numberic or character. Numberic Operations: Character Operations: Summary Line Left:Summary - Number of Day searched Center:Number of Items Found Right:Minimum/Maximum for item searched Result Table Column One: Date of match. Click selects date. Column two: Information. Click selects date. Then Jumps the appropiate tab. Wildcard Pattern Matching: Wildcards use 3 characters: Asterisk Question Mark Backslash. Asterisk matches any number of characters. Question Mark matches a single character. Backslash matches next character. DateErrorDisplay ERROR The start date MUST be before the end date The entered start date %1 is after the end date %2 Hint: Change the end date first The entered end date %1 is before the start date %1 Hint: Change the start date first 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 device 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 Show Standard Report Standard Show Monthly Report Monthly Show Range Report Date Range Select Report Date Report Date Statistics Στατιστικά Daily Overview Oximetry Import Help &File &View &Reset Graphs &Help Troubleshooting &Data &Advanced 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 - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> Advanced - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> 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 Purge ALL Device 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 Couldn't find any valid Device Data at %1 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 No supported data was found 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. 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. Note as a precaution, the backup folder will be left in place. Are you <b>absolutely sure</b> you want to proceed? A file permission error caused the purge process to fail; you will have to delete the following folder manually: 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 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" Are you sure you want to rebuild all CPAP data for the following device: 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 device: Would you like to import from your own backups now? (you will have no data visible for this device until you do) OSCAR does not have any backups for this device! Unless you have made <i>your <b>own</b> backups for ALL of your data for this device</i>, <font size=+2>you will lose this device's data <b>permanently</b>!</font> You are about to <font size=+2>obliterate</font> OSCAR's device database for the following device:</p> There was a problem opening MSeries block File: MSeries Import complete You must select and open the profile you wish to modify 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 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. This software is being designed to assist you in reviewing the data produced by your CPAP Devices and related equipment. 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 Layout Save and Restore Graph Layout Settings 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) Hide All Graphs Show 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> 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. <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 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 I started this oximeter recording at (or near) the same time as a session on my CPAP device. <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 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. Flow Restriction Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. Duration of airflow restriction s Event Duration 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 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.) <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> Calculate Unintentional Leaks When Not Present 4 cmH2O 20 cmH2O Note: A linear calculation method is used. Changing these values requires a recalculation. 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 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 <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> 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 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 <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></p></body></html> l/min <html><head/><body><p>Cumulative Indices</p></body></html> Oximetry Settings <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</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) This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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>Provide an alert when importing data from any device model that has not yet been tested by OSCAR developers.</p></body></html> Warn when importing data from an untested device This calculation requires Total Leaks data to be provided by the CPAP device. (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. Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device missed. This option must be enabled before import, otherwise a purge is required. This experimental option attempts to use OSCAR's event flagging system to improve device detected event positioning. Resync Device Detected Events (Experimental) Allow duplicates near device events. Show flags for device detected events that haven't been identified yet. <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices do not support changing these settings.</p></body></html> Clinical Clinical Settings Select Oscar Operating Mode Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. Clinical Mode permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. Permissive Mode Hours <!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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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 device serial number on device settings changes report Print reports in black and white, which can be more legible on non-color printers Print reports in black and white (monochrome) For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. Enables SessionBar in Event Flags Graph 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 No CPAP devices detected Will you be using a ResMed brand device? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, 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? ResMed S9 devices routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). 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 Clinical Mode: Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. Basically replicates the reports and data stored on the devices data card. This includes pap devices, oximeters, etc. Compliance reports fall under this mode. Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. Permissive Mode: Allows user to select which data sets/ sessions to be used for calculations and display. Additional charts and calculations may be available that are not available from the vendor data. Changing the Oscar Operating Mode: Requires a reload of the user's profile. Data will be saved and restored. Minor Flag Span Always Minor Never This may not be a good idea 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 cmH2O Med. Min: %1 Min: Max: Max: %1 %1 (%2 days): %1 (%2 day): % in %1 Hours Min %1 Length: %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 milliSeconds 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 EEPAP Min EEPAP Max EEPAP 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 Device No Data Available App key: Operating system: Built with Qt %1 on %2 Graphics Engine: Graphics Engine type: Compiler: Software Engine ANGLE / OpenGLES Desktop OpenGL m cm in kg l/min Only Settings and Compliance Data Available Summary Data Only Bookmarks Σελιδοδείκτες Mode Model Brand Serial Series 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 device's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Non Data Capable Device Your %1 CPAP Device (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 device. Device Untested Your %1 CPAP Device (Model %2) has not been tested yet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Device Unsupported Sorry, your %1 CPAP Device (%2) is not supported yet. The developers need a .zip copy of this device's SD card and matching clinician .pdf reports to make it work with OSCAR. Getting Ready... Scanning Files... Importing Sessions... UNKNOWN APAP (std) APAP (dyn) Auto S Auto S/T AcSV SoftPAP Mode Pressure relief during exhalation Slight Softstart pressure Pressure during soft start period PSoft Softstart minimum pressure Minimum pressure during soft start period PSoftMin Auto start Automatically turn on the device by breathing Softstart time Lenght of soft start period Soft start maximum time Maximum lenght of soft start period Soft start max. time Soft start pressure Higher End Expiratory Pressure Humidifier level Tube type Obstruction level Obstruction level in percentage rRMVFluctuation Relative respiratory minute volume fluctuation Relative respiratory minute volume Measured pressure Full flow Artefact Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) Epoch (2 mins) with Flow Limitation Deep Sleep Deep sleep, stable respiration Timed breath BiSoft Mode BiSoft 1 BiSoft 2 TriLevel PMaxOA EEPAPMin Lower End Expiratory Pressure EEPAPMax rRMV ART CriticalLeak Mask leakage is above a critical treshold CL eMO Epoch (2 mins) with Mild Obstruction eSO Epoch (2 mins) with Severe Obstruction eS Epoch (2 mins) with Snoring eFL DS Finishing up... 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 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 A few breaths automatically starts device Device automatically switches off Whether or not device allows Mask checking. 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 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 A period during a session where the device could not detect flow. 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 Off Mask Alert Show AHI Whether or not device shows AHI via built-in display. The number of days in the Auto-CPAP trial period, after which the device will revert to CPAP Breathing Not Detected BND Timed Breath Machine Initiated Breath TB 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 device 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 device 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? Device Database Changes Sorry, the purge operation failed, which means this version of OSCAR can't start. The device data folder needs to be removed manually. 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. Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. 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 End 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 Cheyne Stokes Respiration (CSR) Periodic Breathing (PB) Clear Airway (CA) An apnea caused by airway obstruction A partially obstructed airway UA A vibratory snore Pressure Pulse A pulse of pressure 'pinged' to detect a closed airway. A type of respiratory event that won't respond to a pressure increase. Intellipap event where you breathe out your mouth. SensAwake feature will reduce pressure when waking is detected. Heart rate in beats per minute Blood-oxygen saturation percentage Plethysomogram An optical Photo-plethysomogram showing heart rhythm A sudden (user definable) change in heart rate A sudden (user definable) drop in blood oxygen saturation SD Breathing flow rate waveform 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 Ratio between Inspiratory and Expiratory time ratio Pressure Min Pressure Max Pressure Set Pressure Setting IPAP Set IPAP Setting EPAP Set EPAP Setting An abnormal period of Cheyne Stokes Respiration CSR An abnormal period of Periodic Breathing An apnea that couldn't be determined as Central or Obstructive. An apnea reported by your CPAP device. A restriction in breathing from normal, causing a flattening of the flow waveform. A vibratory snore as detected by a System One device 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 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 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. Obstructive Apnea (OA) Hypopnea (H) Unclassified Apnea (UA) Apnea (A) Flow Limitation (FL) RERA (RE) Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. Vibratory Snore (VS) Leak Flag (LF) A large mask leak affecting device performance. Large Leak (LL) Non Responding Event (NR) Expiratory Puff (EP) SensAwake (SA) User Flag #1 (UF1) User Flag #2 (UF2) User Flag #3 (UF3) Pulse Change (PC) SpO2 Drop (SD) I/E Value Apnea Hypopnea Index (AHI) Respiratory Disturbance Index (RDI) 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? OSCAR Reminder Don't forget to place your datacard back in your CPAP device 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 oximetry 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 (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 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) Min EEPAP %1 Max EEPAP %2 PDIFF %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 Selection Length 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 Device auto starts by breathing Response Device auto stops by breathing Patient View RiseEnable RiseTime Cycle Trigger TiMax TiMin Your ResMed CPAP device (Model %1) has not been tested yet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card to make sure it works with OSCAR. SmartStart 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 Smart Stop Simple Advanced 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... Loading summaries Permissive Mode Total disabled sessions: %1, found in %2 days Total disabled sessions: %1 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. 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 Löwenstein Prisma Smart Resvent/Hoffrichter iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings Add Add Feature inhibited. The maximum number of Items has been exceeded. creates new copy of current settings. Restore Restores saved settings from selection. Rename Renames the selection. Must edit existing name then press enter. Update Updates the selection with current settings. Delete Deletes the selection. Expanded Help menu. Exits the Layout menu. <h4>Help Menu - Manage Layout Settings</h4> Exits the help menu. Exits the dialog menu. This feature manages the saving and restoring of Layout Settings. Layout Settings control the layout of a graph or chart. Different Layouts Settings can be saved and later restored. Button Description Creates a copy of the current Layout Settings. The default description is the current date. The description may be changed. The Add button will be greyed out when maximum number is reached. Other Buttons Greyed out when there are no selections Loads the Layout Settings from the selection. Automatically exits. io Modify the description of the selection. Same as a double click.io Saves the current Layout Settings to the selection. Prompts for confirmation. Deletes the selecton. Control Exit (Red circle with a white "X".) Returns to OSCAR menu. Return Next to Exit icon. Only in Help Menu. Returns to Layout menu. Escape Key Exit the Help or Layout menu. Layout Settings * Name * Pinning * Plots Enabled * Height * Order * Event Flags * Dotted Lines * Height Options General Information Maximum description size = 80 characters. Maximum Saved Layout Settings = 30. Saved Layout Settings can be accessed by all profiles. Layout Settings only control the layout of a graph or chart. They do not contain any other data. They do not control if a graph is displayed or not. Layout Settings for daily and overview are managed independantly. Maximum number of Items exceeded. No Item Selected Ok to Update? Ok To Delete? SessionBar %1h %2m No Sessions Present SleepStyleLoader Import Error This device 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 Device Information Changes to Device Settings Database has No %1 data available. Database has %1 day of %2 Data on %3 Database has %1 days of %2 Data, between %3 and %4 Total Days: %1 Days Not Used: %1 Days Used: %1 Days %1 %2 Hours: %3 Best Device Setting Worst Device Setting 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. Date: %1 - %2 AHI: %1 Total Hours: %1 Most Recent Compliance (%1 hrs/day) OSCAR is free open-source CPAP report software No data found?!? Oscar has no data to report :( Last Week Last 30 Days Last 6 Months Last Year Last Session Details Days Pressure Relief Pressure Settings 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 device.</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 device is detected First import can take a few minutes. The last time you used your %1... last night today %2 days ago was %1 (on %2) %1 hours, %2 minutes and %3 seconds <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 pressure was under %1 %2 for %3% of the time. 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. 1 day ago Your device was on for %1. Your CPAP device used a constant %1 %2 of air Your device used a constant %1-%2 %3 of air. Your device was under %1-%2 %3 for %4% of the time. Your EEPAP pressure was under %1 %2 for %3% of the time. Your average leaks were %1 %2, which is %3 your %4 day average of %5. No CPAP data has been imported yet. gGraph Double click Y-axis: Return to AUTO-FIT Scaling Double click Y-axis: Return to DEFAULT Scaling Double click Y-axis: Return to OVERRIDE Scaling Double click Y-axis: For Dynamic Scaling Double click Y-axis: Select DEFAULT Scaling Double click Y-axis: Select AUTO-FIT Scaling %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.5.1/Translations/Deutsch.de.ts000066400000000000000000017314621450332542600205240ustar00rootroot00000000000000 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 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 Search Suche 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 B.M.I. B.M.I. Sleep Stage Sessions Schlafstadium Sitzungen Oximeter Information Oxymeter Informationen Events Ereignisse CPAP Sessions CPAP Sitzung Medium Mittel Starts Startet Weight Gewicht i i Zombie Nicht gut Bookmarks Lesezeichen Layout Layout Save and Restore Graph Layout Settings Diagrammlayouteinstellungen speichern und wiederherstellen Session End Times Sitzungsendzeit enable aktivieren %1 events %1 Ereignisse events Ereignisse Event Breakdown Ereignis Pannen Click to %1 this session. Klicken Sie auf %1 dieser Sitzung. SpO2 Desaturations SpO2 Entsättigungen "Nothing's here!" "Keine Daten vorhanden!" %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 Clinical Mode Klinischer Modus Disabling Sessions requires the Permissive Mode Das Deaktivieren von Sitzungen erfordert den Permissive-Modus no data :( keine Daten :( Sorry, this device only provides compliance data. Tut mir leid, dieses Gerät liefert nur Konformitätsdaten. Bookmark at %1 Lesezeichen bei %1 Statistics Statistiken Breakdown Aufschlüsselung Disable Warning Warnung deaktivieren Disabling a session will remove this session data from all graphs, reports and statistics. The Search tab can find disabled sessions Continue ? Durch das Deaktivieren einer Sitzung werden diese Sitzungsdaten entfernt aus allen Grafiken, Berichten und Statistiken. Die Registerkarte „Suchen“ kann deaktivierte Sitzungen finden Weitermachen ? Unknown Session Unbekannte Sitzung Device Settings Geräteeinstellungen This CPAP device does NOT record detailed data Dieses CPAP-Gerät zeichnet KEINE detaillierten Daten auf 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.) Hide All Events Alle Ereignisse verbergen Show All Events Alle Ereignisse anzeigen Hide All Graphs Alle Diagramme ausblenden Show All Graphs Alle Diagramme anzeigen DailySearchTab Match: Übereinstimmung: Select Match Wählen Sie Übereinstimmung aus Clear Klar Bookmark Jumps to Date's Bookmark Lesezeichen Springt zum Lesezeichen des Datums Start Search Suche starten DATE Jumps to Date DATUM Springt zum Datum Match Übereinstimmung Notes Notizen Notes containing Notizen enthalten Bookmarks Lesezeichen Bookmarks containing Lesezeichen enthalten AHI AHI Daily Duration Tägliche Dauer Session Duration Sitzungsdauer Days Skipped Tage übersprungen Disabled Sessions Deaktivierte Sitzungen Number of Sessions Anzahl der Sitzungen Click HERE to close Help Klicken Sie HIER, um die Hilfe zu schließen Help Hilfe No Data Jumps to Date's Details Keine Daten Springt zu den Details des Datums Number Disabled Session Jumps to Date's Details Nummer deaktivierte Sitzung Springt zu den Details des Datums Note Jumps to Date's Notes Notiz Springt zu den Notizen des Datums Jumps to Date's Bookmark Springt zum Lesezeichen von Daten AHI Jumps to Date's Details AHI Springt zu den Details des Datums EventsPerHour Ereignisse pro Stunde Session Duration Jumps to Date's Details Sitzungsdauer Springt zu den Details des Datums Minutes Protokoll Number of Sessions Jumps to Date's Details Anzahl der Sitzungen Springt zu den Details des Datums Sessions Sitzungen Daily Duration Jumps to Date's Details Tägliche Dauer Springt zu den Details des Datums Hours Zeit Number of events Jumps to Date's Events Anzahl der Ereignisse Springt zu den Ereignissen von Daten Events Veranstaltungen Automatic start Automatischer Start More to Search Mehr zum Suchen Continue Search Suche fortsetzen End of Search Ende der Suche No Matches Keine Treffer Skip:%1 Überspringen:%1 %1/%2%3 days. %1/%2%3 Tage. %1/%2%3 days %1/%2%3 Tage Found %1 gefunden %1 Finds days that match specified criteria. Findet Tage, die bestimmten Kriterien entsprechen. Searches from last day to first day. Suchen vom letzten Tag bis zum ersten Tag. First click on Match Button then select topic. Klicken Sie zuerst auf die Match-Schaltfläche und wählen Sie dann das Thema aus. Then click on the operation to modify it. Klicken Sie dann auf die Operation, um sie zu ändern. or update the value oder aktualisieren Sie den Wert Topics without operations will automatically start. Themen ohne Operationen werden automatisch gestartet. Compare Operations: numberic or character. Vergleichsoperationen: numerisch oder Zeichen. Numberic Operations: Numerische Operationen: Character Operations: Charakteroperationen: Summary Line Zusammenfassungszeile Left:Summary - Number of Day searched Links: Zusammenfassung – Anzahl der gesuchten Tage Center:Number of Items Found Mitte: Anzahl der gefundenen Elemente Right:Minimum/Maximum for item searched Rechts: Minimum/Maximum für das gesuchte Element Result Table Ergebnistabelle Column One: Date of match. Click selects date. Spalte Eins: Datum des Spiels. Klick wählt Datum aus. Column two: Information. Click selects date. Spalte zwei: Informationen. Klick wählt Datum aus. Then Jumps the appropiate tab. Springt dann zum entsprechenden Reiter. Wildcard Pattern Matching: Wildcard-Musterabgleich: Wildcards use 3 characters: Platzhalter verwenden 3 Zeichen: Asterisk Sternchen Question Mark Fragezeichen Backslash. Backslash. Asterisk matches any number of characters. Asterisk entspricht einer beliebigen Anzahl von Zeichen. Question Mark matches a single character. Fragezeichen entspricht einem einzelnen Zeichen. Backslash matches next character. Backslash passt zum nächsten Zeichen. Found %1. %1 gefunden. DateErrorDisplay ERROR The start date MUST be before the end date ERROR Das Startdatum MUSS vor dem Enddatum liegen The entered start date %1 is after the end date %2 Das eingegebene Startdatum %1 liegt nach dem Enddatum %2 Hint: Change the end date first Tipp: Ändern Sie zuerst das Enddatum The entered end date %1 Das eingegebene Enddatum %1 is before the start date %1 liegt vor dem Startdatum %1 Hint: Change the start date first Tipp: Ändern Sie zuerst das Startdatum 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 Import Error Import Fehler The Day records overlap with already existing content. Die Aufzeichnungen dieses Tages überschneiden sich mit bereits vorhandenen Inhalt. This device Record cannot be imported in this profile. Dieser Gerätedatensatz kann nicht in dieses Profil importiert werden. 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" Couldn't find any valid Device Data at %1 Konnte keine gültigen Gerätedaten finden unter %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 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. 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. 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 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 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 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 Purge ALL Device Data Löschen Sie ALLE Gerätedaten Reset Graph &Heights Graph &Höhen zurücksetzen Troubleshooting Fehlerbehebung Show Standard Report Standardbericht anzeigen Show Monthly Report Monatsbericht anzeigen Show Range Report Bereichsbericht anzeigen Select Report Date Wählen Sie das Berichtsdatum aus Report Date Berichtsdatum &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) You must select and open the profile you wish to modify Sie müssen das Profil, das Sie ändern möchten, auswählen und öffnen 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 Standard - CPAP, APAP Standard - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> <html><head/><body><p>Standarddiagrammreihenfolge, gut für CPAP, APAP, Basic BPAP</p></body></html> Advanced - BPAP, ASV Erweitert - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> <html><head/><body><p>Erweiterte Diagrammreihenfolge, gut für BPAP mit BU, ASV, AVAPS, IVAPS</p></body></html> 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 No supported data was found Es wurden keine unterstützten Daten gefunden Are you sure you want to rebuild all CPAP data for the following device: Möchten Sie wirklich alle CPAP-Daten für das folgende Gerät neu erstellen: For some reason, OSCAR does not have any backups for the following device: Aus irgendeinem Grund hat OSCAR keine Sicherungen für das folgende Gerät: Would you like to import from your own backups now? (you will have no data visible for this device until you do) Möchten Sie jetzt aus Ihren eigenen Backups importieren? (Bis dahin sind keine Daten für dieses Gerät sichtbar) OSCAR does not have any backups for this device! OSCAR hat keine Backups für dieses Gerät! Unless you have made <i>your <b>own</b> backups for ALL of your data for this device</i>, <font size=+2>you will lose this device's data <b>permanently</b>!</font> Sofern Sie nicht <i>Ihre <b>eigenen</b> Sicherungen für ALLE Ihre Daten für dieses Gerät erstellt haben</i>, <font size=+2>werden Sie die Daten dieses Geräts <b>dauerhaft</b verlieren >!</font> You are about to <font size=+2>obliterate</font> OSCAR's device database for the following device:</p> Sie sind dabei, die Gerätedatenbank von OSCAR für das folgende Gerät <font size=+2>zu löschen</font>:</p> A file permission error caused the purge process to fail; you will have to delete the following folder manually: Ein Dateiberechtigungsfehler führte dazu, dass der Löschvorgang fehlschlug; Sie müssen den folgenden Ordner manuell löschen: 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. This software is being designed to assist you in reviewing the data produced by your CPAP Devices and related equipment. Diese Software wurde entwickelt, um Sie bei der Überprüfung der von Ihren CPAP-Geräten und zugehörigen Geräten erzeugten Daten zu unterstützen. 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> 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 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 Layout Layout Save and Restore Graph Layout Settings Diagrammlayouteinstellungen speichern und wiederherstellen Last Two Months Letzten 2 Monate Snapshot Schnappschuss Hide All Graphs Alle Diagramme ausblenden Show All Graphs Alle Diagramme anzeigen 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 I started this oximeter recording at (or near) the same time as a session on my CPAP device. Ich habe diese Oximeteraufzeichnung zur (oder nahezu) gleichen Zeit wie eine Sitzung auf meinem CPAP-Gerät gestartet. &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 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 &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... 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) <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 Clinical Mode: Klinischer Modus: Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. Meldet, was sich auf der Datenkarte befindet, einschließlich aller im Permissivmodus abgewählten Daten. Basically replicates the reports and data stored on the devices data card. Repliziert grundsätzlich die auf der Datenkarte des Geräts gespeicherten Berichte und Daten. This includes pap devices, oximeters, etc. Compliance reports fall under this mode. Dazu gehören Pap-Geräte, Oximeter usw. Compliance-Berichte fallen in diesen Modus. Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. Compliance-Berichte umfassen immer alle Daten innerhalb des ausgewählten Compliance-Zeitraums, auch wenn dies anders deaktiviert ist. Permissive Mode: Permissiver Modus: Allows user to select which data sets/ sessions to be used for calculations and display. Ermöglicht dem Benutzer die Auswahl der Datensätze/Sitzungen, die für Berechnungen und Anzeige verwendet werden sollen. Additional charts and calculations may be available that are not available from the vendor data. Möglicherweise sind zusätzliche Diagramme und Berechnungen verfügbar, die in den Anbieterdaten nicht verfügbar sind. Changing the Oscar Operating Mode: Ändern des Oscar-Betriebsmodus: Requires a reload of the user's profile. Data will be saved and restored. Erfordert ein Neuladen des Benutzerprofils. Die Daten werden gespeichert und wiederhergestellt. Span Spannweite No CPAP devices detected Keine CPAP-Geräte erkannt Will you be using a ResMed brand device? Werden Sie ein Gerät der Marke ResMed verwenden? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, days will <b>split at noon</b> like in ResMed's commercial software.</p> <p><b>Bitte beachten Sie:</b> Die erweiterten Sitzungsaufteilungsfunktionen von OSCAR sind mit <b>ResMed</b>-Geräten aufgrund einer Einschränkung in der Art und Weise, wie ihre Einstellungen und Zusammenfassungsdaten gespeichert werden, nicht möglich, und deshalb sind sie es wurde für dieses Profil deaktiviert.</p><p>Auf ResMed-Geräten werden die Tage <b>mittags geteilt</b> wie in der kommerziellen Software von ResMed.</p> ResMed S9 devices 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 Ihrer SD-Karte, die älter als 7 und 30 Tage sind (je nach Auflösung). &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. 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 Graph Titles Diagrammtitel Zero Reset Nullsetzung 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? Flow Restriction Durchflussbegrenzung <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Benutzerdefinierte Kennzeichnung ist eine experimentelle Methode zur Erkennung von Ereignissen, die vom Gerät übersehen wurden. Sie sind </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">nicht</span><span style=" font-family:'Sans' ; font-size:10pt;"> in AHI enthalten.</span></p></body></html> 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 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 <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Sitzungen, die kürzer als diese Dauer sind, werden nicht angezeigt.</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> 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 <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:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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><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:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Synchronisierung von Oximetrie- und CPAP-Daten</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50-Daten, die von SpO2Review (aus .spoR-Dateien) oder der seriellen Importmethode importiert werden, haben </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">nicht</span><span style=" font-family:'Sans'; font-size:10pt;"> den korrekten Zeitstempel, der für die Synchronisierung benötigt wird.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Der Live-View-Modus (unter Verwendung eines seriellen Kabels) ist eine Möglichkeit, eine genaue Synchronisierung bei CMS50-Oximetern zu erreichen, gleicht jedoch nicht die CPAP-Taktdrift aus.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Wenn Sie den Aufzeichnungsmodus Ihres Oximeters </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">genau </span><span style=" font-family:'Sans'; font-size:10pt;">zu dem Zeitpunkt starten, zu dem Sie Ihr CPAP-Gerät einschalten, können Sie nun auch eine Synchronisierung erreichen. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Für den Serienimport wird die Startzeit der ersten CPAP-Sitzung der letzten Nacht verwendet. (Denken Sie daran, Ihre CPAP-Daten zuerst zu importieren!)</span></p></body></html> Search Suche Time Weighted average of Indice Zeit Gewichteter Durchschnitt der Indice Middle Calculations Mittel Berechnungen Skip over Empty Days Leere Tage überspringen 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. AHI/Hour Graph Time Window AHI/Stunde Diagramm Zeitfenster Import without asking for confirmation Import ohne weitere Bestätigung This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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 Sicherungskopie der SD-Kartendaten für ResMed-Geräte verwaltet, Geräte der ResMed S9-Serie löschen hochauflösende Daten, die älter als 7 Tage sind, und Diagrammdaten, die älter als 30 Tage sind. OSCAR kann eine Kopie dieser Daten aufbewahren, falls Sie jemals eine Neuinstallation durchführen müssen. (Sehr empfehlenswert, es sei denn, Sie haben wenig Speicherplatz oder interessieren sich nicht für die Diagrammdaten) <html><head/><body><p>Provide an alert when importing data from any device model that has not yet been tested by OSCAR developers.</p></body></html> <html><head/><body><p>Seien Sie gewarnt, wenn Sie Daten von einem Gerätemodell importieren, das noch nicht von OSCAR-Entwicklern getestet wurde.</p></body></html> Warn when importing data from an untested device Warnung beim Importieren von Daten von einem ungetesteten Gerät This calculation requires Total Leaks data to be provided by the CPAP device. (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. Für diese Berechnung müssen vom CPAP-Gerät Gesamtleckagedaten bereitgestellt werden. (z. B. PRS1, aber nicht ResMed, das diese bereits hat) Die hier verwendeten unbeabsichtigten Leckageberechnungen sind linear, sie modellieren nicht die Maskenentlüftungskurve. Wenn Sie einige verschiedene Masken verwenden, wählen Sie stattdessen Durchschnittswerte. Es sollte noch nah genug sein. Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device missed. This option must be enabled before import, otherwise a purge is required. Aktivieren/deaktivieren Sie experimentelle Verbesserungen der Ereigniskennzeichnung. Es ermöglicht die Erkennung grenzwertiger Ereignisse und einige, die das Gerät übersehen hat. Diese Option muss vor dem Import aktiviert werden, andernfalls ist eine Bereinigung erforderlich. This experimental option attempts to use OSCAR's event flagging system to improve device detected event positioning. Diese experimentelle Option versucht, das Ereignismarkierungssystem von OSCAR zu verwenden, um die Positionierung von vom Gerät erkannten Ereignissen zu verbessern. Resync Device Detected Events (Experimental) Neue Syncronisierung vom Gerät erkannte Ereignisse (experimentell) Allow duplicates near device events. Duplikate in der Nähe von Geräteereignissen zulassen. Show flags for device detected events that haven't been identified yet. Markierungen für noch nicht identifizierte Geräteerkennungsereignisse anzeigen. l/min l/min <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p>Kumulative Indizes</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices do not support changing these settings.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Hinweis: </span>Aufgrund von Zusammenfassungsdesignbeschränkungen unterstützen ResMed-Geräte das Ändern dieser Einstellungen nicht.</p ></body></html> Clinical Klinisch Clinical Settings Klinische Einstellungen Select Oscar Operating Mode Wählen Sie den Oscar-Betriebsmodus Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. Der klinische Modus lässt keine deaktivierten Sitzungen zu. Deaktivierte Sitzungen werden nicht für grafische Darstellungen oder Statistiken verwendet. Clinical Mode Klinischer Modus permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. Der permissive Modus erlaubt deaktivierte Sitzungen. Deaktivierte Sitzungen werden für die grafische Darstellung und Statistik verwendet. Permissive Mode Permissiver Modus Hours Zeit 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" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Synchronisieren von Oximetrie- und CPAP-Daten</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50-Daten, die aus SpO2Review (aus .spoR-Dateien) oder der seriellen Importmethode importiert wurden, tun dies </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;"> Sie müssen über den richtigen Zeitstempel verfügen, der für die Synchronisierung erforderlich ist.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Der Live-Ansichtsmodus (mit einem seriellen Kabel) ist eine Möglichkeit, eine genaue Synchronisierung auf CMS50-Oximetern zu erreichen, wirkt sich jedoch nicht gegen die Abweichung der CPAP-Uhr aus.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Wenn Sie den Aufzeichnungsmodus Ihres Oximeters starten </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">gleichzeitig mit dem Starten Ihres CPAP-Geräts können Sie nun auch eine Synchronisierung erreichen. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Der serielle Importvorgang übernimmt die Startzeit der ersten CPAP-Sitzung gestern Abend. (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 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. 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 &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 For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. Bei mehreren Sitzungen wird oben im Ereigniskennzeichnungsdiagramm für jede Sitzung eine dünne graue Linie angezeigt. Enables SessionBar in Event Flags Graph Aktiviert SessionBar im Ereignisflag-Diagramm Fonts (Application wide settings) Schriften (Application Größeneinstellungen) Use Pixmap Caching Verwenden Pixmap Zwischenspeicherung 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 Include Serial Number Seriennummer einbeziehen <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 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 Whether to include device serial number on device settings changes report Ob die Seriennummer des Geräts in den Änderungsbericht der Geräteeinstellungen aufgenommen werden soll 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 O2 O2 OA OA NR NR PB PB PC PC No Nein PP PP PS PS Device Gerät On An RE RE S9 S9 SA SA SD SD UNKNOWN UNBEKANNT APAP (std) APAP (std) APAP (dyn) APAP (dyn) Auto S Auto S Auto S/T Auto S/T AcSV AcSV SoftPAP Mode SoftPAP-Modus Pressure relief during exhalation Druckentlastung beim Ausatmen Slight Leicht Softstart pressure Softstart-Druck Pressure during soft start period Druck während der Sanftanlaufphase PSoft PSoft Softstart minimum pressure Mindestdruck für Softstart Minimum pressure during soft start period Mindestdruck während der Sanftanlaufphase PSoftMin PSoftMin Auto start Auto-Start Automatically turn on the device by breathing Schalten Sie das Gerät automatisch durch Atmen ein Softstart time Softstartzeit Lenght of soft start period Länge der Sanftanlaufphase Soft start maximum time Maximale Softstartzeit Maximum lenght of soft start period Maximale Länge der Sanftanlaufphase Soft start max. time Sanftanlauf max. Zeit Soft start pressure Sanfter Startdruck Higher End Expiratory Pressure Höherer exspiratorischer Enddruck Humidifier level Luftbefeuchterstufe Tube type Schlauch Typ Obstruction level Hindernisebene Obstruction level in percentage Hindernisgrad in Prozent rRMVFluctuation rRMV Fluktuation Relative respiratory minute volume fluctuation Relative Schwankung des Atemminutenvolumens Relative respiratory minute volume Relatives Atemminutenvolumen Measured pressure Gemessener Druck Full flow Voller Fluss Artefact Artefakt Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) Unregelmäßigkeiten in den gemessenen Daten, die kein Atemereignis darstellen (z. B. Schlucken, Husten oder Sprechen) Epoch (2 mins) with Flow Limitation Epoche (2 Min.) mit Durchflussbegrenzung Deep Sleep Tiefschlaf Deep sleep, stable respiration Tiefer Schlaf, stabile Atmung Tiefer Schlaf, stabile Atmung Timed breath Zeitgesteuerter Atem BiSoft Mode BiSoft-Modus BiSoft 1 BiSoft 1 BiSoft 2 BiSoft 2 TriLevel dritte Ebene AutoStart Auto-Start Softstart_Time Softstart_Zeit Softstart_TimeMax Softstart_ZeitMax Softstart_Pressure Softstart_Druck PMaxOA PMaxOA EEPAPMin EEPAPMin Lower End Expiratory Pressure Ausatemdruck am unteren Ende EEPAPMax EEPAPMax HumidifierLevel Befeuchterstufe TubeType Schlauchtyp ObstructLevel Hindernisebene Obstruction Level Hindernisstufe rMVFluctuation rMV-Schwankung rRMV rRMV PressureMeasured Druck gemessen FlowFull voller Durchfluss SPRStatus SPR- Status Artifact Artefakt ART ART CriticalLeak Kritisches Leck Mask leakage is above a critical treshold Die Leckage der Maske liegt über einem kritischen Schwellenwert CL CL eMO eMO Epoch (2 mins) with Mild Obstruction Epoche (2 Min.) mit leichter Obstruktion eSO eSO Epoch (2 mins) with Severe Obstruction Epoche (2 Min.) mit schwerer Behinderung eS eS Epoch (2 mins) with Snoring Epoche (2 Min.) mit Schnarchen eFL eFL DeepSleep Tiefschlaf DS DS TimedBreath Zeitgesteuerter Atem 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 Compiler: Verfasser: in in kg kg milliSeconds Millisekunden l/min l/min EEPAP EEPAP Min EEPAP Min EEPAP Max EEPAP Max EEPAP 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 <i>Your old device 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 neu generiert werden, sofern diese Sicherungsfunktion nicht bei einem vorherigen Datenimport in den Einstellungen deaktiviert wurde.</i> This means you will need to import this device data again afterwards from your own backups or data card. Das bedeutet, dass Sie diese Gerätedaten anschließend erneut von Ihrer eigenen Sicherung oder Datenkarte importieren müssen. Device Database Changes Änderungen der Gerätedatenbank The device data folder needs to be removed manually. Der Gerätedatenordner muss manuell entfernt werden. 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 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 cmH2O cmH2O Pressure Support Druckunterstützung Bedtime: %1 Schlafenszeit: %1 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 Respironics Respironics Heart rate in beats per minute Die Herzfrequenz in Schlägen pro Minute 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 Disconnected Getrennt Sleep Stage Schlafstadium Minute Vent. Minuten Vent. Ramp Event Rampenereignis SensAwake feature will reduce pressure when waking is detected. SensAwake reduziert den Druck beim Erkennen des Wachzustandes. 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 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 device'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 genau, daher möchten die Entwickler eine .zip-Kopie der SD-Karte dieses Geräts und passende .pdf-Berichte des Arztes, um sicherzustellen, dass OSCAR die Daten korrekt verarbeitet. Non Data Capable Device Nicht datenfähiges Gerät Your %1 CPAP Device (Model %2) is unfortunately not a data capable model. Ihr CPAP-Gerät %1 (Modell %2) ist leider kein datenfähiges Modell. I'm sorry to report that OSCAR can only track hours of use and very basic settings for this device. Es tut mir leid, Ihnen mitteilen zu müssen, dass OSCAR nur Nutzungsstunden und sehr grundlegende Einstellungen für dieses Gerät verfolgen kann. Device Untested Gerät ungetestet Your %1 CPAP Device (Model %2) has not been tested yet. Ihr CPAP-Gerät %1 (Modell %2) wurde noch nicht getestet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Es scheint anderen Geräten ähnlich genug zu sein, dass es funktionieren könnte, aber die Entwickler möchten eine .zip-Kopie der SD-Karte dieses Geräts und passende .pdf-Berichte des Arztes, um sicherzustellen, dass es mit OSCAR funktioniert. Device Unsupported Gerät wird nicht unterstützt Sorry, your %1 CPAP Device (%2) is not supported yet. Entschuldigung, Ihr CPAP-Gerät %1 (%2) wird noch nicht unterstützt. The developers need a .zip copy of this device'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 passende klinische .pdf-Berichte, 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 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 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 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 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. Connected Angeschlossen Low Usage Days: %1 Tage mit geringer Nutzung: %1 PS Max PS Max PS Min PS Min Selection Length Auswahllänge 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 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: 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 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? Are you sure you want to reset all your oximetry settings to defaults? Möchten Sie wirklich alle Oximetrieeinstellungen auf die Standardwerte zurücksetzen? BrainWave Gehirnwellen Inspiratory Pressure Einatmungsdruck 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+ 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 Days: %1 Tage: %1 Plethysomogram Plethysomogramm 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 Length: %1 Dauer: %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 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 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 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'. 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 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 End Expiratory Pressure Beenden Sie den Ausatmungsdruck An abnormal period of Cheyne Stokes Respiration Eine abnormale Periode von Cheyne-Stokes-Atmung Cheyne Stokes Respiration (CSR) Cheyne-Stokes-Atmung (CSR) Periodic Breathing (PB) Periodische Atmung (PB) Clear Airway (CA) Freie Atemwege (CA) Obstructive Apnea (OA) Obstruktive Apnoe (OA) Hypopnea (H) Hypopnoe (H) Unclassified Apnea (UA) Nicht klassifizierte Apnoe (UA) Apnea (A) Apnoe (A) An apnea reportred by your CPAP device. Eine von Ihrem CPAP-Gerät gemeldete Apnoe. Flow Limitation (FL) Durchflussbegrenzung (FL) RERA (RE) RERA (RE) Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. Atemanstrengungsbedingte Erregung: Eine Atembehinderung, die entweder zu Aufwach- oder Schlafstörungen führt. Vibratory Snore (VS) Vibrationsschnarchen (VS) Leak Flag (LF) Leck-Kennzeichnung (LF) A large mask leak affecting device performance. Ein großes Maskenleck beeinträchtigt die Geräteleistung. Large Leak (LL) Großes Leck (LL) 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 Summary Only Nur Zusammenfassung Bookmark End Ende Lesezeichen Bi-Level Bi Ebene Intellipap Intellipap 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... Flex Mode Flex-Modus Sessions Sitzungen 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 An apnea reported by your CPAP device. Eine von Ihrem CPAP-Gerät gemeldete Apnoe. A vibratory snore as detected by a System One device Ein vibrierendes Schnarchen, wie es von einem System One-Gerät erkannt wird Non Responding Event (NR) Keine-Antwort-Ereignis (NR) Expiratory Puff (EP) Ausatmungsstoß (EP) SensAwake (SA) SensAwake (SA) User Flag #1 (UF1) Benutzerkennzeichnung #1 (UF1) User Flag #2 (UF2) Benutzerkennzeichnung #2 (UF2) User Flag #3 (UF3) Benutzerkennzeichnung #3 (UF3) Pulse Change (PC) Pulsänderung (PC) SpO2 Drop (SD) SpO2-Abfall (SD) I/E Value I/E-Wert Apnea Hypopnea Index (AHI) Apnoe-Hypopnoe-Index (AHI) Respiratory Disturbance Index (RDI) Atemstörungsindex (RDI) 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. %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 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 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: Data directory: Datenverzeichnis: Permissive Mode Permissiver Modus Total disabled sessions: %1, found in %2 days Insgesamt deaktivierte Sitzungen: %1, gefunden in %2 Tagen Total disabled sessions: %1 Gesamtzahl der deaktivierten Sitzungen: %1 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. Dauer der längsten deaktivierten Sitzung: %1 Minuten, Gesamtdauer aller deaktivierten Sitzungen: %2 Minuten. 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 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). Don't forget to place your datacard back in your CPAP device Vergessen Sie nicht, Ihre Datenkarte wieder in Ihr CPAP-Gerät einzulegen 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 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 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 A few breaths automatically starts device Ein paar Atemzüge startet das Gerät automatisch Device automatically switches off Gerät schaltet automatisch ab Whether or not device allows Mask checking. Ob das Gerät die Maskenprüfung zulässt oder nicht. Whether or not device shows AHI via built-in display. Ob das Gerät AHI über das eingebaute Display anzeigt oder nicht. The number of days in the Auto-CPAP trial period, after which the device will revert to CPAP Die Anzahl der Tage im Auto-CPAP-Testzeitraum, nach denen das Gerät wieder auf CPAP umschaltet A period during a session where the device could not detect flow. Ein Zeitraum während einer Sitzung, in dem das Gerät keinen Durchfluss erkennen konnte. 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. varies variiert Min EEPAP %1 Max EEPAP %2 PDIFF %3-%4 (%5) Min. EEPAP %1 Max. EEPAP %2 PDIFF %3-%4 (%5) 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 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 Device auto starts by breathing Das Gerät startet automatisch durch Atmen SmartStop Intelligenter Stop RiseEnable Aufstieg aktivieren RiseTime Anstiegszeit Cycle Zyklus Trigger Auslöser TiMax TiMax TiMin TiMin Your ResMed CPAP device (Model %1) has not been tested yet. Ihr ResMed CPAP-Gerät (Modell %1) wurde noch nicht getestet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card to make sure it works with OSCAR. Es scheint anderen Geräten ähnlich genug zu sein, dass es funktionieren könnte, aber die Entwickler möchten eine .zip-Kopie der SD-Karte dieses Geräts, um sicherzustellen, dass es mit OSCAR funktioniert. Smart Stop Intelligenter Stop Device auto stops by breathing Das Gerät stoppt automatisch durch Atmen Simple Einfach Advanced Fortgeschrittene Humidity Luftfeuchtigkeit SleepStyle Schlafstil 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 Löwenstein Löwenstein Prisma Smart Prisma Smart Resvent/Hoffrichter Resvent/Hoffrichter iBreeze/Point3 iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings Layouteinstellungen speichern verwalten Add Hinzufügen Add Feature inhibited. The maximum number of Items has been exceeded. Funktion hinzufügen gesperrt. Die maximale Anzahl von Artikeln wurde überschritten. creates new copy of current settings. Erstellt eine neue Kopie der aktuellen Einstellungen. Restore Wiederherstellen Restores saved settings from selection. Stellt gespeicherte Einstellungen aus der Auswahl wieder her. Rename Umbenennen Renames the selection. Must edit existing name then press enter. Benennt die Auswahl um. Vorhandenen Namen bearbeiten und dann Enter drücken. Update Aktualisieren Updates the selection with current settings. Aktualisiert die Auswahl mit den aktuellen Einstellungen. Delete Löschen Deletes the selection. Löscht die Auswahl. Expanded Help menu. Erweitertes Hilfemenü. Exits the Layout menu. Beendet das Layout-Menü. <h4>Help Menu - Manage Layout Settings</h4> <h4>Hilfemenü – Layouteinstellungen verwalten</h4> Exits the help menu. Beendet das Hilfemenü. Exits the dialog menu. Beendet das Dialogmenü. This feature manages the saving and restoring of Layout Settings. Diese Funktion verwaltet das Speichern und Wiederherstellen von Layouteinstellungen. Layout Settings control the layout of a graph or chart. Die Layouteinstellungen steuern das Layout eines Diagramms. Different Layouts Settings can be saved and later restored. Verschiedene Layouteinstellungen können gespeichert und später wiederhergestellt werden. Button Taste Description Beschreibung Creates a copy of the current Layout Settings. Erstellt eine Kopie der aktuellen Layouteinstellungen. The default description is the current date. Die Standardbeschreibung ist das aktuelle Datum. The description may be changed. Die Beschreibung kann geändert werden. The Add button will be greyed out when maximum number is reached. Die Schaltfläche „Hinzufügen“ wird ausgegraut, wenn die maximale Anzahl erreicht ist. Other Buttons Andere Tasten Greyed out when there are no selections Ausgegraut, wenn keine Auswahl vorhanden ist Loads the Layout Settings from the selection. Automatically exits. io Lädt die Layouteinstellungen aus der Auswahl. Wird automatisch beendet. io Modify the description of the selection. Same as a double click.io Ändern Sie die Beschreibung der Auswahl. Dasselbe wie ein Doppelklick.io Saves the current Layout Settings to the selection. Speichert die aktuellen Layouteinstellungen in der Auswahl. Prompts for confirmation. Fordert zur Bestätigung auf. Deletes the selecton. Löscht die Auswahl. Control Kontrolle Exit Beenden (Red circle with a white "X".) Returns to OSCAR menu. (Roter Kreis mit einem weißen „X“.) Kehrt zum OSCAR-Menü zurück. Return Zurückkehren Next to Exit icon. Only in Help Menu. Returns to Layout menu. Neben dem Exit-Symbol. Nur im Hilfemenü. Kehrt zum Layout-Menü zurück. Escape Key Escape-Taste Exit the Help or Layout menu. Verlassen Sie das Hilfe- oder Layoutmenü. Layout Settings Layouteinstellungen * Name * Name * Pinning * Anheften * Plots Enabled * Grundstücke aktiviert * Height * Höhe * Order * Befehl * Event Flags * Ereignisflaggen * Dotted Lines * Gepunktete Linien * Height Options * Höhenoptionen General Information Allgemeine Informationen Maximum description size = 80 characters. Maximale Beschreibungsgröße = 80 Zeichen. Maximum Saved Layout Settings = 30. Maximal gespeicherte Layouteinstellungen = 30. Saved Layout Settings can be accessed by all profiles. Auf gespeicherte Layouteinstellungen kann von allen Profilen zugegriffen werden. Layout Settings only control the layout of a graph or chart. Die Layouteinstellungen steuern nur das Layout eines Diagramms. They do not contain any other data. Sie enthalten keine weiteren Daten. They do not control if a graph is displayed or not. Sie steuern nicht, ob ein Diagramm angezeigt wird oder nicht. Layout Settings for daily and overview are managed independantly. Die Layouteinstellungen für Tages- und Übersichtslayout werden unabhängig voneinander verwaltet. <p style="color:black;"> This feature manages the saving and restoring of Layout Settings. <br> Layout Settings control the layout of a graph or chart. <br> Different Layouts Settings can be saved and later restored. <br> </p> <table width="100%"> <tr><td><b>Button</b></td> <td><b>Description</b></td></tr> <tr><td valign="top">Add</td> <td>Creates a copy of the current Layout Settings. <br> The default description is the current date. <br> The description may be changed. <br> The Add button will be greyed out when maximum number is reached.</td></tr> <br> <tr><td><i><u>Other Buttons</u> </i></td> <td>Greyed out when there are no selections</td></tr> <tr><td>Restore</td> <td>Loads the Layout Settings from the selection. Automatically exits. </td></tr> <tr><td>Rename </td> <td>Modify the description of the selection. Same as a double click.</td></tr> <tr><td valign="top">Update</td><td> Saves the current Layout Settings to the selection.<br> Prompts for confirmation.</td></tr> <tr><td valign="top">Delete</td> <td>Deletes the selecton. <br> Prompts for confirmation.</td></tr> <tr><td><i><u>Control</u> </i></td> <td></td></tr> <tr><td>Exit </td> <td>(Red circle with a white "X".) Returns to OSCAR menu.</td></tr> <tr><td>Return</td> <td>Next to Exit icon. Only in Help Menu. Returns to Layout menu.</td></tr> <tr><td>Escape Key</td> <td>Exit the Help or Layout menu.</td></tr> </table> <p><b>Layout Settings</b></p> <table width="100%"> <tr> <td>* Name</td> <td>* Pinning</td> <td>* Plots Enabled </td> <td>* Height</td> </tr> <tr> <td>* Order</td> <td>* Event Flags</td> <td>* Dotted Lines</td> <td>* Height Options</td> </tr> </table> <p><b>General Information</b></p> <ul style=margin-left="20"; > <li> Maximum description size = 80 characters. </li> <li> Maximum Saved Layout Settings = 30. </li> <li> Saved Layout Settings can be accessed by all profiles. <li> Layout Settings only control the layout of a graph or chart. <br> They do not contain any other data. <br> They do not control if a graph is displayed or not. </li> <li> Layout Settings for daily and overview are managed independantly. </li> </ul> <p style="color:black;"> Diese Funktion verwaltet das Speichern und Wiederherstellen von Layouteinstellungen. <br> Layouteinstellungen steuern das Layout einer Grafik oder eines Diagramms. <br> Verschiedene Layout-Einstellungen können gespeichert und später wiederhergestellt werden. <br> </p> <table width="100%"> <tr><td><b>Schaltfläche</b></td> <td><b>Beschreibung</b></td></tr> <tr><td valign="top">Hinzufügen</td> <td>Erstellt eine Kopie der aktuellen Layout-Einstellungen. <br> Die Standardbeschreibung ist das aktuelle Datum. <br> Die Beschreibung kann geändert werden. <br> Die Schaltfläche „Hinzufügen“ wird ausgegraut, wenn die maximale Anzahl erreicht ist.</td></tr> <br> <tr><td><i><u>Andere Schaltflächen</u> </i></td> <td>Ausgegraut, wenn keine Auswahl vorhanden ist</td></tr> <tr><td>Wiederherstellen</td></tr> <tr><td>Restore</td> <td>Lädt die Layout-Einstellungen aus der Auswahl. Beendet automatisch. </td></tr> <tr><td>Umbenennen </td> <td>Beschreibung der Auswahl ändern. Gleich wie ein Doppelklick.</td></tr> <tr><td valign="top">Aktualisieren</td><td> Speichert die aktuellen Layout-Einstellungen in der Auswahl.<br> Fordert zur Bestätigung auf.<br> Prompts for confirmation.</td></tr> <tr><td valign="top">Löschen</td> <td>Löscht die Auswahl. <br> Aufforderung zur Bestätigung.</td></tr> <tr><td><i><u>Steuerung</u> </i></td> <td></td></tr> <tr><td>Beenden </td> <td>(Roter Kreis mit weißem „X“.) Kehrt zum OSCAR-Menü zurück.</td></tr> <tr><td>Zurück</td> <td>Neben dem Beenden-Symbol. Nur im Hilfemenü. Kehrt zum Layout-Menü zurück.</td></tr> <tr><td>Escape-Taste</td> <td>Verlässt das Hilfe- oder Layout-Menü.</td></tr> </table> <p><b>Layout-Einstellungen</b></p> <table width="100%"> <tr> <td>* Name</td> <td>*Anheften</td> <td>* Diagramme aktiviert </td> <td>* Höhe</td> </tr> <tr> <td>*Reihenfolger</td> <td>*Ereignismarkierungen</td> <td>* Gepunktete Linien</td> <td>* Höhenoptionen</td> </tr> </table> <p><b>Allgemeine Informationen</b></p> <ul style=margin-left="20"; > <li> Maximale Beschreibungsgröße = 80 Zeichen. </li> <li> Maximal gespeicherte Layouteinstellungen = 30. </li> <li>Alle Profile können auf gespeicherte Layouteinstellungen zugreifen. <li> Layouteinstellungen steuern nur das Layout einer Grafik oder eines Diagramms. <br> Sie enthalten keine weiteren Daten. <br> Sie kontrollieren nicht, ob ein Diagramm angezeigt wird oder nicht. </li> <li> Layouteinstellungen für Tages- und Übersicht werden unabhängig voneinander verwaltet. </li> </ul> Maximum number of Items exceeded. Maximale Anzahl von Artikeln überschritten. No Item Selected Kein Element ausgewählt Ok to Update? Fertig zum Aktualisieren? Ok To Delete? OK zum Löschen? SessionBar %1h %2m %1h %2m No Sessions Present Gegenwärtig keine Sitzung SleepStyleLoader Import Error Import Fehler This device Record cannot be imported in this profile. Dieser Gerätedatensatz kann nicht in dieses Profil 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 Device Information Geräteinformation Changes to Device Settings Änderungen an den Geräteeinstellungen Most Recent Der letzte Tag Database has No %1 data available. In der Datenbank sind keine %1 Daten verfügbar. Database has %1 day of %2 Data on %3 Die Datenbank hat %1 Tag von %2 Daten am %3 Database has %1 days of %2 Data, between %3 and %4 Die Datenbank verfügt über %1 Tage mit %2 Daten, zwischen %3 und %4 Total Days: %1 Gesamtzahl der Tage: %1 Days Not Used: %1 Nicht genutzte Tage: %1 Days %1 %2 Hours: %3 Tage %1 %2 Stunden: %3 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. Worst Device Setting Schlechteste Geräteeinstellung 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 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) No data found?!? Keine Daten gefunden?!?? Best Device Setting Beste Geräteeinstellung 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 Note that some preferences are forced when a ResMed device is detected Beachten Sie, dass einige Einstellungen erzwungen werden, wenn ein ResMed-Gerät erkannt wird today Heute Your device was on for %1. Ihr Gerät war %1 lang eingeschaltet. Your CPAP device used a constant %1 %2 of air Ihr CPAP-Gerät hat konstant %1 %2 Luft verbraucht Your device used a constant %1-%2 %3 of air. Ihr Gerät hat konstant %1-%2 %3 Luft verbraucht. Your device was under %1-%2 %3 for %4% of the time. Ihr Gerät war %4 % der Zeit unter %1-%2 %3. Your EEPAP pressure was under %1 %2 for %3% of the time. Ihr EEPAP-Druck lag %3 % der Zeit unter %1 %2. 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. 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 <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 device.</span></p></body></html> <span style=" font-weight:600;">Warnung: </span><span style=" color:#ff0000;">ResMed S9 SDCards müssen gesperrt werden </span><span style=" font-weight :600; color:#ff0000;">bevor Sie sie in Ihren Computer einsetzen.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Einige Betriebssysteme schreiben Indexdateien auf die Karte ohne zu fragen, wodurch Ihre Karte für Ihr cpap-Gerät unlesbar werden kann.</span></p></body></html> reasonably close to ziemlich nahe an %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 pressure was under %1 %2 for %3% of the time. Ihr Druck lag unter %1 %2 für %3% dieser Zeit. 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. 1 day ago vor 1 Tag gGraph Double click Y-axis: Return to AUTO-FIT Scaling Doppelklicken Sie auf die Y-Achse: Zurück zur AUTO-FIT-Skalierung Double click Y-axis: Return to DEFAULT Scaling Doppelklick auf Y-Achse: Rückkehr zur STANDARD-Skalierung Double click Y-axis: Return to OVERRIDE Scaling Doppelklick Y-Achse: Zurück zu Skalierung übersteuern Double click Y-axis: For Dynamic Scaling Doppelklicken Sie auf die Y-Achse: Für dynamische Skalierung Double click Y-axis: Select DEFAULT Scaling Doppelklicken Sie auf die Y-Achse: Wählen Sie DEFAULT-Skalierung Double click Y-axis: Select AUTO-FIT Scaling Doppelklicken Sie auf die Y-Achse: Wählen Sie AUTO-FIT-Skalierung %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.5.1/Translations/English.en_UK.ts000066400000000000000000015467151450332542600211340ustar00rootroot00000000000000 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. 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 i 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 Search Layout Save and Restore Graph Layout Settings Show/hide available graphs. Breakdown events UF1 UF2 Time at Pressure Clinical Mode Disabling Sessions requires the Permissive Mode No %1 events are recorded this day %1 event %1 events Session Start Times Session End Times Session Information Oximetry Sessions Duration Device Settings (Mode and Pressure settings missing; yesterday's shown.) This CPAP device does NOT record detailed data no data :( Sorry, this device only provides compliance data. This bookmark is in a currently disabled area.. CPAP Sessions Details Sleep Stage Sessions Position Sensor Sessions Unknown Session 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 "Nothing's here!" No data is available for this day. 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 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?? Complain to your Equipment Provider! Pick a Colour Bookmark at %1 Hide All Events Show All Events Hide All Graphs Show All Graphs DailySearchTab Clear Bookmark Jumps to Date's Bookmark Start Search DATE Jumps to Date Match Notes Notes containing Bookmarks Bookmarks containing AHI Daily Duration Session Duration Days Skipped Disabled Sessions Number of Sessions Click HERE to close Help Help No Data Jumps to Date's Details Number Disabled Session Jumps to Date's Details Note Jumps to Date's Notes AHI Jumps to Date's Details EventsPerHour Session Duration Jumps to Date's Details Minutes Number of Sessions Jumps to Date's Details Sessions Daily Duration Jumps to Date's Details Hours Number of events Jumps to Date's Events Events Automatic start Continue Search End of Search No Matches Skip:%1 %1/%2%3 days Found %1 Finds days that match specified criteria. Searches from last day to first day. First click on Match Button then select topic. Then click on the operation to modify it. or update the value Topics without operations will automatically start. Compare Operations: numberic or character. Numberic Operations: Character Operations: Summary Line Left:Summary - Number of Day searched Center:Number of Items Found Right:Minimum/Maximum for item searched Result Table Column One: Date of match. Click selects date. Column two: Information. Click selects date. Then Jumps the appropiate tab. Wildcard Pattern Matching: Wildcards use 3 characters: Asterisk Question Mark Backslash. Asterisk matches any number of characters. Question Mark matches a single character. Backslash matches next character. DateErrorDisplay ERROR The start date MUST be before the end date The entered start date %1 is after the end date %2 Hint: Change the end date first The entered end date %1 is before the start date %1 Hint: Change the start date first 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 device 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 Show Standard Report Standard Show Monthly Report Monthly Show Range Report Date Range Select Report Date Report Date Statistics Daily Overview Oximetry Import Help &File &View &Reset Graphs &Help Troubleshooting &Data &Advanced 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 - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> Advanced - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> 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 Purge ALL Device 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 Couldn't find any valid Device Data at %1 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. No help is available. You must select and open the profile you wish to modify %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. No supported data was found Please open a profile first. Check for updates not implemented Are you sure you want to rebuild all CPAP data for the following device: For some reason, OSCAR does not have any backups for the following device: 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. Note as a precaution, the backup folder will be left in place. OSCAR does not have any backups for this device! Unless you have made <i>your <b>own</b> backups for ALL of your data for this device</i>, <font size=+2>you will lose this device's data <b>permanently</b>!</font> You are about to <font size=+2>obliterate</font> OSCAR's device database for the following device:</p> Are you <b>absolutely sure</b> you want to proceed? A file permission error caused the purge process to fail; you will have to delete the following folder manually: 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 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. Would you like to import from your own backups now? (you will have no data visible for this device until you do) 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 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. This software is being designed to assist you in reviewing the data produced by your CPAP Devices and related equipment. 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 Layout Save and Restore Graph Layout Settings 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) Hide All Graphs Show 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> 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. <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 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 I started this oximeter recording at (or near) the same time as a session on my CPAP device. <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 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. 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. Duration of airflow restriction s Event Duration 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 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 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.) <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 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 <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> 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 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 <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></p></body></html> l/min <html><head/><body><p>Cumulative Indices</p></body></html> Oximetry Settings <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</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 Include Serial Number Graphics Engine (Requires Restart) This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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>Provide an alert when importing data from any device model that has not yet been tested by OSCAR developers.</p></body></html> Warn when importing data from an untested device This calculation requires Total Leaks data to be provided by the CPAP device. (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. Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device missed. This option must be enabled before import, otherwise a purge is required. This experimental option attempts to use OSCAR's event flagging system to improve device detected event positioning. Resync Device Detected Events (Experimental) Allow duplicates near device events. Show flags for device detected events that haven't been identified yet. <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices do not support changing these settings.</p></body></html> Clinical Clinical Settings Select Oscar Operating Mode Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. Clinical Mode permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. Permissive Mode Hours <!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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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 device serial number on device settings changes report Print reports in black and white, which can be more legible on non-color printers Print reports in black and white (monochrome) For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. Enables SessionBar in Event Flags Graph 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 No CPAP devices detected Will you be using a ResMed brand device? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, 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? ResMed S9 devices routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). 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 Clinical Mode: Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. Basically replicates the reports and data stored on the devices data card. This includes pap devices, oximeters, etc. Compliance reports fall under this mode. Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. Permissive Mode: Allows user to select which data sets/ sessions to be used for calculations and display. Additional charts and calculations may be available that are not available from the vendor data. Changing the Oscar Operating Mode: Requires a reload of the user's profile. Data will be saved and restored. Minor Flag Span Always Minor Never This may not be a good idea 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 cmH2O Med. Min: %1 Min: Max: Max: %1 %1 (%2 days): %1 (%2 day): % in %1 Hours Min %1 Length: %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 milliSeconds 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 EEPAP Min EEPAP Max EEPAP 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 Device No Data Available App key: Operating system: Built with Qt %1 on %2 Graphics Engine: Graphics Engine type: Compiler: Software Engine ANGLE / OpenGLES Desktop OpenGL m cm in kg l/min Only Settings and Compliance Data Available Summary Data Only Bookmarks Mode Model Brand Serial Series 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 device's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Non Data Capable Device Your %1 CPAP Device (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 device. Device Untested Your %1 CPAP Device (Model %2) has not been tested yet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Device Unsupported Sorry, your %1 CPAP Device (%2) is not supported yet. The developers need a .zip copy of this device's SD card and matching clinician .pdf reports to make it work with OSCAR. Getting Ready... Scanning Files... Importing Sessions... UNKNOWN APAP (std) APAP (dyn) Auto S Auto S/T AcSV SoftPAP Mode Pressure relief during exhalation Slight Softstart pressure Pressure during soft start period PSoft Softstart minimum pressure Minimum pressure during soft start period PSoftMin Auto start Automatically turn on the device by breathing Softstart time Lenght of soft start period Soft start maximum time Maximum lenght of soft start period Soft start max. time Soft start pressure Higher End Expiratory Pressure Humidifier level Tube type Obstruction level Obstruction level in percentage rRMVFluctuation Relative respiratory minute volume fluctuation Relative respiratory minute volume Measured pressure Full flow Artefact Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) Epoch (2 mins) with Flow Limitation Deep Sleep Deep sleep, stable respiration Timed breath BiSoft Mode BiSoft 1 BiSoft 2 TriLevel PMaxOA EEPAPMin Lower End Expiratory Pressure EEPAPMax rRMV ART CriticalLeak Mask leakage is above a critical treshold CL eMO Epoch (2 mins) with Mild Obstruction eSO Epoch (2 mins) with Severe Obstruction eS Epoch (2 mins) with Snoring eFL DS Finishing up... 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 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 A few breaths automatically starts device Device automatically switches off Whether or not device allows Mask checking. 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 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 A period during a session where the device could not detect flow. 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 Off Mask Alert Show AHI Whether or not device shows AHI via built-in display. The number of days in the Auto-CPAP trial period, after which the device will revert to CPAP Breathing Not Detected BND Timed Breath Machine Initiated Breath TB 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 device 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 device 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? Device Database Changes Sorry, the purge operation failed, which means this version of OSCAR can't start. The device data folder needs to be removed manually. 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. Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. 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 Cheyne Stokes Respiration (CSR) Periodic Breathing (PB) Clear Airway (CA) Obstructive Apnea (OA) Hypopnea (H) An apnea that couldn't be determined as Central or Obstructive. An apnoea that couldn't be determined as Central or Obstructive. Unclassified Apnea (UA) Apnea (A) A restriction in breathing from normal, causing a flattening of the flow waveform. Flow Limitation (FL) RERA (RE) Vibratory Snore (VS) Vibratory Snore (VS2) Leak Flag (LF) A large mask leak affecting device performance. Large Leak (LL) Non Responding Event (NR) Expiratory Puff (EP) SensAwake (SA) User Flag #1 (UF1) User Flag #2 (UF2) User Flag #3 (UF3) Pulse Change (PC) SpO2 Drop (SD) A ResMed data item: Trigger Cycle Event Apnea Hypopnea Index (AHI) Respiratory Disturbance Index (RDI) 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 A partially obstructed airway UA A vibratory snore Pressure Pulse A pulse of pressure 'pinged' to detect a closed airway. A type of respiratory event that won't respond to a pressure increase. Intellipap event where you breathe out your mouth. SensAwake feature will reduce pressure when waking is detected. Heart rate in beats per minute Blood-oxygen saturation percentage Plethysomogram An optical Photo-plethysomogram showing heart rhythm A sudden (user definable) change in heart rate A sudden (user definable) drop in blood oxygen saturation SD Breathing flow rate waveform 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 Ratio between Inspiratory and Expiratory time ratio Pressure Min Pressure Max Pressure Set Pressure Setting IPAP Set IPAP Setting EPAP Set EPAP Setting CSR An abnormal period of Periodic Breathing 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 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 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. End Expiratory Pressure An apnea reported by your CPAP device. Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. A vibratory snore as detected by a System One device I/E Value 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? OSCAR Reminder Don't forget to place your datacard back in your CPAP device 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 oximetry 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 (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 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) Min EEPAP %1 Max EEPAP %2 PDIFF %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 Selection Length 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 Device auto starts by breathing Response Device auto stops by breathing Patient View RiseEnable RiseTime Cycle Trigger TiMax TiMin Your ResMed CPAP device (Model %1) has not been tested yet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card to make sure it works with OSCAR. SmartStart 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 Smart Stop Simple Advanced 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... Permissive Mode Total disabled sessions: %1, found in %2 days Total disabled sessions: %1 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. Updating Statistics cache Usage Statistics 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 Löwenstein Prisma Smart Resvent/Hoffrichter iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings Add Add Feature inhibited. The maximum number of Items has been exceeded. creates new copy of current settings. Restore Restores saved settings from selection. Rename Renames the selection. Must edit existing name then press enter. Update Updates the selection with current settings. Delete Deletes the selection. Expanded Help menu. Exits the Layout menu. <h4>Help Menu - Manage Layout Settings</h4> Exits the help menu. Exits the dialog menu. This feature manages the saving and restoring of Layout Settings. Layout Settings control the layout of a graph or chart. Different Layouts Settings can be saved and later restored. Button Description Creates a copy of the current Layout Settings. The default description is the current date. The description may be changed. The Add button will be greyed out when maximum number is reached. Other Buttons Greyed out when there are no selections Loads the Layout Settings from the selection. Automatically exits. io Modify the description of the selection. Same as a double click.io Saves the current Layout Settings to the selection. Prompts for confirmation. Deletes the selecton. Control Exit (Red circle with a white "X".) Returns to OSCAR menu. Return Next to Exit icon. Only in Help Menu. Returns to Layout menu. Escape Key Exit the Help or Layout menu. Layout Settings * Name * Pinning * Plots Enabled * Height * Order * Event Flags * Dotted Lines * Height Options General Information Maximum description size = 80 characters. Maximum Saved Layout Settings = 30. Saved Layout Settings can be accessed by all profiles. Layout Settings only control the layout of a graph or chart. They do not contain any other data. They do not control if a graph is displayed or not. Layout Settings for daily and overview are managed independantly. Maximum number of Items exceeded. No Item Selected Ok to Update? Ok To Delete? SessionBar %1h %2m No Sessions Present SleepStyleLoader Import Error This device 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 Device Information Changes to Device Settings Database has No %1 data available. Database has %1 day of %2 Data on %3 Database has %1 days of %2 Data, between %3 and %4 Total Days: %1 Days Not Used: %1 Days Used: %1 Days %1 %2 Hours: %3 Best Device Setting Worst Device Setting 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. Date: %1 - %2 AHI: %1 Total Hours: %1 Most Recent Compliance (%1 hrs/day) OSCAR is free open-source CPAP report software No data found?!? Oscar has no data to report :( Last Week Last 30 Days Last 6 Months Last Year Last Session Details Days Pressure Relief Pressure Settings 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 device.</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 device is detected First import can take a few minutes. The last time you used your %1... last night today %2 days ago was %1 (on %2) %1 hours, %2 minutes and %3 seconds <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 pressure was under %1 %2 for %3% of the time. 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. 1 day ago Your device was on for %1. Your CPAP device used a constant %1 %2 of air Your device used a constant %1-%2 %3 of air. Your device was under %1-%2 %3 for %4% of the time. Your EEPAP pressure was under %1 %2 for %3% of the time. Your average leaks were %1 %2, which is %3 your %4 day average of %5. No CPAP data has been imported yet. gGraph Double click Y-axis: Return to AUTO-FIT Scaling Double click Y-axis: Return to DEFAULT Scaling Double click Y-axis: Return to OVERRIDE Scaling Double click Y-axis: For Dynamic Scaling Double click Y-axis: Select DEFAULT Scaling Double click Y-axis: Select AUTO-FIT Scaling %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.5.1/Translations/Espaniol.es.ts000066400000000000000000016271511450332542600207150ustar00rootroot00000000000000 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. 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 i 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 Search Buscar Layout Save and Restore Graph Layout Settings 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? 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 Device Settings <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.) Start Inicio End Final This bookmark is in a currently disabled area.. Session Information Información de sesión Clinical Mode Disabling Sessions requires the Permissive Mode 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 This CPAP device does NOT record detailed data no data :( Sorry, this device only provides compliance data. "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 Hide All Events Ocultar todos los eventos Show All Events Mostrar todos los eventos Hide All Graphs Show All Graphs DailySearchTab Clear Bookmark Jumps to Date's Bookmark Start Search DATE Jumps to Date Match Notes Notas Notes containing Bookmarks Marcadores Bookmarks containing AHI Daily Duration Session Duration Days Skipped Disabled Sessions Number of Sessions Click HERE to close Help Help Ayuda No Data Jumps to Date's Details Number Disabled Session Jumps to Date's Details Note Jumps to Date's Notes AHI Jumps to Date's Details EventsPerHour Session Duration Jumps to Date's Details Minutes Number of Sessions Jumps to Date's Details Sessions Daily Duration Jumps to Date's Details Hours Number of events Jumps to Date's Events Events Automatic start Continue Search End of Search No Matches Skip:%1 %1/%2%3 days %1 días {1/%2%3 ?} Found %1 Finds days that match specified criteria. Searches from last day to first day. First click on Match Button then select topic. Then click on the operation to modify it. or update the value Topics without operations will automatically start. Compare Operations: numberic or character. Numberic Operations: Character Operations: Summary Line Left:Summary - Number of Day searched Center:Number of Items Found Right:Minimum/Maximum for item searched Result Table Column One: Date of match. Click selects date. Column two: Information. Click selects date. Then Jumps the appropiate tab. Wildcard Pattern Matching: Wildcards use 3 characters: Asterisk Question Mark Backslash. Asterisk matches any number of characters. Question Mark matches a single character. Backslash matches next character. DateErrorDisplay ERROR The start date MUST be before the end date The entered start date %1 is after the end date %2 Hint: Change the end date first The entered end date %1 is before the start date %1 Hint: Change the start date first 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 device Record cannot be imported in this profile. 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 Purge ALL Device Data &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 Show Standard Report Show Monthly Report Show Range Report Select Report Date Report Date Purge Oximetry Data Purga de datos de oximetría &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 - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> Advanced - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> 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" 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 No supported data was found 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. Would you like to import from your own backups now? (you will have no data visible for this device until you do) OSCAR does not have any backups for this device! Unless you have made <i>your <b>own</b> backups for ALL of your data for this device</i>, <font size=+2>you will lose this device's data <b>permanently</b>!</font> You are about to <font size=+2>obliterate</font> OSCAR's device database for the following device:</p> A file permission error caused the purge process to fail; you will have to delete the following folder manually: 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 You must select and open the profile you wish to modify 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. 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) Couldn't find any valid Device Data at %1 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 Are you sure you want to rebuild all CPAP data for the following device: For some reason, OSCAR does not have any backups for the following device: 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. 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. 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 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 This software is being designed to assist you in reviewing the data produced by your CPAP Devices 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. 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 Layout Save and Restore Graph Layout Settings 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) Hide All Graphs Show All Graphs 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> 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. <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 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 I started this oximeter recording at (or near) the same time as a session on my CPAP device. <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 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 This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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!) 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 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. Duration of airflow restriction Duración de la restricción de flujo s s Event Duration Duración del evento 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 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. <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices do not support changing these settings.</p></body></html> &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 <html><head/><body><p>Provide an alert when importing data from any device model that has not yet been tested by OSCAR developers.</p></body></html> Warn when importing data from an untested device This calculation requires Total Leaks data to be provided by the CPAP device. (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. Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device missed. This option must be enabled before import, otherwise a purge is required. This experimental option attempts to use OSCAR's event flagging system to improve device detected event positioning. Resync Device Detected Events (Experimental) Allow duplicates near device events. General CPAP and Related Settings CPAP General y Ajustes Relacionados Show flags for device detected events that haven't been identified yet. 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.) <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 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 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. Whether to include device serial number on device settings changes report 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 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. 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 Clinical Clinical Settings Select Oscar Operating Mode Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. Clinical Mode permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. Permissive Mode Hours Oximetry Settings Ajustes de Oximetría <!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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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 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) <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></p></body></html> 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> 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) For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. Enables SessionBar in Event Flags Graph 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 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. Clinical Mode: Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. Basically replicates the reports and data stored on the devices data card. This includes pap devices, oximeters, etc. Compliance reports fall under this mode. Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. Permissive Mode: Allows user to select which data sets/ sessions to be used for calculations and display. Additional charts and calculations may be available that are not available from the vendor data. Changing the Oscar Operating Mode: Requires a reload of the user's profile. Data will be saved and restored. No CPAP devices detected Will you be using a ResMed brand device? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, days will <b>split at noon</b> like in ResMed's commercial software.</p> 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 ResMed S9 devices routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). 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 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? 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 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 Length: %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 EEPAP Min EEPAP Max EEPAP 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 Device Min IPAP Built with Qt %1 on %2 Operating system: Graphics Engine: Graphics Engine type: Compiler: App key: Software Engine Motor de software ANGLE / OpenGLES Desktop OpenGL Escritorio OpenGL m m cm in kg Minutes Minutos Seconds Segundos milliSeconds h h m m s s ms ms Events/hr Eventos/hora Hz Hz l/min 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 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 device's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Non Data Capable Device Your %1 CPAP Device (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 device. Device Untested Your %1 CPAP Device (Model %2) has not been tested yet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Device Unsupported Sorry, your %1 CPAP Device (%2) is not supported yet. The developers need a .zip copy of this device's SD card and matching clinician .pdf reports to make it work with OSCAR. Getting Ready... Preparándose.... Scanning Files... Escaneo de archivos.... Importing Sessions... Importacion de Sesiones... UNKNOWN APAP (std) APAP (dyn) Auto S Auto S/T AcSV SoftPAP Mode Pressure relief during exhalation Slight Softstart pressure Pressure during soft start period PSoft Softstart minimum pressure Minimum pressure during soft start period PSoftMin Auto start Automatically turn on the device by breathing Softstart time Lenght of soft start period Soft start maximum time Maximum lenght of soft start period Soft start max. time Soft start pressure Higher End Expiratory Pressure Humidifier level Tube type Obstruction level Obstruction level in percentage rRMVFluctuation Relative respiratory minute volume fluctuation Relative respiratory minute volume Measured pressure Full flow Artefact Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) Epoch (2 mins) with Flow Limitation Deep Sleep Deep sleep, stable respiration Timed breath BiSoft Mode BiSoft 1 BiSoft 2 TriLevel PMaxOA EEPAPMin Lower End Expiratory Pressure EEPAPMax rRMV ART CriticalLeak Mask leakage is above a critical treshold CL eMO Epoch (2 mins) with Mild Obstruction eSO Epoch (2 mins) with Severe Obstruction eS Epoch (2 mins) with Snoring eFL DS Finishing up... Terminado... Untested Data 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 A few breaths automatically starts device Device automatically switches off Whether or not device allows Mask checking. 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 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 A period during a session where the device could not detect flow. 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 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 Auto Off Apagado automático Mask Alert Alerta de mascarilla Show AHI Mostrar IAH Whether or not device shows AHI via built-in display. The number of days in the Auto-CPAP trial period, after which the device will revert to CPAP Breathing Not Detected Respiración No Detectada 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 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> <i>Your old device 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. OSCAR no tiene todavía ninguna copia de seguridad automática de la tarjeta almacenada para este dispositivo. 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? Device Database Changes 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. The device data folder needs to be removed manually. 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. OSCAR %1 needs to upgrade its database for %2 %3 %4 This means you will need to import this device data again afterwards from your own backups or data card. Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. 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? OSCAR Reminder Recordatorio OSCAR Don't forget to place your datacard back in your CPAP device 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 oximetry settings to defaults? 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 End Expiratory Pressure 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 Cheyne Stokes Respiration (CSR) Periodic Breathing (PB) Clear Airway (CA) An apnea caused by airway obstruction Una apnea provocada por obstrucción de la vía aérea A partially obstructed airway Una vía aérea parcialmente obstruida UA ANC A vibratory snore Un ronquido vibratorio 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 LL LL 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. 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. 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 A sudden (user definable) change in heart rate Un repentino (definible por el usuario) cambio en el ritmo cardiaco 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 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 An abnormal period of Cheyne Stokes Respiration Un período anormal de respiración Cheyne Stokes CSR An abnormal period of Periodic Breathing Un período anormal de Respiración Periódica 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 An apnea reported by your CPAP device. 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. A vibratory snore as detected by a System One device 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 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 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 Obstructive Apnea (OA) Hypopnea (H) Unclassified Apnea (UA) Apnea (A) Flow Limitation (FL) RERA (RE) Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. Vibratory Snore (VS) Leak Flag (LF) A large mask leak affecting device performance. Large Leak (LL) Non Responding Event (NR) Expiratory Puff (EP) SensAwake (SA) User Flag #1 (UF1) User Flag #2 (UF2) User Flag #3 (UF3) Pulse Change (PC) SpO2 Drop (SD) I/E Value Apnea Hypopnea Index (AHI) Respiratory Disturbance Index (RDI) 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) 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) Min EEPAP %1 Max EEPAP %2 PDIFF %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 Device auto starts by breathing Response Device auto stops by breathing Patient View RiseEnable RiseTime Cycle Trigger TiMax TiMin Your ResMed CPAP device (Model %1) has not been tested yet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card to make sure it works with OSCAR. SmartStart 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 Smart Stop Simple Advanced 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 Selection Length 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 Loading summaries Permissive Mode Total disabled sessions: %1, found in %2 days Total disabled sessions: %1 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. 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 Löwenstein Prisma Smart Resvent/Hoffrichter iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings Add Add Feature inhibited. The maximum number of Items has been exceeded. creates new copy of current settings. Restore Restores saved settings from selection. Rename Renames the selection. Must edit existing name then press enter. Update Updates the selection with current settings. Delete Deletes the selection. Expanded Help menu. Exits the Layout menu. <h4>Help Menu - Manage Layout Settings</h4> Exits the help menu. Exits the dialog menu. This feature manages the saving and restoring of Layout Settings. Layout Settings control the layout of a graph or chart. Different Layouts Settings can be saved and later restored. Button Description Creates a copy of the current Layout Settings. The default description is the current date. The description may be changed. The Add button will be greyed out when maximum number is reached. Other Buttons Greyed out when there are no selections Loads the Layout Settings from the selection. Automatically exits. io Modify the description of the selection. Same as a double click.io Saves the current Layout Settings to the selection. Prompts for confirmation. Deletes the selecton. Control Exit Salir (Red circle with a white "X".) Returns to OSCAR menu. Return Next to Exit icon. Only in Help Menu. Returns to Layout menu. Escape Key Exit the Help or Layout menu. Layout Settings * Name * Pinning * Plots Enabled * Height * Order * Event Flags * Dotted Lines * Height Options General Information Maximum description size = 80 characters. Maximum Saved Layout Settings = 30. Saved Layout Settings can be accessed by all profiles. Layout Settings only control the layout of a graph or chart. They do not contain any other data. They do not control if a graph is displayed or not. Layout Settings for daily and overview are managed independantly. Maximum number of Items exceeded. No Item Selected Ok to Update? Ok To Delete? SessionBar %1h %2m %1h %2m No Sessions Present No hay sesiones presentes SleepStyleLoader Import Error Error de Importación This device Record cannot be imported in this profile. 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: Device Information Changes to Device Settings Oscar has no data to report :( Oscar no tiene datos que reportar :( Database has No %1 data available. Database has %1 day of %2 Data on %3 Database has %1 days of %2 Data, between %3 and %4 Total Days: %1 Days Not Used: %1 Days Used: %1 Días de uso. %1 Days %1 %2 Hours: %3 Best Device Setting Worst Device Setting 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 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 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 device.</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 device is detected 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 today %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 <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 pressure was under %1 %2 for %3% of the time. Su presión estuvo debajo de %1 %2 por el %3% del tiempo. 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. 1 day ago Your device was on for %1. Your CPAP device used a constant %1 %2 of air Your device used a constant %1-%2 %3 of air. Your device was under %1-%2 %3 for %4% of the time. Your EEPAP pressure was under %1 %2 for %3% of the time. 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 Double click Y-axis: Return to AUTO-FIT Scaling Double click Y-axis: Return to DEFAULT Scaling Double click Y-axis: Return to OVERRIDE Scaling Double click Y-axis: For Dynamic Scaling Double click Y-axis: Select DEFAULT Scaling Double click Y-axis: Select AUTO-FIT Scaling %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.5.1/Translations/Espaniol.es_MX.ts000066400000000000000000015707011450332542600213170ustar00rootroot00000000000000 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. 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 i 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 Search Buscar Layout Save and Restore Graph Layout Settings 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? 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 Device Settings <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.) Start Inicio End Fin This bookmark is in a currently disabled area.. Session Information Información de sesión Clinical Mode Disabling Sessions requires the Permissive Mode 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 This CPAP device does NOT record detailed data no data :( Sorry, this device only provides compliance data. "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 Hide All Events Ocultar todos los eventos Show All Events Mostrar todos los eventos Hide All Graphs Show All Graphs DailySearchTab Clear Bookmark Jumps to Date's Bookmark Start Search DATE Jumps to Date Match Notes Notas Notes containing Bookmarks Marcadores Bookmarks containing AHI Daily Duration Session Duration Days Skipped Disabled Sessions Number of Sessions Click HERE to close Help Help Ayuda No Data Jumps to Date's Details Number Disabled Session Jumps to Date's Details Note Jumps to Date's Notes AHI Jumps to Date's Details EventsPerHour Session Duration Jumps to Date's Details Minutes Number of Sessions Jumps to Date's Details Sessions Daily Duration Jumps to Date's Details Hours Number of events Jumps to Date's Events Events Automatic start Continue Search End of Search No Matches Skip:%1 %1/%2%3 days Found %1 Finds days that match specified criteria. Searches from last day to first day. First click on Match Button then select topic. Then click on the operation to modify it. or update the value Topics without operations will automatically start. Compare Operations: numberic or character. Numberic Operations: Character Operations: Summary Line Left:Summary - Number of Day searched Center:Number of Items Found Right:Minimum/Maximum for item searched Result Table Column One: Date of match. Click selects date. Column two: Information. Click selects date. Then Jumps the appropiate tab. Wildcard Pattern Matching: Wildcards use 3 characters: Asterisk Question Mark Backslash. Asterisk matches any number of characters. Question Mark matches a single character. Backslash matches next character. DateErrorDisplay ERROR The start date MUST be before the end date The entered start date %1 is after the end date %2 Hint: Change the end date first The entered end date %1 is before the start date %1 Hint: Change the start date first 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 device Record cannot be imported in this profile. 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 Show Standard Report Standard Estándar Show Monthly Report Monthly Mensual Show Range Report Date Range Intérvalo de fechas Select Report Date Report Date 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 Purge ALL Device 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 - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> Advanced - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> 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 &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" 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 No supported data was found Are you sure you want to rebuild all CPAP data for the following device: 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 device: Would you like to import from your own backups now? (you will have no data visible for this device until you do) OSCAR does not have any backups for this device! Unless you have made <i>your <b>own</b> backups for ALL of your data for this device</i>, <font size=+2>you will lose this device's data <b>permanently</b>!</font> You are about to <font size=+2>obliterate</font> OSCAR's device database for the following device:</p> A file permission error caused the purge process to fail; you will have to delete the following folder manually: 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 You must select and open the profile you wish to modify 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. 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? Couldn't find any valid Device Data at %1 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. 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 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 This software is being designed to assist you in reviewing the data produced by your CPAP Devices 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. 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 Layout Save and Restore Graph Layout Settings 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) Hide All Graphs Show All Graphs 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> 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. <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 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 I started this oximeter recording at (or near) the same time as a session on my CPAP device. <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 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 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. Duration of airflow restriction Duración de la restricción de flujo s s Event Duration Duración del evento 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 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.) <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 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 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 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. Tooltip Timeout Visibilidad del menú de ayuda contextual Graph Tooltips Ayuda contextual del gráfico Top Markers <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> 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 <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></p></body></html> l/min <html><head/><body><p>Cumulative Indices</p></body></html> Oximetry Settings <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</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 Include Serial Number Graphics Engine (Requires Restart) This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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>Provide an alert when importing data from any device model that has not yet been tested by OSCAR developers.</p></body></html> Warn when importing data from an untested device This calculation requires Total Leaks data to be provided by the CPAP device. (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. Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device missed. This option must be enabled before import, otherwise a purge is required. This experimental option attempts to use OSCAR's event flagging system to improve device detected event positioning. Resync Device Detected Events (Experimental) Allow duplicates near device events. Show flags for device detected events that haven't been identified yet. <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices do not support changing these settings.</p></body></html> Clinical Clinical Settings Select Oscar Operating Mode Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. Clinical Mode permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. Permissive Mode Hours <!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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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 device serial number on device settings changes report Print reports in black and white, which can be more legible on non-color printers Print reports in black and white (monochrome) For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. Enables SessionBar in Event Flags Graph 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 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. Clinical Mode: Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. Basically replicates the reports and data stored on the devices data card. This includes pap devices, oximeters, etc. Compliance reports fall under this mode. Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. Permissive Mode: Allows user to select which data sets/ sessions to be used for calculations and display. Additional charts and calculations may be available that are not available from the vendor data. Changing the Oscar Operating Mode: Requires a reload of the user's profile. Data will be saved and restored. No CPAP devices detected Will you be using a ResMed brand device? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, days will <b>split at noon</b> like in ResMed's commercial software.</p> 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 ResMed S9 devices routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). 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 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? 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 cmH2O Med. Min: %1 Min: Max: Max: %1 %1 (%2 days): %1 (%2 day): % in %1 Hours Horas Min %1 Mín %1 Length: %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 EEPAP Min EEPAP Max EEPAP 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 Device Min IPAP App key: Operating system: Built with Qt %1 on %2 Graphics Engine: Graphics Engine type: Compiler: Software Engine ANGLE / OpenGLES Desktop OpenGL m m cm in kg Minutes Minutos Seconds Segundos milliSeconds h h m m s s ms Events/hr Eventos/hora Hz Hz l/min 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 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 device's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Non Data Capable Device Your %1 CPAP Device (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 device. Device Untested Your %1 CPAP Device (Model %2) has not been tested yet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Device Unsupported Sorry, your %1 CPAP Device (%2) is not supported yet. The developers need a .zip copy of this device's SD card and matching clinician .pdf reports to make it work with OSCAR. Getting Ready... Scanning Files... Importing Sessions... UNKNOWN APAP (std) APAP (dyn) Auto S Auto S/T AcSV SoftPAP Mode Pressure relief during exhalation Slight Softstart pressure Pressure during soft start period PSoft Softstart minimum pressure Minimum pressure during soft start period PSoftMin Auto start Automatically turn on the device by breathing Softstart time Lenght of soft start period Soft start maximum time Maximum lenght of soft start period Soft start max. time Soft start pressure Higher End Expiratory Pressure Humidifier level Tube type Obstruction level Obstruction level in percentage rRMVFluctuation Relative respiratory minute volume fluctuation Relative respiratory minute volume Measured pressure Full flow Artefact Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) Epoch (2 mins) with Flow Limitation Deep Sleep Deep sleep, stable respiration Timed breath BiSoft Mode BiSoft 1 BiSoft 2 TriLevel PMaxOA EEPAPMin Lower End Expiratory Pressure EEPAPMax rRMV ART CriticalLeak Mask leakage is above a critical treshold CL eMO Epoch (2 mins) with Mild Obstruction eSO Epoch (2 mins) with Severe Obstruction eS Epoch (2 mins) with Snoring eFL DS Finishing up... 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 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 A few breaths automatically starts device Device automatically switches off Whether or not device allows Mask checking. 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 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 A period during a session where the device could not detect flow. 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 Auto Off Apagado automático Mask Alert Alerta de mascarilla Show AHI Mostrar IAH Whether or not device shows AHI via built-in display. The number of days in the Auto-CPAP trial period, after which the device will revert to CPAP Breathing Not Detected 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 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> <i>Your old device 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. 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? Device Database Changes Sorry, the purge operation failed, which means this version of OSCAR can't start. The device data folder needs to be removed manually. 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. OSCAR %1 needs to upgrade its database for %2 %3 %4 This means you will need to import this device data again afterwards from your own backups or data card. Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. 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? OSCAR Reminder Don't forget to place your datacard back in your CPAP device 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 oximetry 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 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 End Expiratory Pressure 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 Cheyne Stokes Respiration (CSR) Periodic Breathing (PB) Clear Airway (CA) An apnea caused by airway obstruction Una apnea provocada por obstrucción de la vía aérea A partially obstructed airway Una vía aérea parcialmente obstruida UA ANC A vibratory snore Un ronquido vibratorio 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 LL LL 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. 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. 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 A sudden (user definable) change in heart rate Un repentino (definible por el usuario) cambio en el ritmo cardiaco 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 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 An abnormal period of Cheyne Stokes Respiration CSR An abnormal period of Periodic Breathing 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 An apnea reported by your CPAP device. A restriction in breathing from normal, causing a flattening of the flow waveform. A vibratory snore as detected by a System One device 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 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 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 Obstructive Apnea (OA) Hypopnea (H) Unclassified Apnea (UA) Apnea (A) Flow Limitation (FL) RERA (RE) Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. Vibratory Snore (VS) Leak Flag (LF) A large mask leak affecting device performance. Large Leak (LL) Non Responding Event (NR) Expiratory Puff (EP) SensAwake (SA) User Flag #1 (UF1) User Flag #2 (UF2) User Flag #3 (UF3) Pulse Change (PC) SpO2 Drop (SD) I/E Value Apnea Hypopnea Index (AHI) Respiratory Disturbance Index (RDI) 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) 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) Min EEPAP %1 Max EEPAP %2 PDIFF %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 Device auto starts by breathing Response Device auto stops by breathing Patient View RiseEnable RiseTime Cycle Trigger TiMax TiMin Your ResMed CPAP device (Model %1) has not been tested yet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card to make sure it works with OSCAR. SmartStart 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 Smart Stop Simple Advanced Parsing STR.edf records... Auto Mask ResMed Mask Setting Pillows Full Face Nasal Ramp Enable Selection Length 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... Loading summaries Permissive Mode Total disabled sessions: %1, found in %2 days Total disabled sessions: %1 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. 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 Löwenstein Prisma Smart Resvent/Hoffrichter iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings Add Add Feature inhibited. The maximum number of Items has been exceeded. creates new copy of current settings. Restore Restores saved settings from selection. Rename Renames the selection. Must edit existing name then press enter. Update Updates the selection with current settings. Delete Deletes the selection. Expanded Help menu. Exits the Layout menu. <h4>Help Menu - Manage Layout Settings</h4> Exits the help menu. Exits the dialog menu. This feature manages the saving and restoring of Layout Settings. Layout Settings control the layout of a graph or chart. Different Layouts Settings can be saved and later restored. Button Description Creates a copy of the current Layout Settings. The default description is the current date. The description may be changed. The Add button will be greyed out when maximum number is reached. Other Buttons Greyed out when there are no selections Loads the Layout Settings from the selection. Automatically exits. io Modify the description of the selection. Same as a double click.io Saves the current Layout Settings to the selection. Prompts for confirmation. Deletes the selecton. Control Exit Salir (Red circle with a white "X".) Returns to OSCAR menu. Return Next to Exit icon. Only in Help Menu. Returns to Layout menu. Escape Key Exit the Help or Layout menu. Layout Settings * Name * Pinning * Plots Enabled * Height * Order * Event Flags * Dotted Lines * Height Options General Information Maximum description size = 80 characters. Maximum Saved Layout Settings = 30. Saved Layout Settings can be accessed by all profiles. Layout Settings only control the layout of a graph or chart. They do not contain any other data. They do not control if a graph is displayed or not. Layout Settings for daily and overview are managed independantly. Maximum number of Items exceeded. No Item Selected Ok to Update? Ok To Delete? SessionBar %1h %2m %1h %2m No Sessions Present No hay sesiones presentes SleepStyleLoader Import Error Error de Importación This device Record cannot be imported in this profile. 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: Device Information Changes to Device Settings Oscar has no data to report :( Database has No %1 data available. Database has %1 day of %2 Data on %3 Database has %1 days of %2 Data, between %3 and %4 Total Days: %1 Days Not Used: %1 Days Used: %1 Days %1 %2 Hours: %3 Best Device Setting Worst Device Setting 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 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 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 device.</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 device 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 today %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 <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 pressure was under %1 %2 for %3% of the time. Su presión estuvo bajo %1 %2 por %3 del tiempo. 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. 1 day ago Your device was on for %1. Your CPAP device used a constant %1 %2 of air Your device used a constant %1-%2 %3 of air. Your device was under %1-%2 %3 for %4% of the time. Your EEPAP pressure was under %1 %2 for %3% of the time. 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 Double click Y-axis: Return to AUTO-FIT Scaling Double click Y-axis: Return to DEFAULT Scaling Double click Y-axis: Return to OVERRIDE Scaling Double click Y-axis: For Dynamic Scaling Double click Y-axis: Select DEFAULT Scaling Double click Y-axis: Select AUTO-FIT Scaling %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.5.1/Translations/Filipino.fil.ts000066400000000000000000015644521450332542600210630ustar00rootroot00000000000000 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. 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 i 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 Search Hanapin Layout Save and Restore Graph Layout Settings Show/hide available graphs. Ipakita/itago ang talaguhitan. Breakdown Ang Pagsusuri events Mga pangyayari UF1 UF1 UF2 UF2 Time at Pressure Oras sa Pressure Clinical Mode Disabling Sessions requires the Permissive Mode 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.) no data :( Sorry, this device only provides compliance data. 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 Model %1 - %2 Modelo %1- %2 PAP Mode: %1 PAP Mode:%1 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 "Nothing's here!" "Walang nandito!" No data is available for this day. Walang makukuhang data sa araw na ito. 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 Device Settings <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 This CPAP device does NOT record detailed data 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?? Complain to your Equipment Provider! Magreklamo sa nagbenta sayo ng aparato! Pick a Colour Pumili ng Kulay Bookmark at %1 Bookmark sa %1 Hide All Events Show All Events Hide All Graphs Show All Graphs DailySearchTab Clear Bookmark Jumps to Date's Bookmark Start Search DATE Jumps to Date Match Notes Notes containing Bookmarks Bookmarks containing AHI Daily Duration Session Duration Days Skipped Disabled Sessions Number of Sessions Click HERE to close Help Help Tulong No Data Jumps to Date's Details Number Disabled Session Jumps to Date's Details Note Jumps to Date's Notes AHI Jumps to Date's Details EventsPerHour Session Duration Jumps to Date's Details Minutes Number of Sessions Jumps to Date's Details Sessions Daily Duration Jumps to Date's Details Hours Number of events Jumps to Date's Events Events Automatic start Continue Search End of Search No Matches Skip:%1 %1/%2%3 days %1 mga Araw {1/%2%3 ?} Found %1 Finds days that match specified criteria. Searches from last day to first day. First click on Match Button then select topic. Then click on the operation to modify it. or update the value Topics without operations will automatically start. Compare Operations: numberic or character. Numberic Operations: Character Operations: Summary Line Left:Summary - Number of Day searched Center:Number of Items Found Right:Minimum/Maximum for item searched Result Table Column One: Date of match. Click selects date. Column two: Information. Click selects date. Then Jumps the appropiate tab. Wildcard Pattern Matching: Wildcards use 3 characters: Asterisk Question Mark Backslash. Asterisk matches any number of characters. Question Mark matches a single character. Backslash matches next character. DateErrorDisplay ERROR The start date MUST be before the end date The entered start date %1 is after the end date %2 Hint: Change the end date first The entered end date %1 is before the start date %1 Hint: Change the start date first 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 device Record cannot be imported in this profile. 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 Show Standard Report Standard Standard Show Monthly Report Monthly Buwanan Show Range Report Date Range Date Range Select Report Date Report Date Statistics Statistics Daily Daily Overview Overview Oximetry Oximetry Import i-Import Help Tulong &File &File &View &Tingnan &Help &Tulong Troubleshooting &Data &Data &Advanced &Advanced 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 - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> Advanced - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> 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 Purge ALL Device 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 Couldn't find any valid Device Data at %1 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. 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. 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 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 No supported data was found 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 Are you sure you want to rebuild all CPAP data for the following device: 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. For some reason, OSCAR does not have any backups for the following device: Would you like to import from your own backups now? (you will have no data visible for this device until you do) OSCAR does not have any backups for this device! Unless you have made <i>your <b>own</b> backups for ALL of your data for this device</i>, <font size=+2>you will lose this device's data <b>permanently</b>!</font> You are about to <font size=+2>obliterate</font> OSCAR's device database for the following device:</p> A file permission error caused the purge process to fail; you will have to delete the following folder manually: 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 You must select and open the profile you wish to modify 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 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. This software is being designed to assist you in reviewing the data produced by your CPAP Devices and related equipment. 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 Layout Save and Restore Graph Layout Settings 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) Hide All Graphs Show All 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> 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. <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 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 I started this oximeter recording at (or near) the same time as a session on my CPAP device. <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 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. Flow Restriction Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. Duration of airflow restriction s Event Duration 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 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.) <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> Calculate Unintentional Leaks When Not Present 4 cmH2O 20 cmH2O Note: A linear calculation method is used. Changing these values requires a recalculation. 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 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 <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> 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 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 <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></p></body></html> l/min <html><head/><body><p>Cumulative Indices</p></body></html> Oximetry Settings <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</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) This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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>Provide an alert when importing data from any device model that has not yet been tested by OSCAR developers.</p></body></html> Warn when importing data from an untested device This calculation requires Total Leaks data to be provided by the CPAP device. (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. Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device missed. This option must be enabled before import, otherwise a purge is required. This experimental option attempts to use OSCAR's event flagging system to improve device detected event positioning. Resync Device Detected Events (Experimental) Allow duplicates near device events. Show flags for device detected events that haven't been identified yet. <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices do not support changing these settings.</p></body></html> Clinical Clinical Settings Select Oscar Operating Mode Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. Clinical Mode permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. Permissive Mode Hours <!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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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 device serial number on device settings changes report Print reports in black and white, which can be more legible on non-color printers Print reports in black and white (monochrome) For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. Enables SessionBar in Event Flags Graph 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. Overview Overview No CPAP devices detected Will you be using a ResMed brand device? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, 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? ResMed S9 devices routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). 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 Clinical Mode: Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. Basically replicates the reports and data stored on the devices data card. This includes pap devices, oximeters, etc. Compliance reports fall under this mode. Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. Permissive Mode: Allows user to select which data sets/ sessions to be used for calculations and display. Additional charts and calculations may be available that are not available from the vendor data. Changing the Oscar Operating Mode: Requires a reload of the user's profile. Data will be saved and restored. Minor Flag Span Always Minor Never This may not be a good idea 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 cmH2O Med. Min: %1 Min: Max: Max: %1 %1 (%2 days): %1 (%2 day): % in %1 Hours Min %1 Length: %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 milliSeconds 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 EEPAP Min EEPAP Max EEPAP 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 Device No Data Available App key: Operating system: Built with Qt %1 on %2 Graphics Engine: Graphics Engine type: Compiler: Software Engine ANGLE / OpenGLES Desktop OpenGL m cm in kg l/min Only Settings and Compliance Data Available Summary Data Only Bookmarks Σελιδοδείκτες Mode Model Brand Serial Series 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 device's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Non Data Capable Device Your %1 CPAP Device (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 device. Device Untested Your %1 CPAP Device (Model %2) has not been tested yet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Device Unsupported Sorry, your %1 CPAP Device (%2) is not supported yet. The developers need a .zip copy of this device's SD card and matching clinician .pdf reports to make it work with OSCAR. Getting Ready... Scanning Files... Importing Sessions... UNKNOWN APAP (std) APAP (dyn) Auto S Auto S/T AcSV SoftPAP Mode Pressure relief during exhalation Slight Softstart pressure Pressure during soft start period PSoft Softstart minimum pressure Minimum pressure during soft start period PSoftMin Auto start Automatically turn on the device by breathing Softstart time Lenght of soft start period Soft start maximum time Maximum lenght of soft start period Soft start max. time Soft start pressure Higher End Expiratory Pressure Humidifier level Tube type Obstruction level Obstruction level in percentage rRMVFluctuation Relative respiratory minute volume fluctuation Relative respiratory minute volume Measured pressure Full flow Artefact Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) Epoch (2 mins) with Flow Limitation Deep Sleep Deep sleep, stable respiration Timed breath BiSoft Mode BiSoft 1 BiSoft 2 TriLevel PMaxOA EEPAPMin Lower End Expiratory Pressure EEPAPMax rRMV ART CriticalLeak Mask leakage is above a critical treshold CL eMO Epoch (2 mins) with Mild Obstruction eSO Epoch (2 mins) with Severe Obstruction eS Epoch (2 mins) with Snoring eFL DS Finishing up... Untested Data 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 A few breaths automatically starts device Device automatically switches off Whether or not device allows Mask checking. 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 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 A period during a session where the device could not detect flow. 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 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 Auto Off Mask Alert Show AHI Whether or not device shows AHI via built-in display. The number of days in the Auto-CPAP trial period, after which the device will revert to CPAP Breathing Not Detected BND Timed Breath Machine Initiated Breath TB 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 device 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 device 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? Device Database Changes Sorry, the purge operation failed, which means this version of OSCAR can't start. The device data folder needs to be removed manually. 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. Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. 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 End 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 Cheyne Stokes Respiration (CSR) Periodic Breathing (PB) Clear Airway (CA) An apnea caused by airway obstruction A partially obstructed airway UA A vibratory snore Pressure Pulse A pulse of pressure 'pinged' to detect a closed airway. A type of respiratory event that won't respond to a pressure increase. Intellipap event where you breathe out your mouth. SensAwake feature will reduce pressure when waking is detected. Heart rate in beats per minute Blood-oxygen saturation percentage Plethysomogram An optical Photo-plethysomogram showing heart rhythm A sudden (user definable) change in heart rate A sudden (user definable) drop in blood oxygen saturation SD Breathing flow rate waveform 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 Ratio between Inspiratory and Expiratory time ratio Pressure Min Pressure Max Pressure Set Pressure Setting IPAP Set IPAP Setting EPAP Set EPAP Setting An abnormal period of Cheyne Stokes Respiration CSR An abnormal period of Periodic Breathing An apnea that couldn't be determined as Central or Obstructive. An apnea reported by your CPAP device. A restriction in breathing from normal, causing a flattening of the flow waveform. A vibratory snore as detected by a System One device 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 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 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. Obstructive Apnea (OA) Hypopnea (H) Unclassified Apnea (UA) Apnea (A) Flow Limitation (FL) RERA (RE) Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. Vibratory Snore (VS) Leak Flag (LF) A large mask leak affecting device performance. Large Leak (LL) Non Responding Event (NR) Expiratory Puff (EP) SensAwake (SA) User Flag #1 (UF1) User Flag #2 (UF2) User Flag #3 (UF3) Pulse Change (PC) SpO2 Drop (SD) I/E Value Apnea Hypopnea Index (AHI) Respiratory Disturbance Index (RDI) 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? OSCAR Reminder Don't forget to place your datacard back in your CPAP device 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 oximetry 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 (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 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) Min EEPAP %1 Max EEPAP %2 PDIFF %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 Selection Length 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 Device auto starts by breathing Response Device auto stops by breathing Patient View RiseEnable RiseTime Cycle Trigger TiMax TiMin Your ResMed CPAP device (Model %1) has not been tested yet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card to make sure it works with OSCAR. SmartStart 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 Smart Stop Simple Advanced 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... Loading summaries Permissive Mode Total disabled sessions: %1, found in %2 days Total disabled sessions: %1 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. 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 Löwenstein Prisma Smart Resvent/Hoffrichter iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings Add Add Feature inhibited. The maximum number of Items has been exceeded. creates new copy of current settings. Restore Restores saved settings from selection. Rename Renames the selection. Must edit existing name then press enter. Update Updates the selection with current settings. Delete Deletes the selection. Expanded Help menu. Exits the Layout menu. <h4>Help Menu - Manage Layout Settings</h4> Exits the help menu. Exits the dialog menu. This feature manages the saving and restoring of Layout Settings. Layout Settings control the layout of a graph or chart. Different Layouts Settings can be saved and later restored. Button Description Creates a copy of the current Layout Settings. The default description is the current date. The description may be changed. The Add button will be greyed out when maximum number is reached. Other Buttons Greyed out when there are no selections Loads the Layout Settings from the selection. Automatically exits. io Modify the description of the selection. Same as a double click.io Saves the current Layout Settings to the selection. Prompts for confirmation. Deletes the selecton. Control Exit Exit (Red circle with a white "X".) Returns to OSCAR menu. Return Next to Exit icon. Only in Help Menu. Returns to Layout menu. Escape Key Exit the Help or Layout menu. Layout Settings * Name * Pinning * Plots Enabled * Height * Order * Event Flags * Dotted Lines * Height Options General Information Maximum description size = 80 characters. Maximum Saved Layout Settings = 30. Saved Layout Settings can be accessed by all profiles. Layout Settings only control the layout of a graph or chart. They do not contain any other data. They do not control if a graph is displayed or not. Layout Settings for daily and overview are managed independantly. Maximum number of Items exceeded. No Item Selected Ok to Update? Ok To Delete? SessionBar %1h %2m No Sessions Present SleepStyleLoader Import Error May mali sa pag Import This device Record cannot be imported in this profile. 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: Device Information Changes to Device Settings Database has No %1 data available. Database has %1 day of %2 Data on %3 Database has %1 days of %2 Data, between %3 and %4 Total Days: %1 Days Not Used: %1 Days Used: %1 Days %1 %2 Hours: %3 Best Device Setting Worst Device Setting 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. Date: %1 - %2 AHI: %1 Total Hours: %1 Most Recent Compliance (%1 hrs/day) This report was prepared on %1 by OSCAR %2 OSCAR is free open-source CPAP report software 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 Days Pressure Relief Pressure Settings 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 device.</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 device is detected First import can take a few minutes. The last time you used your %1... last night today %2 days ago was %1 (on %2) %1 hours, %2 minutes and %3 seconds <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 pressure was under %1 %2 for %3% of the time. 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. 1 day ago Your device was on for %1. Your CPAP device used a constant %1 %2 of air Your device used a constant %1-%2 %3 of air. Your device was under %1-%2 %3 for %4% of the time. Your EEPAP pressure was under %1 %2 for %3% of the time. Your average leaks were %1 %2, which is %3 your %4 day average of %5. No CPAP data has been imported yet. gGraph Double click Y-axis: Return to AUTO-FIT Scaling Double click Y-axis: Return to DEFAULT Scaling Double click Y-axis: Return to OVERRIDE Scaling Double click Y-axis: For Dynamic Scaling Double click Y-axis: Select DEFAULT Scaling Double click Y-axis: Select AUTO-FIT Scaling %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.5.1/Translations/Filipino.ph.ts000066400000000000000000015644521450332542600207200ustar00rootroot00000000000000 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. 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 i 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 Search Hanapin Layout Save and Restore Graph Layout Settings Show/hide available graphs. Ipakita/itago ang talaguhitan. Breakdown Ang Pagsusuri events Mga pangyayari UF1 UF1 UF2 UF2 Time at Pressure Oras sa Pressure Clinical Mode Disabling Sessions requires the Permissive Mode 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.) no data :( Sorry, this device only provides compliance data. 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 Model %1 - %2 Modelo %1- %2 PAP Mode: %1 PAP Mode:%1 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 "Nothing's here!" "Walang nandito!" No data is available for this day. Walang makukuhang data sa araw na ito. 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 Device Settings <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 This CPAP device does NOT record detailed data 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?? Complain to your Equipment Provider! Magreklamo sa nagbenta sayo ng aparato! Pick a Colour Pumili ng Kulay Bookmark at %1 Bookmark sa %1 Hide All Events Show All Events Hide All Graphs Show All Graphs DailySearchTab Clear Bookmark Jumps to Date's Bookmark Start Search DATE Jumps to Date Match Notes Notes containing Bookmarks Bookmarks containing AHI Daily Duration Session Duration Days Skipped Disabled Sessions Number of Sessions Click HERE to close Help Help Tulong No Data Jumps to Date's Details Number Disabled Session Jumps to Date's Details Note Jumps to Date's Notes AHI Jumps to Date's Details EventsPerHour Session Duration Jumps to Date's Details Minutes Number of Sessions Jumps to Date's Details Sessions Daily Duration Jumps to Date's Details Hours Number of events Jumps to Date's Events Events Automatic start Continue Search End of Search No Matches Skip:%1 %1/%2%3 days %1 mga Araw {1/%2%3 ?} Found %1 Finds days that match specified criteria. Searches from last day to first day. First click on Match Button then select topic. Then click on the operation to modify it. or update the value Topics without operations will automatically start. Compare Operations: numberic or character. Numberic Operations: Character Operations: Summary Line Left:Summary - Number of Day searched Center:Number of Items Found Right:Minimum/Maximum for item searched Result Table Column One: Date of match. Click selects date. Column two: Information. Click selects date. Then Jumps the appropiate tab. Wildcard Pattern Matching: Wildcards use 3 characters: Asterisk Question Mark Backslash. Asterisk matches any number of characters. Question Mark matches a single character. Backslash matches next character. DateErrorDisplay ERROR The start date MUST be before the end date The entered start date %1 is after the end date %2 Hint: Change the end date first The entered end date %1 is before the start date %1 Hint: Change the start date first 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 device Record cannot be imported in this profile. 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 Show Standard Report Standard Standard Show Monthly Report Monthly Buwanan Show Range Report Date Range Date Range Select Report Date Report Date Statistics Statistics Daily Daily Overview Overview Oximetry Oximetry Import i-Import Help Tulong &File &File &View &Tingnan &Help &Tulong Troubleshooting &Data &Data &Advanced &Advanced 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 - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> Advanced - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> 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 Purge ALL Device 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 Couldn't find any valid Device Data at %1 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. 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. 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 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 No supported data was found 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 Are you sure you want to rebuild all CPAP data for the following device: 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. For some reason, OSCAR does not have any backups for the following device: Would you like to import from your own backups now? (you will have no data visible for this device until you do) OSCAR does not have any backups for this device! Unless you have made <i>your <b>own</b> backups for ALL of your data for this device</i>, <font size=+2>you will lose this device's data <b>permanently</b>!</font> You are about to <font size=+2>obliterate</font> OSCAR's device database for the following device:</p> A file permission error caused the purge process to fail; you will have to delete the following folder manually: 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 You must select and open the profile you wish to modify 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 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. This software is being designed to assist you in reviewing the data produced by your CPAP Devices and related equipment. 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 Layout Save and Restore Graph Layout Settings 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) Hide All Graphs Show All 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> 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. <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 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 I started this oximeter recording at (or near) the same time as a session on my CPAP device. <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 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. Flow Restriction Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. Duration of airflow restriction s Event Duration 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 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.) <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> Calculate Unintentional Leaks When Not Present 4 cmH2O 20 cmH2O Note: A linear calculation method is used. Changing these values requires a recalculation. 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 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 <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> 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 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 <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></p></body></html> l/min <html><head/><body><p>Cumulative Indices</p></body></html> Oximetry Settings <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</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) This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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>Provide an alert when importing data from any device model that has not yet been tested by OSCAR developers.</p></body></html> Warn when importing data from an untested device This calculation requires Total Leaks data to be provided by the CPAP device. (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. Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device missed. This option must be enabled before import, otherwise a purge is required. This experimental option attempts to use OSCAR's event flagging system to improve device detected event positioning. Resync Device Detected Events (Experimental) Allow duplicates near device events. Show flags for device detected events that haven't been identified yet. <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices do not support changing these settings.</p></body></html> Clinical Clinical Settings Select Oscar Operating Mode Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. Clinical Mode permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. Permissive Mode Hours <!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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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 device serial number on device settings changes report Print reports in black and white, which can be more legible on non-color printers Print reports in black and white (monochrome) For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. Enables SessionBar in Event Flags Graph 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. Overview Overview No CPAP devices detected Will you be using a ResMed brand device? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, 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? ResMed S9 devices routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). 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 Clinical Mode: Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. Basically replicates the reports and data stored on the devices data card. This includes pap devices, oximeters, etc. Compliance reports fall under this mode. Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. Permissive Mode: Allows user to select which data sets/ sessions to be used for calculations and display. Additional charts and calculations may be available that are not available from the vendor data. Changing the Oscar Operating Mode: Requires a reload of the user's profile. Data will be saved and restored. Minor Flag Span Always Minor Never This may not be a good idea 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 cmH2O Med. Min: %1 Min: Max: Max: %1 %1 (%2 days): %1 (%2 day): % in %1 Hours Min %1 Length: %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 milliSeconds 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 EEPAP Min EEPAP Max EEPAP 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 Device No Data Available App key: Operating system: Built with Qt %1 on %2 Graphics Engine: Graphics Engine type: Compiler: Software Engine ANGLE / OpenGLES Desktop OpenGL m cm in kg l/min Only Settings and Compliance Data Available Summary Data Only Bookmarks Σελιδοδείκτες Mode Model Brand Serial Series 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 device's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Non Data Capable Device Your %1 CPAP Device (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 device. Device Untested Your %1 CPAP Device (Model %2) has not been tested yet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Device Unsupported Sorry, your %1 CPAP Device (%2) is not supported yet. The developers need a .zip copy of this device's SD card and matching clinician .pdf reports to make it work with OSCAR. Getting Ready... Scanning Files... Importing Sessions... UNKNOWN APAP (std) APAP (dyn) Auto S Auto S/T AcSV SoftPAP Mode Pressure relief during exhalation Slight Softstart pressure Pressure during soft start period PSoft Softstart minimum pressure Minimum pressure during soft start period PSoftMin Auto start Automatically turn on the device by breathing Softstart time Lenght of soft start period Soft start maximum time Maximum lenght of soft start period Soft start max. time Soft start pressure Higher End Expiratory Pressure Humidifier level Tube type Obstruction level Obstruction level in percentage rRMVFluctuation Relative respiratory minute volume fluctuation Relative respiratory minute volume Measured pressure Full flow Artefact Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) Epoch (2 mins) with Flow Limitation Deep Sleep Deep sleep, stable respiration Timed breath BiSoft Mode BiSoft 1 BiSoft 2 TriLevel PMaxOA EEPAPMin Lower End Expiratory Pressure EEPAPMax rRMV ART CriticalLeak Mask leakage is above a critical treshold CL eMO Epoch (2 mins) with Mild Obstruction eSO Epoch (2 mins) with Severe Obstruction eS Epoch (2 mins) with Snoring eFL DS Finishing up... Untested Data 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 A few breaths automatically starts device Device automatically switches off Whether or not device allows Mask checking. 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 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 A period during a session where the device could not detect flow. 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 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 Auto Off Mask Alert Show AHI Whether or not device shows AHI via built-in display. The number of days in the Auto-CPAP trial period, after which the device will revert to CPAP Breathing Not Detected BND Timed Breath Machine Initiated Breath TB 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 device 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 device 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? Device Database Changes Sorry, the purge operation failed, which means this version of OSCAR can't start. The device data folder needs to be removed manually. 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. Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. 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 End 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 Cheyne Stokes Respiration (CSR) Periodic Breathing (PB) Clear Airway (CA) An apnea caused by airway obstruction A partially obstructed airway UA A vibratory snore Pressure Pulse A pulse of pressure 'pinged' to detect a closed airway. A type of respiratory event that won't respond to a pressure increase. Intellipap event where you breathe out your mouth. SensAwake feature will reduce pressure when waking is detected. Heart rate in beats per minute Blood-oxygen saturation percentage Plethysomogram An optical Photo-plethysomogram showing heart rhythm A sudden (user definable) change in heart rate A sudden (user definable) drop in blood oxygen saturation SD Breathing flow rate waveform 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 Ratio between Inspiratory and Expiratory time ratio Pressure Min Pressure Max Pressure Set Pressure Setting IPAP Set IPAP Setting EPAP Set EPAP Setting An abnormal period of Cheyne Stokes Respiration CSR An abnormal period of Periodic Breathing An apnea that couldn't be determined as Central or Obstructive. An apnea reported by your CPAP device. A restriction in breathing from normal, causing a flattening of the flow waveform. A vibratory snore as detected by a System One device 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 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 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. Obstructive Apnea (OA) Hypopnea (H) Unclassified Apnea (UA) Apnea (A) Flow Limitation (FL) RERA (RE) Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. Vibratory Snore (VS) Leak Flag (LF) A large mask leak affecting device performance. Large Leak (LL) Non Responding Event (NR) Expiratory Puff (EP) SensAwake (SA) User Flag #1 (UF1) User Flag #2 (UF2) User Flag #3 (UF3) Pulse Change (PC) SpO2 Drop (SD) I/E Value Apnea Hypopnea Index (AHI) Respiratory Disturbance Index (RDI) 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? OSCAR Reminder Don't forget to place your datacard back in your CPAP device 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 oximetry 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 (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 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) Min EEPAP %1 Max EEPAP %2 PDIFF %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 Selection Length 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 Device auto starts by breathing Response Device auto stops by breathing Patient View RiseEnable RiseTime Cycle Trigger TiMax TiMin Your ResMed CPAP device (Model %1) has not been tested yet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card to make sure it works with OSCAR. SmartStart 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 Smart Stop Simple Advanced 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... Loading summaries Permissive Mode Total disabled sessions: %1, found in %2 days Total disabled sessions: %1 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. 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 Löwenstein Prisma Smart Resvent/Hoffrichter iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings Add Add Feature inhibited. The maximum number of Items has been exceeded. creates new copy of current settings. Restore Restores saved settings from selection. Rename Renames the selection. Must edit existing name then press enter. Update Updates the selection with current settings. Delete Deletes the selection. Expanded Help menu. Exits the Layout menu. <h4>Help Menu - Manage Layout Settings</h4> Exits the help menu. Exits the dialog menu. This feature manages the saving and restoring of Layout Settings. Layout Settings control the layout of a graph or chart. Different Layouts Settings can be saved and later restored. Button Description Creates a copy of the current Layout Settings. The default description is the current date. The description may be changed. The Add button will be greyed out when maximum number is reached. Other Buttons Greyed out when there are no selections Loads the Layout Settings from the selection. Automatically exits. io Modify the description of the selection. Same as a double click.io Saves the current Layout Settings to the selection. Prompts for confirmation. Deletes the selecton. Control Exit Exit (Red circle with a white "X".) Returns to OSCAR menu. Return Next to Exit icon. Only in Help Menu. Returns to Layout menu. Escape Key Exit the Help or Layout menu. Layout Settings * Name * Pinning * Plots Enabled * Height * Order * Event Flags * Dotted Lines * Height Options General Information Maximum description size = 80 characters. Maximum Saved Layout Settings = 30. Saved Layout Settings can be accessed by all profiles. Layout Settings only control the layout of a graph or chart. They do not contain any other data. They do not control if a graph is displayed or not. Layout Settings for daily and overview are managed independantly. Maximum number of Items exceeded. No Item Selected Ok to Update? Ok To Delete? SessionBar %1h %2m No Sessions Present SleepStyleLoader Import Error May mali sa pag Import This device Record cannot be imported in this profile. 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: Device Information Changes to Device Settings Database has No %1 data available. Database has %1 day of %2 Data on %3 Database has %1 days of %2 Data, between %3 and %4 Total Days: %1 Days Not Used: %1 Days Used: %1 Days %1 %2 Hours: %3 Best Device Setting Worst Device Setting 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. Date: %1 - %2 AHI: %1 Total Hours: %1 Most Recent Compliance (%1 hrs/day) This report was prepared on %1 by OSCAR %2 OSCAR is free open-source CPAP report software 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 Days Pressure Relief Pressure Settings 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 device.</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 device is detected First import can take a few minutes. The last time you used your %1... last night today %2 days ago was %1 (on %2) %1 hours, %2 minutes and %3 seconds <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 pressure was under %1 %2 for %3% of the time. 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. 1 day ago Your device was on for %1. Your CPAP device used a constant %1 %2 of air Your device used a constant %1-%2 %3 of air. Your device was under %1-%2 %3 for %4% of the time. Your EEPAP pressure was under %1 %2 for %3% of the time. Your average leaks were %1 %2, which is %3 your %4 day average of %5. No CPAP data has been imported yet. gGraph Double click Y-axis: Return to AUTO-FIT Scaling Double click Y-axis: Return to DEFAULT Scaling Double click Y-axis: Return to OVERRIDE Scaling Double click Y-axis: For Dynamic Scaling Double click Y-axis: Select DEFAULT Scaling Double click Y-axis: Select AUTO-FIT Scaling %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.5.1/Translations/Francais.fr.ts000066400000000000000000017366571450332542600207050ustar00rootroot00000000000000 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 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 Search Rechercher 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 B.M.I. I.M.C. Sleep Stage Sessions Sessions du sommeil Oximeter Information Informations de l'oxymètre Events Évènements CPAP Sessions Sessions PPC Medium Moyen Starts Début Weight Poids i i Zombie Zombie Bookmarks Favoris Session End Times Fin de session %1 events %1 évènements events évènements Event Breakdown Répartition des évènements SpO2 Desaturations Désaturations SpO₂ no data :( Pas de données :( Sorry, this device only provides compliance data. Désolé, votre appareil ne fournit que des données d'observance. "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 Clinical Mode Mode clinique Disabling Sessions requires the Permissive Mode Désactiver des sessions ne peut se faire qu'en mode Permission Unknown Session Session inconnue This CPAP device does NOT record detailed data L'appareil PPC NE contient AUCUNE donnée détaillée 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... Layout Mise en page Save and Restore Graph Layout Settings Sauvegarder et restaurer les paramètres de mise en page du graphique Show/hide available graphs. Affiche ou cache les graphiques. Details Détails Time at Pressure Durée à cette pression Disable Warning Désactiver l'avertissement Disabling a session will remove this session data from all graphs, reports and statistics. The Search tab can find disabled sessions Continue ? La désactivation d'une session supprime les données de cette session, de tous les graphiques, rapports et statistiques. L'onglet Recherche permet de trouver les sessions désactivées. Continuer ? 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 Device Settings Réglages de l'appareil PAP Mode: %1 Mode PAP : %1 Start Début End Fin Unable to display Pie Chart on this system Impossible d'afficher des graphiques sur ce système 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.) Hide All Events Cacher tous les évènements Show All Events Afficher tous les évènements Hide All Graphs Cacher tous les graphiques Show All Graphs Afficher tous les graphiques DailySearchTab Match: Rechercher : Select Match Sélectionner un critère Clear Effacer Bookmark Jumps to Date's Bookmark Aller au signet de la date donnée Start Search Lancer la recherche DATE Jumps to Date DATE Aller à la date Match Cela permet de sélectionner "les critères" de recherche dans le totem de gauche Critères Notes Notes Notes containing Notes contenant Bookmarks Signets Bookmarks containing Signets contenant AHI IAH Daily Duration Durée journalière Session Duration Durée de la session Days Skipped Jours non pris en compte Disabled Sessions Sessions désactivées Number of Sessions Nombre de sessions Click HERE to close Help Cliquez ICI pour fermer l'aide Help Aide No Data Jumps to Date's Details Pas de données Aller aux détails du jour Number Disabled Session Jumps to Date's Details Nombre de sessions désactivées Aller aux détails du jour Note Jumps to Date's Notes Note Aller aux notes du jour Jumps to Date's Bookmark Aller au signet du jour AHI Jumps to Date's Details IAH Aller aux détails du jour EventsPerHour Evénements par heure Session Duration Jumps to Date's Details Durée de la session Aller aux détails du jour Minutes minutes Number of Sessions Jumps to Date's Details Nombre de sessions Aller aux détails du jour Sessions sessions Daily Duration Jumps to Date's Details Durée journalière Aller aux détails du jour Hours heures Number of events Jumps to Date's Events Nombre d'évènements Aller aux détails des évènements Events événements Automatic start Démarrage automatique More to Search Continuer la recherche Continue Search Continuer la recherche End of Search Fin de la recherche No Matches Pas de correspondance Skip:%1 Sauter : %1 %1/%2%3 days. %1/%2%3 jours. %1/%2%3 days %1 jours {1/%2%3 ?} Found %1 Trouvé %1. {1 ?} Finds days that match specified criteria. Recherche les jours correspondants aux critères spécifiés. Searches from last day to first day. Recherche antéchronologique. First click on Match Button then select topic. Cliquez d'abord sur le bouton "Correspondance", puis sélectionnez le sujet. Then click on the operation to modify it. Cliquez ensuite sur l'opération pour la modifier. or update the value ou mettre à jour la valeur Topics without operations will automatically start. Les sujets sans opérations démarreront automatiquement. Compare Operations: numberic or character. Opérateurs de comparaison : numérique ou alpha. Numberic Operations: Opérateurs numériques : Character Operations: Opérateurs alpha : Summary Line Ligne récapitulative Left:Summary - Number of Day searched Gauche : Résumé - Nombre de jours recherchés Center:Number of Items Found Centre : Nombre d'éléments trouvés Right:Minimum/Maximum for item searched Droite : Minimum/Maximum pour la recherche Result Table Tableau des résultats Column One: Date of match. Click selects date. Première colonne : Date correspondante. Cliquez pour sélectionner la date. Column two: Information. Click selects date. Deuxième colonne : Informations. Cliquez pour sélectionner la date. Then Jumps the appropiate tab. Passe ensuite à l'onglet approprié. Wildcard Pattern Matching: Correspondance avec métacaractères (jokers) : Wildcards use 3 characters: Les métacaractères (jokers) utilisent trois caractères : Asterisk Astérisque Question Mark Point d'interrogation Backslash. Antislash. Asterisk matches any number of characters. L'astérisque remplace un ou plusieurs caractères. Question Mark matches a single character. Le point d'interrogation remplace un seul caractère. Backslash matches next character. L'antislash remplace le caractère suivant. Found %1. Trouvé %1. DateErrorDisplay ERROR The start date MUST be before the end date La date de début doit être antérieure à celle de fin The entered start date %1 is after the end date %2 La date de début (%1) est postérieure à la date de fin (%2) Hint: Change the end date first Astuce : Changer d'abord la date de fin The entered end date %1 La date de fin entrée : (%1) is before the start date %1 est antérieure à la date de début (%1) Hint: Change the start date first Astuce : Changer d'abord la date de début 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 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. This device Record cannot be imported in this profile. Import de données impossible depuis cet appareil danc ce profil. 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. 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 Export for Review Export pour relecture Take &Screenshot &Faire une copie d'écran Show Standard Report Affiche le rapport standart Show Monthly Report Affiche le rapport mensuel Show Range Report Affiche le rapport sur une période donnée Select Report Date Sélectionner la date du rapport Report Date Date du rapport Overview Aperçus Purge ALL Device Data Purge TOUTES les données de l'appareil 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. 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 Standard - CPAP, APAP Standard - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> <html><head/><body><p>Ordre standard des graphiques, bon pour CPAP, APAP, BPAP de base</p></body></html> Advanced - BPAP, ASV Avancé - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> <html><head/><body><p>Ordre avancé des graphiques , bon pour BPAP avec BU, ASV, AVAPS, IVAPS</p></body></html> 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 Are you sure you want to rebuild all CPAP data for the following device: Ê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 device: Pour une raison quelconque, OSCAR n'a pas de sauvegardes internes pour l'appareil suivant : Would you like to import from your own backups now? (you will have no data visible for this device 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) OSCAR does not have any backups for this device! 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 device</i>, <font size=+2>you will lose this device'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> You are about to <font size=+2>obliterate</font> OSCAR's device database for the following device:</p> Vous êtes sur le point de <font size=+2>détruire</font> les données d'OSCAR pour l'appareil suivant :</p> The Glossary will open in your default browser Le glossaire sera ouvert dans le navigateur par défaut OSCAR Information Informations sur OSCAR Troubleshooting Dépannage &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) Couldn't find any valid Device Data at %1 Impossible de trouver des données de l'appareil valides à %1 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 No supported data was found Aucune donnée prise en charge n'a été trouvée Choose where to save screenshot Choisir où enregistrer la capture d’écran Image files (*.png) Fichiers image (*.png) A file permission error caused the purge process to fail; you will have to delete the following folder manually: Une erreur a provoqué l'échec du processus de purge ; vous devrez supprimer manuellement le dossier suivant : You must select and open the profile you wish to modify Vous devez sélectionner et ouvrir le profil que vous souhaitez modifier 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 This software is being designed to assist you in reviewing the data produced by your CPAP Devices 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. 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 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 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 Layout Mise en page Save and Restore Graph Layout Settings Enregistrer et restaurer les paramètres de mise en page du graphique Last Two Months 2 derniers mois Hide All Graphs Masquer tous les graphiques Show All Graphs Afficher les graphiques 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é I started this oximeter recording at (or near) the same time as a session on my CPAP device. J'ai démarré l'oxymètre en même temps que la session de l'appareil à Pression Positive Continue. &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 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 &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... 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) <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 Clinical Mode: Mode clinique : Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. Rapporte les données qui sont sur la SD card, tout ce qui est inclus et toutes les données désélectionnées dans le monde permissif. Basically replicates the reports and data stored on the devices data card. De manière basique duplique le rapport et les données stockées sur la carte. This includes pap devices, oximeters, etc. Compliance reports fall under this mode. Ce qui inclus les machines pap, les oxymètres, etc. Le rapport de conformité ne sera pas émis dans ce mode. Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. Les rapports de conformité incluent toujours toutes les données de la période choisie, même si certaines sont désélectionnées. Permissive Mode: Mode permissif : Allows user to select which data sets/ sessions to be used for calculations and display. Autorise l'utilisateur à sélectionner les données à utiliser lors des calculs et lors de l'affichage. Additional charts and calculations may be available that are not available from the vendor data. Les graphiques additionnels et les calculs disponibles pourraient ne pas l'être d'après le vendeur. Changing the Oscar Operating Mode: Change le mode d'opération pour Oscar : Requires a reload of the user's profile. Data will be saved and restored. Necessite un rechargement du profil de l'utilisateur. Les données seront sauvegardées puis restaurées. Span Envergure No CPAP devices detected Pas d'appareil PPC détecté Will you be using a ResMed brand device? Utiliserez-vous un appareil Resmed ? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, 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> ResMed S9 devices 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). &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. 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 Graph Titles Titres des graphiques Zero Reset Remettre à zéro Flow Restriction Restriction de flux <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Une méthode expérimentale de dtéection des événements non détectés par la machine a été mise en place. Ils</span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">ne sont pas</span><span style=" font-family:'Sans'; font-size:10pt;"> intégré dans le calcul de l'IAH.</span></p></body></html> 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 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 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 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 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. 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><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Les sessions d'une durée inférieure ne sont pas affichées</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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 une copie de la carte SD des appareils ResMed. Les appareils ResMed série S9 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>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> <html><head/><body><p>Provide an alert when importing data from any device 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 device Avertir lors de l’importation de données à partir d’un appareil non testé This calculation requires Total Leaks data to be provided by the CPAP device. (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. Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device 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. This experimental option attempts to use OSCAR's event flagging system to improve device 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. Resync Device Detected Events (Experimental) Resynchronisation des évènements détectés par l'appareil (expérimental) Allow duplicates near device events. Autoriser la duplication des évènements proches. Show flags for device detected events that haven't been identified yet. Afficher les marqueurs d'évènements détectés mais non identifiés. Changes to the following settings needs a restart, but not a recalc. Un changement des réglages ci-dessous nécessitera un redémarrage. <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices 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> Clinical Clinique Clinical Settings Mode clinique Select Oscar Operating Mode Selection du mode d'opération pour Oscar Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. Le mode clinique n'autorise pas les sessions désactivées.\nLes sessions désactivées ne seront pas utilisées pour les graphiques ou les statistiques. Clinical Mode Mode clinique permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. Le mode permissif autorise les sessions désactivées.\nLes sessions désactivées seront utilisées pour les graphiques et les statistiques. Permissive Mode Mode permissif Hours heures <!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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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 entre l'oxym&egrave;tre et les donn&eacute;es du respirateur</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Les donn&eacute;es CMS50 import&eacute;es depuis le SpO2Review ou la m&eacute;thode d'import en s&eacute;rie</span><span style=" font-family:'Sans'; font-size:10pt;"> doivent avoir la date/heure correctement param&eacute;tr&eacute;e afin de pouvoir &ecirc;tre synchronis&eacute;es.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Le mode affichage en direct (utilisant le c&acirc;ble s&eacute;rie) est undes moyens pour synchroniser les oxym&egrave;tre CLS50, mais pas de prendre en compte le décalage de l'horloge du respirateur.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;"> Si vous d&eacute;marrez l'enregistrement </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactement </span><span style=" font-family:'Sans'; font-size:10pt;">au m&ecirc;me moment,vous pouvez alors synchoniser les donn&eacute; avec le respirateur. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Le processus d'import en s&eacute;rie prend en compte l'heure de d&eacute; de la session de la nuit derni&egrave;re comme heure de d&eacute;but '(Rappelez-vous d'avoir importer les donn&eacute;es du respirateur avant)</span></p></body></html> &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 For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. Pour des sessions multiples, affiche une fine ligne grise pour chaque session en haut du graphe d'événements. Enables SessionBar in Event Flags Graph Active laberre de session dans les graphes d'événements 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 Oximetry Settings Réglages de l'oxymétrie <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:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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><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:12px; margin-bottom:12px; 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 entre l'Oxymètre et le PCC</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Les données importées CMS50 depuis le SpO2 (spoR files) ou la méthode d'import série </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 un horodatage correct nécessitant une synchronisation </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Le mode de visualisation en direct (grâce à un câble série) est un moyen d'effectuer une synchronisation sur les oxymètres CMS50, mais ne prend pas en compte le décalage d'horloge.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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 l'enregistrement de l'oxymètre </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactement </span><span style=" font-family:'Sans'; font-size:10pt;">au même moment que le PCC, alors vous pourrez les .synchroniser</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Le processus d'import série prend en compte l'heure de démarrage de la session du PCC lors de la nuit précédente. (Rappelez-vous de bien importer les données du PCC en premier !)</span></p></body></html> 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 <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 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). 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 Include Serial Number Inclure le numéro de série <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 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 Whether to include device serial number on device 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 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 O2 O₂ OA AO NR NR PB PB PC PC No Non PP PP PS PS Device Machine 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 Compiler: Compiler : in en kg kg milliSeconds milliseconds l/min l/mn EEPAP EEPAP Min EEPAP EEPAP Minimum Max EEPAP EEPAP Maximum UF1 UF1 UF2 UF2 UF3 UF3 Sep Sep VS2 VS2 Yes Oui Zeo Zeo bpm bpm varies varie Min EEPAP %1 Max EEPAP %2 PDIFF %3-%4 (%5) Min EEPAP %1 Max EEPAP %2 PDIFF %3-%4 (%5) 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 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 cmH2O cmH₂O Pressure Support Pression supportée Bedtime: %1 Coucher : %1 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 Respironics Respironics Heart rate in beats per minute Pouls en battements par minute 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 : Disconnected Déconnecté Sleep Stage Phase du sommeil Minute Vent. Vent. minute. Ramp Event Évènement de rampe Cheyne Stokes Respiration (CSR) Respiration de Cheyne Stockes (RCS) Periodic Breathing (PB) Respiration Périodique (RP) Clear Airway (CA) Voies respiratoires dégagées (CA) Obstructive Apnea (OA) Apnées obstructives (AO) Hypopnea (H) Hypopnées (H) Unclassified Apnea (UA) Apnées non classifiées (NC) Apnea (A) Apnée (A) An apnea reportred by your CPAP device. Apnée signalée par votre appareil CPAP. Flow Limitation (FL) Limitation du débit (LB) RERA (RE) RERA (RE) Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. Éveil lié à l'effort respiratoire : une ralentissement de la respiration provoquant soit l'éveil, soit des troubles du sommeil. Vibratory Snore (VS) Ronflement vibratoire (RV) Leak Flag (LF) Marqueur de fuite (MF) A large mask leak affecting device performance. Grosses fuites affectant les performances de l'appareil. Large Leak (LL) Grosses fuites (LL) Non Responding Event (NR) Évènement ne répondant pas (NR) Expiratory Puff (EP) Bouffée expiratoire 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 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 device'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 et du pdf des données cliniques afin de s'assurer qu'OSCAR gère les données de manière correcte. Non Data Capable Device Appareil ne prenant pas en charge les données Your %1 CPAP Device (Model %2) is unfortunately not a data capable model. Votre machine %1 (Modèle %2) n'est malheureusement pas compatible. I'm sorry to report that OSCAR can only track hours of use and very basic settings for this device. Désolé, OSCAR ne peut suivre que les heures d'utilisation et quelques informations de base pour cet appareil. Device Untested Appareil non testé Your %1 CPAP Device (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 devices that it might work, but the developers would like a .zip copy of this device'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. Device Unsupported Appareil non supporté Sorry, your %1 CPAP Device (%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 device's SD card and matching clinician .pdf reports to make it work with OSCAR. Les developpeurs auraient besoin d'une copie de la carte zip et des relevés cliniques afin de faire évoluer 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 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 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 Pt. Access Accès patient CMS50F CMS50F ASV (Fixed EPAP) ASV (EPAP fixe) Patient Triggered Breaths Respirations activées par le patient 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é. Connected Connecté Low Usage Days: %1 Jours de faible usage : %1 PS Max PS maxi PS Min PS mini Selection Length Longueur de sélection 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 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 : 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 End Expiratory Pressure Pression de fin d'expiration An apnea reported by your CPAP device. Apnée signalée par votre appareil CPAP. A vibratory snore as detected by a System One device Ronflement vibratoire détecté par l'appareil "One device" SensAwake (SA) SensAwake (SA) User Flag #1 (UF1) Évènement utilisateur #1 (EU1) User Flag #2 (UF2) Évènement utilisateur #2 (EU2) User Flag #3 (UF3) Évènement utilisateur #3 (EU3) Pulse Change (PC) Changement de pulsations (CP) SpO2 Drop (SD) Baisse de SpO₂ (BS) Pat. Trig. Breaths Resp. activées par le patient % in %1 % en %1 Humidity Level Niveau humidité Address Adresse 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 ? Are you sure you want to reset all your oximetry settings to defaults? Êtes-vous sûr de vouloir réinitialiser tous vos paramètres d'oxymétrie aux valeurs par défaut ? Inspiratory Pressure Pression d'inspiration 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+ Median Leak Rate Volume moyen de fuite (%3 sec) (%3 sec) Rate of breaths per minute Respirations par minute Permissive Mode Mode permissif Total disabled sessions: %1, found in %2 days Total de sessions désactivées : %1, trouvée sur %2 jours Total disabled sessions: %1 Total de sessions désactivées : %1 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. Duréee de la plus longue session désactivée : %1 minutes, Durrée totale des sessions désactivées : %2 minutes. 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 Days: %1 Jours : %1 Plethysomogram Pléthysmogramme 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 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 Length: %1 Longueur : %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 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 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 Humid. Status État humidificateur (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'. 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 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 Summary Only Résumé seulement Bi-Level Bi-Level Intellipap Intellipap 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 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 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 A few breaths automatically starts device Mise en marche par respiration Device automatically switches off Arrêt automatique de l'appareil Whether or not device allows Mask checking. Selon que l'appareil permette ou non la vérification du masque. Whether or not device shows AHI via built-in display. Selon que l'écran de l'appareil affiche ou non l'IAH. The number of days in the Auto-CPAP trial period, after which the device will revert to CPAP Nombre de jours de la période d'essai Auto-CPAP, après lequel la machine reviendra au CPAP A period during a session where the device could not detect flow. Période pendant une session sans flux détectée. Machine Initiated Breath Respiration provoquée par l'appareil 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... UNKNOWN INCONNU APAP (std) APAP (std) APAP (dyn) APAP (dyn) Auto S Auto S Auto S/T Auto S/T AcSV AcSV SoftPAP Mode Mode SoftPAP Pressure relief during exhalation Relief de la pression pendant l'expiration Slight Slight Softstart pressure pression de démarrage de la rampe Pressure during soft start period Pression durant la période la rampe PSoft PSoft Softstart minimum pressure Pression minimum de la rampe Minimum pressure during soft start period Pression minimum durant la rampe PSoftMin PSoftMin Auto start Démarrage automatique Automatically turn on the device by breathing Démarrage automatique en respirant Softstart time Temps de la rampe Lenght of soft start period Durée de la rampe Soft start maximum time Durrée maximum de la rampe Maximum lenght of soft start period Durrée maximum de la rampe Soft start max. time Durrée max. de la rampe Soft start pressure Pression de la rampe Higher End Expiratory Pressure Pression la plus haute lors de l'expiration Humidifier level Niveau d'humidification Tube type Type de tube Obstruction level Niveau d'obstruction Obstruction level in percentage Niveau de l'obstruction en pourcentage rRMVFluctuation Fluctuation rRMV Relative respiratory minute volume fluctuation Fluctuation du volume relatif de respiration Relative respiratory minute volume Volume relatif de respiration Measured pressure Pression mesurée Full flow Flux complet Artefact Artefact Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) La non régularité des données mesurées n'indique pas forcément un événement respiratoire (par ex : avalement, toussement, parole) Epoch (2 mins) with Flow Limitation Période (2 mn) avec une limitation du flux Deep Sleep Sommeil profond Deep sleep, stable respiration Sommeil profond, respiration stable Timed breath Respiration mesurée BiSoft Mode Mode BiSoft BiSoft 1 BiSoft 1 BiSoft 2 BiSoft 2 TriLevel Triniveau AutoStart Démarrage automatique Softstart_Time Softstart_Time Softstart_TimeMax Softstart_TimeMax Softstart_Pressure Softstart_Pressure PMaxOA PMaxOA EEPAPMin EEPAPMin Lower End Expiratory Pressure Pression en fin d'expiration la plus basse EEPAPMax EEPAPMax HumidifierLevel Niveau d'humidification TubeType Type de tuyau ObstructLevel Niveau d'obstruction Obstruction Level Niveau d'obstruction rMVFluctuation rMVFluctuation rRMV rRMV PressureMeasured Pression mesurée FlowFull Flux complet SPRStatus Statut SPR Artifact Artéfacts ART ART CriticalLeak Fuite critique Mask leakage is above a critical treshold Les fuites du masque dépassent le seuil critique CL CL eMO eMO Epoch (2 mins) with Mild Obstruction Période (2 mn) avec une obstruction modérée eSO eSO Epoch (2 mins) with Severe Obstruction Période (2 mn) avec une obstruction sévère eS eS Epoch (2 mins) with Snoring Période (2 mn) avec ronflements eFL eFL DeepSleep Sommeil profond DS DS TimedBreath Respiration synchronisée Finishing up... Finalisation... Breathing Not Detected Respiration non détectée BND BND iVAPS iVAPS Soft Doux Standard Standard SmartStop Smart Stop Smart Stop Smart Stop Response Réponse Device auto starts by breathing Démarrage auto par respiration Device auto stops by breathing Arrêt auto par respiration Patient View Vue patient Simple Simple Your ResMed CPAP device (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 devices that it might work, but the developers would like a .zip copy of this device'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. Advanced Avancé BiPAP-T BiPAP-T BiPAP-S BiPAP-S BiPAP-S/T BiPAP-S/T PAC PAC RiseEnable Activation de la montée RiseTime Temps de montée Cycle Cycle Trigger Trigger TiMax TiMax TiMin TiMin 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 : <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> <i>Your old device 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> 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. This means you will need to import this device 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. 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 ? Device Database Changes La base de données de l'appareil a changé 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. The device data folder needs to be removed manually. Le répertroire de données de l'appareil doit être effacé manuellement. 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 Don't forget to place your datacard back in your CPAP device N'oubliez pas de remettre la carte SD dans votre appareil 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 : 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 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 humidificateur 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) I/E Value Valeur I/E A ResMed data item: Trigger Cycle Event Élément de données Resmed : événement du cycle de déclenchement Apnea Hypopnea Index (AHI) Index Apnées Hypopnées (IAH) Respiratory Disturbance Index (RDI) Index des troubles respiratoires 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 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 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. Inspiration. chrono. Auto-Trial Duration Durée de l'Auto-Trial 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 Löwenstein Löwenstein Prisma Smart Prisma Smart Resvent/Hoffrichter Resvent/Hoffrichter iBreeze/Point3 iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings Gère les paramètres d'enregistrement de la mise en page Add Ajouter Add Feature inhibited. The maximum number of Items has been exceeded. L'ajout d'une fonctionnalité est bloqué. Le nombre maximum d'éléments a été dépassé. creates new copy of current settings. crée une nouvelle copie des paramètres actuels. Restore Restaurer Restores saved settings from selection. Restaure les paramètres enregistrés à partir de la sélection. Rename Renommer Renames the selection. Must edit existing name then press enter. Renomme la sélection. Il faut modifier le nom existant, puis appuyer sur Entrée. Update Mise à jour Updates the selection with current settings. Met à jour la sélection avec les paramètres actuels. Delete Supprimer Deletes the selection. Supprime la sélection. Expanded Help menu. Menu d'aide étendu. Exits the Layout menu. Quitte le menu de Mise en page. <h4>Help Menu - Manage Layout Settings</h4> <h4>Menu Aide - Paramètres de mise en page</h4> Exits the help menu. Quitte le menu Aide. Exits the dialog menu. Quitte le menu de dialogue. This feature manages the saving and restoring of Layout Settings. Cette fonctionnamité permet de sauvegarder et de restaurer les paramètres de mise en page. Layout Settings control the layout of a graph or chart. Les paramétres de mise en page permettent de controler la mise en page des graphiques et des tabeaux. Different Layouts Settings can be saved and later restored. Différentes mises en page peuvent être sauvegardées et restaurées plus tard. Button Bouton Description Description Creates a copy of the current Layout Settings. Créer une copie de la mise à page actuelle. The default description is the current date. La description par défaut est la date actuelle. The description may be changed. La description peut être modifiée. The Add button will be greyed out when maximum number is reached. Le bouton "Ajouter" est grisé dès que le maximum est atteint. Other Buttons Les autres boutons Greyed out when there are no selections Grisé s'il y a aucune sélection Loads the Layout Settings from the selection. Automatically exits. io Charge les réglages de mise en page de la sélection. Puis sort automatiquement. io Modify the description of the selection. Same as a double click.io Modifie la description de la sélection comme pour un double-clic.io Saves the current Layout Settings to the selection. Sauvegarde les réglages de mise en page comme sélection. Prompts for confirmation. Affiche une confirmation. Deletes the selecton. Efface la sélection. Control Controle Exit Quitter (Red circle with a white "X".) Returns to OSCAR menu. (Rond rouge avec une croix blanche) Retour au menu OSCAR. Return Retour Next to Exit icon. Only in Help Menu. Returns to Layout menu. Suivant vers l'icône de Sortie. Seulement dans le menu d'aide. Reviens au menu de mise en page. Escape Key touche echap Exit the Help or Layout menu. Sort de l'aide ou du menu de mise en page. Layout Settings Réglages de mise en page * Name * Nom * Pinning *. Épinglage * Plots Enabled * Points activés * Height * Hauteur * Order * Ordre * Event Flags * Indicateur d'événement * Dotted Lines *. Lignes pointillées * Height Options * Options de hauteur General Information Informations générales Maximum description size = 80 characters. Taille maximum de la description : 80 caractères. Maximum Saved Layout Settings = 30. Maximum de sauvegardes des mise en pages : 30. Saved Layout Settings can be accessed by all profiles. Les réglages de mise en page sont accessibles à tous les profils. Layout Settings only control the layout of a graph or chart. Les paramétres de mise en page permettent de controler seulemnt la mise en page des graphiques et des tabeaux. They do not contain any other data. Ils ne contiennent pas d'autres données. They do not control if a graph is displayed or not. Ils ne controlent pas si un graphique est affiché ou pas. Layout Settings for daily and overview are managed independantly. Les réglages de mise page pour l'onglet Quotidien et Aperçus sont indépendants. <p style="color:black;"> This feature manages the saving and restoring of Layout Settings. <br> Layout Settings control the layout of a graph or chart. <br> Different Layouts Settings can be saved and later restored. <br> </p> <table width="100%"> <tr><td><b>Button</b></td> <td><b>Description</b></td></tr> <tr><td valign="top">Add</td> <td>Creates a copy of the current Layout Settings. <br> The default description is the current date. <br> The description may be changed. <br> The Add button will be greyed out when maximum number is reached.</td></tr> <br> <tr><td><i><u>Other Buttons</u> </i></td> <td>Greyed out when there are no selections</td></tr> <tr><td>Restore</td> <td>Loads the Layout Settings from the selection. Automatically exits. </td></tr> <tr><td>Rename </td> <td>Modify the description of the selection. Same as a double click.</td></tr> <tr><td valign="top">Update</td><td> Saves the current Layout Settings to the selection.<br> Prompts for confirmation.</td></tr> <tr><td valign="top">Delete</td> <td>Deletes the selecton. <br> Prompts for confirmation.</td></tr> <tr><td><i><u>Control</u> </i></td> <td></td></tr> <tr><td>Exit </td> <td>(Red circle with a white "X".) Returns to OSCAR menu.</td></tr> <tr><td>Return</td> <td>Next to Exit icon. Only in Help Menu. Returns to Layout menu.</td></tr> <tr><td>Escape Key</td> <td>Exit the Help or Layout menu.</td></tr> </table> <p><b>Layout Settings</b></p> <table width="100%"> <tr> <td>* Name</td> <td>* Pinning</td> <td>* Plots Enabled </td> <td>* Height</td> </tr> <tr> <td>* Order</td> <td>* Event Flags</td> <td>* Dotted Lines</td> <td>* Height Options</td> </tr> </table> <p><b>General Information</b></p> <ul style=margin-left="20"; > <li> Maximum description size = 80 characters. </li> <li> Maximum Saved Layout Settings = 30. </li> <li> Saved Layout Settings can be accessed by all profiles. <li> Layout Settings only control the layout of a graph or chart. <br> They do not contain any other data. <br> They do not control if a graph is displayed or not. </li> <li> Layout Settings for daily and overview are managed independantly. </li> </ul> <p style="color:black;"> Cette fonction gère l'enregistrement et la restauration des paramètres de mise en page. <br> Les paramètres contrôlent la mise en page d'un graphique ou d'un diagramme. <br> D'autres paramètres de mise en page peuvent être enregistrés et restaurés ultérieurement. <br> </p> <table width="100%"> <tr><td><b>Bouton</b></td> <td><b>Description</b></td></tr> <tr><td valign="top">Ajouter</td> <td>Copie les paramètres de mise en page actuels. <br> La description par défaut est la date actuelle. <br> La description peut être modifiée. <br> Le bouton "Ajouter" sera grisé lorsque le nombre maximum sera atteint.</td></tr> <br> <tr><td><i><u>Autres boutons</u> </i></td> <td>Grisés lorsqu'il n'y a pas de sélection</td></tr> <tr><td>Restaurer</td> <td>Charge les paramètres de mise en page à partir de la sélection. Quitte automatiquement. </td></tr> <tr><td>Renommer </td> <td>Modifie la description de la sélection. Identique à un double clic.</td></tr> <tr><td valign="top">Mise à jour</td><td> Enregistre les paramètres de mise en page actuels dans la sélection.<br> Demande de confirmation.</td></tr> <tr><td valign="top">Supprimer</td> <td>Supprime la sélection. <br> Demande de confirmation.</td></tr> <tr><td><i><u>Contrôle</u> </i></td> <td></td></tr> <tr><td>Sortie </td> <td>(Cercle rouge avec un "X" blanc.) Revient au menu OSCAR.</td></tr> <tr><td>Retour</td> <td>À côté de l'icône Quitter.<br>Uniquement dans le menu Aide.<br>Revient au menu Mise en page.</td></tr> <tr><td>Touche Echapp</td> <td>Quitte le menu Aide ou Mise en page.</td></tr> </table> <p><b>Paramètres de mise en page</b></p> <table width="100%"> <tr> <td>* Nom</td> <td>* Épingler</td> <td>* Plots activés </td> <td>* Hauteur</td> </tr> <tr> <td>* Ordre</td> <td>* Drapeaux d'événement</td> <td>* Pointillés</td> <td>* Options de hauteur</td> </tr> </table> <p><b>Informations générales</b></p> <ul style=margin-left="20"; > <li> Longueur maximum = 80 caractères. </li> <li> Paramètres de mise en page enregistrés maximum = 30. </li> <li> Les paramètres de mise en page enregistrés sont accessibles par tous les profils. <li> Ces paramètres contrôlent uniquement la mise en page d'un graphique ou d'un tableau. <li> Ne contiennent aucune autre donnée. <li> Ne contrôlent pas l'affichage d'un graphique. </li> <li> Les paramètres de mise en page pour "Quotidien et Aperçu" sont gérés indépendamment. </li> </ul> Maximum number of Items exceeded. Nombre maximum d'éléments atteint. No Item Selected Aucun élément sélectionné Ok to Update? Ok pour mettre à jour ? Ok To Delete? OK pour supprimer ? SessionBar No Sessions Present Pas de sessions présentes %1h %2m %1h %2m SleepStyleLoader Import Error Erreur d'import This device Record cannot be imported in this profile. Import de données impossible 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 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 : Device Information Informations de l'appareil Changes to Device Settings Changements de réglages de l'appareil Database has No %1 data available. La base de données n'a pas de %1 de données disponibles. Database has %1 day of %2 Data on %3 La base de données a %1 jours sur %2 j sur %3 Database has %1 days of %2 Data, between %3 and %4 La base de données a des données entre %3 et %4 pour %1 jours sur %2 Total Days: %1 Jours total : %1 Days Not Used: %1 Jours de non utilisation : %1 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. Worst Device Setting Paramétrage du respirateur pour le pire 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) No data found?!? Aucune donnée disponible !? Days %1 %2 Hours: %3 Jours %1 %2 Heures : %3 Best Device Setting Paramétrage du respirateur pour le meilleur 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 <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 device.</span></p></body></html> <span style=" font-weight:600;">Warning: </span><span style=" color:#ff0000;">Les cartes SD des ResMed S9 doivent être verrouillées </span><span style=" font-weight:600; color:#ff0000;">avant d'être insérées dans un ordinateur.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Certains système d'exploitation écrivent sur la carte sans avertissement, ce qui pourrait la rendre illisible pour le PCC</span></p></body></html> Note that some preferences are forced when a ResMed device is detected Certaines préférences sont forcées avec les appareils ResMed 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 today aujourd'hui %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 device was on for %1. L'appareil a fonctionné pendant %1. Your CPAP device used a constant %1 %2 of air L'appareil PPC a utilisé une pression d'air constante de %1 %2 Your device used a constant %1-%2 %3 of air. L'appareil a utilisé une pression d'air constante de %1 %2 %3. Your device was under %1-%2 %3 for %4% of the time. L'appreil était en dessous de %1-%2 %3 pendant %4% du temps. <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 EEPAP pressure was under %1 %2 for %3% of the time. La pression est en dessous de %1 %2 pour %3 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. Welcome to the Open Source CPAP Analysis Reporter Bienvenue dans O.S.C.A.R. (Open Source CPAP Analysis Reporter) Your pressure was under %1 %2 for %3% of the time. La pression a été inférieure à %1 %2 pendant %3% du temps. 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. 1 day ago Il y a 1 jour gGraph Double click Y-axis: Return to AUTO-FIT Scaling Double clic axe des ordonnées : Retour à l'échelle AUTOMATIQUE Double click Y-axis: Return to DEFAULT Scaling Double clic axe des ordonnées : Retour à l'échelle PAR DEFAUT Double click Y-axis: Return to OVERRIDE Scaling Double clic axe des ordonnées : Retour à l'échelle PERSONNALISÉE Double click Y-axis: For Dynamic Scaling Double clic axe des ordonnées : Retour à l'échelle DYNAMIQUE Double click Y-axis: Select DEFAULT Scaling Double clic axe des ordonnées : Sélection de l'échelle PAR DÉFAUT Double click Y-axis: Select AUTO-FIT Scaling Double clic axe des ordonnées : Sélection de l'échelle AUTOMATIQUE %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.5.1/Translations/Greek.el.ts000066400000000000000000017517221450332542600201730ustar00rootroot00000000000000 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. Λυπούμαστε, δεν εντοπίσατε τις Σημειώσεις έκδοσης. 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 Ημερολόγιο διάφορων πράξεων i 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 Κατάργηση σελιδοδείκτη Search Αναζήτηση Layout Save and Restore Graph Layout Settings Show/hide available graphs. Εμφάνιση / απόκρυψη διαθέσιμων γραφημάτων. Breakdown Ανάλυση events Περιστατικα UF1 UF1 UF2 UF2 Time at Pressure Χρόνος στην πίεση Clinical Mode Disabling Sessions requires the Permissive Mode 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.) no data :( Sorry, this device only provides compliance data. This bookmark is in a currently disabled area.. Αυτός ο σελιδοδείκτης βρίσκεται σε μια περιοχή με ειδικές ανάγκες. CPAP Sessions Συνεδρίες CPAP Sleep Stage Sessions Συνεδρίες Στάδιο Ύπνου Position Sensor Sessions Συνεδρίες θέσης αισθητήρα Unknown Session άγνωστη Συνεδρία 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 Χρόνος εκτός ράμπας Start Αρχή End Τέλος Unable to display Pie Chart on this system Δεν είναι δυνατή η εμφάνιση του Πίνακα πίτας σε αυτό το σύστημα "Nothing's here!" "Τίποτα δεν είναι εδώ!" No data is available for this day. Δεν υπάρχουν διαθέσιμα δεδομένα για αυτήν την ημέρα. Oximeter Information πληροφορίες Οξύμετρο Details Λεπτομέριες Click to %1 this session. Κάντε κλικ στο %1 αυτή την περίοδο σύνδεσης. disable καθιστώ ανίκανο enable επιτρέπω %1 Session #%2 %1 Συνεδρίαση #%2 %1h %2m %3s %1ώ %2λ %3δ Device Settings <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 Κατανομή εκδήλωσης This CPAP device does NOT record detailed data 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 Αποθηκεύετε με σελιδοδείκτη στο %1 Hide All Events Απόκρυψη όλων των συμβάντων Show All Events Εμφάνιση όλων των συμβάντων Hide All Graphs Show All Graphs DailySearchTab Clear Bookmark Jumps to Date's Bookmark Start Search DATE Jumps to Date Match Notes Σημειώσεις Notes containing Bookmarks Σελιδοδείκτες Bookmarks containing AHI Daily Duration Session Duration Days Skipped Disabled Sessions Number of Sessions Click HERE to close Help Help Βοήθεια No Data Jumps to Date's Details Number Disabled Session Jumps to Date's Details Note Jumps to Date's Notes AHI Jumps to Date's Details EventsPerHour Session Duration Jumps to Date's Details Minutes Number of Sessions Jumps to Date's Details Sessions Daily Duration Jumps to Date's Details Hours Number of events Jumps to Date's Events Events Automatic start Continue Search End of Search No Matches Skip:%1 %1/%2%3 days %1 ημέρες {1/%2%3 ?} Found %1 Finds days that match specified criteria. Searches from last day to first day. First click on Match Button then select topic. Then click on the operation to modify it. or update the value Topics without operations will automatically start. Compare Operations: numberic or character. Numberic Operations: Character Operations: Summary Line Left:Summary - Number of Day searched Center:Number of Items Found Right:Minimum/Maximum for item searched Result Table Column One: Date of match. Click selects date. Column two: Information. Click selects date. Then Jumps the appropiate tab. Wildcard Pattern Matching: Wildcards use 3 characters: Asterisk Question Mark Backslash. Asterisk matches any number of characters. Question Mark matches a single character. Backslash matches next character. DateErrorDisplay ERROR The start date MUST be before the end date The entered start date %1 is after the end date %2 Hint: Change the end date first The entered end date %1 is before the start date %1 Hint: Change the start date first 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 device 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 Λειτουργία αναφοράς Show Standard Report Standard Πρότυπο Show Monthly Report Monthly Μηνιαίο Show Range Report Date Range Εύρος ημερομηνιών Select Report Date Report Date Statistics Στατιστική Daily Καθημερινά Overview Συνολική εικόνα Oximetry Οξυμετρία Import Εισαγωγή Help Βοήθεια &File &Αρχείο &View &Θέα &Reset Graphs &Επαναφορά γραφημάτων &Help &Βοήθεια Troubleshooting Αντιμετώπιση προβλημάτων &Data &Δεδομένα &Advanced &Προχωρημένος 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 Δεδομένα Standard - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> Advanced - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> Purge Current Selected Day &CPAP &CPAP &Oximetry &Οξυμετρία &Sleep Stage &Position &All except Notes All including &Notes Show &Line Cursor Εμφάνιση δρομέα &γραμμής Purge ALL Device Data 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 Εμφάνιση πίνακα πίτας στην ημερήσια σελίδα 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 Πρόβλημα εισαγωγής Couldn't find any valid Device Data at %1 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 No supported data was found 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. Εάν μπορείτε να διαβάσετε αυτό, η εντολή επανεκκίνησης δεν λειτούργησε. Θα πρέπει να το κάνετε μόνοι σας με το χέρι. No help is available. Δεν υπάρχει βοήθεια. You must select and open the profile you wish to modify %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 Are you sure you want to rebuild all CPAP data for the following device: For some reason, OSCAR does not have any backups for the following device: 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. Επειδή δεν υπάρχουν εσωτερικά αντίγραφα ασφαλείας για την αποκατάσταση, θα πρέπει να αποκαταστήσετε από τη δική σας. Note as a precaution, the backup folder will be left in place. Σημειώστε προληπτικά ότι ο φάκελος δημιουργίας αντιγράφων ασφαλείας θα παραμείνει στη θέση του. OSCAR does not have any backups for this device! Unless you have made <i>your <b>own</b> backups for ALL of your data for this device</i>, <font size=+2>you will lose this device's data <b>permanently</b>!</font> You are about to <font size=+2>obliterate</font> OSCAR's device database for the following device:</p> Are you <b>absolutely sure</b> you want to proceed? Είστε <b> απολύτως σίγουροι </b> που θέλετε να συνεχίσετε; A file permission error caused the purge process to fail; you will have to delete the following folder manually: 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 Ενημερωμένος 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 έχουν απενεργοποιηθεί. Would you like to import from your own backups now? (you will have no data visible for this device until you do) 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 ανοιχτού κώδικα 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>, και δεν συνοδεύεται από καμία εγγύηση και χωρίς αξιώσεις για καταλληλότητα για οποιοδήποτε σκοπό. This software is being designed to assist you in reviewing the data produced by your CPAP Devices and related equipment. 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 Επαναφορά προβολής σε επιλεγμένο εύρος ημερομηνιών Layout Save and Restore Graph Layout Settings 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) Hide All Graphs Show 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> 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. Θέλω να χρησιμοποιήσω το χρόνο που αναφέρθηκε από το ενσωματωμένο ρολόι του οξυγονομέτρου μου. <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 &Σελίδα πληροφοριών 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 I started this oximeter recording at (or near) the same time as a session on my CPAP device. <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 Αγνοήστε σύντομες περιόδους σύνδεσης 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 (πιο αργή εισαγωγή, αλλά μικρότερο αντίγραφο ασφαλείας) This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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>Provide an alert when importing data from any device model that has not yet been tested by OSCAR developers.</p></body></html> Warn when importing data from an untested device &CPAP &CPAP This calculation requires Total Leaks data to be provided by the CPAP device. (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. Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device missed. This option must be enabled before import, otherwise a purge is required. This experimental option attempts to use OSCAR's event flagging system to improve device detected event positioning. Resync Device Detected Events (Experimental) Allow duplicates near device events. Show flags for device detected events that haven't been identified yet. Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. Λάβετε υπόψη τις ημέρες με τη χρήση αυτή ως "μη συμβατές". 4 ώρες συνήθως θεωρούνται συμμορφούμενες. hours ώρες Flow Restriction Περιορισμός ροής Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. Ποσοστό περιορισμού στην ροή αέρα από τη μέση τιμή. Μια τιμή 20% λειτουργεί καλά για την ανίχνευση των άπνοιων. Duration of airflow restriction Διάρκεια περιορισμού ροής αέρα s s Event Duration Διάρκεια συμβάντος 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 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 <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices do not support changing these settings.</p></body></html> 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 μικρότερα, αλλά η αλλαγή ημέρας είναι πιο αργή.) <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> 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 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 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 Clinical Clinical Settings Select Oscar Operating Mode Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. Clinical Mode permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. Permissive Mode Hours Oximetry Settings Ρυθμίσεις οξυμετρίας <!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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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 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 device serial number on device settings changes report Include Serial Number Συμπεριλάβετε τον σειριακό αριθμό Graphics Engine (Requires Restart) Μηχανή γραφικών (απαιτείται επανεκκίνηση) <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></p></body></html> 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> Print reports in black and white, which can be more legible on non-color printers Print reports in black and white (monochrome) For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. Enables SessionBar in Event Flags Graph 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 ΣΦΑΙΡΙΚΗ ΕΙΚΟΝΑ No CPAP devices detected Will you be using a ResMed brand device? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, days will <b>split at noon</b> like in ResMed's commercial software.</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? Απαιτείται μια διαδικασία 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? Μία ή περισσότερες από τις αλλαγές που κάνατε θα απαιτήσουν την επανεκκίνηση αυτής της εφαρμογής, για να τεθούν σε ισχύ αυτές οι αλλαγές. Θα θέλατε να το κάνετε τώρα; ResMed S9 devices routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). 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 Σημαία Clinical Mode: Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. Basically replicates the reports and data stored on the devices data card. This includes pap devices, oximeters, etc. Compliance reports fall under this mode. Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. Permissive Mode: Allows user to select which data sets/ sessions to be used for calculations and display. Additional charts and calculations may be available that are not available from the vendor data. Changing the Oscar Operating Mode: Requires a reload of the user's profile. Data will be saved and restored. Minor Flag Μικρή σημαία Span Σπιθαμή Always Minor Πάντα Μικρά Never Ποτέ This may not be a good idea Αυτό μπορεί να μην είναι μια καλή ιδέα 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 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 Length: %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 Δευτερόλεπτα milliSeconds 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 EEPAP Min EEPAP Max EEPAP 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 Ανακούφιση πίεσης Device No Data Available Δεν υπάρχουν διαθέσιμα δεδομένα App key: Κλειδί εφαρμογής: Operating system: Λειτουργικό σύστημα: Built with Qt %1 on %2 Κατασκευάστηκε με Qt %1 στο %2 Graphics Engine: Μηχανή γραφικών: Graphics Engine type: Γραφικά Τύπος κινητήρα: Compiler: Software Engine Μηχανή Λογισμικού ANGLE / OpenGLES ANGLE / OpenGLES Desktop OpenGL Desktop OpenGL m m cm cm in kg l/min Only Settings and Compliance Data Available Summary Data Only Bookmarks Σελιδοδείκτες Mode Τρόπος Model Μοντέλο Brand Μάρκα Serial Αύξων αριθμός Series Σειρά 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 device's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Non Data Capable Device Your %1 CPAP Device (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 device. Device Untested Your %1 CPAP Device (Model %2) has not been tested yet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Device Unsupported Sorry, your %1 CPAP Device (%2) is not supported yet. The developers need a .zip copy of this device's SD card and matching clinician .pdf reports to make it work with OSCAR. Getting Ready... Ετοιμάζομαι... Scanning Files... Σάρωση αρχείων ... Importing Sessions... Εισαγωγή περιόδων σύνδεσης ... UNKNOWN APAP (std) APAP (dyn) Auto S Auto S/T AcSV SoftPAP Mode Pressure relief during exhalation Slight Softstart pressure Pressure during soft start period PSoft Softstart minimum pressure Minimum pressure during soft start period PSoftMin Auto start Automatically turn on the device by breathing Softstart time Lenght of soft start period Soft start maximum time Maximum lenght of soft start period Soft start max. time Soft start pressure Higher End Expiratory Pressure Humidifier level Tube type Obstruction level Obstruction level in percentage rRMVFluctuation Relative respiratory minute volume fluctuation Relative respiratory minute volume Measured pressure Full flow Artefact Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) Epoch (2 mins) with Flow Limitation Deep Sleep Deep sleep, stable respiration Timed breath BiSoft Mode BiSoft 1 BiSoft 2 TriLevel PMaxOA EEPAPMin Lower End Expiratory Pressure EEPAPMax rRMV ART CriticalLeak Mask leakage is above a critical treshold CL eMO Epoch (2 mins) with Mild Obstruction eSO Epoch (2 mins) with Severe Obstruction eS Epoch (2 mins) with Snoring eFL DS Finishing up... Τελειώνω... 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 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 Κλείδωμα αντίστασης μάσκας A few breaths automatically starts device Device automatically switches off Whether or not device allows Mask checking. 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 Διάρκεια αυτόματης δοκιμής 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: Πιθανώς μεταβλητή αναπνοή, που είναι περιόδους υψηλής απόκλισης από την κορυφαία τάση εισπνοής ροής A period during a session where the device could not detect flow. 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 Auto Off Auto Off Mask Alert Προειδοποίηση μάσκας Show AHI Εμφάνιση AHI Whether or not device shows AHI via built-in display. The number of days in the Auto-CPAP trial period, after which the device will revert to CPAP Breathing Not Detected Η αναπνοή δεν εντοπίστηκε BND BND Timed Breath Χρονική αναπνοή Machine Initiated Breath Μηχανική διέγερση της αναπνοής TB TB Windows User Χρήστης των Windows Using Χρησιμοποιώντας , found SleepyHead - , βρέθηκε SleepyHead - You must run the OSCAR Migration Tool Πρέπει να εκτελέσετε το Εργαλείο μετεγκατάστασης OSCAR 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> <i>Your old device 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. Το OSCAR δεν διαθέτει ακόμη αυτόματα αποθηκευμένα αντίγραφα ασφαλείας για αυτήν τη συσκευή. This means you will need to import this device 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; Device Database Changes Sorry, the purge operation failed, which means this version of OSCAR can't start. Λυπούμαστε, η διαδικασία καθαρισμού απέτυχε, πράγμα που σημαίνει ότι αυτή η έκδοση του OSCAR δεν μπορεί να ξεκινήσει. The device data folder needs to be removed manually. 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 και ολοκληρώστε τη διαδικασία αναβάθμισης. Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. Μόλις αναβαθμίσετε, <font size=+1> δεν μπορείτε </font> να χρησιμοποιήσετε πάλι αυτό το προφίλ με την προηγούμενη έκδοση. 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 Υψηλότερη αναπνευστική πίεση End 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 vibratory snore as detected by a System One device 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 Μια άπνοια όπου ο αεραγωγός είναι ανοιχτός Cheyne Stokes Respiration (CSR) Periodic Breathing (PB) Clear Airway (CA) An apnea caused by airway obstruction Άπνοια προκαλούμενη από απόφραξη των αεραγωγών A partially obstructed airway Ένας μερικώς παρεμποδισμένος αεραγωγός UA UA A vibratory snore Ένα δονητικό ροχαλητό Pressure Pulse Πίεση πίεσης A pulse of pressure 'pinged' to detect a closed airway. Ένας παλμός πίεσης 'pinged' για να ανιχνεύσει έναν κλειστό αεραγωγό. A type of respiratory event that won't respond to a pressure increase. Ένας τύπος αναπνευστικού συμβάντος που δεν ανταποκρίνεται σε αύξηση της πίεσης. Intellipap event where you breathe out your mouth. Event Intellipap όπου εκπνέετε το στόμα σας. SensAwake feature will reduce pressure when waking is detected. Η λειτουργία SensAwake θα μειώσει την πίεση κατά την ανίχνευση ξυπνητηριού. Heart rate in beats per minute Καρδιακός ρυθμός σε παλμούς ανά λεπτό Blood-oxygen saturation percentage Ποσοστό κορεσμού οξυγόνου-οξυγόνου Plethysomogram Πλεισματολογία An optical Photo-plethysomogram showing heart rhythm Ένα οπτικό φωτοφραγματογράφημα που δείχνει καρδιακό ρυθμό A sudden (user definable) change in heart rate Μια ξαφνική (καθορίσιμη από το χρήστη) αλλαγή στον καρδιακό ρυθμό A sudden (user definable) drop in blood oxygen saturation Μια ξαφνική (καθορίσιμη από το χρήστη) πτώση του κορεσμού οξυγόνου αίματος SD SD Breathing flow rate waveform Κοιλιακή μορφή ρυθμού ροής αναπνοής 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 An abnormal period of Cheyne Stokes Respiration Μια ανώμαλη περίοδος αναπνοής του Cheyne Stokes CSR CSR An abnormal period of Periodic Breathing Μία ανώμαλη περίοδος περιοδικής αναπνοής An apnea that couldn't be determined as Central or Obstructive. Μια άπνοια που δεν μπορούσε να προσδιοριστεί ως Κεντρική ή Αποφρακτική. An apnea reported by your CPAP device. A restriction in breathing from normal, causing a flattening of the flow waveform. Ένας περιορισμός στην αναπνοή από το φυσιολογικό, προκαλώντας μια ισοπέδωση της κυματομορφής ροής. 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 Μέγιστη διαρροή 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 Μέσες διαρροές 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. Obstructive Apnea (OA) Hypopnea (H) Unclassified Apnea (UA) Apnea (A) Flow Limitation (FL) RERA (RE) Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. Vibratory Snore (VS) Leak Flag (LF) A large mask leak affecting device performance. Large Leak (LL) Non Responding Event (NR) Expiratory Puff (EP) SensAwake (SA) User Flag #1 (UF1) User Flag #2 (UF2) User Flag #3 (UF3) Pulse Change (PC) SpO2 Drop (SD) I/E Value Apnea Hypopnea Index (AHI) Respiratory Disturbance Index (RDI) 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? Είστε βέβαιοι ότι θέλετε να χρησιμοποιήσετε αυτόν τον φάκελο; OSCAR Reminder Υπενθύμιση OSCAR Don't forget to place your datacard back in your CPAP device 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 oximetry 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) 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) Min EEPAP %1 Max EEPAP %2 PDIFF %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 Προσωπικός προπονητής ύπνου Selection Length 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 Επίπεδο ανακούφισης πίεσης εκπνοής Device auto starts by breathing Response Device auto stops by breathing Patient View RiseEnable RiseTime Cycle Trigger TiMax TiMin Your ResMed CPAP device (Model %1) has not been tested yet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card to make sure it works with OSCAR. SmartStart SmartStart 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 Smart Stop Simple Advanced Προχωρημένος 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... Παρακαλώ περιμένετε... Permissive Mode Total disabled sessions: %1, found in %2 days Total disabled sessions: %1 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. Updating Statistics cache Ενημέρωση της προσωρινής μνήμης στατιστικών στοιχείων Usage Statistics Στατιστικά χρήσης 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 Löwenstein Prisma Smart Resvent/Hoffrichter iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings Add Add Feature inhibited. The maximum number of Items has been exceeded. creates new copy of current settings. Restore Restores saved settings from selection. Rename Renames the selection. Must edit existing name then press enter. Update Updates the selection with current settings. Delete Deletes the selection. Expanded Help menu. Exits the Layout menu. <h4>Help Menu - Manage Layout Settings</h4> Exits the help menu. Exits the dialog menu. This feature manages the saving and restoring of Layout Settings. Layout Settings control the layout of a graph or chart. Different Layouts Settings can be saved and later restored. Button Description Creates a copy of the current Layout Settings. The default description is the current date. The description may be changed. The Add button will be greyed out when maximum number is reached. Other Buttons Greyed out when there are no selections Loads the Layout Settings from the selection. Automatically exits. io Modify the description of the selection. Same as a double click.io Saves the current Layout Settings to the selection. Prompts for confirmation. Deletes the selecton. Control Exit Εξοδος (Red circle with a white "X".) Returns to OSCAR menu. Return Next to Exit icon. Only in Help Menu. Returns to Layout menu. Escape Key Exit the Help or Layout menu. Layout Settings * Name * Pinning * Plots Enabled * Height * Order * Event Flags * Dotted Lines * Height Options General Information Maximum description size = 80 characters. Maximum Saved Layout Settings = 30. Saved Layout Settings can be accessed by all profiles. Layout Settings only control the layout of a graph or chart. They do not contain any other data. They do not control if a graph is displayed or not. Layout Settings for daily and overview are managed independantly. Maximum number of Items exceeded. No Item Selected Ok to Update? Ok To Delete? SessionBar %1h %2m %1h %2m No Sessions Present Δεν υπάρχουν Συνεδρίες SleepStyleLoader Import Error Σφάλμα εισαγωγής This device 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 Device Information Changes to Device Settings Database has No %1 data available. Database has %1 day of %2 Data on %3 Database has %1 days of %2 Data, between %3 and %4 Total Days: %1 Days Not Used: %1 Days Used: %1 Ημέρες που χρησιμοποιήθηκαν: %1 Days %1 %2 Hours: %3 Best Device Setting Worst Device Setting 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 ανοιχτού κώδικα 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 Ρυθμίσεις πίεσης 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 device.</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 device is detected First import can take a few minutes. Η πρώτη εισαγωγή μπορεί να διαρκέσει μερικά λεπτά. The last time you used your %1... Την τελευταία φορά που χρησιμοποιήσατε το %1 σας ... last night την προηγούμενη νύχτα today %2 days ago πριν %2 μέρες was %1 (on %2) ήταν %1 (την %2) %1 hours, %2 minutes and %3 seconds %1 ώρες, %2 λεπτά και %3 δευτερόλεπτα <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 pressure was under %1 %2 for %3% of the time. Η πίεσή σας ήταν κάτω από %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% του χρόνου. 1 day ago χθες Your device was on for %1. Your CPAP device used a constant %1 %2 of air Your device used a constant %1-%2 %3 of air. Your device was under %1-%2 %3 for %4% of the time. Your EEPAP pressure was under %1 %2 for %3% of the time. 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 Double click Y-axis: Return to AUTO-FIT Scaling Double click Y-axis: Return to DEFAULT Scaling Double click Y-axis: Return to OVERRIDE Scaling Double click Y-axis: For Dynamic Scaling Double click Y-axis: Select DEFAULT Scaling Double click Y-axis: Select AUTO-FIT Scaling %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.5.1/Translations/Hebrew.he.ts000066400000000000000000015526261450332542600203500ustar00rootroot00000000000000 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. 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 יומן i 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 משקל גוף משמש לחישוב BMI Search חיפוש Layout Save and Restore Graph Layout Settings Show/hide available graphs. החלף נראות גרפים. Color צבע u B Zombie זומבי Weight משקל Awesome מדהים B.M.I. Bookmarks סימניות Add Bookmark הוסף סימניה Starts מתחיל Remove Bookmark הסר סימניה Breakdown סיווג events אירועים Clinical Mode Disabling Sessions requires the Permissive Mode 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 אין אפשרות להציג גרף עוגה This CPAP device does NOT record detailed data המכשיר לא רושם נתונים מפורטים Sessions all off! כל השימושים מכובים! Sessions exist for this day but are switched off. קיימים שימושים ליום זה אבל הם כובו. Impossibly short session שימוש קצר מדי Zero hours?? אפס שעות?? Complain to your Equipment Provider! התלונן לספק הציוד שלך! Statistics סטטיסטיקה Oximeter Information מידע אוקסימטר SpO2 Desaturations ירידות בריווי חמצן Pulse Change events אירועי שינוי דופק SpO2 Baseline Used נתון בסיס ריווי חמצן UF1 UF2 Time at Pressure Keep chart titles in English so they can be posted to an English forum Session Start Times זמני התחלת שימוש Session End Times זמני סיום שימוש Session Information נתוני שימוש Position Sensor Sessions Unknown Session שימוש כללי Duration משך Click to %1 this session. ללחוץ כדי %1 שימוש זה. disable להסתיר enable לכלול %1 Session #%2 %1 שימוש %2 %1h %2m %3s Device Settings הגדרות המכשיר (Mode and Pressure settings missing; yesterday's shown.) (חסרות הגדרות מצב ולחץ: מראה של אתמול) This bookmark is in a currently disabled area.. סימניה באזור שכרגע מוסתר. Model %1 - %2 מודל %1 - %2 PAP Mode: %1 מצב פאפ: %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 סוף "Nothing's here!" לא קיימים נתונים CPAP Sessions שימושי סיפאפ Oximetry Sessions שימושי אוקסימטריה Sleep Stage Sessions שימושי שלב שינה no data :( חסרים נתונים :( Sorry, this device only provides compliance data. המכשיר לא רושם נתונים מפורטים. No data is available for this day. אין נתונים ליום זה. Pick a Colour בחר צבע Bookmark at %1 סימניה ב %1 Hide All Events Show All Events Hide All Graphs Show All Graphs DailySearchTab Clear Bookmark Jumps to Date's Bookmark Start Search DATE Jumps to Date Match Notes הערות Notes containing Bookmarks סימניות Bookmarks containing AHI Daily Duration Session Duration Days Skipped Disabled Sessions Number of Sessions Click HERE to close Help Help עזרה No Data Jumps to Date's Details Number Disabled Session Jumps to Date's Details Note Jumps to Date's Notes AHI Jumps to Date's Details EventsPerHour Session Duration Jumps to Date's Details Minutes Number of Sessions Jumps to Date's Details Sessions Daily Duration Jumps to Date's Details Hours Number of events Jumps to Date's Events Events Automatic start Continue Search End of Search No Matches Skip:%1 %1/%2%3 days Found %1 Finds days that match specified criteria. Searches from last day to first day. First click on Match Button then select topic. Then click on the operation to modify it. or update the value Topics without operations will automatically start. Compare Operations: numberic or character. Numberic Operations: Character Operations: Summary Line Left:Summary - Number of Day searched Center:Number of Items Found Right:Minimum/Maximum for item searched Result Table Column One: Date of match. Click selects date. Column two: Information. Click selects date. Then Jumps the appropiate tab. Wildcard Pattern Matching: Wildcards use 3 characters: Asterisk Question Mark Backslash. Asterisk matches any number of characters. Question Mark matches a single character. Backslash matches next character. DateErrorDisplay ERROR The start date MUST be before the end date תאריך התחלה חייבת להיות לפני תאריך סיום The entered start date %1 is after the end date %2 תאריך התחלה %1 הוא אחרי תאריך סיום %2 Hint: Change the end date first כדאי קודם לשנות את תאריך הסיום The entered end date %1 תאריך סיום %1 is before the start date %1 לפני תאריך התחלה %1 Hint: Change the start date first כדאי קודם לשנות את תאריך ההתחלה 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 device 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 סוג דוח Show Standard Report Standard רגיל Show Monthly Report Monthly חודשי Show Range Report Date Range תאריכים Select Report Date Report Date Navigation מסכים Profiles פרופילים Statistics סטטיסטיקה Daily יומי Overview מבט על Oximetry אוקסימטריה Import יבוא Help עזרה Records רשומות &Reset Graphs Troubleshooting פתרונות Purge Oximetry Data Purge ALL Device Data Rebuild CPAP Data Exit יציאה Show Daily view מבט יומי Show Overview view מבט על &About OSCAR &אודות OSCAR &Maximize Toggle Maximize window Reset Graph &Heights איפוס &גובה הגרפים Reset sizes of graphs איפוס גודל הגרפים O&ximetry Wizard &Automatic Oximetry Cleanup Standard - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> Advanced - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> 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 הצג גרף עוגה במסך היומי Show Personal Data הצג נתונים אישיים Check For &Updates בדוק האם גירסה &חדשה זמינה Daily Sidebar סרגל צד יומי &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 אשף יצוא CSV 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" %1 (Profile: %2) %1 (פרופיל: %2) Imported %1 CPAP session(s) from %2 נקראו %1 רשומות שימוש מ- %2 Import Success הייבוא הצליח Already up to date with CPAP data at %1 כבר מעודכן בנתונים ב- %1 Up to date מעודכן 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: נתונים מתאימים ל-%1 %2 נמצאו ב: A %1 file structure was located at: נתונים מתאימים ל-%1 נמצאו ב: CPAP Data Located נמצאו נתונים A file permission error caused the purge process to fail; you will have to delete the following folder manually: 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 הסתיים ייבוא הנתונים 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" כשל בשמירת צילום מסך לקובץ "%1" Screenshot saved to file "%1" צילום מסך נשמר לקובץ "%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. 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. Note as a precaution, the backup folder will be left in place. Check for updates not implemented Couldn't find any valid Device Data at %1 אין נתונים תקינים ב-%1 No supported data was found Are you sure you want to rebuild all CPAP data for the following device: For some reason, OSCAR does not have any backups for the following device: Would you like to import from your own backups now? (you will have no data visible for this device until you do) OSCAR does not have any backups for this device! Unless you have made <i>your <b>own</b> backups for ALL of your data for this device</i>, <font size=+2>you will lose this device's data <b>permanently</b>!</font> You are about to <font size=+2>obliterate</font> OSCAR's device database for the following device:</p> Are you <b>absolutely sure</b> you want to proceed? 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. You must select and open the profile you wish to modify %1's Journal היומן של %1 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 OSCAR - תוכנה להצגת נתוני סיפאפ This software is being designed to assist you in reviewing the data produced by your CPAP Devices 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 אפס תחום לטווח התאריכים הנבחר Layout Save and Restore Graph Layout Settings 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) Hide All Graphs Show All Graphs OximeterImport Oximeter Import Wizard Skip this page next time. Where would you like to import from? 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 <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 I started this oximeter recording at (or near) the same time as a session on my CPAP device. 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. <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 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) This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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>Provide an alert when importing data from any device model that has not yet been tested by OSCAR developers.</p></body></html> Warn when importing data from an untested device &CPAP This calculation requires Total Leaks data to be provided by the CPAP device. (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. Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device missed. This option must be enabled before import, otherwise a purge is required. This experimental option attempts to use OSCAR's event flagging system to improve device detected event positioning. Resync Device Detected Events (Experimental) Allow duplicates near device events. Show flags for device detected events that haven't been identified yet. Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. Flow Restriction Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. Duration of airflow restriction s Event Duration 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 <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices do not support changing these settings.</p></body></html> 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-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> <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.) 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 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 שניות 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 <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></p></body></html> l/min <html><head/><body><p>Cumulative Indices</p></body></html> Show in Event Breakdown Piechart General CPAP and Related Settings 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 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 Clinical Clinical Settings Select Oscar Operating Mode Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. Clinical Mode permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. Permissive Mode Hours <!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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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> 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 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> I want to be notified of test versions. (Advanced users only please.) Animations && Fancy Stuff Whether to include device serial number on device settings changes report Print reports in black and white, which can be more legible on non-color printers Print reports in black and white (monochrome) For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. Enables SessionBar in Event Flags Graph Font גופן Size גודל Bold Italic Application כללי Graph Text גרפים Graph Titles כותרות הגרפים Big Text טקסט גדול Details פרטים &Cancel &בטל &Ok Clinical Mode: Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. Basically replicates the reports and data stored on the devices data card. This includes pap devices, oximeters, etc. Compliance reports fall under this mode. Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. Permissive Mode: Allows user to select which data sets/ sessions to be used for calculations and display. Additional charts and calculations may be available that are not available from the vendor data. Changing the Oscar Operating Mode: Requires a reload of the user's profile. Data will be saved and restored. Flag Minor Flag Span Always Minor No CPAP devices detected לא נמצא מכשיר Will you be using a ResMed brand device? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, 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? כדי להחיל את השינויים, התוכנה צריכה לאתחל מחדש. לאתחל עכשיו? ResMed S9 devices routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). 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? This may not be a good idea 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 (Sess: %1) Bedtime: %1 Waketime: %1 (Summary Only) No Data אין נתונים ft רגל lb ליברה oz אונקיה cmH2O Med. Min: %1 Min: Max: Max: %1 %1 (%2 days): %1 (%2 day): % in %1 Hours שעות Min %1 מינימום %1 Length: %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 l/min Error שגיאה Warning אזהרה Only Settings and Compliance Data Available Summary Data Only Min IPAP IPAP מינימלי Max IPAP IPAP מקסימלי Device מכשיר On דלוק Off כבוי BMI App key: Operating system: Built with Qt %1 on %2 Graphics Engine: Graphics Engine type: Compiler: Software Engine ANGLE / OpenGLES Desktop OpenGL m cm in kg Minutes Seconds שניות milliSeconds h m s ms Events/hr Hz 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 EEPAP Min EEPAP Max EEPAP Min EPAP EPAP מינימלי Max EPAP 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 Keep chart titles in English so they can be posted to an English forum Resp. Event אירוע נשימתי Flow Limitation הגבלת זרימה Flow Limit מגבלת זרימה SensAwake Pat. Trig. Breath תבנית נשימה פציינט Tgt. Min. Vent יעד אורור מינימלי Target Vent. יעד אוורור Minute Vent. Keep chart titles in English so they can be posted to an English forum 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 Keep chart titles in English so they can be posted to an English forum 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 Keep chart titles in English so they can be posted to an English forum Snore Keep chart titles in English so they can be posted to an English forum Leak Keep chart titles in English so they can be posted to an English forum Leaks דליפות Large Leak דליפה גדולה LL Total Leaks סה"כ דליפות Unintentional Leaks דליפות לא מכוונות MaskPressure Keep chart titles in English so they can be posted to an English forum Flow Rate Keep chart titles in English so they can be posted to an English forum Sleep Stage Keep chart titles in English so they can be posted to an English forum Usage שימוש Sessions שימושים Pr. Relief הפחתת לחץ Bookmarks סימניות Mode מצב Model מודל Brand יצרן Serial מספר סידורי Series סדרה 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 Keep chart titles in English so they can be posted to an English forum Daily יומי Overview מבט על Oximetry אוקסימטריה Event Flags Keep chart titles in English so they can be posted to an English forum 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> OSCAR does not yet have any automatic card backups stored for this device. 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? Sorry, the purge operation failed, which means this version of OSCAR can't start. <i>Your old device 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 device data again afterwards from your own backups or data card. Device Database Changes The device 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. Selection Length 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 מידע על המכשיר 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) Min EEPAP %1 Max EEPAP %2 PDIFF %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... 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 device's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Non Data Capable Device Your %1 CPAP Device (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 device. Device Untested Your %1 CPAP Device (Model %2) has not been tested yet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Device Unsupported Sorry, your %1 CPAP Device (%2) is not supported yet. The developers need a .zip copy of this device's SD card and matching clinician .pdf reports to make it work with OSCAR. Scanning Files... Importing Sessions... UNKNOWN APAP (std) APAP (dyn) Auto S Auto S/T AcSV SoftPAP Mode Pressure relief during exhalation Slight Softstart pressure Pressure during soft start period PSoft Softstart minimum pressure Minimum pressure during soft start period PSoftMin Auto start Automatically turn on the device by breathing Softstart time Lenght of soft start period Soft start maximum time Maximum lenght of soft start period Soft start max. time Soft start pressure Higher End Expiratory Pressure Humidifier level Tube type Obstruction level Obstruction level in percentage rRMVFluctuation Relative respiratory minute volume fluctuation Relative respiratory minute volume Measured pressure Full flow Artefact Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) Epoch (2 mins) with Flow Limitation Deep Sleep Deep sleep, stable respiration Timed breath BiSoft Mode BiSoft 1 BiSoft 2 TriLevel PMaxOA EEPAPMin Lower End Expiratory Pressure EEPAPMax rRMV ART CriticalLeak Mask leakage is above a critical treshold CL eMO Epoch (2 mins) with Mild Obstruction eSO Epoch (2 mins) with Severe Obstruction eS Epoch (2 mins) with Snoring eFL DS Finishing up... 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 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 A few breaths automatically starts device Device automatically switches off Whether or not device allows Mask checking. 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 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 A period during a session where the device could not detect flow. 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 Off Mask Alert Show AHI Whether or not device shows AHI via built-in display. The number of days in the Auto-CPAP trial period, after which the device will revert to CPAP Breathing Not Detected 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 Device auto starts by breathing Response Device auto stops by breathing Patient View RiseEnable RiseTime Cycle Trigger TiMax TiMin Your ResMed CPAP device (Model %1) has not been tested yet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card to make sure it works with OSCAR. SmartStart 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 Smart Stop Simple Advanced 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 An abnormal period of Cheyne Stokes Respiration CSR An abnormal period of Periodic Breathing An apnea where the airway is open An apnea caused by airway obstruction A partially obstructed airway UA An apnea that couldn't be determined as Central or Obstructive. An apnea reported by your CPAP device. A restriction in breathing from normal, causing a flattening of the flow waveform. A vibratory snore Vibratory Snore (VS2) LF A type of respiratory event that won't respond to a pressure increase. Intellipap event where you breathe out your mouth. SensAwake feature will reduce pressure when waking is detected. A user definable event detected by OSCAR's flow waveform processor. 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 % A sudden (user definable) change in heart rate 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. End Expiratory Pressure A vibratory snore as detected by a System One device 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 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 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 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 Cheyne Stokes Respiration (CSR) Periodic Breathing (PB) Clear Airway (CA) Obstructive Apnea (OA) Hypopnea (H) Unclassified Apnea (UA) Apnea (A) Flow Limitation (FL) RERA (RE) Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. Vibratory Snore (VS) Leak Flag (LF) A large mask leak affecting device performance. Large Leak (LL) Non Responding Event (NR) Expiratory Puff (EP) SensAwake (SA) User Flag #1 (UF1) User Flag #2 (UF2) User Flag #3 (UF3) Pulse Change (PC) SpO2 Drop (SD) I/E Value Apnea Hypopnea Index (AHI) Respiratory Disturbance Index (RDI) 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? OSCAR Reminder Don't forget to place your datacard back in your CPAP device 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 oximetry 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. Permissive Mode Total disabled sessions: %1, found in %2 days Total disabled sessions: %1 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. Updating Statistics cache Usage Statistics 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 Löwenstein Prisma Smart Resvent/Hoffrichter iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings Add Add Feature inhibited. The maximum number of Items has been exceeded. creates new copy of current settings. Restore Restores saved settings from selection. Rename Renames the selection. Must edit existing name then press enter. Update Updates the selection with current settings. Delete Deletes the selection. Expanded Help menu. Exits the Layout menu. <h4>Help Menu - Manage Layout Settings</h4> Exits the help menu. Exits the dialog menu. This feature manages the saving and restoring of Layout Settings. Layout Settings control the layout of a graph or chart. Different Layouts Settings can be saved and later restored. Button Description Creates a copy of the current Layout Settings. The default description is the current date. The description may be changed. The Add button will be greyed out when maximum number is reached. Other Buttons Greyed out when there are no selections Loads the Layout Settings from the selection. Automatically exits. io Modify the description of the selection. Same as a double click.io Saves the current Layout Settings to the selection. Prompts for confirmation. Deletes the selecton. Control Exit יציאה (Red circle with a white "X".) Returns to OSCAR menu. Return Next to Exit icon. Only in Help Menu. Returns to Layout menu. Escape Key Exit the Help or Layout menu. Layout Settings * Name * Pinning * Plots Enabled * Height * Order * Event Flags * Dotted Lines * Height Options General Information Maximum description size = 80 characters. Maximum Saved Layout Settings = 30. Saved Layout Settings can be accessed by all profiles. Layout Settings only control the layout of a graph or chart. They do not contain any other data. They do not control if a graph is displayed or not. Layout Settings for daily and overview are managed independantly. Maximum number of Items exceeded. No Item Selected Ok to Update? Ok To Delete? SessionBar %1h %2m No Sessions Present אין רשומות שימושים SleepStyleLoader Import Error שגיאה ביבוא This device 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 חציון %1 Average %1 ממוצע %1 Min %1 מינימום %1 Max %1 מקסימום %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 First Use שימוש ראשון Last Use שימוש אחרון Days ימים Pressure Relief הפחתת לחץ Pressure Settings הגדרות לחץ Database has No %1 data available. Database has %1 day of %2 Data on %3 Database has %1 days of %2 Data, between %3 and %4 Total Days: %1 Days Not Used: %1 Days %1 %2 Hours: %3 Best Device Setting Worst Device Setting Compliance (%1 hrs/day) OSCAR is free open-source CPAP report software Device Information אודות המכשיר Changes to Device 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 פרטים %1 days of %2 Data, between %3 and %4 %1 ימי נתוני %2, מ-%3 ועד %4 Days Used: %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 OSCAR - תוכנה להצגת נתוני סיפאפ 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 device.</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 device is detected First import can take a few minutes. יבוא ראשון יכול לקחת כמה דקות. The last time you used your %1... השימוש האחרון ב-%1... last night אמש today היום %2 days ago לפני %2 ימים was %1 (on %2) היה %1 (ב-%2) %1 hours, %2 minutes and %3 seconds %1 שעות, %2 דקות, ו %3 שניות <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. ה-AHI היה %1, %2ממוצע של %4 על פני %3 ימים. Your pressure was under %1 %2 for %3% of the time. לחץ האוויר היה מתחת ל-%1 %2 במשך %3% מהזמן. Your EPAP pressure fixed at %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% מהזמן. 1 day ago לפני יום Your device was on for %1. המכשיר פעל במשך %1. Your CPAP device used a constant %1 %2 of air המכשיר השתמש באפן קבוע ב-%1 %2 אוויר Your device used a constant %1-%2 %3 of air. המכשיר השתמש באפן קבוע ב-%2-%1 %3 אוויר. Your device was under %1-%2 %3 for %4% of the time. המכשיר פעל מתחת ל-%2-%1 %3 במשך %4% מהזמן. Your EEPAP pressure was under %1 %2 for %3% of the time. 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. יש לייבא נתונים. gGraph Double click Y-axis: Return to AUTO-FIT Scaling Double click Y-axis: Return to DEFAULT Scaling Double click Y-axis: Return to OVERRIDE Scaling Double click Y-axis: For Dynamic Scaling Double click Y-axis: Select DEFAULT Scaling Double click Y-axis: Select AUTO-FIT Scaling %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.5.1/Translations/Italiano.it.ts000066400000000000000000017505211450332542600207060ustar00rootroot00000000000000 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. 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 i 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 Search Ricerca Layout Formato Save and Restore Graph Layout Settings Salva e ripristina le impostazioni di lformatot grafico Show/hide available graphs. Mostra/nascondi i grafici disponibili. Breakdown Interruzione events eventi UF1 UF1 UF2 UF2 Time at Pressure Tempo pressione Disable Warning Disabilitare Avviso Disabling a session will remove this session data from all graphs, reports and statistics. The Search tab can find disabled sessions Continue ? La disattivazione di una sessione rimuoverà i dati della sessione da tutti i grafici, rapporti e statistiche. La scheda Ricerca può trovare le sessioni disabilitate continuare ? 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 <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 This bookmark is in a currently disabled area.. Questo segnalibro si trova in un'area attualmente disabilitata.. Oximetry Sessions Sessioni di ossimetria Details Dettagli Clinical Mode Modalità clinica Disabling Sessions requires the Permissive Mode La disattivazione delle sessioni richiede la modalità permissiva 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 Device Settings impostazioni del dispositivo 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 no data :( nessun dato :( Sorry, this device only provides compliance data. Siamo spiacenti, questo dispositivo fornisce solo i dati di conformità. Event Breakdown eventi Interruzione Unable to display Pie Chart on this system Impossibile visualizzare il grafico a torta su questo sistema This CPAP device does NOT record detailed data Questo dispositivo CPAP NON registra dati dettagliati 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?? 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 Hide All Events Nascondi tutti gli eventi Show All Events Mostra tutti gli eventi Hide All Graphs Nascondi tutti i grafici Show All Graphs Mostra tutti i grafici DailySearchTab Match: Evento: Select Match Seleszione Evento Clear Cancellare Bookmark Jumps to Date's Bookmark Segnalibro Salta al segnalibro della data Start Search Inizia la ricerca DATE Jumps to Date DATA Salai alla data Match Evento Notes Appunti Notes containing Contenuto Note Bookmarks Segnalibri Bookmarks containing Contenuto segnalibri AHI AHI Daily Duration durata giornaliera Session Duration durata della sessione Days Skipped Giorni saltati Disabled Sessions Sessione disabilitata Number of Sessions Numero di sessioni Click HERE to close Help Clicca QUI per chiudere Aiuto Help Aiuto No Data Jumps to Date's Details No Data Salta ai dettagli della data Number Disabled Session Jumps to Date's Details Numero Sessione disabilitata Salta ai dettagli della data Note Jumps to Date's Notes Note Salta alle note della data Jumps to Date's Bookmark Salta al segnalibro della data AHI Jumps to Date's Details AHI Salta ai dettagli della data EventsPerHour Eventi PerOra Session Duration Jumps to Date's Details Durata della sessione Salta ai dettagli della data Minutes Minuti Number of Sessions Jumps to Date's Details Numero di sessioni Salta ai dettagli della data Sessions Sessioni Daily Duration Jumps to Date's Details Durata giornaliera Salta ai dettagli della data Hours Ore Number of events Jumps to Date's Events Numero di eventi Salta agli eventi della data Events Eventi Automatic start Avvio automatico More to Search Altro da cercare Continue Search Continua Ricerca End of Search Fine della ricerca No Matches Nessuna corrispondenza Skip:%1 Salta:%1 %1/%2%3 days. %1/%2%3 giorni. %1/%2%3 days %1/%2%3 giorni Found %1 Trovato %1 Finds days that match specified criteria. Trova i giorni che corrispondono ai criteri specificati. Searches from last day to first day. Ricerche dall'ultimo giorno al primo giorno. First click on Match Button then select topic. Prima clicca sul pulsante Match e poi seleziona argomento. Then click on the operation to modify it. Quindi fare clic sull'operazione per modificarla. or update the value o aggiornare il valore Topics without operations will automatically start. Gli argomenti senza operazioni si avvieranno automaticamente. Compare Operations: numberic or character. Confronta operazioni: numero o carattere. Numberic Operations: Operazioni numeriche: Character Operations: Operazioni sul carattere: Summary Line Sommario Linea Left:Summary - Number of Day searched Sinistra:Sommario - Numero di giorni cercati Center:Number of Items Found Centro:Numero di articoli trovati Right:Minimum/Maximum for item searched Destra:Minimo/Massimo per l'articolo cercato Result Table Tabella dei risultati Column One: Date of match. Click selects date. Colonna uno: Data della corrispondenza. Fare clic su seleziona la data. Column two: Information. Click selects date. Colonna due: Informazioni. Fare clic su seleziona la data. Then Jumps the appropiate tab. Poi salta alla scheda appropiata. Wildcard Pattern Matching: Corrispondenza di pattern con caratteri jolly: Wildcards use 3 characters: I caratteri jolly usano 3 caratteri: Asterisk Asterisco Question Mark Punto interrogativo Backslash. barra rovesciata. Asterisk matches any number of characters. Asterisco corrisponde a qualsiasi numero di caratteri. Question Mark matches a single character. Il punto interrogativo corrisponde a un singolo carattere. Backslash matches next character. Barra rovesciata corrisponde al prossimo personaggio. Found %1. Trovato %1. DateErrorDisplay ERROR The start date MUST be before the end date ERRORE La data di inizio DEVE essere prima della data di fine The entered start date %1 is after the end date %2 La data di inizio %1 è dopo la data di fine %2 Hint: Change the end date first Suggerimento: cambia prima la data di fine The entered end date %1 data di fine inserita %1 is before the start date %1 è prima della data di inizio %1 Hint: Change the start date first Suggerimento: cambia prima la data di inizio 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 device Record cannot be imported in this profile. 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 Show Standard Report Mostra rapporto standard Standard Normale Show Monthly Report Mostra rapporto mensile Monthly Mensile Show Range Report Visualizza intervallo report Date Range Intervallo di date Select Report Date Seleziona la data del report Report Date Data Report 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 Purge ALL Device Data Eliminare tutti i dati del dispositivo 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 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 &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 Termini &Glossario 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 Standard - CPAP, APAP Standard - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> <html><head/><body><p>Ordine grafico standard, buono per CPAP, APAP, Base BPAP</p></body></html> Advanced - BPAP, ASV Avanzato - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> <html><head/><body><p>Ordine grafico avanzato, buono per BPAP w/ BU, ASV, AVAPS, IVAPSS</p></body></html> 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 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. 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... Would you like to import from your own backups now? (you will have no data visible for this device until you do) Vuoi importare dai tuoi backup ora? (non avrai dati visibili per questo dispositivo fino a quando non lo fai) OSCAR does not have any backups for this device! OSCAR non ha alcun backup per questo dispositivo! Unless you have made <i>your <b>own</b> backups for ALL of your data for this device</i>, <font size=+2>you will lose this device's data <b>permanently</b>!</font> A meno che tu non abbia fatto <i>il tuo <b>proprio</b> backup per tutti i dati per questo dispositivo</i>, <font size=+2>si perderanno i dati di questo dispositivo<b>permanentemente</b>!</font> You are about to <font size=+2>obliterate</font> OSCAR's device database for the following device:</p> stai per <font size=+2>cancellare</font> OSCAR'il database dei dispositivi per questo dispositivo:</p> 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" Couldn't find any valid Device Data at %1 Impossibile trovare i dati del dispositivo validi all'indirizzo %1 Import Reminder Importa promemoria Find your CPAP data card Trova la tua scheda dati CPAP No supported data was found Non sono stati trovati dati supportati 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 Are you sure you want to rebuild all CPAP data for the following device: Sei sicuro di voler ricostruire tutti i dati CPAP per il seguente dispositivo: 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. For some reason, OSCAR does not have any backups for the following device: Per qualche ragione, OSCAR non ha alcun backup per il seguente dispositivo: 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. Note as a precaution, the backup folder will be left in place. Nota come precauzione, la cartella di backup verrà lasciata al suo posto. Are you <b>absolutely sure</b> you want to proceed? Sei assolutamente sicuro <b></b> di voler procedere? A file permission error caused the purge process to fail; you will have to delete the following folder manually: Un errore di autorizzazione del file ha causato il fallimento del processo di purificazione; dovrai eliminare manualmente la seguente cartella: 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. You must select and open the profile you wish to modify È necessario selezionare e aprire il profilo che si desidera modificare %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 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. This software is being designed to assist you in reviewing the data produced by your CPAP Devices and related equipment. Questo software è stato progettato per aiutarti a rivedere i dati prodotti dai tuoi dispositivi CPAP e le relative apparecchiature. 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 Layout Formato Save and Restore Graph Layout Settings Salva e ripristina le impostazioni di lformatot 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) Hide All Graphs Nascondi tutti i grafici Show All Graphs Mostra 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? 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> CMS50 dati importati da SpO2Review (da .Spor file) o il metodo di importazione seriale fare not avere il timestamp corretto necessario per la sincronizzazione. CMS50 dati importati da SpO2Review (da .Spor file) o il metodo di importazione seriale fare not avere il timestamp corretto necessario per la sincronizzazione. CMS50 dati importati da SpO2Review (da .Spor file) o il metodo di importazione seriale fare not avere il timestamp corretto necessario per la sincronizzazione. <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 I started this oximeter recording at (or near) the same time as a session on my CPAP device. Ho iniziato questa registrazione ossimetro (o vicino) allo stesso tempo di una sessione sul mio dispositivo CPAP. <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 <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. <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 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) This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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) Questo mantiene un backup dei dati della scheda SD per i dispositivi ResMed, I dispositivi della serie ResMed S9 eliminano i dati ad alta risoluzione più vecchi di 7 giorni, e dati grafici più vecchi di 30 giorni OSCAR può mantenere una copia di questi dati se avete bisogno di reinstallare (Altamente recommandato, a meno che il vostro spazio su disco non basti o non si interessano i dati del grafico) 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 dd MMMM yyyy 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 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. Custom CPAP User Event Flagging Contrassegno evento utente CPAP personalizzato s s Flow Restriction Limitazione del flusso 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 Event Duration Durata dell'evento General CPAP and Related Settings CPAP generale e impostazioni correlate 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 <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Le sessioni di durata inferiore non verranno visualizzate</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> <html><head/><body><p>Provide an alert when importing data from any device model that has not yet been tested by OSCAR developers.</p></body></html> <html><head/><body><p>Fornire un avviso durante l'importazione di dati da qualsiasi modello di dispositivo che non è ancora stato testato dagli sviluppatori OSCAR.</p></body></html> Warn when importing data from an untested device Avvisa quando si importano dati da un dispositivo non testato This calculation requires Total Leaks data to be provided by the CPAP device. (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 delle perdite totali siano forniti dal dispositivo CPAP. (Ad esempio, PRS1, ma non ResMed, che li ha già) I calcoli di perdita involontaria utilizzati qui sono lineari, non modellano la curva di sfiato della maschera. Se usi alcune maschere diverse, scegli invece i valori medi. Dovrebbe comunque essere abbastanza vicino. Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device missed. This option must be enabled before import, otherwise a purge is required. Abilita/disabilita i miglioramenti di segnalazione degli eventi sperimentali. Consente di rilevare eventi marginali, e quelli che il dispositivo a perso. Questa opzione deve essere abilitata prima dell'importazione, altrimenti è necessario purificare. This experimental option attempts to use OSCAR's event flagging system to improve device detected event positioning. Questa opzione sperimentale tenta di utilizzare il sistema di segnalazione degli eventi di OSCAR per migliorare il posizionamento degli eventi rilevato dal dispositivo. Resync Device Detected Events (Experimental) Eventi rilevati dal dispositivo di risincronizzazione (sperimentale) Allow duplicates near device events. Consenti duplicati vicino agli eventi del dispositivo. Show flags for device detected events that haven't been identified yet. Mostra i segnali per gli eventi rilevati dal dispositivo che non sono ancora stati identificati. 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) <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 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 &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 Clinical Modalità Clinica Clinical Settings impostazioni cliniche Select Oscar Operating Mode Selezionare la modalità operativa Oscar Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. La modalità clinica non consente le sessioni disabilitate. La sessione non viene utilizzata per la grafica o le statistiche. Clinical Mode Modalità clinica permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. La modalità permissiva consente di disabilitare le sessioni. Le sessioni libere vengono utilizzate per la grafica e le statistiche. Permissive Mode Si tratta di una modalità di trattamento con CPAP che consente un livello di pressione più basso rispetto alla modalità standard. Questa modalità può essere utile per le persone che hanno difficoltà ad adattarsi alla CPAP standard o che hanno altre condizioni mediche che richiedono una pressione più bassa. Modalità permissiva Hours orario <!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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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 nome="qrichtext" contenuto="1" /><style tipo="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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 dell'ossimetria e dei dati di CPAP</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">I dati CMS50 importati da SpO2Review (da file .spoR) o dal 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;"> hanno il timestamp corretto necessario per la sincronizzazione. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">La modalità di visualizzazione live (usando un cavo seriale) è un modo per ottenere una sincronizzazione accurata sugli ossimetri CMS50, ma non contrasta la deriva dell'orologio CPAP. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;"Se si avvia la modalità di registrazione Oximeters a </span>span<span style="font-family:'Sans'; font-size:10pt; font-style:italic;">exactly</span><span style=" font-family:'Sans'; font-size:10pt;">lo stesso momento in cui avvii il tuo dispositivo CPAP, ora puoi anche ottenere la sincronizzazione. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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 il tempo di inizio dalle ultime notti prima sessione CPAP. (Ricordati di importare i tuoi dati CPAP prima!) </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;"> 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 device serial number on device settings changes report Indica se includere il numero di serie del dispositivo nelle impostazioni del dispositivo 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 Oximetry Settings Impostazioni ossimetria <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:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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><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:12px; margin-bottom:12px; 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 di ossimetria e dati CPAP</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 dati importati da SpO2Review (da .Spor file) o il metodo di importazione seriale </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;"> avere il data e ora corretta necessaria per la sincronizzazione.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">La modalità di visualizzazione dal vivo (utilizzando un cavo seriale) è un modo per ottenere una sincronizzazione accurata sugli ossimetri CMS50, ma non contrasta o spostamento dell'orologio CPAP.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Si avvia la modalità di registrazione Oximeters a </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exactly </span><span style=" font-family:'Sans'; font-size:10pt;">nello stesso momento in cui avvii il tuo dispositivo CPAP, ora puoi anche ottenere la sincronizzazione. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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 il tempo di partenza dalle ultime notti prima sessione CPAP. (Ricordarsi di importare i dati CPAP prima!)</span></p></body></html> 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) <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">La segnalazione personalizzata è un metodo sperimentale per rilevare gli eventi persi dal dispositivo. Sono </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> inclusi in AHI.</span></p></body></html> 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><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices do not support changing these settings.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note: </span>A causa delle limitazioni di progettazione sommaria, i dispositivi ResMed non supportano la modifica di queste impostazioni.</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> 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) For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. Per più sessioni, visualizza una sottile linea grigia per ogni sessione nella parte superiore del grafico Bandiera evento. Enables SessionBar in Event Flags Graph Abilita SessionBar nel grafico dei flag di evento 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 Never Mai ResMed S9 devices routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). ResMed S9 dispositivi di routine eliminare alcuni dati dalla scheda SD più vecchio di 7 e 30 giorni (a seconda della risoluzione). Name Nome Clinical Mode: Modalità clinica: Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. Riporta ciò che è sulla scheda dati, tutto compreso tutti i dati deselezionati in modalità Permissiva. Basically replicates the reports and data stored on the devices data card. Fondamentalmente replica i rapporti ei dati memorizzati sulla scheda di dati dispositivi. This includes pap devices, oximeters, etc. Compliance reports fall under this mode. Ciò include dispositivi pap, ossimetri, ecc. I rapporti di conformità rientrano in questa modalità. Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. I report di conformità includono sempre tutti i dati entro il periodo di conformità scelto, anche se altrimenti deselezionati. Permissive Mode: Modalità permissiva: Allows user to select which data sets/ sessions to be used for calculations and display. Consente all'utente di selezionare quali set di dati/ sessioni da utilizzare per i calcoli e la visualizzazione. Additional charts and calculations may be available that are not available from the vendor data. Ulteriori grafici e calcoli possono essere disponibili che non sono disponibili dai dati del fornitore. Changing the Oscar Operating Mode: Modifica della modalità operativa Oscar: Requires a reload of the user's profile. Data will be saved and restored. Richiede un ricaricamento del profilo dell'utente. I dati saranno salvati e ripristinati. No CPAP devices detected Nessun dispositivo CPAP rilevato Will you be using a ResMed brand device? Userete un dispositivo di marca ResMed? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, days will <b>split at noon</b> like in ResMed's commercial software.</p> <p><b>Please Note:</b> OSCAR'le funzionalità avanzate di suddivisione della sessione non sono possibili con <b>ResMed</b> dispositivi a causa di una limitazione nel modo in cui le loro impostazioni e dati di riepilogo sono memorizzati, e quindi sono stati disabilitati per questo profilo.</p><p>Sui dispositivi ResMed, i giorni saranno <b>divisi a mezzogiorno </b>come nel software commerciale di ResMed.</p> Color Colore Overview Panoramica 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 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 Selection Length Lunghezza della selezione 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 Length: %1 Lunghezza: %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 cmH2O cmH2O Minutes Minuti Seconds secondi h h m m s s ms ms Events/hr Eventi / hr Hz Hz bpm bpm 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 Compiler: Compilatore: in in kg milliSeconds millisecondi l/min 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 EEPAP EEPAP Min EEPAP Min EEPAP". Si riferisce all'impostazione minima per la minima pressione positiva espiratoria delle vie aeree. Min EEPAP Max EEPAP Max EEPAP 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 Device dispositivo 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 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) Min EEPAP %1 Max EEPAP %2 PDIFF %3-%4 (%5) EEPAP minimo %1 EEPAP massimo %2 PDIFF %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 device's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. I dati importati potrebbero non essere del tutto precisi, quindi gli sviluppatori vorrebbero una copia . zip della scheda SD di questo dispositivo e corrispondente clinico . pdf report per assicurarsi che OSCAR sta gestendo i dati correttamente. Non Data Capable Device Dispositivo non compatibile con i dati Your %1 CPAP Device (Model %2) is unfortunately not a data capable model. Il tuo dispositivo CPAP %1 (modello %2) purtroppo non è un modello compatibile con i dati. I'm sorry to report that OSCAR can only track hours of use and very basic settings for this device. Mi dispiace segnalare che OSCAR può solo tenere traccia delle ore di utilizzo e delle impostazioni di base per questo dispositivo. Device Untested Dispositivo Non testato Your %1 CPAP Device (Model %2) has not been tested yet. Il tuo dispositivo CPAP %1 (modello %2) non è stato ancora testato. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Sembra abbastanza simile ad altri dispositivi che potrebbe funzionare, ma gli sviluppatori vorrebbero un . copia zip della scheda SD di questo dispositivo e corrispondenti clinico . rapporti pdf per assicurarsi che funziona con OSCAR. Device Unsupported Dispositivo Non supportato Sorry, your %1 CPAP Device (%2) is not supported yet. Spiacenti, il tuo dispositivo CPAP %1 (%2) non è ancora supportato. The developers need a .zip copy of this device's SD card and matching clinician .pdf reports to make it work with OSCAR. Gli sviluppatori hanno bisogno di una copia . zip della scheda SD di questo dispositivo e corrispondenti clinico . rapporti pdf per farlo funzionare con OSCAR. Scanning Files... Scansione dei file ... Importing Sessions... Importazione di sessioni ... UNKNOWN SCONOSCIUTO APAP (std) APAP (std) APAP (dyn) APAP (dyn) Auto S Auto S Auto S/T Auto S/T AcSV SoftPAP Mode SoftPAP Modo Pressure relief during exhalation Sollievo dalla pressione durante l'espirazione Slight Moderato Softstart pressure Pressione del avvio graduale Pressure during soft start period Pressione durante il periodo diavvio graduale PSoft PSoft Softstart minimum pressure avvio graduale pressione minima Minimum pressure during soft start period Pressione minima durante il periodo di avvio graduale PSoftMin PSoft Minimo Auto start avvio automatico Automatically turn on the device by breathing Accendere automaticamente il dispositivo respirando Softstart time Ora del avvio graduale Lenght of soft start period Durata del periodo del avvio graduale Soft start maximum time Tempo massimo di avvio graduale Maximum lenght of soft start period Durata massima del periodo del avvio graduale Soft start max. time avvio graduale tempo max Soft start pressure Pressione di avvio graduale Higher End Expiratory Pressure Pressione espiratoria di fascia alta Humidifier level Livello dell'umidificatore Tube type tipo di tubo Obstruction level Livello di ostruzione Obstruction level in percentage Livello di ostruzione in percentuale rRMVFluctuation rRMVFluttuazione Relative respiratory minute volume fluctuation Fluttuazione del volume del minuto respiratorio relativo Relative respiratory minute volume Volume relativo del minuto respiratorio Measured pressure pressione misurata Full flow flusso totale Artefact artefatto Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) Irregolarità nei dati misurati, che non rappresenta un evento di respirazione (ad esempio deglutizione, tosse, o parlare) Epoch (2 mins) with Flow Limitation periodo (2 min) con limitazione di flusso Deep Sleep sonno profondo Deep sleep, stable respiration Sonno profondo, respirazione stabile Timed breath Il respiro a tempo è una tecnica di respirazione che prevede l'inspirazione e l'espirazione per un determinato periodo di tempo. Questo può essere fatto contando il numero di secondi inspirati ed espirati o usando un timer. Il respiro programmato può essere un modo utile per rallentare la respirazione e rilassare il corpo. Respiro temporizzato BiSoft Mode Modalità BiSoft BiSoft 1 BiSoft 1 BiSoft 2 BiSoft 2 TriLevel TriLevel è un tipo di terapia a pressione positiva continua delle vie aeree (CPAP) che utilizza tre diverse impostazioni di pressione: Ispirazione: l'impostazione della pressione utilizzata durante l'inspirazione. Espirazione: l'impostazione della pressione utilizzata durante l'espirazione. Titolazione automatica: l'impostazione della pressione che si regola automaticamente tra inspirazione ed espirazione. TriLevel AutoStart Avvio Automatico Softstart_Time Softstart_Tempo Softstart_TimeMax Softstart_Tempo Massimo Softstart_Pressure Softstart_Pressione PMaxOA PMaxOA EEPAPMin EEPAPMin Lower End Expiratory Pressure Pressione espiratoria inferiore EEPAPMax EEPAPMax HumidifierLevel Livello Umidità TubeType Tipo di Tubo ObstructLevel Livello Ostruzioni Obstruction Level Livello di ostruzioni rMVFluctuation rmv Fluttuazione rRMV rRMV PressureMeasured Pressione misurata FlowFull Fondo Scala SPRStatus SPR Stato Artifact Reperto ART ART CriticalLeak Perdita Critica Mask leakage is above a critical treshold perdita della maschera è al di sopra di una soglia critica CL CL eMO eMO Epoch (2 mins) with Mild Obstruction tempo (2 min) con lieve ostruzione eSO eSO Epoch (2 mins) with Severe Obstruction tempo (2 min) con grave ostruzione eS eS Epoch (2 mins) with Snoring tempo (2 min) con russamento eFL eFL DeepSleep Sonno profondo DS DS TimedBreath Tempo Respirazione Finishing up... Terminando... 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 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 A few breaths automatically starts device Con un paio di respiri avvii automaticamente il dispositivo Device automatically switches off Dispositivo si spegne automaticamente Whether or not device allows Mask checking. Indica se il dispositivo consente o meno il controllo della maschera. 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 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 A period during a session where the device could not detect flow. Periodo durante una sessione in cui il dispositivo non è stato in grado di rilevare il flusso. 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à Umido 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 Auto Off Auto Off Mask Alert Avviso Maschera Show AHI Mostra AHI Whether or not device shows AHI via built-in display. Indica se il dispositivo mostra o meno AHI tramite display integrato. The number of days in the Auto-CPAP trial period, after which the device will revert to CPAP Il numero di giorni nel periodo di prova Auto-CPAP, dopo di che il dispositivo tornerà a CPAP Breathing Not Detected Respirazione Non Rilevata 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 Device auto starts by breathing Avvio automatico del dispositivo tramite respirazione Response Risposta Device auto stops by breathing Dispositivo si ferma automaticamente Patient View Vista Paziente RiseEnable RiseEnable è un'azienda che fornisce soluzioni per la terapia dell'apnea notturna. Offrono una varietà di prodotti, tra cui macchine CPAP, maschere e accessori. RiseEnable offre anche un programma di coaching del sonno che può aiutarti a ottenere il massimo dalla tua terapia. RiseEnable RiseTime RiseTime è una funzione su alcune macchine CPAP che ti aiuta a svegliarti gradualmente. Lo fa aumentando gradualmente la pressione dell'aria che viene erogata alla maschera per un periodo di tempo. Questo può aiutare a ridurre lo stordimento e rendere più facile svegliarsi sentendosi riposati. RiseTime Cycle ciclo Trigger Nel contesto della terapia CPAP, un trigger è un segnale che indica alla macchina CPAP di iniziare a erogare aria. Esistono due tipi principali di trigger trigger TiMax Timax TiMin TiMin Your ResMed CPAP device (Model %1) has not been tested yet. Il tuo dispositivo ResMed CPAP (Modello %1) non è stato ancora testato. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card to make sure it works with OSCAR. Sembra abbastanza simile ad altri dispositivi che potrebbe funzionare, ma gli sviluppatori vorrebbero un . copia zip della scheda SD di questo dispositivo per assicurarsi che funzioni con OSCAR. SmartStart SmartStart 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 Smart Stop Arresto Intelligente Simple Semplice Advanced Avanzato 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. <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 <i>Your old device data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> <i>I tuoi vecchi dati del dispositivo dovrebbero essere rigenerati a condizione che questa funzione di backup non sia stata disabilitata nelle preferenze durante una precedente importazione di dati.</i> 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 device data again afterwards from your own backups or data card. Ciò significa che sarà necessario importare nuovamente i dati del dispositivo in seguito ai propri backup o 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? Device Database Changes Modifiche al database dei dispositivi 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. The device data folder needs to be removed manually. La cartella dei dati del dispositivo deve essere rimossa manualmente. 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. 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. 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 An abnormal period of Cheyne Stokes Respiration Un periodo anormale di Cheyne Stokes Respiration CSR CSR An abnormal period of Periodic Breathing Un periodo anormale di respirazione periodica An apnea where the airway is open Un'apnea in cui le vie aeree sono aperte An apnea caused by airway obstruction Un'apnea causata da ostruzione delle vie aeree A partially obstructed airway Una via aerea parzialmente ostruita UA ANC An apnea that couldn't be determined as Central or Obstructive. Un'apnea che non può essere determinata come centrale o ostruttiva. An apnea reported by your CPAP device. Un'apnea segnalata dal tuo dispositivo CPAP. 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. A vibratory snore Un russare vibratorio Vibratory Snore (VS2) Russamento Vibratorio (VS2) LF LF 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. 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. 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. 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% A sudden (user definable) change in heart rate Un improvviso (definibile dall'utente) cambiamento nella frequenza cardiaca (eventi per ora) 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. End Expiratory Pressure Fine pressione espiratoria Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. Stimolazione respiratoria correlata allo sforzo: una restrizione della respirazione che provoca il risveglio o disturbi del sonno. A vibratory snore as detected by a System One device Rilevato russamento da un dispositivo System One 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 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 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 Cheyne Stokes Respiration (CSR) Il respiro periodico e di Cheyne-Stokes è una particolare forma patologica di respiro caratterizzata dall'alternanza ciclica di periodi di apnea, interruzione del respiro, e di iperpnea, cioè l'aumento del volume d'aria introdotto nei polmoni a ogni respiro e della frequenza respiratoria Cheyne Stokes Respirazione (CSR) Periodic Breathing (PB) Respirazione periodica (PB) Clear Airway (CA) Vie aeree libere (CA) Obstructive Apnea (OA) Apnea ostruttiva (OA) Hypopnea (H) Ipopnea (H) Unclassified Apnea (UA) Apnea non classificata (UA) Apnea (A) Apnea (A) An apnea reportred by your CPAP device. Un'apnea segnalata dal tuo dispositivo CPAP. Flow Limitation (FL) Limitazione del flusso (FL) RERA (RE) Il Respiratory Effort Related Arous al (RERA) è una limitazione del flusso respiratorio con progressivo incremento dello s forzo respiratorio e contestuale arousal a livello elettroencefalografico. L'evento respiratorio (apnea ed ipoapnea) deve avere durata > 10 s ec. RERA (RE) Vibratory Snore (VS) Russare vibratorio (VS) Leak Flag (LF) Indicatore di perdita (LF) A large mask leak affecting device performance. Una grande perdita maschera che influenzano le prestazioni del dispositivo. Large Leak (LL) Grande perdita (LL) Non Responding Event (NR) Evento non corispondente (NR) Expiratory Puff (EP) Flusso Espiratorio (EP) SensAwake (SA) SensAwake sollievo dalla pressione reattiva per fornire un sonno migliore in generale. Regolazione automatica della pressione per un trattamento personalizzato durante il sonno SensAwake (SA) User Flag #1 (UF1) Flag utente#1 (UF1) User Flag #2 (UF2) Flag utente#1 (UF2) User Flag #3 (UF3) Flag utente#1 (UF3) Pulse Change (PC) Cambio di impulso (PC) SpO2 Drop (SD) Calo SpO2 (SD) I/E Value rapporto inspiratorio-espiratorio valore I/E Apnea Hypopnea Index (AHI) Indice di apnea ipopnea (AHI) Respiratory Disturbance Index (RDI) Indice di disturbi respiratori (RDI) 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? OSCAR Reminder Promemoria OSCAR Don't forget to place your datacard back in your CPAP device Non dimenticare di rimettere la tua SD card nel tuo dispositivo CPAP 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 oximetry settings to defaults? Sei sicuro di voler reimpostare tutte le impostazioni di ossimetria ai valori predefiniti? 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... Permissive Mode modalità permissiva Total disabled sessions: %1, found in %2 days Totale delle sessioni disabilitate: %1, trovato in %2 giorni Total disabled sessions: %1 Totale sessioni disabilitate: %1 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. Updating Statistics cache Aggiornamento della cache delle statistiche Usage Statistics Statistiche di utilizzo 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 Löwenstein Löwenstein Prisma Smart Prisma Smart Resvent/Hoffrichter Resvent e Hoffrichter sono entrambe aziende che producono macchine e maschere CPAP. Resvent è una società tedesca, mentre Hoffrichter è una società svizzera. Entrambe le società offrono una varietà di prodotti, tra cui macchine per la titolazione automatica, umidificatori e maschere. Resvent/Hoffrichter iBreeze/Point3 iBreeze e Point3 sono entrambi marchi di macchine CPAP. Sono entrambi prodotti da ResMed, un'azienda specializzata in prodotti per la terapia dell'apnea notturna. iBreeze è una macchina più semplice, mentre Point3 è una macchina più avanzata. iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings Imposta layout: apre la finestra di impostazione del layout Add Aggiungere Add Feature inhibited. The maximum number of Items has been exceeded. Aggiungi funzionalità inibita. Il numero massimo di elementi è stato superato. creates new copy of current settings. crea una nuova copia delle impostazioni correnti. Restore Ripristinare Restores saved settings from selection. Ripristina le impostazioni salvate dalla selezione. Rename Rinominare Renames the selection. Must edit existing name then press enter. Rinomina la selezione. Deve modificare il nome esistente e premere invio. Update Aggiornamento Updates the selection with current settings. Aggiorna la selezione con le impostazioni correnti. Delete Eliminare Deletes the selection. Cancella la selezione. Expanded Help menu. Ampliare Menu Aiuto. Exits the Layout menu. Esce dal menu Layout. <h4>Help Menu - Manage Layout Settings</h4> <h4>Menu Aiuto - Gestione delle impostazioni di layout</h4> Exits the help menu. Esce dal menu di aiuto. Exits the dialog menu. Esce dal menu di dialogo. This feature manages the saving and restoring of Layout Settings. Questa funzione gestisce il salvataggio e il ripristino delle impostazioni di layout. Layout Settings control the layout of a graph or chart. Le impostazioni di layout controllano il layout di un grafico o di un grafico. Different Layouts Settings can be saved and later restored. Diverse impostazioni di layout possono essere salvate e successivamente ripristinate. Button pulsante Description descrizione Creates a copy of the current Layout Settings. Crea una copia delle impostazioni di layout correnti. The default description is the current date. La descrizione predefinita è la data corrente. The description may be changed. La descrizione può essere modificata. The Add button will be greyed out when maximum number is reached. Il pulsante Aggiungi verrà visualizzato in grigio quando viene raggiunto il numero massimo. Other Buttons altri pulsanti Greyed out when there are no selections Visualizzato in grigio quando non ci sono selezioni Loads the Layout Settings from the selection. Automatically exits. io Carica le impostazioni di layout dalla selezione. Esce automaticamente. io Modify the description of the selection. Same as a double click.io Modifica la descrizione della selezione. Come un doppio click.io Saves the current Layout Settings to the selection. Salva le impostazioni di layout correnti nella selezione. Prompts for confirmation. Richieste di conferma. Deletes the selecton. Cancella la selezione. Control controllo Exit Uscita (Red circle with a white "X".) Returns to OSCAR menu. (Cerchio rosso con una "X" bianca.) Ritorna al menu OSCAR. Return Ritorna Next to Exit icon. Only in Help Menu. Returns to Layout menu. Accanto all'icona Esci. Solo nel menu Aiuto. Ritorna al menu Layout. Escape Key tasto Esc Exit the Help or Layout menu. Uscire dal menu Aiuto o Layout. Layout Settings impostazioni di layout * Name * Nome * Pinning Il pinning è l'atto di attaccare qualcosa a una superficie in modo che non possa essere facilmente spostato. Nel contesto dei computer, il blocco si riferisce all'atto di allegare un file, una cartella o un'applicazione al menu Start o alla barra delle applicazioni in modo da potervi accedere facilmente. * attaccare * Plots Enabled * Grafici abilitati * Height * Altezza * Order * Ordine * Event Flags * Bandiere di eventi * Dotted Lines * Linee tratteggiate * Height Options * Opzioni di altezza General Information informazioni generali Maximum description size = 80 characters. Dimensione massima descrizione = 80 caratteri.. Maximum Saved Layout Settings = 30. Impostazioni di layout salvate massime = 30. Saved Layout Settings can be accessed by all profiles. Le impostazioni di layout salvate sono accessibili da tutti i profili. Layout Settings only control the layout of a graph or chart. Le impostazioni di layout controllano solo il layout di un grafico o di un grafico. They do not contain any other data. Non contengono altri dati. They do not control if a graph is displayed or not. Non controllano se un grafico viene visualizzato o meno. Layout Settings for daily and overview are managed independantly. Le impostazioni di layout per il quotidiano e la panoramica sono gestite in modo indipendente. <p style="color:black;"> This feature manages the saving and restoring of Layout Settings. <br> Layout Settings control the layout of a graph or chart. <br> Different Layouts Settings can be saved and later restored. <br> </p> <table width="100%"> <tr><td><b>Button</b></td> <td><b>Description</b></td></tr> <tr><td valign="top">Add</td> <td>Creates a copy of the current Layout Settings. <br> The default description is the current date. <br> The description may be changed. <br> The Add button will be greyed out when maximum number is reached.</td></tr> <br> <tr><td><i><u>Other Buttons</u> </i></td> <td>Greyed out when there are no selections</td></tr> <tr><td>Restore</td> <td>Loads the Layout Settings from the selection. Automatically exits. </td></tr> <tr><td>Rename </td> <td>Modify the description of the selection. Same as a double click.</td></tr> <tr><td valign="top">Update</td><td> Saves the current Layout Settings to the selection.<br> Prompts for confirmation.</td></tr> <tr><td valign="top">Delete</td> <td>Deletes the selecton. <br> Prompts for confirmation.</td></tr> <tr><td><i><u>Control</u> </i></td> <td></td></tr> <tr><td>Exit </td> <td>(Red circle with a white "X".) Returns to OSCAR menu.</td></tr> <tr><td>Return</td> <td>Next to Exit icon. Only in Help Menu. Returns to Layout menu.</td></tr> <tr><td>Escape Key</td> <td>Exit the Help or Layout menu.</td></tr> </table> <p><b>Layout Settings</b></p> <table width="100%"> <tr> <td>* Name</td> <td>* Pinning</td> <td>* Plots Enabled </td> <td>* Height</td> </tr> <tr> <td>* Order</td> <td>* Event Flags</td> <td>* Dotted Lines</td> <td>* Height Options</td> </tr> </table> <p><b>General Information</b></p> <ul style=margin-left="20"; > <li> Maximum description size = 80 characters. </li> <li> Maximum Saved Layout Settings = 30. </li> <li> Saved Layout Settings can be accessed by all profiles. <li> Layout Settings only control the layout of a graph or chart. <br> They do not contain any other data. <br> They do not control if a graph is displayed or not. </li> <li> Layout Settings for daily and overview are managed independantly. </li> </ul> <p style="color:black;"> <p style="color:black;"> Questa funzione gestisce il salvataggio e il ripristino delle impostazioni di layout. <br> Le impostazioni di layout controllano il layout di un grafico o di un grafico. <br> Diverse impostazioni di layout possono essere salvate e successivamente ripristinate. <br> </p> <larghezza della tabella="100%"> <tr><td><b>Pulsante</b></td> <td><b>Descrizione</b></td><tr><tr><td valign="top">Aggiungi</td> <td>Crea una copia della corrente .... <br> Layout Le impostazioni controllano il layout di un grafico o di un grafico. <br> Diverse impostazioni di layout possono essere salvate e successivamente ripristinate. <br> </p> <table larghezza="100%"> <tr><td><b>Bottone</b></td> <td><b>Descrizione</b></td></tr> <tr><td valign="top">Add</td> <td>Crea una copia delle impostazioni di layout correnti. <br> La descrizione predefinita è la data corrente. <br> La descrizione può essere modificata. <br> Il pulsante Aggiungi sarà in grigio quando viene raggiunto il numero massimo.</td></tr> <br> <tr><td><i><u>Altri pulsanti</u> </i></td> <td>In grigio quando non ci sono selezioni</td></tr> <tr><td>Restore</td> <td>Carica le impostazioni di layout dalla selezione. Esci automaticamentes. </td></tr> <tr><td>Rinominare </td> <td>Modifica la descrizione della selezione. Come un doppio clic.</td></tr> <tr><td valign="top">Aggiorna</td><td> Salva le impostazioni di layout correnti nella selezione.<br> Richieste di conferma.</td></tr> <tr><td valign="top">Cancella</td> <td>Elimina la selezione. <br> Richiesta di conferma.</td></tr> <tr><td><i><u>Control</u> </i></td> <td></td></tr> <tr><td>Exit </td> <td>(Cerchio rosso con una "X" bianca.) Ritorna al menu OSCAR.</td></tr> <tr><td>Return</td> <td>Accanto all'icona Esci. Solo nel menu Aiuto. Ritorna al menu Layout.</td></tr> <tr><td>tasto Esc</td> <td>Uscire dal menu Aiuto o Layout.</td></tr> </table> <p><b>impostazioni di layout</b></p> <table width="100%"> <tr> <td>* Name</td> <td>* Pinning</td> <td>* Plots Enabled </td> <td>* Altezza</td> </tr> <tr> <td>* Ordine</td> <td>* Bandiere degli eventi</td> <td>* linee tratteggiate</td> <td>* opzioni di altezza</td> </tr> </table> <p><b>Informazioni generali</b></p> <ul style=margine sinitro="20"; > <li> Dimensione massima descrizione = 80 caratteri. </li> <li> Impostazioni di layout salvate massime = 30. </li> <li> Le impostazioni di layout salvate sono accessibili da tutti i profili. <li>Le impostazioni di layout controllano solo il layout di un grafico o di un grafico. <br> Non contengono altri dati. <br> Non controllano se un grafico viene visualizzato o meno. </li> <li>Le impostazioni di layout per il quotidiano e la panoramica sono gestite in modo indipendente. </li> </ul> Maximum number of Items exceeded. Superato il numero massimo di elementi. No Item Selected Nessun elemento selezionato Ok to Update? Ok per aggiornare? Ok To Delete? OK per eliminare? 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 device Record cannot be imported in this profile. Questo Record dispositivo non può essere importato in questo 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 Device Information informazioni sul dispositivo First Use Primo utilizzo Last Use Ultimo utilizzo Changes to Device Settings Cambia le impostazioni del dispositivo Days Giorni Pressure Relief Riduzione della pressione Pressure Settings Impostazioni di pressione Database has No %1 data available. Il database non ha dati %1 disponibili. Database has %1 day of %2 Data on %3 Il database ha %1 giorno di %2 Dati su %3 Database has %1 days of %2 Data, between %3 and %4 Il database ha %1 giorni di %2 Dati, tra %3 e %4 Total Days: %1 Giorni totali: %1 Days Not Used: %1 Giorni non utilizzati: %1 Days Used: %1 Giorni di uso: %1 Days %1 %2 Hours: %3 Giorni %1 %2 Ore: %3 Best Device Setting Migliore impostazione del dispositivo Worst Device Setting Impostazione dispositivo peggiore 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 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 device.</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 del dispositivo 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 device is detected Si noti che alcune preferenze vengono forzate quando viene rilevata un dispositivo 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 today Oggi %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 <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 pressure was under %1 %2 for %3% of the time. La tua pressione era sotto %1 %2 per %3% del tempo. 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. 1 day ago 1 giorno fa Your device was on for %1. Il tuo dispositivo era acceso per %1. Your CPAP device used a constant %1 %2 of air Il tuo dispositivo CPAP ha usato una costante %1 %2 di aria Your device used a constant %1-%2 %3 of air. Il tuo dispositivo ha usato una costante %1-%2 %3 di aria. Your device was under %1-%2 %3 for %4% of the time. Il tuo dispositivo era sotto %1-%2 %3 per %4% delle volte. Your EEPAP pressure was under %1 %2 for %3% of the time. La tua pressione EEPAP era sotto %1 %2 per %3% del tempo. 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 Double click Y-axis: Return to AUTO-FIT Scaling Doppio clic sull'asse Y: Ritorna al ridimensionamento AUTO-FIT Double click Y-axis: Return to DEFAULT Scaling Doppio clic sull'asse Y: Ritorna al ridimensionamento PREDEFINITO Double click Y-axis: Return to OVERRIDE Scaling Doppio clic sull'asse Y: Ritorna al ridimensionamento OVERRIDE Double click Y-axis: For Dynamic Scaling Doppio clic sull'asse Y: per il ridimensionamento dinamico Double click Y-axis: Select DEFAULT Scaling Doppio clic sull'asse Y: selezionare DEFAULT Scaling Double click Y-axis: Select AUTO-FIT Scaling Fare doppio clic sull'asse Y: selezionare Ridimensionamento AUTO-FIT %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.5.1/Translations/Japanese.ja.ts000066400000000000000000020153211450332542600206430ustar00rootroot00000000000000 AboutDialog &About There is &A accelarator, but since this is in Welcome screen and does not do anything, I am removing &A from translation. このソフトウェアについて 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. 「リリースノート」が見つかりません。 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 日誌 i i B B u u Color Small Medium Big Zombie ゾンビ I'm feeling ... Because Japanese sentences end with verbs, the order of word won't be the same. This may be problematic on UI. …と感じる Weight 体重 If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value 設定ダイアログで身長が 0 より大きいなら、ここに体重を設定することで、BMI (Body Mass Index) 値が表示されます Awesome 素晴らしい B.M.I. It is usual in Japanese to write BMI without periods. BMI Bookmarks ブックマーク Add Bookmark ブックマークを追加 Starts 開始 Remove Bookmark ブックマークを削除 Search 検索 Layout レイアウト Save and Restore Graph Layout Settings グラフ レイアウト設定の保存と復元 Show/hide available graphs. グラフを表示する/隠す. Breakdown I could not think of a good word for graph breakdown, so I used "detail" instead 詳細 events イベント UF1 UF1 UF2 UF2 Time at Pressure 加圧時間 Clinical Mode クリニカルモード Disabling Sessions requires the Permissive Mode セッションを無効にするには選択モードにする必要があります No %1 events are recorded this day この日には %1 のイベントは記録されていません %1 event %1 イベント %1 events Nouns do not conjugate even if they are plural in Japanese. %1 イベント Session Start Times セッション開始時間 Session End Times セッション終了時間 Session Information セッション情報 Oximetry Sessions オキシメトリーセッション Duration 長さ Device Settings 機器設定 (Mode and Pressure settings missing; yesterday's shown.) (モードと加圧設定がありません。昨日のものを表示しています) This CPAP device does NOT record detailed data この CPAP 機は詳細なデータを記録しません no data :( データがありません :( Sorry, this device only provides compliance data. 残念ながらこのデバイスはコンプライアンスのデータのみを提供しています。 This bookmark is in a currently disabled area.. このブックマークは現在無効のエリアにあります。 CPAP Sessions CPAP セッション Details 詳細 Sleep Stage Sessions 睡眠のステージセッション Position Sensor Sessions 位置センサーセッション Unknown Session 不明なセッション 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 ランプ外時間 Start 開始 End 終了 Unable to display Pie Chart on this system このシステムでは円グラフを表示できません "Nothing's here!" 「何もありません!」 No data is available for this day. この日のデータはありません。 Oximeter Information オキシメーターの情報 Click to %1 this session. %1 needs to be translated to form a complete Japanese sentence, このセッションを %1 するにはクリックしてください。 Disable Warning 警告を止める Disabling a session will remove this session data from all graphs, reports and statistics. The Search tab can find disabled sessions Continue ? セッションを無効にするとすべてのグラフ、 レポート、統計のセッション情報が削除されます。 検索タブはセッションの検索には利用できません。 つづけますか? 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 "desaturate" has different translation, but since it is about lowered of saturation, I used the word "lower." SpO2 低下 Pulse Change events Fonts are in Chinese chracter, not Japanese fonts. This is annoying in other tranlsations too. 脈拍変化イベント 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?? 0時間?? Complain to your Equipment Provider! 機器提供者に苦情を言いましょう! Pick a Colour 色を選んでください Bookmark at %1 %1 をブックマークします Hide All Events すべてのイベントを隠す Show All Events すべてイベントを表示する Hide All Graphs すべてのグラフを隠す Show All Graphs すべてのグラフを表示する DailySearchTab Match: 検索条件: Select Match 検索条件を選択 Clear クリア Bookmark Jumps to Date's Bookmark ブックマーク 日付のブックマークにジャンプ Start Search 検索開始 DATE Jumps to Date 日付 日付に移動 Match 検索条件 Notes ノート Notes containing 次を含むノート Bookmarks ブックマーク Bookmarks containing 次を含むブックマーク AHI AHI Daily Duration 一日の長さ Session Duration セッションの長さ Days Skipped スキップした日 Disabled Sessions 無効にされたセッション Number of Sessions セッション数 Click HERE to close Help ヘルプを閉じるには「ここ」をクリック Help ヘルプ No Data Jumps to Date's Details データなし 日付の詳細に移動 Number Disabled Session Jumps to Date's Details 無効になったセッション数 日付の詳細に移動 Note Jumps to Date's Notes ノート 日付のノートに移動 Jumps to Date's Bookmark 日付のブックマークに移動 AHI Jumps to Date's Details AHI 日付の詳細に移動 EventsPerHour イベント毎時 Session Duration Jumps to Date's Details セッションの長さ 日付の詳細に移動 Minutes Number of Sessions Jumps to Date's Details セッション数 日付の詳細に移動 Sessions セッション Daily Duration Jumps to Date's Details 一日あたりの長さ 日付の詳細に移動 Hours 時間 Number of events Jumps to Date's Events イベントの数 日付のイベントに移動 Events イベント Automatic start 自動スタート More to Search さらに検索する Continue Search 検索を継続 End of Search 検索の終わり No Matches 該当なし Skip:%1 %1 を飛ばす %1/%2%3 days. %1/%2%3 日。 Found %1. 該当件数: %1 %1/%2%3 days %1日 {1/%2%3 ?} Found %1 該当件数: %1 Finds days that match specified criteria. 指定した条件に一致する日を検索します。 Searches from last day to first day. Japanese sentences don't require dobule spaces between sentences. 最終日から初日まで検索します。 First click on Match Button then select topic. 最初に一致ボタンをクリックしてから、トピックを選択します。 Then click on the operation to modify it. Japanese sentences don't require dobule spaces between sentences. 次に、操作をクリックして変更します。 or update the value Japanese don't have white spaces in senences unless we want characters to stand out if they are English words in alphabets, abbrebiations, or symbols. または値を更新します Topics without operations will automatically start. 操作のないトピックは自動的に開始されます。 Compare Operations: numberic or character. 比較演算: 数値または文字。 Numberic Operations: Japanese don't have white spaces in senences unless we want characters to stand out if they are English words in alphabets, abbrebiations, or symbols. 数値演算: Character Operations: Japanese don't have white spaces in senences unless we want characters to stand out if they are English words in alphabets, abbrebiations, or symbols. 文字操作: Summary Line 要約行 Left:Summary - Number of Day searched 左:概要 - 検索した日数 Center:Number of Items Found 中央:見つかったアイテムの数 Right:Minimum/Maximum for item searched 右:検索項目の最小/最大 Result Table 結果表 Column One: Date of match. Click selects date. 列 1: 合致した日付。 日付をクリックして選択します。 Column two: Information. Click selects date. 列 2: 情報。 日付をクリックして選択します。 Then Jumps the appropiate tab. 次に、適切なタブにジャンプします。 Wildcard Pattern Matching: ワイルドカード パターン: Wildcards use 3 characters: ワイルドカードは次の 3 文字を使用します: Asterisk アスタリスク Question Mark 疑問符 Backslash. バックスラッシュ。 Asterisk matches any number of characters. アスタリスクは任意の数の文字に一致します。 Question Mark matches a single character. 疑問符は任意の1文字のキャラクタとマッチします。 Backslash matches next character. バックスラッシュは次のキャラクタとマッチします。 DateErrorDisplay ERROR The start date MUST be before the end date エラー 開始日は終了日の前でなくてはなりません The entered start date %1 is after the end date %2 入力された開始日付 %1 は終了日付 %2 よりも後です Hint: Change the end date first ヒント: 終了日を先に変更してください The entered end date %1 入力された終了日付は %1 です is before the start date %1 は開始日 %1 より前です Hint: Change the start date first ヒント: 開始日を先に変更してください ExportCSV Export as CSV CSV形式でエクスポートする Dates: 日付: Resolution: データの粒度: Details 詳細 Sessions セッション Daily 日次 Filename: ファイル名: Cancel キャンセル Export エクスポート Start: 開始: End: 終了: Quick Range: Not sure where this is used in the software. Without a context, this is hard to translate. 範囲: Most Recent Day 最も最近の日 Last Week 先週 Last Fortnight We don't have word "fortnight" so I needed to translate it as "last two weeks." 過去2週間 Last Month Last 6 Months 過去6ヶ月 Last Year 昨年 Everything すべて Custom カスタム Details_ If this is used as a part of file name, you would like to make sure your code is UTF-8 free. 詳細_ Sessions_ If this is used as a part of file name, you would like to make sure your code is UTF-8 free. セッション_ Summary_ If this is used as a part of file name, you would like to make sure your code is UTF-8 free. サマリー_ Select file to export to The dialog allows the user to either choose file to expoert to or write the name of the file, therefore, the original text may not be valid. This is a cosmetic issue since it is a title for a CSV export dialog, but if you are concerned, I can change the translation. エクスポート先のファイルを選んでください CSV Files (*.csv) CSV ファイル (*.csv) DateTime 日時 Session セッション Event イベント Data/Duration I may have to look at the context for "duration" to translate it precisely. データ/長さ Date 日付 Session Count セッション数 Start 開始 End 終了 Total Time 合計時間 AHI AHI Count 回数 FPIconLoader Import Error インポートエラー This device 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" 「%2」について %1 件あります clear クリア MD300W1Loader Could not find the oximeter file: オキシメーターのファイルが見つかりません: Could not open the oximeter file: オキシメーターのファイルが開けられません: MainWindow &Statistics If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. 統計 (&S) Report Mode レポートモード Show Standard Report 標準レポートを表示 Standard 標準 Show Monthly Report 月次レポートを表示 Monthly 月次 Show Range Report 範囲指定のレポートを表示 Date Range 日付の範囲 Select Report Date レポートの日付を選択 Report Date レポート日 Statistics 統計 Daily 日次 Overview 概要 Oximetry オキシメーター Import インポート Help ヘルプ &File If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &Fファイル &View If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &V表示 &Reset Graphs &Rグラフをリセット &Help If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &Hヘルプ Troubleshooting トラブルシューティング &Data If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &Dデータ &Advanced If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &A高度な操作 Rebuild CPAP Data CPAPのデータを再構築する &Import CPAP Card Data If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &I CPAP のカードからインポートする Show Daily view 日次情報を表示 Show Overview view 概要を表示 &Maximize Toggle &M 最大/通常画面の切り替え Maximize window ウインドウの最大化 Reset Graph &Heights If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &H グラフの高さを元に戻す Reset sizes of graphs グラフのサイズを元に戻す Show Right Sidebar 右のスライドバーを表示 Show Statistics view 統計情報を表示 Import &Dreem Data &Dreemのデータをインポート Show &Line Cursor If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &Lラインカーソルを表示 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 If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &P円グラフを表示する Show Pie Chart on Daily page 日次のページに円グラフを表示する Standard - CPAP, APAP 標準 - CPAP、APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> <html><head/><body><p>CPAP、APAP、Basic BPAP に適した標準的なグラフの並び</p></body></html> Advanced - BPAP, ASV 高度 - BPAP、ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> <html><head/><body><p>高度なグラフのならび、BPAP w/BU、ASV、AVAPS、IVAPS に適しています</p></body></html> Show Personal Data 個人情報を表示 Check For &Updates &Uアップデートがあるか確認する Purge Current Selected Day I am unsure what "current selected day" means. Is it "currently selected day?" 選んだ日の現在の情報をパージする &CPAP &CPAP &Oximetry If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &Oオキシメーター &Sleep Stage If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &S睡眠の段階 &Position If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &P位置 &All except Notes If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &Aノートを除くすべて All including &Notes If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &Nノートを含むすべて &Preferences If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &Pプリファレンス &Profiles If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &Pプロフィール &About OSCAR If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &A OSCARについて Show Performance Information 性能の情報を表示 CSV Export Wizard CSVエクスポートウィザード Export for Review レビュー用にエクスポート E&xit If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &x終了 Exit 終了 View &Daily If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &D日次を表示 View &Overview If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &O概要を表示 View &Welcome If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &Wようこそを表示する Use &AntiAliasing If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &Aアンチエイリアシングを使う Show Debug Pane デバッグ情報を表示する Take &Screenshot If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &Sスクリーンショットをとる O&ximetry Wizard If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &xオキシメーターウィザード Print &Report If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &Rレポートを印刷 &Edit Profile If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &Eプロフィールを編集 Import &Viatom/Wellue Data If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &Viatom/Wellue のデータをインポート Daily Calendar 日次カレンダー Backup &Journal &ジャーナルをバックアップする Online Users &Guide If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. オンラインユーザー&ガイド &Frequently Asked Questions If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &Fよくある質問 &Automatic Oximetry Cleanup If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &A オキシメーターを自動でクリーンアップする Change &User &ユーザーを変更する Purge &Current Selected Day I am not sure what is the context of "current selected day." It this "currenlty selected day?" Also, if &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &C現在選択されている日をパージする Right &Sidebar If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. 右&Sサイドバー Daily Sidebar 日次サイドバー View S&tatistics If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &t統計を表示 Navigation ナビゲーション Bookmarks ブックマーク Records レコード Exp&ort Data If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &データをエクスポート Profiles プロフィール Purge Oximetry Data オキシメーターのデータをパージ Purge ALL Device Data 全デバイスのデータをパージ View Statistics 統計情報を表示 Import &ZEO Data If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &ZEO データをインポート Import RemStar &MSeries Data If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. RemStar &M シリーズのデータをインポート Sleep Disorder Terms &Glossary If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. 睡眠障害の語句&一覧 Change &Language If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &言語を変更する Change &Data Folder If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &データフォルダを変更する Import &Somnopose Data If &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &Somnopose のデータをインポートする Current Days 最近 Welcome ようこそ &About Since "About" does not make sense without a noun in Japanese, I translate it as "About this software." Also, if &<alphabet> means a shortcut for the menu, I may have to revisit and assign them because Japanese characters are not on the keyboard. &このソフトウェアについて Please wait, importing from backup folder(s)... しばらくお待ちください。バックアップフォルダからインポートしてます… Import Problem インポートの問題 Couldn't find any valid Device Data at %1 %1 に有効なデータがありませんでした。 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. このメッセージを読んでいるようであれば、再起動コマンドはうまくいきませんでした。手動で再起動してください。 No help is available. ヘルプがありません。 You must select and open the profile you wish to modify 変更したいプロフィールを選んで開いてください %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? このカードをZIP圧縮しますか? Choose where to save zip ZIPファイルの保存先を選んでください ZIP files (*.zip) ZIPファイル(*.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. データカードの下位のフォルダではなく、最上位のフォルダあるいはドライブレターを選択してください。 No supported data was found サポートされているデータが見つかりませんでした Please open a profile first. プロフィールを先に開いてください。 Check for updates not implemented 更新の確認は実装されていません Are you sure you want to rebuild all CPAP data for the following device: There is no white space before or adter translation, but Linguist detects them. 本当に以下のデバイス上のCPAPのデータをすべて再構築しますか: For some reason, OSCAR does not have any backups for the following device: 原因不明ですが、OSCARには以下のデバイのバックアップが存在しません: 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データのすべてのバックアップ</>を取ってあるのであれば、この操作を完了できますが、バックアップから手作業でリストアする必要があります。 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. 再構築のための内部バックアップがないため、ご自分のバックアップからリストアしていただく必要があります。 Note as a precaution, the backup folder will be left in place. 注意事項ですが、バックアップフォルダは現在の場所に残ります。 OSCAR does not have any backups for this device! OSCARはこのデバイスにはバックアップがありません! Unless you have made <i>your <b>own</b> backups for ALL of your data for this device</i>, <font size=+2>you will lose this device's data <b>permanently</b>!</font> <i>ご自分<b>自身の</b>CPAPデータのすべてのバックアップ</>を取ったのでない限り、<font size=+2>このデバイス上のデータを<b>永遠に</b>失うことになります!</font> You are about to <font size=+2>obliterate</font> OSCAR's device database for the following device:</p> OSCARのデバイスデータベースから、以下のデバイスを<font size=+2>消し去ろう</font> としています</p> Are you <b>absolutely sure</b> you want to proceed? <b>本当に</b>この操作を実施ししても良いですか? A file permission error caused the purge process to fail; you will have to delete the following folder manually: ファイルへのアクセス権の問題で、パージの操作が失敗したため、以下のフォルダーを手作業で削除していただく必要があります: The Glossary will open in your default browser デフォルトのブラウザで用語集が開きます There was a problem opening %1 Data File: %2 データファイル %2 で %1 を開く際に問題が発生しました %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 %2 から %1 の CPAP セッションをインポートしました Import Success インポート成功 Already up to date with CPAP data at %1 CPAPデータは %1 の最新のものです Up to date 最新 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 Correction after review 指定する 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のバックアップが有効になっていない場合、この操作はデータを破壊する可能性があります。 Would you like to import from your own backups now? (you will have no data visible for this device until you do) ご自分のバックアップからインポートしますか?(インポートするまでこのデバイスのデータは見ることができません。) 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 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 Japanese don't have distinctive words to describe gender and sex, thus a bit of foot note in parentheses. 性別(生物学上の) 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 患者ID &Cancel &Cキャンセル &Back &B戻る &Next &N次 Select Country 国を選択 Welcome to the Open Source CPAP Analysis Reporter オープンソース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 Copyright is often left as is in English, so I leave it as is. 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>でリリースされ、いかなる用途への適合性を保証せず、現状有姿で提供されます。 This software is being designed to assist you in reviewing the data produced by your CPAP Devices and related equipment. 本ソフトウェアは、CPAPや関連機器からデータを取り出し、参照する補助としてデザインされました。 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 &F完了 &Close this window &Cウインドウを閉じる 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 選んだ日付の範囲に表示をリセット Layout レイアウト Save and Restore Graph Layout Settings グラフ レイアウト設定の保存と復元 Drop down to see list of graphs to switch on/off. 表示をon/offするグラフをドロップダウンで見る。 Graphs グラフ Respiratory Disturbance Index People knows about RDI in abbrebiated form than translation. 呼吸 障害 指数 (RDI) Apnea Hypopnea Index People knows about AHI in abbrebiated form than translation. 無呼吸 低呼吸 指数 (AHI) Usage 使用 Usage (hours) 使用 (時間) Session Times セッション時間 Total Time in Apnea 無呼吸合計時間 Total Time in Apnea (Minutes) 無呼吸合計時間 (分) Body Mass Index People knows about BMI in abbrebiated form than translation. ボディ マス 指数 (BMI) How you felt (0-10) どう感じたか (0-10) Hide All Graphs すべてのグラフを隠す Show 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> 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) 接続したコンピュータに夜間記録を取ります(プレチスモグラムが作成されます) <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. オキシメーターの内蔵の時計を使う。 <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 &Cキャンセル &Information Page &I 情報ページ 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> Correcting a typo in translation. <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 日時の記録が(通常は)開始されました I started this oximeter recording at (or near) the same time as a session on my CPAP device. オキシメーターの記録をCPAPデバイスのセッションと同時(あるいはほぼ同時)に開始しました。 <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 &R 再試行 &Choose Session &C セッションを選ぶ &End Recording &E 記録を終了 &Sync and Save &S 同期して保存 &Save and Finish &S 保存して終了 &Start &S 開始 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台のみお持ちで、プロフィール間で共有している場合、両方のプロフィールで同じ名称を設定してください。 "%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 が Oximeter デバイスを見つけて直接読み取れるようにするには、コンピュータに正しいデバイス ドライバ (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 %2 の %1 セッション、%3 に開始 No CPAP data available on %1 %1 にはCPAPのデータがありません Recording... 記録中… Finger not detected This text itself is a bit of horror :-) maybe it should be "oximeter can't find the finger." 指が見つかりません 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, CMS50 および 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. 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 CM500D+ は内蔵の時計がないため、開始時間を記録しません。記録をリンクする 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 &Rリセット Pulse 心拍 &Open .spo/R File &O .spo/Rファイルを開く Serial &Import &I シリアルからインポート &Start Live &S リアルタイム収集開始 Serial Port シリアルポート &Rescan Ports &R ポートを再スキャン PreferencesDialog Preferences 設定 &Import &I インポート Combine Close Sessions 閉じたセッションを統合 Minutes Multiple sessions closer together than this value will be kept on the same day. この値より近い複数のセッションは、同じ日として保管されます。 Ignore Short Sessions 短いセッションを無視 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 時間 Flow Restriction 流量制限 Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. 中央値からの流量制限のパーセンテージ。 20% の値は、無呼吸の検出に適しています。 Duration of airflow restriction 気流制限の時間 s If this is "plural" s, Japanese nouns do not conjugate when it is plural Event Duration イベント期間 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 Spelling out month name in English does not render correctly in Japanese locale. We usually write Year-Month-Day. yyyy MMMM dd User definable threshold considered large leak 大きな漏れとして扱うユーザー定義可能なしきい値 Whether to show the leak redline in the leak graph 漏れのグラフに漏れの赤い線を表示する Search 検索 &Oximetry &O オキシメトリー Show in Event Breakdown Piechart イベント毎円グラフで表示する 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 &G 一般 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 カードを毎晩持ち、少なくとも週に 1 回はインポートする場合、これに関する問題はあまり見られません。</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 確認せずにインポートする 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>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 カスタム CPAP ユーザー イベントのフラグ設定 Events イベント Reset &Defaults &D デフォルトにリセットする <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 のデータが半分くらいの容量になります。 一方、インポートと日付の切り替えにより長い時間がかかります。 もしお使いのコンピュータが容量の小さい SSD を使っているのであれば、これは良いオプションです。 Compress Session Data (makes OSCAR data smaller, but day changing slower.) セッションデータを圧縮する(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 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 &A 表示 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><p>推奨値は、50 ミリ秒です。</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 グラフの高さ <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">これより短いセッションは表示されません。</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> 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 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 mpレートでで空気を排出します Your masks vent rate at 4 cmH2O pressure マスクは 4 cmH2O mpレートでで空気を排出します <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">カスタム フラグは、デバイスが見逃したイベントを検出する実験的な方法です。これらはAHIに含まれて </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">いません。</span><span style=" font-family:'Sans'; font-size:10pt;"></span></p></body></html> l/min リットル/分 <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p>累積指数</p></body></html> Oximetry Settings オキシメトリー設定 <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> <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:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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><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:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 データが SpO2レビュー (あるいは .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=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">ライブビューモード(シリアルケーブル利用)はCM50オキシメーター立と正しく同期する唯一の方法ですが、CPAPの時計のずれに対応するものではありません。</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">もしCPAPデバイスの開始に</span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">正確に</span><span style=" font-family:'Sans'; font-size:10pt;">オキシメーターの記録を開すれば同期することができます。 </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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> 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をお使いです。テストバージョンは7日毎に最低1回更新がないかチェックします。7日より短い間隔に設定することができます。 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の開発中のバージョンの試験のお手伝いをなさりたい方は、OSCARの試験についてのWikiページをご覧下さい。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. アンチエイリアシングはグラフの描画をスムーズにします。 グラフの種類によってはこの設定がONの方が見栄えが良くなります。 印刷されるレポートにも影響のある設定です。 試してみて気に入るかどうか確認してください。 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 Y軸のラベルをダブルクリックした際Y軸がスケールすることを許すことを許可するかどうか Allow YAxis Scaling Y軸のスケールを許可する Include Serial Number シリアル番号を含める Graphics Engine (Requires Restart) グラフィックエンジン(再起動が必要) This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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>Provide an alert when importing data from any device 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 device 試験したことのない機器からのデータからのインポートである場合警告します This calculation requires Total Leaks data to be provided by the CPAP device. (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は既にこのデータがあります。) 予期しない漏れの計算は線形であり、マスクのベントカーブに基づくモデルではありません。 複数のマスクをお持ちの場合は、平均値を使ってください。充分に(真の値に)近い値となると思われます。 Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device missed. This option must be enabled before import, otherwise a purge is required. 実験的なイベント フラグ機能の強化を有効/無効にします。 これにより、デバイスが見逃したイベントや境界イベントの検出が可能になります。 このオプションは、インポートの前に有効にする必要があります。もしくは、いったんパージが必要になります。 This experimental option attempts to use OSCAR's event flagging system to improve device detected event positioning. この実験的なオプションは、OSCAR のイベント フラグ システムを使用して、デバイスで検出されたイベントの位置を改善しようとします。 Resync Device Detected Events (Experimental) デバイスが検出したイベントの再同期 (実験的) Allow duplicates near device events. デバイス イベント付近の重複を許可する。 Show flags for device detected events that haven't been identified yet. まだ識別されていないデバイス検出イベントのフラグを表示します。 <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices do not support changing these settings.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">注: </span>概要設計上の制限により、ResMed デバイスはこれらの設定の変更をサポートしていません。</p></body></html> Clinical クリニカル Clinical Settings クリニカル設定 Select Oscar Operating Mode OSCAR の動作モードを選択 Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. クリニカルモードでは無効にされたセッションは利用できません。\n無効なセッションはグラフや統計の作成に利用されません。 Clinical Mode クリニカルモード permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. 選択モードでは、無効なセッションを扱うことができます。\n無効なセッションはグラフや統計の作成に利用されます。 Permissive Mode 選択モード Hours 時間 <!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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> 同期に必要な正しいタイムスタンプが含まれています。</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">(シリアルケーブルを使った)ライブビューモードが CM50 オキシメーターと正しく同期する方法で蓮が、CPAP の時計のずれは考慮されません。</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">シアルインポートを行った場合、昨晩のデータから開始時刻を求めます。(先にCPAPのデータのインポートを行ってください!)</span></p></body></html> Whether to include device serial number on device settings changes report デバイス設定変更レポートにデバイスのシリアル番号を含めるかどうか Print reports in black and white, which can be more legible on non-color printers レポートを白黒で印刷します。これにより、カラー以外のプリンターでも読みやすくなります Print reports in black and white (monochrome) レポートを白黒(モノクロ)で印刷する For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. 複数のセッションの場合、イベント フラグ グラフの上部にセッションごとに細い灰色の線が表示されます。 Enables SessionBar in Event Flags Graph イベントフラググラフでセッションバーを有効にします Fonts (Application wide settings) フォント (アプリケーション全体の設定) Font フォント Size Bold 太字 Italic イタリック Application アプリケーション Graph Text グラフの文字 Graph Titles グラフのタイトル Big Text 大きな文字 Details 詳細 &Cancel &Cキャンセル &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 概要 No CPAP devices detected CPAPデバイスが検出されません Will you be using a ResMed brand device? RedMedブランドの機器を使いますか? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, 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 の商用ソフトウェアと同様に、<b>正午に 1 日が分割されます</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? 行った変更の 1 つ以上を有効にするには、このアプリケーションを再起動する必要があります。 今すぐ再起動しますか? ResMed S9 devices routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). ResMed S9 デバイスは、SD カードから 7 日および 30 日より古い特定のデータを定期的に削除します (解像度によって異なります)。 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 フラグ Clinical Mode: クリニカルモード: Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. データ カードの内容をレポートします。これには、選択モードで選択解除されたすべてのデータが含まれます。 Basically replicates the reports and data stored on the devices data card. 基本的には、デバイスのデータカードに保存されているレポートとデータを複製します。 This includes pap devices, oximeters, etc. Compliance reports fall under this mode. これには、PAP デバイス、オキシメーターなどが含まれます。コンプライアンス レポートは、このモードに分類されます。 Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. コンプライアンス レポートには、選択が解除されている場合でも、選択したコンプライアンス期間内のすべてのデータが常に含まれます。 Permissive Mode: 選択モード: Allows user to select which data sets/ sessions to be used for calculations and display. ユーザーが計算したり表示するデータセットやセッションを選ぶことを許可する。 Additional charts and calculations may be available that are not available from the vendor data. ベンダーのデータからは入手できない追加のグラフや計算が利用できる場合があります。 Changing the Oscar Operating Mode: OSCAR の動作モードを変更: Requires a reload of the user's profile. Data will be saved and restored. ユーザーのプロファイルをリロードする必要があります。 データは保存および復元されます。 Minor Flag マイナーフラグ Span 範囲 Always Minor 常にマイナー Never 行わない This may not be a good idea あまり良い考えではありません ProfileSelector Filter: フィルタ: Reset filter to see all profiles すべてのプロフィールを見るためにフィルターをリセット Version バージョン &Open Profile &O プロフィールを開く &Edit Profile &E プロフィールを編集 &New Profile &N 新規プロフィール 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. ご注意ください。この操作はプロフィールと%2に保管されたすべての<b>バックアップデータ</b>を復元不可能な形で消去します。 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 バイト KB KB MB MB GB GB TB PB TB 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> Eメール: <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 Sep 9月 Oct 10月 Nov 11月 Dec 12月 ft フィート lb ポンド oz オンス 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 分 Length: %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 milliSeconds ミリ秒 h m s 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 &Y はい &No &N いいえ &Cancel &Cキャンセル &Destroy &D消去 &Save &S保存 BMI BMI Weight 体重 Zombie ゾンビ Pulse Rate 心拍 Plethy Refer to https://www.ei-navi.jp/dictionary/content/plethysmograph/ プレシモグラフ Pressure 圧力 Daily 日次 Profile プロフィール Overview 概要 Oximetry オキシメトリー Oximeter オキシメーター Event Flags イベントフラグ Default デフォルト CPAP CPAP BiPAP BiPAP Bi-Level Bi-Level EPAP EPAP EEPAP EEPAP Min EEPAP 最小EEPAP Max EEPAP 最大EEPAP Min EPAP Min EPAP Max EPAP IPAP IPAP Min IPAP Min IPAP Max IPAP Max IPAP APAP APAP ASV ASV AVAPS AVAPS ST/ASV ST/ASV Humidifier 加湿器 H H OA OA A A 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. Japanese translation is not omitting anything, so we don't need at dot at the end. 分時換気 Tidal Volume 一回換気量 Resp. Rate 呼吸レート Snore いびき Leak 漏れ Leaks 漏れ Large Leak 大きな漏れ LL LL Total Leaks 漏れ計 Unintentional Leaks 意図しない漏れ MaskPressure マスク圧力 Flow Rate 流量 Sleep Stage 睡眠の段階 Usage 使用 Sessions セッション Pr. Relief 圧力リリーフ Device 機器 No Data Available データなし App key: App key: Operating system: オペレーティングシステム: Built with Qt %1 on %2 %2上でQt %1 でビルドされました Graphics Engine: グラフィックエンジン: Graphics Engine type: グラフィックエンジンの種類: Compiler: コンパイラー: Software Engine ソフトウェアエンジン ANGLE / OpenGLES ANGLE / OpenGLES Desktop OpenGL デスクトップ OpenGL m m cm cm in の中 kg kg l/min l/min Only Settings and Compliance Data Available 設定とコンプライアンスデータのみ Summary Data Only 概要データのみ Bookmarks ブックマーク Mode モード Model モデル Brand ブランド Serial シリアル番号 Series シリーズ 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) で作成されたデータは、OSCARで認識できない形式です。 The imported data may not be entirely accurate, so the developers would like a .zip copy of this device's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. インポートされたデータは完全に正確ではない可能性があるため、OSCAR がデータを正しく処理していることを確認するために、開発者はこのデバイスの SD カードの .zip コピーと一致する臨床医の .pdf レポートを必要としています。 Non Data Capable Device 非データ対応機器 Your %1 CPAP Device (Model %2) is unfortunately not a data capable model. 残念ながら、%1 CPAP デバイス (モデル %2) はデータ対応モデルではありません。 I'm sorry to report that OSCAR can only track hours of use and very basic settings for this device. 申し訳ありませんが、OSCAR が追跡できるのは、このデバイスの使用時間と非常に基本的な設定のみです。 Device Untested 未テストの機器 Your %1 CPAP Device (Model %2) has not been tested yet. %1 CPAP デバイス (モデル %2) はまだテストされていません。 It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card and matching clinician .pdf reports to make sure it works with OSCAR. 動作する可能性がある他のデバイスと十分に似ているように見えますが、開発者は、このデバイスの SD カードの .zip コピーと、担当臨床医の .pdf レポートで OSCAR で動作することを確認したいと考えています。 Device Unsupported サポートされない機器 Sorry, your %1 CPAP Device (%2) is not supported yet. 申し訳ありませんが、%1 CPAP デバイス (%2) はまだサポートされていません。 The developers need a .zip copy of this device's SD card and matching clinician .pdf reports to make it work with OSCAR. 開発者は、OSCARで正しく動くようにするため、このデバイスの SD カードの .zip コピーと、担当臨床医の .pdf レポートが必要です。 Getting Ready... 準備中... Scanning Files... ファイルをスキャン中... Importing Sessions... セッションをインポート中... UNKNOWN 不明 APAP (std) APAP (std) APAP (dyn) APAP (dyn) Auto S Auto S Auto S/T Auto S/T AcSV AcSV SoftPAP Mode SoftPAP モード Pressure relief during exhalation 呼気圧力軽減 Slight Slight Softstart pressure ソフトスタート圧力 Pressure during soft start period ソフトスタート期間中の圧力 PSoft PSoft Softstart minimum pressure ソフトスタートの最低圧力 Minimum pressure during soft start period ソフトスタート期間中の最低の圧力 PSoftMin PSoftMin Auto start 自動スタート Automatically turn on the device by breathing 呼吸をすることでデバイスが自動起動 Softstart time ソフトスタート開示時刻 Lenght of soft start period ソフトスタート期間の長さ Soft start maximum time ソフトスタートの最大時間 Maximum lenght of soft start period ソフトスタート期間の最大の長さ Soft start max. time ソフトスタートの最大時間 Soft start pressure ソフトスタート圧力 Higher End Expiratory Pressure 上端呼気圧 Humidifier level 加湿器レベル Tube type チューブの種類 Obstruction level 閉塞レベル Obstruction level in percentage 閉塞レベル(パーセント) rRMVFluctuation rMVFluctuation Relative respiratory minute volume fluctuation 相対呼吸量変動 Relative respiratory minute volume 相対呼吸量(分時呼吸量) Measured pressure 計測された圧力 Full flow 全流量 Artefact アーティファクト Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) 呼吸イベントを示さない測定された多データの不規則性(例:飲み込み、咳、会話など) Epoch (2 mins) with Flow Limitation 流量制限をともなうエポック(2分間) Deep Sleep 深い眠り Deep sleep, stable respiration 深い眠り、安定した呼吸 Timed breath 呼吸時間 BiSoft Mode BiSoft Mode BiSoft 1 BiSoft 1 BiSoft 2 BiSoft 2 TriLevel TriLevel AutoStart 自動スタート Softstart_Time Softstart_Time Softstart_TimeMax Softstart_TimeMax Softstart_Pressure Softstart_Pressure PMaxOA PMaxOA EEPAPMin EEPAPMin Lower End Expiratory Pressure 下端呼気圧 EEPAPMax EEPAPMax HumidifierLevel 加湿器レベル TubeType チューブの種類 ObstructLevel 障害物レベル Obstruction Level corrected after review 閉塞レベル rMVFluctuation rMVFluctuation rRMV rRMV PressureMeasured 計測された圧力 FlowFull FlowFull SPRStatus SPRStatus Artifact SPRStatus ART ART CriticalLeak 重大な漏れ Mask leakage is above a critical treshold クリニカルモードのしきい値を超えるマスクの漏れ CL CL eMO eMO Epoch (2 mins) with Mild Obstruction 軽度の閉塞を伴うエポック (2 分) eSO eSO Epoch (2 mins) with Severe Obstruction 重度の閉塞を伴うエポック (2 分) eS eS Epoch (2 mins) with Snoring いびきを伴うエポック (2 分) eFL eFL DeepSleep 深い眠り DS DS TimedBreath 時間による呼吸 Finishing up... 終了中... 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 立ち上がりロック Mask Resistance Setting マスク抵抗設定 Mask Resist. マスク抵抗. Hose Diam. ホース径. 15mm 15mm 22mm 22mm Backing Up Files... ファイルをバックアップしています... Untested Data テストされていないデータ model %1 モデル %1 unknown model 認識できないモデル CPAP-Check CPAPチェック AutoCPAP AutoCPAP Auto-Trial Auto-Trial AutoBiLevel AutoBiLevel S S S/T S/T 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 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 マスク抵抗ロック A few breaths automatically starts device 少し息をすると機器が自動で動き始めます Device automatically switches off デバイスは自動で電源が切れます Whether or not device allows Mask checking. デバイスがマスク チェックを許可するかどうかを示します。 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 固定バックアップ呼吸 BPM Minimum breaths per minute (BPM) below which a timed breath will be initiated 1 分あたりの最小呼吸数 (BPM) を下回ると、時間指定された呼吸が開始されます Breath BPM ブレスBPM Timed Inspiration 計測された吸気 The time that a timed breath will provide IPAP before transitioning to EPAP The time that a timed breath will provide IPAP before transitioning to EPAP Timed Insp. Nothing is omitted in Japanese, so there is no dot at the end. 計測された吸気 Auto-Trial Duration 自動試行時間 Auto-Trial Dur. 自動試行時間. EZ-Start EZ-Start Whether or not EZ-Start is enabled EZ-Startrが有効かどうか Variable Breathing 不規則な呼吸 (Variable Breathing) UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend 呼吸が不規則に変動している可能性があります。これは、ピーク吸気フロートレンドから大きく逸脱している期間です A period during a session where the device could not detect flow. デバイスがフローを検出できなかったセッション中の期間。 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) アダプティブ (システム 1) 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 Off 自動オフ Mask Alert マスクアラート Show AHI AHIを表示 Whether or not device shows AHI via built-in display. デバイスが内蔵画面にAHIを表示するかどうか。 The number of days in the Auto-CPAP trial period, after which the device will revert to CPAP デバイスが CPAP に戻るまでの Auto-CPAP 試用期間の日数 Breathing Not Detected 呼吸が検出されない BND BND Timed Breath 呼吸時間 Machine Initiated Breath 機械による呼吸 TB TB Windows User Windowsユーザー Using 使用している , found SleepyHead - , 検出された Sleepy Head - You must run the OSCAR Migration Tool You must run the OSCAR Migration Tool 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 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> <i>Your old device data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> <i>前回のデータ インポート時に設定でこのバックアップ機能が無効にされていなければ、古いデバイス データを再生成する必要があります。</i> OSCAR does not yet have any automatic card backups stored for this device. OSCAR には、このデバイス用に保存された自動カード バックアップがまだありません。 This means you will need to import this device 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 を実行できるように、アップグレードする準備はできていますか? Device Database Changes デバイス データベースの変更 Sorry, the purge operation failed, which means this version of OSCAR can't start. 申し訳ありませんが、パージ操作に失敗しました。つまり、このバージョンの OSCAR を開始できません。 The device data folder needs to be removed manually. デバイス データ フォルダは手動で削除する必要があります。 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 を再起動してアップグレード プロセスを完了してください。 Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. アップグレードすると、以前のバージョンでこのプロファイルを<font size=+1>使用できなくなります</font>。 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 チェーンストークス呼吸の異常な期間 Cheyne Stokes Respiration (CSR) チェーンストークス呼吸 Periodic Breathing (PB) 周期的な呼吸 (PB) Clear Airway (CA) 気道確保 (CA) Obstructive Apnea (OA) Corrected after review 閉塞性無呼吸 (OA) Hypopnea (H) 低呼吸 (H) An apnea that couldn't be determined as Central or Obstructive. 中枢性または閉塞性と判断できない無呼吸。 Unclassified Apnea (UA) 未分類の無呼吸 (UA) Apnea (A) 無呼吸 (A) An apnea reportred by your CPAP device. CPAP デバイスによって報告された無呼吸。 A restriction in breathing from normal, causing a flattening of the flow waveform. 通常よりも呼吸が制限され、フロー波形が平坦化します。 Flow Limitation (FL) 流量制限 (FL) RERA (RE) RERA (RE) Vibratory Snore (VS) 振動いびき(VS) Vibratory Snore (VS2) 振動いびき(VS2) Leak Flag (LF) リークフラグ (LF) A large mask leak affecting device performance. デバイスのパフォーマンスに影響を与える大きなマスクの漏れ。 Large Leak (LL) 大きな漏れ (LL) Non Responding Event (NR) Non Responding Event (NR) Expiratory Puff (EP) 呼気パフ (EP) SensAwake (SA) 覚醒検出(SA) User Flag #1 (UF1) Flag #1を使用 (UF1) User Flag #2 (UF2) Flag #2を使用 (UF2) User Flag #3 (UF3) Flag #3を使用 (UF3) Pulse Change (PC) 脈拍変化 (PC) SpO2 Drop (SD) SpO2低下 (SD) A ResMed data item: Trigger Cycle Event ResMedデータ項目: サイクルイベントのトリガー Apnea Hypopnea Index (AHI) 無呼吸低呼吸指数(AHI) Respiratory Disturbance Index (RDI) 呼吸障害指数 (RDI) 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 閉塞性無呼吸 A partially obstructed airway 部分的に塞がった気道 UA UA A vibratory snore 振動をともなういびき Pressure Pulse 脈圧 A pulse of pressure 'pinged' to detect a closed airway. 閉塞気道の検出のため瞬間的な圧力を加えました。 A type of respiratory event that won't respond to a pressure increase. 圧力の上昇に反応しないタイプの呼吸イベント。 Intellipap event where you breathe out your mouth. 口から息を吐くIntellipapイベント。 SensAwake feature will reduce pressure when waking is detected. SensAwake 機能は、覚醒が検出されると圧力を下げます。 Heart rate in beats per minute 一分間あたりの脈拍数 Blood-oxygen saturation percentage 血中酸素飽和度 Plethysomogram プレチスモグラム An optical Photo-plethysomogram showing heart rhythm 心拍リズムを示す光プレチソモグラム A sudden (user definable) change in heart rate 心拍数の突然の (ユーザー定義可能) 変化 A sudden (user definable) drop in blood oxygen saturation 血中酸素飽和度の突然の (ユーザー定義可能) 変化 SD SD Breathing flow rate waveform 呼吸流量波形 Mask Pressure マスク圧力 Amount of air displaced per breath 1回の呼吸で排出される空気の量 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 吸気/呼気の割合 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 設定 CSR CSR An abnormal period of Periodic Breathing 周期的な呼吸の異常な期間 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. The Japanese tranlsation is not omiting anything, therefore no dot at the end. 流量制限 Target Minute Ventilation 目標分時換気量 Maximum Leak 最大の漏れ The maximum rate of mask leakage マスクからの最大の漏れの割合 Max Leaks 最大の漏れ 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 漏れの中央値 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 PAP モード Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. Channels.xml を解釈できません。OSCARは動作を継続できないため終了します。 End Expiratory Pressure 呼気終末圧 An apnea reported by your CPAP device. CPAP デバイスによって無呼吸が報告されました。 Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. 呼吸努力関連覚醒:覚醒または睡眠障害の原因となる呼吸の制限。 A vibratory snore as detected by a System One device System One デバイスによって検出された振動をともなういびき I/E Value I/E Value 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. データフォルダが選ばれていないため、OSCARは終了します。 or CANCEL to skip migration. または、CANCELを選んで移行を飛ばしてください。 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 "from x" in English would be ~から in Japanese. There would be word order issue. から to "to ~" in English would be "~へ" in Japanese. There will be word order issue for this translation. 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. OSCAR は、古いデータを後でこのフォルダーにコピーできます。 Migrate SleepyHead or OSCAR Data? SleepyHead または OSCAR データを移行しますか? On the next screen OSCAR will ask you to select a folder with SleepyHead or OSCAR data 次の画面で OSCAR は、SleepyHead または OSCAR データを含むフォルダを選択するように求めます Click [OK] to go to the next screen or [No] if you do not wish to use any SleepyHead or OSCAR data. [OK] をクリックして次の画面に進むか、SleepyHead または OSCAR データを使用したくない場合は [いいえ] をクリックします。 We suggest you use this folder: 次のフォルダを使用することをお勧めします: Click Ok to accept this, or No if you want to use a different folder. [OK] をクリックしてこれを受け入れるか、別のフォルダーを使用する場合は [いいえ] をクリックします。 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 次の場所にOSCARのデータフォルダを作ることができません Unable to write to OSCAR data directory OSCARのデータフォルダに書き込むことができません 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. デバッグ ログに書き込めません。 デバッグ ペイン (ヘルプ/トラブルシューティング/デバッグ ペインの表示) は引き続き使用できますが、デバッグ ログはディスクに書き込まれません。 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? このフォルダを使用してもよろしいですか? OSCAR Reminder OSCAR リマインダー Don't forget to place your datacard back in your CPAP device データカードを CPAP デバイスに戻すことを忘れないでください You can only work with one instance of an individual OSCAR profile at a time. 一度に操作できるのは、個々の OSCAR プロフィールの 1 つのインスタンスだけです。 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 ChromeOS Files プログラムを使用して、SD カードを Linux と共有する必要があります 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 oximetry 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 "flow" in this context does not make much sense in Japanese, therefore, I tranlated it as "air flow." 一日の空気の流れの波形 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) (セッション: %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 レベル Auto Bi-Level (Fixed PS) 自動 Bi レベル (固定 PS) Auto Bi-Level (Variable PS) 自動 Bi レベル (可変 PS) 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 over %2-%3 (%4) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) 最小 EPAP %1 最大 IPAP %2 PS %3-%4 (%5) Min EEPAP %1 Max EEPAP %2 PDIFF %3-%4 (%5) Min EEPAP %1 Max EEPAP %2 PDIFF %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("daily=%2");'>%1</a> (last night) (昨晩) (1 day ago) (一昨日) (%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シリーズ Philips Respironics Philips Respironics System One System One ResMed ResMed S9 S9 EPR: EPR: Somnopose Somnopose Somnopose Software Somnopose ソフトウェア Zeo Zeo Personal Sleep Coach パーソナル スリープ コーチ Selection Length 選択の長さ 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 ] Japanese usually go with year, month, day in YYYY-MM-DD format. 日 月 年 [ %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 Relief: %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 は古い Journal フォルダを見つけましたが、名前が変更されたようです: 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 はこれらの最初の 1 つだけを選択し、将来それを使用します: 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 VPAP自動 ASVAuto ASV自動 iVAPS iVAPS PAC PAC Auto for Her Auto for Her EPR EPR ResMed Exhale Pressure Relief ResMed 呼気圧力軽減 Patient??? 患者??? EPR Level EPR レベル Exhale Pressure Relief Level 呼気圧力軽減レベル Device auto starts by breathing 息をすることでデバイスが自動起動 Response 応答 Device auto stops by breathing 呼吸で装置が自動停止 Patient View 患者ビュー RiseEnable ライズ有効 RiseTime ライズ時間 Cycle 周期 Trigger トリガー TiMax TiMax TiMin TiMin Your ResMed CPAP device (Model %1) has not been tested yet. ResMed CPAP デバイス (モデル %1) はまだテストされていません。 It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card to make sure it works with OSCAR. 動作する可能性がある他のデバイスと十分に似ているように見えますが、OSCAR で動作することを確認するために、開発者に、このデバイスの SD カードの .zip コピーをのご提供をお願いいたします。 SmartStart SmartStart 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 Pt. Access Essentials 基本 Plus Plus Climate Control Climate 制御 Manual 手動 Soft Soft Standard 標準 BiPAP-T BiPAP-T BiPAP-S BiPAP-T BiPAP-S/T BiPAP-S/T SmartStop SmartStop Smart Stop Smart Stop Simple シンプル Advanced アドバンス 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... %2 の %1 データを読み込んでいます... Scanning Files ファイルのスキャン Migrating Summary File Location サマリー ファイルの場所の移行 Loading Summaries.xml.gz Summaries.xml.gz を読み込んでいます Loading Summary Data 概要データを読み込んでいます Please Wait... お待ちください... Permissive Mode 選択モード Total disabled sessions: %1, found in %2 days 無効なセッション数合計: %2 日間に %1 件 Total disabled sessions: %1 無効なセッション数合計: %1 件 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. 最も長い無効セッション: %1 分、すべての向こうセッションの合計時間: %2 分。 Updating Statistics cache 統計キャッシュの更新 Usage Statistics 使用統計 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. インポートされたデータは完全に正確ではない可能性があるため、OSCAR がデータを正しく処理していることを確認するために、開発者に、Viatom ファイルのコピーの提供をお願いいたします。 Viatom Viatom Viatom Software Viatom ソフトウェア 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 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 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 イベント タイプ Löwenstein レーベンシュタイン Prisma Smart プリズスマート Resvent/Hoffrichter Resvent/Hoffrichter iBreeze/Point3 iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings 保存されたレイアウト設定の管理 Add 追加 Add Feature inhibited. The maximum number of Items has been exceeded. 機能のの追加は禁止が行えません。アイテムの最大数を超えました。 creates new copy of current settings. 現在の設定の複製を新しく作成する。 Restore 復元 Restores saved settings from selection. 選択したものから保存された設定を復元する。 Rename 名前を変更 Renames the selection. Must edit existing name then press enter. 選択したものの名前を変更。現在な名称を編集し、ENTERを押して下さい。 Update 更新 Updates the selection with current settings. 選択したものを現在の設定で更新します。 Delete 削除 Deletes the selection. 選択したものを削除します。 Expanded Help menu. ヘルプメニューを開いた状態にします。 Exits the Layout menu. レイアウトメニューを抜けます。 <h4>Help Menu - Manage Layout Settings</h4> <h4>ヘルプメニュー - レイアウト設定を管理</h4> Exits the help menu. ヘルプメニューを抜ける。 Exits the dialog menu. ダイアログメニューを抜ける。 This feature manages the saving and restoring of Layout Settings. この機能はレイアウト設定の保存と復元を行います。 Layout Settings control the layout of a graph or chart. レイアウト設定はグラフあるいはチャートのレイアウトを制御します。 Different Layouts Settings can be saved and later restored. 複数のレイアウト設定を保管し、後に復元することができます。 Button ボタン Description 説明 Creates a copy of the current Layout Settings. 現在のレイアウト設定の複製を作る。 The default description is the current date. 省略時の説明は現在の日付です。 The description may be changed. 説明は変更することができます。 The Add button will be greyed out when maximum number is reached. 最大の数に達したときに追加ボタンはグレイアウトされます。 Other Buttons その他のボタン Greyed out when there are no selections セクションがないときはグレイアウトされます Loads the Layout Settings from the selection. Automatically exits. io 選択内容からレイアウト設定を読み込みます。 自動的に終了します。 io Modify the description of the selection. Same as a double click.io 選択項目の説明を変更します。ダブルクリックと同じです。io Saves the current Layout Settings to the selection. Prompts for confirmation. 確認を求めるプロンプトが表示されます。 Deletes the selecton. 選択項目を削除します。 Control コントロール Exit 終了 (Red circle with a white "X".) Returns to OSCAR menu. Return Next to Exit icon. Only in Help Menu. Returns to Layout menu. Escape Key Exit the Help or Layout menu. Layout Settings * Name * Pinning * Plots Enabled * Height * Order * Event Flags * Dotted Lines * Height Options General Information Maximum description size = 80 characters. Maximum Saved Layout Settings = 30. Saved Layout Settings can be accessed by all profiles. Layout Settings only control the layout of a graph or chart. They do not contain any other data. They do not control if a graph is displayed or not. Layout Settings for daily and overview are managed independantly. <p style="color:black;"> This feature manages the saving and restoring of Layout Settings. <br> Layout Settings control the layout of a graph or chart. <br> Different Layouts Settings can be saved and later restored. <br> </p> <table width="100%"> <tr><td><b>Button</b></td> <td><b>Description</b></td></tr> <tr><td valign="top">Add</td> <td>Creates a copy of the current Layout Settings. <br> The default description is the current date. <br> The description may be changed. <br> The Add button will be greyed out when maximum number is reached.</td></tr> <br> <tr><td><i><u>Other Buttons</u> </i></td> <td>Greyed out when there are no selections</td></tr> <tr><td>Restore</td> <td>Loads the Layout Settings from the selection. Automatically exits. </td></tr> <tr><td>Rename </td> <td>Modify the description of the selection. Same as a double click.</td></tr> <tr><td valign="top">Update</td><td> Saves the current Layout Settings to the selection.<br> Prompts for confirmation.</td></tr> <tr><td valign="top">Delete</td> <td>Deletes the selecton. <br> Prompts for confirmation.</td></tr> <tr><td><i><u>Control</u> </i></td> <td></td></tr> <tr><td>Exit </td> <td>(Red circle with a white "X".) Returns to OSCAR menu.</td></tr> <tr><td>Return</td> <td>Next to Exit icon. Only in Help Menu. Returns to Layout menu.</td></tr> <tr><td>Escape Key</td> <td>Exit the Help or Layout menu.</td></tr> </table> <p><b>Layout Settings</b></p> <table width="100%"> <tr> <td>* Name</td> <td>* Pinning</td> <td>* Plots Enabled </td> <td>* Height</td> </tr> <tr> <td>* Order</td> <td>* Event Flags</td> <td>* Dotted Lines</td> <td>* Height Options</td> </tr> </table> <p><b>General Information</b></p> <ul style=margin-left="20"; > <li> Maximum description size = 80 characters. </li> <li> Maximum Saved Layout Settings = 30. </li> <li> Saved Layout Settings can be accessed by all profiles. <li> Layout Settings only control the layout of a graph or chart. <br> They do not contain any other data. <br> They do not control if a graph is displayed or not. </li> <li> Layout Settings for daily and overview are managed independantly. </li> </ul> <p style="color:black;"> この機能は、レイアウト設定の保存と復元を管理します。 <br> レイアウト設定は、グラフまたはチャートのレイアウトを制御します。 <br> さまざまなレイアウト設定を保存して、後で復元できます。 <br> </p> <table width="100%"> <tr><td><b>ボタン</b></td> <td><b>説明</b></td>< /tr> <tr><td valign="top">追加</td> <td>現在のレイアウト設定のコピーを作成します。 <br> デフォルトの説明は現在の日付です。 <br> 記載内容は変更になる場合があります。 <br> 最大数に達すると、[追加] ボタンはグレー表示になります。</td></tr> <br> <tr><td><i><u>その他のボタン</u> </i>< /td> <td>選択がない場合は灰色表示</td></tr> <tr><td>復元</td> <td>選択範囲からレイアウト設定を読み込みます。 自動的に終了します。 </td></tr> <tr><td>名前の変更 </td> <td>選択範囲の説明を変更します。 ダブルクリックと同じです。</td></tr> <tr><td valign="top">更新</td><td>現在のレイアウト設定を選択に保存します。<br>確認を求めます。< /td></tr> <tr><td valign="top">削除</td> <td>選択したものを削除します。 <br> 確認を求めます。</td></tr> <tr><td><i><u>コントロール</u> </i></td> <td></td></tr > <tr><td>終了</td> <td>(白い「X」が付いた赤い丸。) OSCAR メニューに戻ります。</td></tr> <tr><td>戻る</td> <td>終了アイコンの横。 ヘルプ メニューのみ。 レイアウト メニューに戻ります。</td></tr> <tr><td>エスケープ キー</td> <td>ヘルプまたはレイアウト メニューを終了します。</td></tr> </table> <p> <b>レイアウト設定</b></p> <table width="100%"> <tr> <td>* 名前</td> <td>* ピン留め</td> <td>* プロット有効 < /td> <td>* 高さ</td> </tr> <tr> <td>* 順序</td> <td>* イベント フラグ</td> <td>* 点線</td> <td >* 高さのオプション</td> </tr> </table> <p><b>一般情報</b></p> <ul style=margin-left="20"; > <li> 説明の最大サイズ = 80 文字。 </li> <li> 保存されたレイアウト設定の最大数 = 30。</li> <li> 保存されたレイアウト設定には、すべてのプロフィールからアクセスできます。 <li> レイアウト設定は、グラフまたはチャートのレイアウトのみを制御します。 <br> その他のデータは含まれていません。 <br> グラフを表示するかどうかは制御しません。 </li> <li> デイリーと概要のレイアウト設定は独立して管理されます。 </li> </ul> Maximum number of Items exceeded. アイテムの最大値を超えました。 No Item Selected 選択されたアイテム数 Ok to Update? 更新して良いですか? Ok To Delete? 削除して良いですか? SessionBar %1h %2m %1時間 %2分 No Sessions Present セッションがありません SleepStyleLoader Import Error インポート失敗 This device 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 This fits better in report. %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 に作成されました Device Information デバイス情報 Changes to Device Settings デバイス設定の変更 Database has No %1 data available. Database has %1 day of %2 Data on %3 Database has %1 days of %2 Data, between %3 and %4 Total Days: %1 Days Not Used: %1 Days Used: %1 使用日数: %1 Days %1 %2 Hours: %3 Best Device Setting 最もよいデバイス設定 Worst Device Setting 最も悪いデバイス設定 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 流量: %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% Date: %1 チェーンストークス呼吸: %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 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 レポート ソフトウェアです 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 %3 のデータ %1/%2 %1 days of %2 Data, between %3 and %4 %1 日分の %2 データ、%3 と %4 の間 Days Pressure Relief 圧力解放 Pressure Settings 圧力設定 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 device.</span></p></body></html> <span style=" font-weight:600;">警告:</span><span style=" font-weight:600; color:#ff0000;">コンピュータに挿入する前に</span><span style=" color:#ff0000;">ResMed S9 SDカードはロックする必要があります </span>&nbsp;&nbsp;&nbsp;<span style=" color:#000000;"><br>OSによっては許可を求めずインデックスファイルをカードの書き込み、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 device is detected ResMedデバイスが見つかったため、いくつかの設定が強制されます First import can take a few minutes. 最初のインポートは数分かかります。 The last time you used your %1... 前回%1を使ったのは… last night 昨晩 today 今日 %2 days ago %2日前 was %1 (on %2) %1です(%2) %1 hours, %2 minutes and %3 seconds %1時間 %2 分 %3 秒 <font color = red>You only had the mask on for %1.</font> <font color = red>%1にマスクをしていました。</font> under over reasonably close to Translation needs to indicate the number is on the right. おおよそ右の値に近く equal to 等しい You had an AHI of %1, which is %2 your %3 day average of %4. corrected after review あなたの AHI は %1で、%2 %3、平均は%4でした。 Your pressure was under %1 %2 for %3% of the time. 気圧は%3%で %1 %2でした。 Your EPAP pressure fixed at %1 %2. EPAPの気圧は、%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. EPAPの気圧は %3%の時間で %1 %2 以下でした。 1 day ago 昨日 Your device was on for %1. デバイス稼動時間 %1。 Your CPAP device used a constant %1 %2 of air CPAPデバイスは、%1 %2 の空気を消費 Your device used a constant %1-%2 %3 of air. デバイスは、%1-%2 %3の空気を消費。 Your device was under %1-%2 %3 for %4% of the time. デバイスは%4%で%1-%2 %3以下でした。 Your EEPAP pressure was under %1 %2 for %3% of the time. 計測時間の %3% の間、EEAP圧力は、%1 %2 以下でした。 Your average leaks were %1 %2, which is %3 your %4 day average of %5. corrected missing tranlation of "day average of" 平均の漏れは %1 %2で、%4日間平均の%5 %3でした。 No CPAP data has been imported yet. CPAPのデータはまだインポートされていません。 gGraph Double click Y-axis: Return to AUTO-FIT Scaling Y軸をダブルクリック: スケールを自動調整に戻る Double click Y-axis: Return to DEFAULT Scaling Y 軸をダブルクリック: デフォルトのスケールに戻る Double click Y-axis: Return to OVERRIDE Scaling Y 軸をダブルクリック: 書き換えたスケールに戻る Double click Y-axis: For Dynamic Scaling Y 軸をダブルクリック: 動的なスケールに戻る Double click Y-axis: Select DEFAULT Scaling Y 軸をダブルクリック: デフォルトのスケールを選ぶ Double click Y-axis: Select AUTO-FIT Scaling Y軸をダブルクリック: スケールを自動調整を選ぶ %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.5.1/Translations/Korean.ko.ts000066400000000000000000017143431450332542600203640ustar00rootroot00000000000000 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. 죄송합니다. 릴리스 노트를 찾을 수 없습니다. 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 일지 i This stands for italic to mark-up the notes, together with Bold and Underline 기울임꼴 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 기본 설정 대화 상자에서 키가 0보다 큰 경우 여기에서 체중을 설정하면 체질량지수(BMI) 값이 표시됩니다 Awesome 활기찬 B.M.I. B.M.I.(체질량지수). Bookmarks 북마크 Add Bookmark 북마크 추가 Starts 시작 Remove Bookmark 북마크 삭제 Search 검색 Layout 레이아웃 Save and Restore Graph Layout Settings 그래프 레이아웃 설정 저장 및 복원 Show/hide available graphs. 유효한 그래프를 표시/숨김. Breakdown 고장 events 이벤트 UF1 UF2 Time at Pressure 압력 시간 Clinical Mode 임상 모드 Disabling Sessions requires the Permissive Mode 세션을 비활성화하려면 허용 모드가 필요합니다 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.) (모드 및 압력 설정이 누락되었습니다. 어제와 같습니다.) no data :( 데이터 없음 :( Sorry, this device only provides compliance data. 죄송합니다. 이 장치는 컴플라이언스 데이터만 제공합니다. This bookmark is in a currently disabled area.. 이 북마크는 현재 비활성 영역에 있습니다. CPAP Sessions CPAP 세션 Details 상세 Sleep Stage Sessions 수면 단계 세션 Position Sensor Sessions 위치 센서 세션 Unknown Session 알수없는 세션 Model %1 - %2 모델 %1 - %2 PAP Mode: %1 PAP 모드: %1 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 이 시스템에 원형 차트를 표시할 수 없습니다 "Nothing's here!" "아무것도 없습니다!" No data is available for this day. 이 날은 자료가 없습니다. Oximeter Information 산소측정기 정보 Click to %1 this session. 이 세션에서 %1을 클릭하십시오. Disable Warning 경고 비활성화 Disabling a session will remove this session data from all graphs, reports and statistics. The Search tab can find disabled sessions Continue ? 세션을 사용하지 않도록 설정하면 이 세션 데이터가 제거됩니다 모든 그래프, 보고서 및 통계에서 사용할 수 있습니다. 검색 탭에서 비활성화된 세션을 찾을 수 있습니다 계속하시겠습니까? disable 비활성 enable 활성 %1 Session #%2 %1 세션 #%2 %1h %2m %3s %1시 %2분 %3초 Device Settings 장치 설정 <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 이벤트 분석 This CPAP device does NOT record detailed data 이 CPAP 장치는 상세 데이터를 기록하지 않습니다 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 북마크 at %1 Hide All Events 모든 이벤트 숨김 Show All Events 모든 이벤트 표시 Hide All Graphs 모든 그래프 숨기기 Show All Graphs 모든 그래프 표시 DailySearchTab Match: 일치: Select Match 일치 선택 Clear 지우기 Bookmark Jumps to Date's Bookmark 북마크 날짜의 책갈피로 이동 Start Search 검색 시작 DATE Jumps to Date 날짜. 날짜로 이동 Match 일치 Notes 메모 Notes containing 노트 포함 Bookmarks 북마크 Bookmarks containing 북마크 포함 AHI AHI Daily Duration 일일 지속기간 Session Duration 세션 지속기간 Days Skipped 건너뛴 일 수 Disabled Sessions 비활성화된 세션 Number of Sessions 세션 수 Click HERE to close Help 도움말을 닫으려면 여기를 클릭하십시오 Help 도움말 No Data Jumps to Date's Details 데이터 없음 날짜 세부 정보로 이동 Number Disabled Session Jumps to Date's Details 비활성화된 세션 수 날짜 세부 정보로 이동 Note Jumps to Date's Notes 메모 날짜 노트로 이동 Jumps to Date's Bookmark 날짜의 북마크로 이동 AHI Jumps to Date's Details AHI 날짜 세부 정보로 이동 EventsPerHour 이벤트당시간 Session Duration Jumps to Date's Details 세션 기간 날짜 세부 정보로 이동 Minutes Number of Sessions Jumps to Date's Details 세션 수 날짜 세부 정보로 이동 Sessions 세션 Daily Duration Jumps to Date's Details 일일 지속 시간 날짜 세부 정보로 이동 Hours 시간 Number of events Jumps to Date's Events 이벤트 수 날짜 이벤트로 이동 Events 이벤트 Automatic start 자동 시작 More to Search 검색할 추가 정보 Continue Search 검색 계속 End of Search 검색 종료 No Matches 일치 항목 없음 Skip:%1 건너뛰기:%1 %1/%2%3 days. %1/%2%3일입니다. %1/%2%3 days %1/%2%3 일 Found %1 찾았습니다. %1 Finds days that match specified criteria. 지정된 기준과 일치하는 날짜를 찾습니다. Searches from last day to first day. 마지막 날부터 첫 번째 날까지 검색합니다. First click on Match Button then select topic. 먼저 일치 버튼을 클릭한 다음 주제를 선택합니다. Then click on the operation to modify it. 그런 다음 작업을 클릭하여 수정합니다. or update the value 또는 값을 업데이트합니다 Topics without operations will automatically start. 작업이 없는 항목은 자동으로 시작됩니다. Compare Operations: numberic or character. 연산 비교: 숫자 또는 문자. Numberic Operations: 숫자 연산: Character Operations: 캐릭터 작업: Summary Line 요약 선 Left:Summary - Number of Day searched 왼쪽:요약 - 검색한 일 수 Center:Number of Items Found 센터: 찾은 항목 수 Right:Minimum/Maximum for item searched 오른쪽: 검색된 항목에 대한 최소/최대 Result Table 결과 표 Column One: Date of match. Click selects date. 첫 번째 열: 일치하는 날짜. 클릭하여 날짜를 선택합니다. Column two: Information. Click selects date. 두 번째 열: 정보. 클릭하여 날짜를 선택합니다. Then Jumps the appropiate tab. 그런 다음 해당 탭을 점프합니다. Wildcard Pattern Matching: 와일드카드 패턴 일치: Wildcards use 3 characters: 와일드카드는 3자를 사용합니다: Asterisk 별표 Question Mark 물음표 Backslash. 백슬래시. Asterisk matches any number of characters. 별표가 문자 수와 일치합니다. Question Mark matches a single character. 질문 표시가 단일 문자와 일치합니다. Backslash matches next character. 백슬래시가 다음 문자와 일치합니다. Found %1. %1을 찾았습니다. DateErrorDisplay ERROR The start date MUST be before the end date 오류 시작 날짜는 종료 날짜 이전이어야 합니다 The entered start date %1 is after the end date %2 입력한 시작 날짜%1은( 는) 종료 날짜%2 이후입니다 Hint: Change the end date first 힌트: 먼저 종료 날짜 변경 The entered end date %1 입력한 종료 날짜%1 is before the start date %1 시작 날짜%1 이전입니다 Hint: Change the start date first 힌트: 먼저 시작 날짜 변경 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 device 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 보고서 모드 Show Standard Report 표준 보고서 표시 Standard 표준 Show Monthly Report 월간 보고서 표시 Monthly 월간 Show Range Report 범위 보고서 표시 Date Range 날짜 범위 Select Report Date 보고 날짜 선택 Report Date 보고 날짜 Statistics 통계 Daily 일간 Overview 개요 Oximetry 산소측정 Import 불러오기 Help 도움말 &File &파일 &View &보기 &Reset Graphs &그래프 재설정 &Help &도움말 Troubleshooting 문제 해결 &Data &데이터 &Advanced &고급설정 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 데이터 가져 오기 Standard - CPAP, APAP 표준 - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> <html><head/><body><p>CPAP,APAP,Basic BPAP에 적합한 표준 그래프 순서 </p></html> Advanced - BPAP, ASV 고급 - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> <html><head/><body><p>고급 그래프 순서, BU, ASV, AVAPS, IVAPS에 적합 </p></html> Purge Current Selected Day 현재 선택한 날짜 삭제 &CPAP &양압기 &Oximetry &산소측정기 &Sleep Stage &수면 단계 &Position &위치 &All except Notes &메모를 제외한 모든 항목 All including &Notes 모두 포함하여 &메모 Show &Line Cursor &줄 커서 표시 Purge ALL Device Data 모든 장치 데이터 삭제 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 일별 페이지에 원형 차트 표시 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 불러오기 문제 Couldn't find any valid Device Data at %1 에서 유효한 장치 데이터를 찾을 수 없습니다. %1 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. 이 내용을 읽을 수 있으면 재시작 명령이 작동하지 않았습니다. 수동으로 시작해야 합니다. No help is available. 도움을받을 수 없습니다. You must select and open the profile you wish to modify 수정할 프로필을 선택하고 열어야 합니다 %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. 데이터 카드의 루트 폴더 또는 드라이브 문자를 선택하고 그 안에있는 폴더는 선택하지 마십시오. No supported data was found 지원되는 데이터를 찾을 수 없습니다 Please open a profile first. 먼저 프로필을 여십시오. Check for updates not implemented 구현되지 않은 업데이트 확인 Are you sure you want to rebuild all CPAP data for the following device: 다음 장치의 모든 CPAP 데이터를 재구성하시겠습니까: For some reason, OSCAR does not have any backups for the following device: 어떠한 이유로 OSCAR에는 다음 장치의 백업이 없습니다: 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. 재구성할 내부 백업이 없으므로 사용자가 직접 복원해야 합니다. Note as a precaution, the backup folder will be left in place. 예방 조치로 백업 폴더는 그대로 유지됩니다. OSCAR does not have any backups for this device! OSCAR에는 이 장치에 대한 백업이 없습니다! Unless you have made <i>your <b>own</b> backups for ALL of your data for this device</i>, <font size=+2>you will lose this device's data <b>permanently</b>!</font> <i>당신 <b>스스로</b> 이 장치의 모든 데이터에 대해 백업</i>을 하지 않는 한,<font>이 장치의 데이터는 항상 손실됩니다.</font> You are about to <font size=+2>obliterate</font> OSCAR's device database for the following device:</p> 다음 장치의 OSCAR 장치 데이터베이스를 <font size=+2>삭제</font>하려고 합니다.</p > Are you <b>absolutely sure</b> you want to proceed? <b>무조건</b> 진행하기를 원하십니까? A file permission error caused the purge process to fail; you will have to delete the following folder manually: 파일 사용 권한 오류로 인해 제거 프로세스가 실패했습니다. 다음 폴더를 수동으로 삭제해야 합니다: 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 최신 정보 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의 백업이 비활성화 된 경우 데이터가 손실 될 수 있습니다. Would you like to import from your own backups now? (you will have no data visible for this device until you do) 지금 백업에서 가져오시겠습니까? (이렇게 할 때까지 이 장치의 데이터는 표시되지 않습니다) 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 분석 리포터에 오신 것을 환영합니다 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 하에 자유롭게 배포되었으며 어떠한 보증도 제공하지 않으며 어떠한 목적으로도 적합성을 주장하지 않습니다. This software is being designed to assist you in reviewing the data produced by your CPAP Devices and related equipment. 이 소프트웨어는 CPAP 장치 및 관련 기기에서 생성된 데이터를 검토하는 데 도움이 되도록 설계되었습니다. 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 선택한 기간으로보기 재설정 Layout 레이아웃 Save and Restore Graph Layout Settings 그래프 레이아웃 설정 저장 및 복원 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) Hide All Graphs 모든 그래프 숨기기 Show 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> 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. 산소측정기가 시계에 내장 된 시간을 보고 싶습니다. <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 &정보 페이지 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 일 녹화가 시작됨(일반적으로 시작되었음) I started this oximeter recording at (or near) the same time as a session on my CPAP device. CPAP 장치의 세션과 같은 시간(또는 가까운 시간)에 이 옥시미터 기록을 시작했습니다. <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 짧은 세션 무시 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 카드 백업 압축 (처음 가져 오기는 느리지만 백업 데이터를 더 작게 만듭니다) This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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>Provide an alert when importing data from any device 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 device 테스트되지 않은 장치에서 데이터를 가져올때 경고 &CPAP &양압기 This calculation requires Total Leaks data to be provided by the CPAP device. (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 장치에서 Total Leaks 데이터가 제공되어야 합니다(예를 들어 PRS1이 제공되지만 ResMed는 제공되지 않습니다). 여기서 사용되는 의도하지 않은 누출 계산은 선형이며 마스크 환기구 곡선을 모델링하지 않습니다. 몇 가지 다른 마스크를 사용하는 경우 대신 평균 값을 선택하십시오. 아직 충분히 가까울 거예요. Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device missed. This option must be enabled before import, otherwise a purge is required. 실험 이벤트 플래그 기능 향상을 활성화/비활성화합니다. 이를 통해 경계선 이벤트 및 일부 장치가 누락되었음을 검출할 수 있습니다. 가져오기 전에 이 옵션을 사용하도록 설정해야 합니다. 그렇지 않으면 삭제가 필요합니다. This experimental option attempts to use OSCAR's event flagging system to improve device detected event positioning. 이 실험 옵션에서는 OSCAR의 이벤트 플래깅 시스템을 사용하여 장치에서 검출된 이벤트 포지셔닝을 개선하려고 합니다. Resync Device Detected Events (Experimental) 장치 검출 이벤트 재동기(실험) Allow duplicates near device events. 장치 이벤트 근처에서 복제를 허용합니다. Show flags for device detected events that haven't been identified yet. 아직 식별되지 않은 장치에서 탐지된 이벤트에 대한 플래그를 표시합니다. Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. 이 사용법을 적용한 날을 "불일치"라고 간주 하십시오. 4 시간은 일반적으로 준수로 간주됩니다. hours 시간 Flow Restriction 유량 제한 Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. 중간 값에서 기류 제한 비율. 20 %의 값은 무호흡을 감지하는데 적합합니다. Duration of airflow restriction 기류 제한 기간 s Event Duration 이벤트 기간 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 이벤트 분석 파이 차트에 표시 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 확인을 요청하지 않고 가져오기 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><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">이 시간보다 짧은 세션은 표시되지 않습니다.</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></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>참고: 이것은 시간대 보정을 위한 것이 아닙니다! 운영 체제의 시계와 시간대 설정이 올바르게 설정되어 있는지 확인하세요.</p></body></html> Hours 시간 <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">커스텀 플래깅은 장치에서 누락된 이벤트를 검출하는 실험적인 방법입니다. 그것들은</span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;"> AHI에 포함되지 않는다.</span></p></body></html> 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 사용자 이벤트 신고 <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices do not support changing these settings.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">메모: </span>요약 설계 제한으로 인해 ResMed 장치는 이러한 설정 변경을 지원하지 않습니다.</p></body></html> <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:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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><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:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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;">not</span><span style=" font-family:'Sans'; font-size:10pt;">동기화하는 데 필요한 올바른 타임스탬프가 있다.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">시리얼 Import 프로세스에서는 어젯밤 첫 번째 CPAP 세션의 시작 시간이 걸립니다.(CPAP 데이터를 먼저 Import하는 것을 잊지 마십시오.)</span></p></body></html> 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 데이터를 더 작게 만들지만 하루 변경 속도가 느려짐.) <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(압력) 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 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 압력에서 마스크 배출 속도 Clinical 임상 Clinical Settings 임상 설정 Select Oscar Operating Mode 오스카 작동 모드 선택 Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. 임상 모드는 비활성화된 세션을 허용하지 않습니다.\n비활성화된 세션은 그래프 또는 통계에 사용되지 않습니다. Clinical Mode 임상 모드 permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. 허용 모드는 비활성화된 세션을 허용합니다.\n비활성화된 세션은 그래프 및 통계에 사용됩니다. Permissive Mode 허용 모드 Hours 시간 Oximetry Settings 산소측적기 설정 <!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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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> SpO2Review에서 가져온 CMS50 데이터(.spoR 파일) 또는 직렬 가져오기 방법을 통해 가져온 데이터는 동기화에 필요한 올바른 타임스탬프가 없습니다. 실시간 보기 모드(직렬 케이블을 사용)는 CMS50 산소포화도 측정기에서 정확한 동기화를 달성하는 한 가지 방법이지만, CPAP 시계 표류를 보정하지는 않습니다. CMS50 산소포화도 측정기의 녹화 모드를 시작하는 시간과 CPAP 장치를 시작하는 시간을 정확히 동일한 시간에 시작한다면 동기화를 달성할 수 있습니다. 직렬 가져오기 프로세스는 전날 밤의 첫 CPAP 세션의 시작 시간을 가져옵니다. (CPAP 데이터를 먼저 가져온 것을 기억하세요!)</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. 오스카의 테스트 버전을 사용하고 있습니다. 테스트 버전은 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. 픽셀맵 캐싱은 그래픽 가속 기술입니다. 플랫폼의 그래프 표시 영역에서 글꼴 그리기에 문제가 발생할 수 있습니다. Use 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 x축 레이블을 두번 클릭하여 y축 배율을 변경할 수 있는지 여부 Allow YAxis Scaling Y축 스케일링 허용 Whether to include device serial number on device 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> Print reports in black and white, which can be more legible on non-color printers 컬러 프린터가 아닌 다른 프린터에서 더 쉽게 읽을 수 있는 흑백 보고서 인쇄 Print reports in black and white (monochrome) 흑백으로 보고서 인쇄(단색) For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. 여러 세션의 경우 이벤트 플래그 그래프 상단에 각 세션에 대한 가는 회색 선을 표시합니다. Enables SessionBar in Event Flags Graph 이벤트 플래그 그래프에서 세션바 활성화 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 개요 No CPAP devices detected CPAP 장치가 검출되지 않았습니다 Will you be using a ResMed brand device? ResMed 브랜드 장치를 사용하시겠습니까? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, days will <b>split at noon</b> like in ResMed's commercial software.</p> <b> ResMed </b> 장치에서는 OSCAR의 고급 세션 분할 기능을 사용할 수 없습니다.이는 설정 및 요약 데이터의 저장 방법에 제한이 있기 때문입니다.따라서 이 프로파일에는 OSCAR가 비활성화되어 있습니다.</p><p>ResMed 장치에서는 ResMed의 상용 소프트웨어와 마찬가지로 낮 12시에 일수가 분할됩니다.</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? 변경 사항 중 하나 이상을 적용하면 이러한 변경 사항을 적용하기 위해이 응용 프로그램을 다시 시작해야합니다. 지금 해보시겠습니까? ResMed S9 devices routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). ResMed S9 장치는 SD카드에서 7~30일 이상 경과한 특정 데이터를 정기적으로 삭제합니다(해상도에 따라 다름). 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 플래그 Clinical Mode: 임상 모드: Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. 허용 모드에서 선택 해제된 모든 데이터를 포함하여 데이터 카드에 있는 내용을 모두 보고합니다. Basically replicates the reports and data stored on the devices data card. 기본적으로 장치 데이터 카드에 저장된 보고서 및 데이터를 복제합니다. This includes pap devices, oximeters, etc. Compliance reports fall under this mode. 여기에는 양압기 장치, 산소 농도계 등이 포함됩니다. 순응 보고서는 이 모드에 속합니다. Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. 순응 보고서에는 선택이 취소된 경우에도 항상 선택한 순응 기간 내의 모든 데이터가 포함됩니다. Permissive Mode: 허용 모드: Allows user to select which data sets/ sessions to be used for calculations and display. 사용자가 계산 및 표시에 사용할 데이터 세트/세션을 선택할 수 있습니다. Additional charts and calculations may be available that are not available from the vendor data. 공급업체 데이터에서 사용할 수 없는 추가 차트 및 계산을 사용할 수 있습니다. Changing the Oscar Operating Mode: 오스카 작동 모드 변경: Requires a reload of the user's profile. Data will be saved and restored. 사용자 프로필을 새로고침해야 합니다. 데이터가 저장되고 복원됩니다. Minor Flag 마이너 플래그 Span 걸침 Always Minor 항상 사소한 Never 결코 This may not be a good idea 이것은 좋은 생각이 아닐 수도 있습니다 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 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 Length: %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 milliSeconds 밀리초 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 호기(날숨)압력 EEPAP 호기 양압 Min EEPAP 최소 호기 양압 Max EEPAP 최대 호기 양압 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 목표 최소 환기량 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 압력 완화 Device 장치 No Data Available 자료 없습니다 App key: 앱 키: Operating system: 운영 체제 : Built with Qt %1 on %2 %2에 Qt %1로 빌드 Graphics Engine: 그래픽 엔진 : Graphics Engine type: 그래픽 엔진 유형 : Compiler: 컴파일러: Software Engine 소프트웨어 엔진 ANGLE / OpenGLES Desktop OpenGL 데스크톱 OpenGL m cm in kg l/min Only Settings and Compliance Data Available 설정 및 순응 데이터만 사용 가능 Summary Data Only 요약 데이터만 Bookmarks 북마크 Mode 모드 Model 모델 Brand 브랜드 Serial 시리얼 Series 시리즈 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 device's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. 불러온 데이터가 완전히 정확하지는 않을 수 있습니다.따라서 개발자는 OSCAR가 데이터를 올바르게 처리하고 있는지 확인하기 위해 이 장치의 SD 카드의 .zip 복사와 일치하는 임상의의 .pdf 보고서를 원합니다. Non Data Capable Device 데이터 용량 장치가 없습니다 Your %1 CPAP Device (Model %2) is unfortunately not a data capable model. 당신의 %1 CPAP 장치(%2)는 안타깝게도 데이터 지원 모델이 아닙니다. I'm sorry to report that OSCAR can only track hours of use and very basic settings for this device. OSCAR는 이 장치의 사용시간과 기본적인 설정만을 추적할 수 있습니다. Device Untested 테스트되지 않은 장치 Your %1 CPAP Device (Model %2) has not been tested yet. 당신의 %1 CPAP 장치(모델 %2)가 아직 테스트되지 않았습니다. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card and matching clinician .pdf reports to make sure it works with OSCAR. 다른 장치와 마찬가지로 동작하는 것 같습니다만, 개발자는 OSCAR에서 동작하는 것을 확인하기 위해서, 이 장치의 SD 카드의 .zip 카피와 일치하는 임상의의 .pdf 리포트를 요구하고 있습니다. Device Unsupported 지원되지 않는 장치 Sorry, your %1 CPAP Device (%2) is not supported yet. 죄송합니다. %1 CPAP 장치(%2)는 아직 지원되지 않습니다. The developers need a .zip copy of this device's SD card and matching clinician .pdf reports to make it work with OSCAR. 개발자들은 OSCAR와 함께 작동하기 위해 이 기기의 SD 카드의 .zip 복사본과 일치하는 임상의 .pdf 보고서가 필요합니다. Getting Ready... 준비 중 ... Scanning Files... 파일 스캔 중 ... Importing Sessions... 세션 가져 오는 중 ... UNKNOWN 알 수 없는 APAP (std) APAP (dyn) Auto S Auto S/T AcSV SoftPAP Mode Pressure relief during exhalation 호기 중 압력 완화 Slight 근소한 Softstart pressure 소프트스타트 압력 Pressure during soft start period 소프트 스타트 기간 동안의 압력 PSoft Softstart minimum pressure 소프트스타트 최소 압력 Minimum pressure during soft start period 소프트 스타트 기간 동안의 최소 압력 PSoftMin Auto start 자동 시작 Automatically turn on the device by breathing 호흡으로 장치를 자동으로 켭니다 Softstart time 소프트스타트 시간 Lenght of soft start period 소프트 스타트 기간 Soft start maximum time 소프트 스타트 최대 시간 Maximum lenght of soft start period 소프트 스타트 기간의 최대 길이 Soft start max. time 소프트 스타트 최대. 시간 Soft start pressure 소프트 스타트 압력 Higher End Expiratory Pressure 더 높은 최종 호기압 Humidifier level 가습기 수준 Tube type 튜브형 Obstruction level 방해 수준 Obstruction level in percentage 방해 수준(백분율) rRMVFluctuation Relative respiratory minute volume fluctuation 상대 호흡량 변동 Relative respiratory minute volume 상대적인 분당 호흡량 Measured pressure 측정된 압력 Full flow 전체 흐름 Artefact 인공물 Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) 호흡 이벤트(예: 삼키기, 기침 또는 말하기)를 나타내지 않는 측정된 데이터의 불규칙성 Epoch (2 mins) with Flow Limitation 흐름 제한이 있는 에포크(2분) Deep Sleep 깊은 잠 Deep sleep, stable respiration 깊은 수면, 안정적인 호흡 Timed breath 시간 제한 호흡 BiSoft Mode BiSoft 1 BiSoft 2 TriLevel AutoStart 자동시작 PMaxOA EEPAPMin Lower End Expiratory Pressure 하단 호기압 EEPAPMax Obstruction Level 방해 수준 rRMV PressureMeasured 압력 측정 ART CriticalLeak 치명적인 누출 Mask leakage is above a critical treshold 마스크 누출이 임계 임계값을 초과함 CL eMO Epoch (2 mins) with Mild Obstruction 경과 시간 (2 분) 동안의 경도한 폐색 eSO Epoch (2 mins) with Severe Obstruction 심한 폐색이 있는 경과 시간 (2 분) eS Epoch (2 mins) with Snoring 코골이가 있는 경과 시간 (2 분) eFL DeepSleep 깊은 잠 DS Finishing up... 끝내는 중 ... 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 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 마스크 저항. 잠금 A few breaths automatically starts device 몇 번 숨을 쉬면 장치가 자동으로 시작됩니다 Device automatically switches off 장치가 자동으로 꺼짐 Whether or not device allows Mask checking. 장치에서 마스크 체크를 허용하는지 여부입니다. 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 자동 평가 기간 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 확인되지 않음 : 최대 흡기 흐름 추세에서 크게 벗어난 기간 인 가변 호흡 가능성 A period during a session where the device could not detect flow. 장치가 흐름을 검출할 수 없었던 세션 기간. 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 자동 켜기 Auto Off 자동 끔 Mask Alert 마스크 경고 Show AHI AHI 보기 Whether or not device shows AHI via built-in display. 장치에 내장된 디스플레이를 통해 AHI가 표시되는지 여부입니다. The number of days in the Auto-CPAP trial period, after which the device will revert to CPAP Auto-CPAP 트라이얼 기간(장치가 CPAP로 복귀할 때까지의 일수) Breathing Not Detected 호흡 무감지 BND BND(호흡무) Timed Breath 시측된 호흡 Machine Initiated Breath 기기 개시 호흡 TB TB Windows User Windows 사용자 Using 사용 , found SleepyHead - , SleepyHead 발견 - You must run the OSCAR Migration Tool OSCAR 마이그레이션 도구를 실행해야합니다 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> <i>Your old device data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> <i>이전 데이터 Import 시 기본 설정에서 이 백업 기능을 사용하지 않도록 설정하지 않은 경우 이전 장치 데이터를 재생성해야 합니다.</i> OSCAR does not yet have any automatic card backups stored for this device. OSCAR에는 아직이 장치 용으로 저장된 자동 카드 백업이 없습니다. This means you will need to import this device 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의 새 버전을 실행할 수 있습니까? Device Database Changes 장치 데이터베이스 변경 Sorry, the purge operation failed, which means this version of OSCAR can't start. 죄송합니다. 제거 작업이 실패했습니다. 즉,이 OSCAR 버전을 시작할 수 없음을 의미합니다. The device data folder needs to be removed manually. 장치 데이터 폴더를 수동으로 제거해야 합니다. 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를 다시 시작하고 업그레이드 프로세스를 완료하십시오. Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. 업그레이드하면 더 이상 이전 버전에서이 프로필을 사용할 수 <font size = + 1> 할 수 없습니다 </font>. 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 교차성 호흡의 이상한시기 Cheyne Stokes Respiration (CSR) Cheyne Stokes 호흡(CSR) Periodic Breathing (PB) 주기적 호흡(PB) Clear Airway (CA) 열린 기도 (CA) Obstructive Apnea (OA) 폐쇄성 무호흡(OA) Hypopnea (H) 저호흡 (H) An apnea that couldn't be determined as Central or Obstructive. 중추 또는 폐쇠로 판단할 수 없는 무호흡증. Unclassified Apnea (UA) 미분류 무호흡(UA) Apnea (A) 호흡(A) An apnea reportred by your CPAP device. CPAP 장치에 의해 보고된 무호흡입니다. A restriction in breathing from normal, causing a flattening of the flow waveform. 정상에서 호흡을 제한하여 흐름 파형을 평평하게 만듭니다. Flow Limitation (FL) 흐름 제한(FL) RERA (RE) 각성(RERA) (RE) Vibratory Snore (VS) 진동 코골이(VS) Vibratory Snore (VS2) 코골이 (VS2) Leak Flag (LF) 누수 플래그 (LF) A large mask leak affecting device performance. 장치 성능에 영향을 주는 대량의 마스크누출. Large Leak (LL) 대량 누출(LL) Non Responding Event (NR) 응답 이벤트 없음(NR) Expiratory Puff (EP) 호기 날숨 (EP) SensAwake (SA) User Flag #1 (UF1) 사용자 플래그 #1 (UF1) User Flag #2 (UF2) 사용자 플래그 #2 (UF2) User Flag #3 (UF3) 사용자 플래그 #3 (UF3) Pulse Change (PC) 펄스 변경(PC) SpO2 Drop (SD) A ResMed data item: Trigger Cycle Event ResMed 데이터 항목 : 트리거주기 이벤트 Apnea Hypopnea Index (AHI) 무저호흡지수(AHI) Respiratory Disturbance Index (RDI) 호흡장애지수(RDI) 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 기도 폐쇄로 인한 무호흡 A partially obstructed airway 부분적으로 폐쇄된 기도 UA A vibratory snore 코골이 Pressure Pulse 압력변화(PP) A pulse of pressure 'pinged' to detect a closed airway. 폐쇄된 기도를 감지하기 위해 '반복된 '압력 변화. A type of respiratory event that won't respond to a pressure increase. 압력 증가에 반응하지 않는 호흡 이벤트 유형. Intellipap event where you breathe out your mouth. 입김을 내뿜는 Intellipap 이벤트. SensAwake feature will reduce pressure when waking is detected. SensAwake 기능은 잠에서 깨어 났을때 압력을 감소시킵니다. Heart rate in beats per minute 분당 비트 수의 심박수 Blood-oxygen saturation percentage 혈액-산소 포화율 Plethysomogram 혈구 혈압 An optical Photo-plethysomogram showing heart rhythm 박동을 보여주는 광학적 사진-생리학 A sudden (user definable) change in heart rate 갑작스런 (사용자가 정의할수 있는) 심박수 변화 A sudden (user definable) drop in blood oxygen saturation 갑작스런 (사용자가 정의할수 있는) 혈중 산소 포화도 감소 SD Breathing flow rate waveform 호흡 유량 파형 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 설정 EPAP Setting EPAP 설정 CSR An abnormal period of Periodic Breathing 주기적 호흡의 이상기 - 무호흡과 저호흡이 주기적(3회이상)으로 나타남 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 최대 누출 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 중간 누출 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 PAP 모드 Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. Channels.xml을 구문 분석할 수 없습니다. OSCAR을 계속할 수 없으며 종료 중입니다. End Expiratory Pressure 호기말 압력 An apnea reported by your CPAP device. CPAP 장치에서 보고된 무호흡. Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. 호흡 노력 관련 각성: 각성 또는 수면 장애를 유발하는 호흡 제한. A vibratory snore as detected by a System One device System One 장치에서 감지한 진동성 코골이 I/E Value 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. 선택한 폴더에 올바른 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, 만약 당신이 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? 이 폴더를 사용 하시겠습니까? OSCAR Reminder OSCAR 알림 Don't forget to place your datacard back in your CPAP device 데이터카드를 CPAP 장치에 다시 배치하는 것을 잊지 마십시오 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 oximetry 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(이중형) (가변 압력) 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) Min EEPAP %1 Max EEPAP %2 PDIFF %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 개인 수면 코치 Selection Length 선택 길이 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 날숨 압력 완화 수준 Device auto starts by breathing 장치 자동은 호흡으로 시작합니다 Response 반응 Device auto stops by breathing 호흡으로 장치 자동 중지 Patient View 환자 보기 RiseEnable RiseTime Cycle Trigger TiMax TiMin Your ResMed CPAP device (Model %1) has not been tested yet. ResMed CPAP 장치(%1)는 아직 테스트되지 않았습니다. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card to make sure it works with OSCAR. 다른 장치와 마찬가지로 동작하는 것 같습니다만, OSCAR에서 동작하는 것을 확인하기 위해서, 개발자는 이 장치의 SD 카드의 .zip 카피를 입수하고 싶다고 생각하고 있습니다. SmartStart 스마트 스타트 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 스마트 스톱 Smart Stop 스마트 스톱 Simple 간단 Advanced 고급 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... 잠시만 기다려 주세요... Permissive Mode 허용 모드 Total disabled sessions: %1, found in %2 days 비활성화된 총 세션: %1, %2일 내에 발견됨 Total disabled sessions: %1 비활성화된 총 세션: %1 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. 비활성화된 가장 긴 세션 시간: %1분, 비활성화된 모든 세션의 총 시간: %2분. Updating Statistics cache 통계 캐시 업데이트 Usage Statistics 사용 통계 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개 Löwenstein Prisma Smart Resvent/Hoffrichter iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings 레이아웃 설정 저장 관리 Add 추가 Add Feature inhibited. The maximum number of Items has been exceeded. 기능 추가가 금지되었습니다. 최대 항목 수를 초과했습니다. creates new copy of current settings. 현재 설정의 사본 생성. Restore 복원 Restores saved settings from selection. 선택 항목에서 저장된 설정을 복원. Rename 이름변경 Renames the selection. Must edit existing name then press enter. 선택한 이름을 변경. 기존 이름을 편집한 후 Enter 키를 눌러야 합니다. Update 업데이트 Updates the selection with current settings. 선택 영역을 현재 설정으로 업데이트. Delete 삭제 Deletes the selection. 선택 항목 삭제. Expanded Help menu. 도움말 메뉴 확장. Exits the Layout menu. 레이아웃 메뉴 종료. <h4>Help Menu - Manage Layout Settings</h4> <h4>도움말 메뉴 - 레이아웃 설정 관리 </h4> Exits the help menu. 도움말 메뉴 종료. Exits the dialog menu. 메뉴 대화 상자 종료. This feature manages the saving and restoring of Layout Settings. 이 기능은 레이아웃 설정의 저장 및 복원을 관리합니다. Layout Settings control the layout of a graph or chart. 레이아웃 설정은 그래프 또는 차트의 레이아웃을 제어합니다. Different Layouts Settings can be saved and later restored. 다른 레이아웃 설정을 저장하고 나중에 복원할 수 있습니다. Button 버튼 Description 설명 Creates a copy of the current Layout Settings. 현재 레이아웃 설정의 복사본을 만듭니다. The default description is the current date. 기본 설명은 현재 날짜입니다. The description may be changed. 설명은 변경될 수 있습니다. The Add button will be greyed out when maximum number is reached. 최대 수에 도달하면 추가 버튼이 회색으로 표시됩니다. Other Buttons 기타 버튼 Greyed out when there are no selections 선택 항목이 없을 때 회색으로 표시됨 Loads the Layout Settings from the selection. Automatically exits. io 선택 항목에서 레이아웃 설정을 로드합니다. 자동으로 종료됩니다. io Modify the description of the selection. Same as a double click.io 선택 항목에 대한 설명을 수정합니다. 더블클릭과 동일.io Saves the current Layout Settings to the selection. 현재 레이아웃 설정을 선택 항목에 저장합니다. Prompts for confirmation. 확인하라는 메시지가 표시됩니다. Deletes the selecton. 선택을 삭제합니다. Control 제어 Exit 나가기 (Red circle with a white "X".) Returns to OSCAR menu. (흰색 "X"가 있는 빨간색 원.) OSCAR 메뉴로 돌아갑니다. Return 리턴 Next to Exit icon. Only in Help Menu. Returns to Layout menu. 종료 아이콘 옆에 있습니다. 도움말 메뉴에서만. 레이아웃 메뉴로 돌아갑니다. Escape Key Escape Key Exit the Help or Layout menu. 도움말 또는 레이아웃 메뉴를 종료합니다. Layout Settings 레이아웃 설정 * Name * 이름 * Pinning * 고정 * Plots Enabled * 플롯 활성화 * Height * 높이 * Order * Event Flags * 이벤트 플래그 * Dotted Lines * 점선 * Height Options * 높이 옵션 General Information 일반 정보 Maximum description size = 80 characters. 최대 설명 크기 = 80자. Maximum Saved Layout Settings = 30. 최대 저장된 레이아웃 설정 = 30. Saved Layout Settings can be accessed by all profiles. 저장된 레이아웃 설정은 모든 프로필에서 액세스할 수 있습니다. Layout Settings only control the layout of a graph or chart. 레이아웃 설정은 그래프 또는 차트의 레이아웃만 제어합니다. They do not contain any other data. 다른 데이터는 포함하지 않습니다. They do not control if a graph is displayed or not. 그래프 표시 여부는 제어하지 않습니다. Layout Settings for daily and overview are managed independantly. 일일 및 개요에 대한 레이아웃 설정은 독립적으로 관리됩니다. Maximum number of Items exceeded. 최대 항목 수를 초과했습니다. No Item Selected 선택한 항목 없음 Ok to Update? 업데이트해도 되겠습니까? Ok To Delete? 삭제해도 되겠습니까? SessionBar %1h %2m %1시 %2분 No Sessions Present 현재 세션이 없습니다 SleepStyleLoader Import Error 불러오기 에러 This device 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에 작성되었습니다 Device Information 장치 정보 Changes to Device Settings 장치 설정 변경 Database has No %1 data available. 데이터베이스에 사용 가능한 %1 데이터가 없습니다. Database has %1 day of %2 Data on %3 데이터베이스는 %3에 %2 데이터의 %1일 있음 Database has %1 days of %2 Data, between %3 and %4 데이터베이스에 %3과 %4 사이의 %2 데이터가 %1일 있음 Total Days: %1 총 일수: %1 Days Not Used: %1 사용하지 않은 일수: %1 Days Used: %1 사용일 : %1 Days %1 %2 Hours: %3 일 %1 %2 시간: %3 Best Device Setting 최고의 장치 설정 Worst Device Setting 최악의 기기 설정 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 보고서 소프트웨어입니다 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 압력 설정 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 device.</span></p></body></html> <span style=" font-weight:600;">경고: </span><span style=" font-weight:600; color:#ff0000;">컴퓨터에 삽입하기 전에.&nbsp;&nbsp;&nbsp;</span><span style=" color:#ff0000;">Resemed S9 SDCards를 잠글 필요가 있다. </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 device is detected ResMed 장치가 검출되면 일부 설정이 강제됩니다 First import can take a few minutes. 처음 가져오기시 몇 분이 소요될수 있습니다. The last time you used your %1... 마지막으로 %1을 사용했습니다 ... last night 지난밤 today 오늘 %2 days ago %2 일전 was %1 (on %2) %1 (%2) 사용함 %1 hours, %2 minutes and %3 seconds %1 시, %2 분 %3 초 <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 pressure was under %1 %2 for %3% of the time. 귀하의 압력은 %3 % 시간 동안 %1 %2 이하였습니다. 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 미만이었습니다. 1 day ago 1일전 Your device was on for %1. 장치가 %1 동안 켜져 있었습니다. Your CPAP device used a constant %1 %2 of air CPAP 장치가 일정 %1 %2의 공기를 사용했습니다 Your device used a constant %1-%2 %3 of air. 장치가 일정 %1-%2 %3의 공기를 사용했습니다. Your device was under %1-%2 %3 for %4% of the time. %4% 동안 장치가 %1-%2 %3 미만이었습니다. Your EEPAP pressure was under %1 %2 for %3% of the time. EEPAP 압력이 시간의 %3% 동안 %1 %2 미만이었습니다. 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 Double click Y-axis: Return to AUTO-FIT Scaling Y축을 두 번 클릭합니다. AUTO-FIT 스케일링으로 돌아가기 Double click Y-axis: Return to DEFAULT Scaling Y축을 두 번 클릭합니다. 기본 스케일링으로 돌아가기 Double click Y-axis: Return to OVERRIDE Scaling Y축을 두 번 클릭합니다. 크기 조정 재정의로 돌아가기 Double click Y-axis: For Dynamic Scaling Y축을 두 번 클릭합니다. 동적 스케일링의 경우 Double click Y-axis: Select DEFAULT Scaling Y축을 두 번 클릭합니다. 기본 스케일링 선택 Double click Y-axis: Select AUTO-FIT Scaling Y축을 두 번 클릭합니다. AUTO-FIT 스케일링 선택 %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.5.1/Translations/Magyar.hu.ts000066400000000000000000016556601450332542600203760ustar00rootroot00000000000000 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. 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ó i i 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 Search Keresés Layout Save and Restore Graph Layout Settings 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 Clinical Mode Disabling Sessions requires the Permissive Mode 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.) no data :( nincs adat :( Sorry, this device only provides compliance data. Elnézést, ez a készülék csak teljesítés adatokat kínál. 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 Model %1 - %2 Model %1 - %2 PAP Mode: %1 PAP mód: %1 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 "Nothing's here!" "Nincs itt semmi!" No data is available for this day. Nem érhető el adat ezen a napon. 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 Device Settings Készülék beállítások <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 This CPAP device does NOT record detailed data Ez a CPAP készülék nem rögzít részletes adatokat 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?? 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 Hide All Events Minden esemény elrejtése Show All Events Minden esemény mutatása Hide All Graphs Show All Graphs DailySearchTab Clear Bookmark Jumps to Date's Bookmark Start Search DATE Jumps to Date Match Notes Jegyzetek Notes containing Bookmarks Bookmarks containing AHI Daily Duration Session Duration Days Skipped Disabled Sessions Number of Sessions Click HERE to close Help Help Súgó No Data Jumps to Date's Details Number Disabled Session Jumps to Date's Details Note Jumps to Date's Notes AHI Jumps to Date's Details EventsPerHour Session Duration Jumps to Date's Details Minutes Number of Sessions Jumps to Date's Details Sessions Daily Duration Jumps to Date's Details Hours Number of events Jumps to Date's Events Events Automatic start Continue Search End of Search No Matches Skip:%1 %1/%2%3 days %1 nap {1/%2%3 ?} Found %1 Finds days that match specified criteria. Searches from last day to first day. First click on Match Button then select topic. Then click on the operation to modify it. or update the value Topics without operations will automatically start. Compare Operations: numberic or character. Numberic Operations: Character Operations: Summary Line Left:Summary - Number of Day searched Center:Number of Items Found Right:Minimum/Maximum for item searched Result Table Column One: Date of match. Click selects date. Column two: Information. Click selects date. Then Jumps the appropiate tab. Wildcard Pattern Matching: Wildcards use 3 characters: Asterisk Question Mark Backslash. Asterisk matches any number of characters. Question Mark matches a single character. Backslash matches next character. DateErrorDisplay ERROR The start date MUST be before the end date HIBA A kezdődátum meg kell előzze a záró dátumot The entered start date %1 is after the end date %2 A megadott kezdő dátum (%1) a záró dátum (%2) után van Hint: Change the end date first Javaslat: Előbb a záró dátumot válassza ki The entered end date %1 A megadott záró dátum %1 is before the start date %1 a kezdődátum előtt van %1 Hint: Change the start date first Javaslat: Előbb a kezdő dátumot válassza ki 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 device Record cannot be imported in this profile. A készülék á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 Show Standard Report Standard Általános Show Monthly Report Monthly Havi Show Range Report Date Range Dátum intervallum Select Report Date Report Date 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 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 - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> Advanced - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> 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 Purge ALL Device Data Minden készülék adat 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 Couldn't find any valid Device Data at %1 Nem található érvényes készülék adat itt: %1 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. No help is available. Súgó nem áll rendelkezésre. You must select and open the profile you wish to modify %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. No supported data was found 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 Are you sure you want to rebuild all CPAP data for the following device: Biztosan újra szeretné építeni az összes CPAP adatot a következő készülék részére: For some reason, OSCAR does not have any backups for the following device: Valamiért az OSCAR nem rendelkezik biztonsági mentéssel a következő készülékről: 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. 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. OSCAR does not have any backups for this device! Az OSCAR nem rendelkezik biztonsági mentéssel erről a készülékről! Unless you have made <i>your <b>own</b> backups for ALL of your data for this device</i>, <font size=+2>you will lose this device's data <b>permanently</b>!</font> Ha csak nem készített <i><b>saját</b> mentést minden adatról ehhez a készülékhez</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 device database for the following device:</p> <font size=+2>Kitörölni</font> készül az OSCAR készülék adatbázisát a következő készülékhez kapcsolódóan:</p> Are you <b>absolutely sure</b> you want to proceed? <b>Egészen biztos</b> benne, hogy folytatni akarja? A file permission error caused the purge process to fail; you will have to delete the following folder manually: 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 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. Would you like to import from your own backups now? (you will have no data visible for this device until you do) Szeretne a saját biztonsági mentéséből importálni most? (nem lesz látható semmilyen adat ehhez a készülékhez amíg ezt nem teszi meg) 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ő 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. This software is being designed to assist you in reviewing the data produced by your CPAP Devices and related equipment. Ez a szoftver azért készült, hogy segítse Önt abban, hogy ellenőrizni tudja a CPAP készüléke által előállított adatokat. 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 Layout Save and Restore Graph Layout Settings 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) Hide All Graphs Show All Graphs 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> 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. <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 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 I started this oximeter recording at (or near) the same time as a session on my CPAP device. Nagyjából akkor indítottam az oximétert amikor a CPAP készüléket. <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 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) This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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 készülékeken. A ResMed S9 series készülékek 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>Provide an alert when importing data from any device 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 készülékről importálnak.</p></body></html> Warn when importing data from an untested device Figyelmeztetés ha nem tesztelt készülékről importálnak adatot &CPAP &CPAP This calculation requires Total Leaks data to be provided by the CPAP device. (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. Ehhez a számításhoz a CPAP-készülék által szolgáltatott teljes szivárgási adatokat kell megadni. (Pl. PRS1, de nem a ResMed, amely már rendelkezik ezekkel). Az itt használt Unintentional Leak számítások lineárisak, nem modellezik a maszk szellőztetési görbéjét. Ha több különböző maszkot használ, válasszon inkább átlagértékeket. Ennek még mindig elég közel kell lennie. Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device 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 készülék 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. This experimental option attempts to use OSCAR's event flagging system to improve device detected event positioning. Ez a kísérleti lehetőség az OSCAR eseményjelző rendszerét próbálja használni az eszköz által észlelt események helymeghatározásának javítására. Resync Device Detected Events (Experimental) Készülék által érzékelt események újraszinkronizálása (Kísérleti) Allow duplicates near device events. Duplikációk engedélyezése a készülék események közelében. Show flags for device detected events that haven't been identified yet. Mutassa azokat az készülék eseményeket amik még nincsenek azonosítva. 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 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. Duration of airflow restriction Légáramlás korlátozás hossza s mp Event Duration Esemény időtartama 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 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 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 A napi nézet navigációs gombok átugranak az adatrekordok nélküli napok felett 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 Az Indice idővel súlyozott átlaga Standard average of indice Az indexek standard átlaga Custom CPAP User Event Flagging Saját CPAP felhasználói esemény jelölések <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices do not support changing these settings.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Megjegyzés: </span>A ResMed készülékek az összefoglaló tervezési korlátozások miatt nem támogatják ezen beállítások módosítását.</p></body></html> 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.) <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>Az OSCAR indítását egy kicsit lassabbá teszi az összes összefoglaló adat előzetes betöltésével, ami felgyorsítja az áttekintő böngészést és néhány más számítást a későbbiekben. </p><p>Ha nagy mennyiségű adattal rendelkezik, érdemes ezt kikapcsolva hagyni, de ha jellemzően mindent az áttekintésben szeretne megtekinteni, akkor <i>mindenképpen</i> érdemes az összes összefoglaló adatot betölteni. </p><p>Vegye figyelembe, hogy ez a beállítás nem érinti a hullámforma- és eseményadatokat, amelyek mindig igény szerint töltődnek be.</p></body></html> 4 cmH2O 4 cmH2O 20 cmH2O 20 cmH2O 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.) Szeretnék értesítést kapni a teszt verziók megjelenéséről (csak haladó felhasználóknak) &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> <html><head/><body><p>Melyik lapot nyissa meg a profil betöltésekor. (Megjegyzés: Alapértelmezés szerint a Profil lesz az, ha az OSCAR úgy van beállítva, hogy indításkor ne nyisson meg profilt)</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. A hullámforma-felülképzési zászlók megjelenítésének vizuális módszere. Standard Bars Szabványos rudak 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 that is somehow different from anything previously seen by OSCAR developers.</p></body></html> <html><head/><body><p>Figyelmeztetés ha az OSCAR fejlesztők által nem tesztelt készülékről importálnak.</p></body></html> Warn when previously unseen data is encountered Figyelmeztetés, ha korábban nem látott adatokkal találkozik 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 Clinical Clinical Settings Select Oscar Operating Mode Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. Clinical Mode permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. Permissive Mode Hours Oximetry Settings Oximetria beállítások <!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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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 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. Az OSCAR teszt verzióját használja. A teszt verziók automatikusan frissítéseket keresnek legfeljebb 7 naponta. Kisebb időszakot is megadhat 7 napnál. 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. Ha szeretnél segíteni az új funkciók és hibajavítások korai tesztelésében, kattints ide. 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 Ha szeretnél segíteni az OSCAR korai verzióinak tesztelésében, kérjük, nézd meg az OSCAR teszteléséről szóló Wiki oldalt. Szeretettel várunk mindenkit, aki szeretne tesztelni az OSCAR-t, segíteni az OSCAR fejlesztésében, és segíteni a meglévő vagy új nyelvekre történő fordításokban. 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 Overlay zászlók 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> <html><head/><body><p>Ezeket a funkciókat nemrégiben megkurtították. Később visszatérnek. </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 device serial number on device settings changes report Vegyük-e bele a riportokba a készülék sorozatszámát a beállítások riportnál Include Serial Number Sorozatszám megjelenítése Graphics Engine (Requires Restart) Grafikus motor (újraindítást igényel) <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Ennél rövidebb szakaszok nem fognak látszani</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Ez egy kísérleti módszer a készülék által nem detektált események megtalálására. </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">nem</span><span style=" font-family:'Sans'; font-size:10pt;"> számolódik bele az AHI-ba.</span></p></body></html> l/min l/perc <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p>Kumulatív indexek</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> deszaturációk jelölési határa </p></body></html> <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:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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><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:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Oximetria és CPAP adatok szinkronizálása</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">A SpO2Review-ból (.spoR fájlokból) vagy a soros import módszerrel importált CMS50 adatok </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">nem</span><span style=" font-family:'Sans'; font-size:10pt;"> rendelkeznek a szinkronizáláshoz szükséges megfelelő időbélyeggel.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Az élő nézet mód (soros kábel használatával) az egyik módja a pontos szinkronizálás elérésének a CMS50 oximétereken, de nem ellensúlyozza a CPAP óra eltolódását.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Ha </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">pontosan </span><span style=" font-family:'Sans'; font-size:10pt;">azzal egy időben indítja el a CPAP-készülékét, akkor most már a szinkronizálást is elérheti. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">A sorozatos importálás a múlt éjszakai első CPAP-kezelés kezdő időpontját veszi alapul. (Ne feledje, hogy először importálja a CPAP-adatokat!)</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) For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. Enables SessionBar in Event Flags Graph 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 No CPAP devices detected Nem található CPAP készülék Will you be using a ResMed brand device? ResMed márkájú készüléket fog használni? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, days will <b>split at noon</b> like in ResMed's commercial software.</p> <p><b>Megjegyzés:</b> Az OSCAR fejlett munkamenet-megosztási képességei nem lehetségesek a <b>ResMed</b> készülékekkel a beállítások és összefoglaló adatok tárolási módjának korlátozása miatt, és ezért ebben a profilban le lettek tiltva.</p><p>A ResMed készülékeken a napok <b>délben</b> osztódnak, mint a ResMed kereskedelmi szoftverében.</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. Van-e a zászlónak dedikált áttekintő diagramja. 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? ResMed S9 devices routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). A ResMed S9 készülék 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. 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? Flag Jelölő Clinical Mode: Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. Basically replicates the reports and data stored on the devices data card. This includes pap devices, oximeters, etc. Compliance reports fall under this mode. Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. Permissive Mode: Allows user to select which data sets/ sessions to be used for calculations and display. Additional charts and calculations may be available that are not available from the vendor data. Changing the Oscar Operating Mode: Requires a reload of the user's profile. Data will be saved and restored. Minor Flag Kis jelölő Span Összevonás Always Minor Mindig apró Never Soha This may not be a good idea Ez nem biztos, hogy egy jó ötlet 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 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 Length: %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 milliSeconds 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 EEPAP Min EEPAP Max EEPAP 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 Cél perc szellőztetés Target Vent. Cél ventilláció Minute Vent. Percenkénti ventilláció 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 Nyomáscsökkentés Device Eszköz 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: Compiler: Software Engine Szoftveres motor ANGLE / OpenGLES ANGLE / OpenGLES Desktop OpenGL Asztali OpenGL m m cm cm in inch kg kg l/min l/perc 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 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 Súly. átl. Your %1 %2 (%3) generated data that OSCAR has never seen before. Az Ön %1 %2 (%3) olyan adatokat generált, amelyeket az OSCAR még soha nem látott. The imported data may not be entirely accurate, so the developers would like a .zip copy of this device's SD card and matching clinician .pdf reports 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 egy .zip másolatot a készülék SD-kártyájáról és a klinikusok megfelelő .pdf jelentéseit, hogy megbizonyosodjanak arról, hogy az OSCAR helyesen kezeli az adatokat. Non Data Capable Device Nem adat képes készülék Your %1 CPAP Device (Model %2) is unfortunately not a data capable model. Az Ön %1 CPAP-készüléke (%2 modell) sajnos nem adatfeldolgozásra alkalmas modell. I'm sorry to report that OSCAR can only track hours of use and very basic settings for this device. Sajnálattal jelentem, hogy az OSCAR csak a használati órákat és a készülék nagyon alapvető beállításait tudja nyomon követni. Device Untested Teszteletlen készülék Your %1 CPAP Device (Model %2) has not been tested yet. Az ön %1 CPAP készüléke (%2 model) még nem lett tesztelve. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Úgy tűnik, eléggé hasonlít más eszközökhöz, hogy működhet, de a fejlesztők szeretnének egy .zip másolatot az eszköz SD-kártyájáról és a megfelelő klinikusok .pdf jelentéseit, hogy megbizonyosodjanak arról, hogy működik-e az OSCAR-ral. Device Unsupported Eszköz nem támogatott Sorry, your %1 CPAP Device (%2) is not supported yet. Sajnáljuk, de a %1 CPAP-készülék (%2) még nem támogatott. The developers need a .zip copy of this device's SD card and matching clinician .pdf reports to make it work with OSCAR. A fejlesztőknek szükségük van az eszköz SD-kártyájának .zip másolatára és a megfelelő klinikai .pdf jelentésekre ahhoz, hogy az OSCAR-ral működjön. Getting Ready... Felkészülés... Scanning Files... Fájlok keresése... Importing Sessions... Mérések betöltése... UNKNOWN APAP (std) APAP (dyn) Auto S Auto S/T AcSV SoftPAP Mode Pressure relief during exhalation Slight Softstart pressure Pressure during soft start period PSoft Softstart minimum pressure Minimum pressure during soft start period PSoftMin Auto start Automatically turn on the device by breathing Softstart time Lenght of soft start period Soft start maximum time Maximum lenght of soft start period Soft start max. time Soft start pressure Higher End Expiratory Pressure Humidifier level Tube type Obstruction level Obstruction level in percentage rRMVFluctuation Relative respiratory minute volume fluctuation Relative respiratory minute volume Measured pressure Full flow Artefact Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) Epoch (2 mins) with Flow Limitation Deep Sleep Deep sleep, stable respiration Timed breath BiSoft Mode BiSoft 1 BiSoft 2 TriLevel PMaxOA EEPAPMin Lower End Expiratory Pressure EEPAPMax rRMV ART CriticalLeak Mask leakage is above a critical treshold CL eMO Epoch (2 mins) with Mild Obstruction eSO Epoch (2 mins) with Severe Obstruction eS Epoch (2 mins) with Snoring eFL DS Finishing up... Befejezés... 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 Az EPAP-ról az IPAP-ra való átálláshoz szükséges idő, minél magasabb a szám, annál lassabb az átállás. Rise Time Lock Emelkedési idő zár Whether Rise Time settings are available to you. Hogy az Emelkedési idő beállításai elérhetőek-e az Ön számára. Rise Lock Rise zár Mask Resistance Setting Maszk ellenállás beállítása Mask Resist. Maszk ellenáll. Hose Diam. Cső átm. 15mm 15mm 22mm 22mm Backing Up Files... Fájlok biztonsági mentése... Untested Data Teszteletlen adat model %1 %1 model unknown model ismeretlen model CPAP-Check CPAP-ellenőrzés AutoCPAP AutoCPAP Auto-Trial Automatikus próba 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 Emelkedési idő 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 Peszách Target Time Célidő PRS1 Humidifier Target Time PRS1 párásító célidő Hum. Tgt Time Párásító célidő Tubing Type Lock Cső típusú zár Whether tubing type settings are available to you. Rendelkezésre állnak-e a csőtípus-beállítások. Tube Lock Csőzár Mask Resistance Lock Maszk ellenállás zár Whether mask resistance settings are available to you. Hogy a maszk ellenállási beállításai elérhetőek-e az Ön számára. Mask Res. Lock Maszk ellenállás zár A few breaths automatically starts device Néhány lélegzetvétel automatikusan elindítja a készüléket Device automatically switches off A készülék automatikusan kikapcsol Whether or not device allows Mask checking. Az eszköz engedélyezi-e vagy sem a maszkellenőrzést. 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+ Ramp+ Backup Breath Mode Biztonsági mentés lélegzetvétel üzemmódban The kind of backup breath rate in use: none (off), automatic, or fixed A használt tartalék légzési sebesség típusa: nincs (kikapcsolva), automatikus vagy rögzített Breath Rate Légzésszám Fixed Fix Fixed Backup Breath BPM Rögzített biztonsági mentés Légzés 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 Időzített inspiráció The time that a timed breath will provide IPAP before transitioning to EPAP Az idő, amíg egy időzített légzés IPAP-t biztosít, mielőtt átvált EPAP-ra. Timed Insp. Időzített belég. Auto-Trial Duration Automatikus próba időtartama Auto-Trial Dur. Automatikus próba időtartama EZ-Start EZ-Start Whether or not EZ-Start is enabled Az EZ-Start engedélyezve van-e vagy sem Variable Breathing Változó légzés UNCONFIRMED: Possibly variable breathing, which are periods of high deviation from the peak inspiratory flow trend UNCONFIRMED: Lehetséges változó légzés, amely a belégzési csúcsáramlási trendtől való nagymértékű eltérés időszakai A period during a session where the device could not detect flow. Olyan időszak a munkamenet során, amikor a készülék nem tudott áramlást érzékelni. Peak Flow Áramlási csúcs Peak flow during a 2-minute interval Csúcsáramlás 2 perces intervallum alatt Humidifier Status Párásító státusz PRS1 humidifier connected? PRS1 párásító csatlakoztatva? Disconnected Lecsatlakoztatva Connected Kapcsolódva Humidification Mode Párásító üzemmód PRS1 Humidification Mode PRS1 Párásítási mód Humid. Mode Párásító mód Fixed (Classic) Fix (classic) Adaptive (System One) Adaptív (System One) Heated Tube Fűtött cső Tube Temperature Csőhőmérséklet PRS1 Heated Tube Temperature PRS1 Fűtött cső hőmérséklete Tube Temp. Cső hőmérs. PRS1 Humidifier Setting PRS1 Párásító beállítás Hose Diameter Cső átmérő Diameter of primary CPAP hose Az elsődleges CPAP-tömlő átmérője 12mm 12mm Auto On Automatikus bekapcsolás Auto Off Automatikus kikapcsolás Mask Alert Maszk figyelmeztetés Show AHI AHI mutatása Whether or not device shows AHI via built-in display. Hogy a készülék a beépített kijelzőn megjeleníti-e az AHI-t vagy sem. The number of days in the Auto-CPAP trial period, after which the device will revert to CPAP Az Auto-CPAP próbaidőszakban lévő napok száma, amely után a készülék visszaáll CPAP-ra Breathing Not Detected Nem észlelt légzés BND BND Timed Breath Időzített lélegzetvétel Machine Initiated Breath Gép által kezdeményezett légzés TB TB Windows User Windows felhasználó Using A használata , found SleepyHead - , talált SleepyHead - You must run the OSCAR Migration Tool Futtatnia kell az OSCAR migrációs eszközt Launching Windows Explorer failed A Windows Intéző elindítása nem sikerült Could not find explorer.exe in path to launch Windows Explorer. Nem találta az explorer.exe fájlt a Windows Intéző elindításának elérési útvonalában. OSCAR %1 needs to upgrade its database for %2 %3 %4 Az OSCAR %1-nek %2 %3 %4 frissítenie kell az adatbázisát <b>OSCAR maintains a backup of your devices data card that it uses for this purpose.</b> <b>Az OSCAR biztonsági másolatot készít a készülékek adatkártyájáról, amelyet erre a célra használ.</b> <i>Your old device data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> <i>A régi eszközadatokat regenerálni kell, feltéve, hogy ez a biztonsági mentés funkció nem volt letiltva a beállításokban egy korábbi adatimport során.</i> OSCAR does not yet have any automatic card backups stored for this device. Az OSCAR még nem tárolt automatikus kártyabiztonsági mentéseket ehhez az eszközhöz. This means you will need to import this device data again afterwards from your own backups or data card. Ez azt jelenti, hogy ezt követően újra be kell majd importálnia a készülék adatait a saját biztonsági mentéseiből vagy adatkártyájáról. Important: Fontos: If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again. Ha aggódik, kattintson a Nem gombra a kilépéshez, és készítsen manuálisan biztonsági másolatot a profiljáról, mielőtt újra elindítja az OSCAR-t. Are you ready to upgrade, so you can run the new version of OSCAR? Készen áll a frissítésre, hogy az OSCAR új verzióját futtathassa? Device Database Changes Eszközadatbázis-változások Sorry, the purge operation failed, which means this version of OSCAR can't start. The device data folder needs to be removed manually. 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. Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. 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 Min nyomás Minimum Therapy Pressure Minimális terápiás nyomás Max Pressure Max nyomás Maximum Therapy Pressure Maximális terápiás nyomás Ramp Time Ramp Delay Period Ramp Pressure Starting Ramp Pressure Ramp Event Ramp An abnormal period of Cheyne Stokes Respiration Cheyne Stokes Respiration (CSR) Cheyne-Stokes légzés (CSR) Periodic Breathing (PB) Periodikus légzés (PB) Clear Airway (CA) Nyitott légút (CA) Obstructive Apnea (OA) Obstruktív Apnoé (OA) Hypopnea (H) Hipopnoé (H) An apnea that couldn't be determined as Central or Obstructive. Unclassified Apnea (UA) Apnea (A) Apnoé (A) A restriction in breathing from normal, causing a flattening of the flow waveform. Flow Limitation (FL) Áramlás limitáció (FL) RERA (RE) Vibratory Snore (VS) Vibrációs horkolás (VS) Vibratory Snore (VS2) Vibrációs horkolás (VS2) Leak Flag (LF) A large mask leak affecting device performance. Large Leak (LL) Non Responding Event (NR) Expiratory Puff (EP) SensAwake (SA) User Flag #1 (UF1) User Flag #2 (UF2) User Flag #3 (UF3) Pulse Change (PC) SpO2 Drop (SD) A ResMed data item: Trigger Cycle Event Apnea Hypopnea Index (AHI) Apnoé Hypopnoé index (AHI) Respiratory Disturbance Index (RDI) Mask On Time Time started according to str.edf Summary Only An apnea where the airway is open An apnea caused by airway obstruction A partially obstructed airway Részlegesen elzáródott légút UA UA A vibratory snore Pressure Pulse A pulse of pressure 'pinged' to detect a closed airway. A type of respiratory event that won't respond to a pressure increase. Intellipap event where you breathe out your mouth. SensAwake feature will reduce pressure when waking is detected. 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 A sudden (user definable) change in heart rate A sudden (user definable) drop in blood oxygen saturation SD SD Breathing flow rate waveform Mask Pressure Amount of air displaced per breath Graph displaying snore volume Minute Ventilation Amount of air displaced per minute Respiratory Rate Légzésszám Rate of breaths per minute Patient Triggered Breaths Percentage of breaths triggered by patient Pat. Trig. Breaths Leak Rate Rate of detected mask leakage 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 CSR CSR An abnormal period of Periodic Breathing 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 Kilégzés idő Time taken to breathe out Inspiratory Time Belégzés idő Time taken to breathe in Respiratory Event Graph showing severity of flow limitations Flow Limit. Áramlás limit. Target Minute Ventilation Maximum Leak The maximum rate of mask leakage Max Leaks 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 Graph showing running RDI for the past hour Sleep position in degrees Upright angle in degrees Movement Mozgás Movement detector CPAP Session contains summary data only PAP Mode PAP mód Couldn't parse Channels.xml, OSCAR cannot continue and is exiting. End Expiratory Pressure An apnea reported by your CPAP device. Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. A vibratory snore as detected by a System One device I/E Value 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 Jegyzetek 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 Migrálás files fájlok from tól to ig 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 Hibakód 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? OSCAR Reminder OSCAR emlékeztető Don't forget to place your datacard back in your CPAP device Ne felejtse el visszatenni a memóriakártyát a CPAP eszközbe 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 oximetry 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 Túl kevés használat: %1 nap (%1% compliant, defined as > %2 hours) (%1% megfelel, kritérium: > %2 óra) (Sess: %1) (Szakasz: %1) Bedtime: %1 Lefekvés ideje: %1 Waketime: %1 Ébredés ideje: %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) 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) PS %1; %2-%3 (%4) felett Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) Min EPAP %1 Max IPAP %2 PS %3-%4 (%5) Min EEPAP %1 Max EEPAP %2 PDIFF %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) (1 napja) (%2 days ago) (%2 napja) 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 Selection Length 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... Adatfájlok beolvasása... 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 ResMed kilégzés könnyítő (EPR) Patient??? Páciens??? EPR Level EPR szint Exhale Pressure Relief Level Kilégzési nyomáskönnyítés szintje Device auto starts by breathing Az eszköz automatikusan indul légzésre Response Válasz Device auto stops by breathing Az eszköz automatikusan leáll légzésre Patient View Páciens nézet RiseEnable RiseTime Cycle Trigger TiMax TiMin Your ResMed CPAP device (Model %1) has not been tested yet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card to make sure it works with OSCAR. SmartStart SmartStart Smart Start Okos indítás Humid. Status Pára állapot Humidifier Enabled Status Humid. Level Páratartalom 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 ClimateLine Hőmérséklet szabályzás engedélyezése Temperature Enable Hőmérséklet engedélyezése AB Filter AB szűrő Antibacterial Filter Antibakteriális szűrő Pt. Access Páciens hozzáférése Essentials Alapok (Essentials) Plus Plusz (Plus) Climate Control Klíma kontrol Manual Manuális Soft Lágy (soft) Standard Általános (standard) BiPAP-T BiPAP-T BiPAP-S BiPAP-S BiPAP-S/T BiPAP-S/T SmartStop SmartStop Smart Stop Smart Stop Simple Egyszerű Advanced Speciális 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... %1 adatok betöltése %2 részére... 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... Permissive Mode Total disabled sessions: %1, found in %2 days Total disabled sessions: %1 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. Updating Statistics cache Statisztika gyorsítótár frissítése Usage Statistics Használati statisztika 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 Az új verziók fájl nem megfelelően formázott A more recent version of OSCAR is available Az OSCAR újabb verziója már elérhető release kiadás test version teszt verzió You are running the latest %1 of OSCAR A legújabb %1 fut az OSCAR-ból 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> Információk az újabb tesztverzióról %1 letölthetők innen: <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 SensAwake szint Expiratory Relief Kilégzés könnyítés Expiratory Relief Level Kilégzés könnyítés szintje (EPR) 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 Löwenstein Prisma Smart Resvent/Hoffrichter iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings Add Add Feature inhibited. The maximum number of Items has been exceeded. creates new copy of current settings. Restore Restores saved settings from selection. Rename Renames the selection. Must edit existing name then press enter. Update Updates the selection with current settings. Delete Deletes the selection. Expanded Help menu. Exits the Layout menu. <h4>Help Menu - Manage Layout Settings</h4> Exits the help menu. Exits the dialog menu. This feature manages the saving and restoring of Layout Settings. Layout Settings control the layout of a graph or chart. Different Layouts Settings can be saved and later restored. Button Description Creates a copy of the current Layout Settings. The default description is the current date. The description may be changed. The Add button will be greyed out when maximum number is reached. Other Buttons Greyed out when there are no selections Loads the Layout Settings from the selection. Automatically exits. io Modify the description of the selection. Same as a double click.io Saves the current Layout Settings to the selection. Prompts for confirmation. Deletes the selecton. Control Exit Kilépés (Red circle with a white "X".) Returns to OSCAR menu. Return Next to Exit icon. Only in Help Menu. Returns to Layout menu. Escape Key Exit the Help or Layout menu. Layout Settings * Name * Pinning * Plots Enabled * Height * Order * Event Flags * Dotted Lines * Height Options General Information Maximum description size = 80 characters. Maximum Saved Layout Settings = 30. Saved Layout Settings can be accessed by all profiles. Layout Settings only control the layout of a graph or chart. They do not contain any other data. They do not control if a graph is displayed or not. Layout Settings for daily and overview are managed independantly. Maximum number of Items exceeded. No Item Selected Ok to Update? Ok To Delete? SessionBar %1h %2m %1ó %2p No Sessions Present Szakaszok nem állnak rendelkezésre SleepStyleLoader Import Error Import hiba This device Record cannot be imported in this profile. Ennek a készüléknek az adatai 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 Device Information Készülék információk Changes to Device Settings Változtatások a készülék beállításában Database has No %1 data available. Database has %1 day of %2 Data on %3 Database has %1 days of %2 Data, between %3 and %4 Total Days: %1 Days Not Used: %1 Days Used: %1 %1 napot használva Days %1 %2 Hours: %3 Best Device Setting Worst Device Setting 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 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 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 device.</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 device 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 today %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 <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 pressure was under %1 %2 for %3% of the time. A nyomás %1 %2 alatt volt %3%-ban. 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. 1 day ago 1 nappal ezelőtt Your device was on for %1. A készüléke %1 üzemelt. Your CPAP device used a constant %1 %2 of air A CPAP készüléke konstans %1 %2 levegővel üzemelt Your device used a constant %1-%2 %3 of air. Az ön készüléke konstans %1-%2 %3 levegőt használt. Your device was under %1-%2 %3 for %4% of the time. A készüléke %1-%2 %3 alatt volt %4%-ban. Your EEPAP pressure was under %1 %2 for %3% of the time. 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 Double click Y-axis: Return to AUTO-FIT Scaling Dupla kattintás az Y tengelyen: vissza az automatikus skálázásra Double click Y-axis: Return to DEFAULT Scaling Dupla kattintás az Y tengelyen: vissza az alapértelmezett skálázásra Double click Y-axis: Return to OVERRIDE Scaling Dupla kattintás az Y tengelyen: vissza az egyedi skálázásra Double click Y-axis: For Dynamic Scaling Dupla kattintás az Y tengelyen: dinamikus skálázás Double click Y-axis: Select DEFAULT Scaling Dupla kattintás az Y tengelyen: alapértelmezett skálázás Double click Y-axis: Select AUTO-FIT Scaling Dupla kattintás az Y tengelyen: automatikus skálázás %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.5.1/Translations/Nederlands.nl.ts000066400000000000000000017321131450332542600212170ustar00rootroot00000000000000 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. 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 i c 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 Search Zoeken Layout Indeling Save and Restore Graph Layout Settings Instellingen voor grafiekindeling opslaan en herstellen 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 Disable Warning Waarschuwing uitschakelen Disabling a session will remove this session data from all graphs, reports and statistics. The Search tab can find disabled sessions Continue ? Het uitschakelen van een sessie verwijdert deze sessiegegevens uit alle grafieken, rapporten en statistieken. Het tabblad Zoeken kan uitgeschakelde sessies vinden. Doorgaan ? No %1 events are recorded this day Er zijn vandaag geen %1 incidenten geweest %1 event %1 incident %1 events %1 incidenten Device Settings Apparaat Instellingen Total time in apnea Totale Tijd in Apneu (TTiA) Time over leak redline Tijdsduur boven de leklimiet Event Breakdown Verdeling incidenten This CPAP device does NOT record detailed data Dit apparaat registreert GEEN gedetailleerde gegevens 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??? 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 Disable Session Schakel deze Sessie uit Disabling Sessions is not valid in Compilance Mode Uitgeschakelde sessies zijn niet geldig in de nalevingsmodus. Disabling a session will remove this session from all graphs, reports and statistics. The Search tab can find disabled sessions Continue to disable session? Het uitschakelen van een sessie verwijdert deze sessie uit alle grafieken, rapporten en statistieken. Het tabblad Zoeken kan uitgeschakelde sessies vinden Sessie toch uitschakelen? 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.) 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 "Nothing's here!" "Er is hier niets!" This bookmark is in a currently disabled area.. Deze bladwijzer staat in een uitgeschakeld gebied.. 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 Details Details Clinical Mode Klinische modus Disabling Sessions requires the Permissive Mode Om sessies uit te schakelen is de vrije modus nodig 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 :( Geen gegevens gevonden Sorry, this device only provides compliance data. Sorry, dit apparaat geeft uitsluitend gegevens over therapietrouw. 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 Hide All Events Verberg alle incidenten Show All Events Toon alle incidenten Hide All Graphs Alle grafieken verbergen Show All Graphs Toon alle grafieken DailySearchTab Match: Zoek naar: Select Match Selecteer overeenkomst Clear Wissen Bookmark Jumps to Date's Bookmark Bladwijzer Springt naar de bladwijzer van de datum Start Search Start zoeken DATE Jumps to Date DATUM Springt naar de datum Match Overeenkomst Notes Notities Notes containing Notities met ... Bookmarks Bladwijzers Bookmarks containing Bladwijzers met ... AHI AHI Daily Duration Dagelijkse duur Session Duration Duur van de sessie Days Skipped Overgeslagen dagen Disabled Sessions Uitgeschakelde sessies Number of Sessions Aantal sessies Click HERE to close Help Klik HIER om Hulp te sluiten Help Hulp No Data Jumps to Date's Details Geen gegevens Springt naar de details van de datum Number Disabled Session Jumps to Date's Details Uitgeschakeld sessienummer Springt naar de details van de datum Note Jumps to Date's Notes Notitie Springt naar de aantekeningen van de datum Jumps to Date's Bookmark Springt naar de bladwijzer van de datum AHI Jumps to Date's Details AHI Springt naar de details van de datum EventsPerHour Gebeurtenissen per uur Session Duration Jumps to Date's Details Duur van de sessie Springt naar de details van de datum Minutes Minuten Number of Sessions Jumps to Date's Details Aantal sessies Springt naar de details van de datum Sessions Sessies Daily Duration Jumps to Date's Details Dagelijkse duur Springt naar de details van de datum Hours Uren Number of events Jumps to Date's Events Aantal gebeurtenissen Springt naar de gebeurtenissen van de datum Events Gebeurtenissen Automatic start Start automatisch More to Search Meer zoeken Continue Search Verder zoeken End of Search Einde zoekopdracht No Matches Geen overeenkomsten Searchng Zoeken Skip:%1 Overslaan: %1 %1/%2%3 days. %1/%2%3 dagen. %1/%2%3 days %1/%2%3 dagen Found %1 %1 gevonden. Finds days that match specified criteria. Vindt dagen die voldoen aan opgegeven criteria. Searches from last day to first day. Zoekt van laatste dag tot eerste dag. First click on Match Button then select topic. Klik eerst op de Overeenkomst knop en selecteer dan het onderwerp. Then click on the operation to modify it. Klik vervolgens op de bewerking om deze te wijzigen. or update the value of werk de waarde bij Topics without operations will automatically start. Onderwerpen zonder bewerkingen worden automatisch gestart. Compare Operations: numberic or character. Vergelijk Bewerkingen: numeriek of karakter. Numberic Operations: Numerieke operaties: Character Operations: Karakter operaties: Summary Line Samenvatting Regel Left:Summary - Number of Day searched Links: Samenvatting - Aantal gezochte dagen Center:Number of Items Found Midden: Aantal gevonden objecten Right:Minimum/Maximum for item searched Rechts: Minimum/Maximum voor gezochte object Result Table Resultaat tabel Column One: Date of match. Click selects date. Eerste kolom: Datum van overeenkomst. Klik selecteert de datum. Column two: Information. Click selects date. Kolom twee: Informatie. Klik selecteert de datum. Then Jumps the appropiate tab. Springt dan naar de juiste tab. Wildcard Pattern Matching: Zoeken met jokertekens: Wildcards use 3 characters: Er zijn drie jokertekens: Asterisk Sterretje Question Mark Vraagteken Backslash. Backslash. Asterisk matches any number of characters. Een sterretje komt overeen met een willekeurig aantal tekens. Question Mark matches a single character. Vraagteken komt overeen met een enkel teken. Backslash matches next character. Backslash komt overeen met het volgende teken. Found %1. %1 gevonden. DateErrorDisplay ERROR The start date MUST be before the end date FOUT De begindatum MOET vóór de einddatum liggen The entered start date %1 is after the end date %2 De ingevoerde begindatum %1 ligt na de einddatum %2 Hint: Change the end date first Tip: Verander eerst de einddatum The entered end date %1 De ingevoerde einddatum %1 is before the start date %1 ligt vóór de begindatum %1 Hint: Change the start date first Tip: Verander eerst de begindatum 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 device 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 Show Standard Report Toon standaard rapport Standard Standaard layout Show Monthly Report Toon maandelijks rapport Monthly Maand layout Show Range Report Toon rapport over een bereik Date Range Tijdspanne Select Report Date Selecteer de rapportdatum Report Date Datum verslag 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 Geavanceerd Purge Oximetry Data Wis oxymetrie gegevens 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 Standard - CPAP, APAP Standaard - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> <html><head/><body><p>Standaard grafiek volgorde, goed voor CPAP, APAP, Standaard BPAP</p></body></html> Advanced - BPAP, ASV Geavanceerd - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> <html><head/><body><p>Geavanceerde grafiekvolgorde, goed voor BPAP met ademsteun, ASV, AVAPS, IVAPS</p></body></html> 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 Purge ALL Device Data Wis ALLE apparaatgegevens Show Overview view Toon overzichtpagina &Maximize Toggle Schermvullend aan/uit Maximize window Maak venster schermvullend Reset Graph &Heights Herstel grafiekhoogten 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 &Reset Graphs Stel grafiekvolgorde in 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 Oxymetrie 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 Exporteer 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 Statistiek 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 Access to Import has been blocked while recalculations are in progress. Tijdens een herberekening kan niet geïmporteerd worden. Import Problem Import probleem Couldn't find any valid Device Data at %1 Kon geen geldige gegevens vinden op %1 CPAP Data Located CPAP gegevens gevonden Import Reminder Import herinnering Find your CPAP data card Zoek uw CPAP-gegevenskaart No supported data was found Geen ondersteunde gegevens gevonden 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 Are you sure you want to rebuild all CPAP data for the following device: 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 device: Om een ​​of andere reden heeft OSCAR geen back-ups voor het volgende apparaat: 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? Would you like to import from your own backups now? (you will have no data visible for this device until you do) WilT U nu importeren vanuit uw eigen back-ups? (U heeft geen zichtbare gegevens voor dit apparaat totdat U dit doet) Note as a precaution, the backup folder will be left in place. Ter geruststelling: de backup map blijft intakt. Are you <b>absolutely sure</b> you want to proceed? Weet U <b>absoluut zeker</b> dat U wilt doorgaan? A file permission error caused 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: The Glossary will open in your default browser De woordenlijst wordt geopend in uw standaardbrowser You must select and open the profile you wish to modify U moet het profiel dat u wilt wijzigen eerst selecteren en openen %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. 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. 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" OSCAR does not have any backups for this device! 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 device</i>, <font size=+2>you will lose this device'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> You are about to <font size=+2>obliterate</font> OSCAR's device database for the following device:</p> U staat op het punt om alle gegevens te <font size=+2>vernietigen</font> van het volgende apparaat:</p> 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 passend 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 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 This software is being designed to assist you in reviewing the data produced by your CPAP Devices and related equipment. Deze software is ontworpen om U te helpen bij het analyseren van de gegevens van uw 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 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 Layout Indeling Save and Restore Graph Layout Settings Instellingen voor grafiekindeling opslaan en herstellen 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) Hide All Graphs Alle grafieken verbergen Show All Graphs Toon 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> 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. <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 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 I started this oximeter recording at (or near) the same time as a session on my CPAP device. Ik startte de oxymeter (ongeveer) tegelijk met de CPAP. <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 Reset 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 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) This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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>Provide an alert when importing data from any device 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 device Waarschuw bij het importeren van gegevens van een niet-getest apparaat &CPAP Masker en apparaat This calculation requires Total Leaks data to be provided by the CPAP device. (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. Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device 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. This experimental option attempts to use OSCAR's event flagging system to improve device detected event positioning. Deze experimentele optie probeert de incident-markeringen te gebruiken om een betere correlatie te kunnen zien. Resync Device Detected Events (Experimental) Synchroniseer de door het apparaat gedetecteerde incidenten opnieuw (experimenteel) Allow duplicates near device events. Sta duplicaten toe vlak naast apparaat-incidenten. Show flags for device detected events that haven't been identified yet. Zet de markeringen voor de zelf gekozen incident-vlaggen aan. 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 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 Duration of airflow restriction 20/9 WJG: Vanaf hier weer verder gegaan Duur van de luchtstroombeperking s s Event Duration Tijdsduur incident 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 CPAP-klok 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 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 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><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Sessies die korter duren dan dit worden niet weergegeven</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></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>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 <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Aangepast markeren is een experimentele methode om door het toestel gemiste gebeurtenissen op te sporen. Ze worden <u>niet</u> opgenomen in de AHI.</span></p></body></html> 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 <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices 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> <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:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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><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:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Synchroniseren van oximetrie- en CPAP-gegevens</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 gegevens geïmporteerd uit SpO2Review (van .spoR bestanden) of de seriële import methode hebben <b>niet</b> de juiste tijdsaanduiding die nodig is om te synchroniseren.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (met behulp van een seriële kabel) is één manier om een nauwkeurige synchronisatie te bereiken op CMS50 oximeters, maar biedt geen compensatie voor CPAP klokdrift.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Als u de opnamemodus van uw Oximeters start op <b>precies hetzelfde moment</b> dat u uw CPAP-apparaat start, kunt u nu ook synchronisatie bereiken. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Het seriële importproces neemt de begintijd van de eerste CPAP-sessie van afgelopen nacht. (Vergeet niet om eerst uw CPAP-gegevens te importeren!)</span></p></body></html> 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). Whether to include device serial number on device settings changes report Of het serienummer van het apparaat moet worden opgenomen in het verslag met wijzigingen in de apparaat-instellingen For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. Geeft voor meerdere sessies een dunne grijze lijn weer voor elke sessie bovenaan de Gebeurtenis grafiek. Enables SessionBar in Event Flags Graph Sessiebalk inschakelen in gebeurtenissen grafiek 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 en 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.) <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> 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 Soort grafieken in overzicht <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 Incidenten The visual method of displaying waveform overlay flags. De visuele methode voor het tonen van markeringen in golfvormgrafieken. Standard Bars Lange markeringen 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> Calculate Unintentional Leaks When Not Present Bereken de onbedoelde lekkage als deze niet 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. Tooltip Timeout Tijdsduur tooltips Graph Tooltips Grafiek tekstballonnen Top Markers Aanduiding aan bovenzijde 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 Laad automatisch het laatste profiel bij opstarten <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 Clinical Klinisch Clinical Settings Klinische instellingen Select Oscar Operating Mode Selecteer de bedieningsmodus van OSCAR Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. Klinische modus staat geen uitgeschakelde sessies toe. Uitgeschakelde sessies worden niet gebruikt voor grafieken of statistieken. Clinical Mode Klinische modus permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. Vrije modus staat het uitzetten van sessies toe. Uitgeschakelde sessies worden gebruikt voor grafieken en statistieken. Permissive Mode Vrije modus Hours Uren Oximetry Settings Oxymetrie instellingen <!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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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><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:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Synchroniseren van oximetrie- en CPAP-gegevens</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 gegevens geïmporteerd uit SpO2Review (van .spoR bestanden) of de seriële import methode hebben <b>niet</b> de juiste tijdsaanduiding die nodig is om te synchroniseren.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view mode (met behulp van een seriële kabel) is één manier om een nauwkeurige synchronisatie te bereiken op CMS50 oximeters, maar biedt geen compensatie voor CPAP klokdrift.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Als u de opnamemodus van uw Oximeters start op <b>precies hetzelfde moment</b> dat u uw CPAP-apparaat start, kunt u nu ook synchronisatie bereiken. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Het seriële importproces neemt de begintijd van de eerste CPAP-sessie van afgelopen nacht. (Vergeet niet om eerst uw CPAP-gegevens te importeren!)</span></p></body></html> 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 Kies 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 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> 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) Allow sessions to be disabled.\nDisabled Session are not used for graphing or Statistics. Uitgeschakelde sessies worden niet gebruikt voor grafieken of statistieken. Compliance Mode Nalevingsmodus 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 Clinical Mode: Klinische modus: Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. Rapporteert wat er op de gegevenskaart staat, alles inclusief alle gegevens die in de vrije modus waren uitgeschakeld. Basically replicates the reports and data stored on the devices data card. Repliceert in essentie de rapporten en gegevens die zijn opgeslagen op de gegevenskaart van het apparaat. This includes pap devices, oximeters, etc. Compliance reports fall under this mode. Dit omvat PAP-apparaten, oximeters, enz. Rapporten over naleving vallen onder deze modus. Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. Nalevingsrapporten bevatten altijd alle gegevens binnen de gekozen nalevingperiode, zelfs als deze elders niet is geselecteerd. Permissive Mode: Vrije mode: Allows user to select which data sets/ sessions to be used for calculations and display. Hiermee kan de gebruiker selecteren welke gegevenssets/sessies moeten worden gebruikt voor berekeningen en weergave. Additional charts and calculations may be available that are not available from the vendor data. Er kunnen extra grafieken en berekeningen beschikbaar zijn die niet beschikbaar zijn via de gegevens van de leverancier. Changing the Oscar Operating Mode: De bedrijfsmodus van Oscar wijzigen: Requires a reload of the user's profile. Data will be saved and restored. Vereist dat het gebruikersprofiel opnieuw wordt geladen. Gegevens worden opgeslagen en hersteld. Minor Flag Kleine vlag Span Bereik Always Minor Altijd klein 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). Overview Overzicht No CPAP devices detected Geen CPAP apparaat gedetecteerd Will you be using a ResMed brand device? Gaat u een apparaat van ResMed inlezen? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, 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> 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 devices 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 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 Length: %1 Lengte: %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 Device Apparaat 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: Compiler: Compiler: Software Engine Software Engine ANGLE / OpenGLES ANGLE / OpenGLES Desktop OpenGL Desktop OpenGL m m cm cm in inch kg kg milliSeconds milliseconden h h m m s s ms ms l/min l/min 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 EEPAP EEPAP Min EEPAP Minimum EEPAP Max EEPAP Maximum EEPAP 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 Ademfreq. 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 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 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> <i>Your old device 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> OSCAR does not yet have any automatic card backups stored for this device. OSCAR heeft nog geen automatische backup-functie voor dit apparaat. 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? Device Database Changes Wijzigingen in de opslag van de apparaatgegevens 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. The device data folder needs to be removed manually. U moet zelf de map OSCAR_Data wissen. 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. OSCAR %1 needs to upgrade its database for %2 %3 %4 OSCAR %1 moet de database voor %2 %3 %4 vernieuwen This means you will need to import this device 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. 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. 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 oximetry settings to defaults? Weet je zeker dat je alle oximetrie-instellingen terug wilt zetten naar de standaardwaarden? 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 device'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 Device Dit apparaat verstrekt geen gegevens Your %1 CPAP Device (Model %2) is unfortunately not a data capable model. Uw %1 CPAP (model %2) is helaas geen model dat gegevens kan verwerken. I'm sorry to report that OSCAR can only track hours of use and very basic settings for this device. Het spijt me dat OSCAR van dit apparaat alleen gebruiksuren en erg simpele instellingen kan verwerken. Device Untested Ongetest apparaat Your %1 CPAP Device (Model %2) has not been tested yet. Uw %1 CPAP (Model %2) is nog niet getest. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device'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. Device Unsupported Niet ondersteund apparaat Sorry, your %1 CPAP Device (%2) is not supported yet. Sorry, uw %1 CPAP (%2) wordt nog niet ondersteund. The developers need a .zip copy of this device'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. Scanning Files... Bestanden bekijken... Importing Sessions... Sessies importeren... UNKNOWN ONBEKEND APAP (std) APAP (std) APAP (dyn) APAP (std) Auto S Auto S Auto S/T Auto S/T AcSV AcSV SoftPAP Mode SoftPAP-modus Pressure relief during exhalation Drukvermindering tijdens uitademing Slight Licht Softstart pressure Softstart-druk Pressure during soft start period Druk tijdens softstartperiode PSoft PSoft Softstart minimum pressure Softstart minimum druk Minimum pressure during soft start period Minimumdruk tijdens de softstartperiode PSoftMin PSoftMin Auto start Automatische start Automatically turn on the device by breathing Zet het apparaat automatisch aan door te ademen Softstart time Tijd voor softstart Lenght of soft start period Duur van de softstartperiode Soft start maximum time Soft start maximale tijd Maximum lenght of soft start period Maximale duur van de softstartperiode Soft start max. time Soft start maximum tijd Soft start pressure Soft start druk Higher End Expiratory Pressure Hogere expiratoire einddruk Humidifier level Bevochtigingsgraad Tube type Slang type Obstruction level Obstructie niveau Obstruction level in percentage Obstructieniveau als percentage rRMVFluctuation rRMVFluctuatie Relative respiratory minute volume fluctuation Relatieve fluctuatie van het ademminuutvolume Relative respiratory minute volume Relatief ademminuutvolume Measured pressure Gemeten druk Full flow Full flow Artefact Artefact Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) Onregelmatigheden in de gemeten gegevens die geen ademhalingsgebeurtenis vertegenwoordigen (bv. slikken, hoesten of spreken) Epoch (2 mins) with Flow Limitation Tijdspanne (2 min.) met stroombeperking Deep Sleep Diepe slaap Deep sleep, stable respiration Diepe slaap, stabiele ademhaling Timed breath Getimede ademhaling BiSoft Mode BiSoft-modus BiSoft 1 BiSoft 1 BiSoft 2 BiSoft 2 TriLevel Drie niveaus AutoStart AutoStart Softstart_Time Softstart_Time Softstart_TimeMax Softstart_TimeMax Softstart_Pressure Softstart_Druk PMaxOA PMaxOA EEPAPMin EEPAPMin Lower End Expiratory Pressure Onderste Expiratoire Einddruk EEPAPMax EEPAPMax HumidifierLevel Bevochtigingsniveau TubeType SlangType ObstructLevel Obstructieniveau Obstruction Level Obstructie niveau rMVFluctuation rMVFluctuatie rRMV rRMV PressureMeasured Gemeten Druk FlowFull FlowVolledig SPRStatus SPRStatus Artifact Artefact (SpO2) ART ART CriticalLeak Kritisch lek Mask leakage is above a critical treshold Maskerlekkage is boven een kritische drempel CL CL eMO eMO Epoch (2 mins) with Mild Obstruction Tijdspanne (2 min) met lichte obstructie eSO eSO Epoch (2 mins) with Severe Obstruction Tijdspanne (2 min) met ernstige obstructie eS eS Epoch (2 mins) with Snoring Tijdsspanne (2 min) met Snurken eFL eFL DeepSleep Diepe Slaap Indicator DS DS TimedBreath Aandeel verplichte ademhalingen Finishing up... Afronden... Untested Data Niet geteste gegevens 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 A few breaths automatically starts device Het apparaat start na enkele ademhalingen Device automatically switches off Het apparaat schakelt automatisch uit Whether or not device allows Mask checking. Of controle van het masker is ingeschakeld. 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 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 A period during a session where the device could not detect flow. Een periode tijdens een sessie waarbij het apparaat geen flow kon detecteren. 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 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 Auto Off Automatisch uit Mask Alert Masker waarschuwing Show AHI Toon AHI Whether or not device shows AHI via built-in display. Of het apparaat al dan niet de AHI weergeeft via het ingebouwde display. The number of days in the Auto-CPAP trial period, after which the device will revert to CPAP Het aantal dagen in de Auto-CPAP-proefperiode waarna de machine terugkeert naar CPAP Breathing Not Detected Geen ademhaling gedetecteerd (BND) tijdfractie BND BND Timed Breath Geforceerde ademhaling Machine Initiated Breath Door apparaat getriggerde ademhaling TB TB OSCAR Reminder OSCAR herinnering Don't forget to place your datacard back in your CPAP device Vergeet niet om de SD-kaart weer in uw apparaat te steken 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 End Expiratory Pressure Expiratoire einddruk 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 CSR CSR An abnormal period of Periodic Breathing Een abnormale tijdsduur van periodieke ademhaling An apnea reported by your CPAP device. Een apneu die door het apparaat is geregistreerd. Leak Flag (LF) Lekmarkering (LF) A large mask leak affecting device performance. Dusdanige lekkage dat het apparaat niet meer goed detecteert. Large Leak (LL) Groot lek (LL) tijdfractie Non Responding Event (NR) Incident zonder reactie (NR) Expiratory Puff (EP) Uitademstoot (EP) SensAwake (SA) SensAwake (SA) A user definable event detected by OSCAR's flow waveform processor. Door de gebruiker instelbaar incident dat door OSCAR wordt herkend. User Flag #1 (UF1) Gebruikersmarkering UF1 User Flag #2 (UF2) Gebruikersmarkering UF2 User Flag #3 (UF3) Gebruikersmarkering UF3 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 % Pulse Change (PC) Wijziging in hartritme (PC) SpO2 Drop (SD) SpO2 verlaging (SD) Mask Pressure (High frequency) Maskerdruk (Hoge resolutie) I/E Value I/E waarde A ResMed data item: Trigger Cycle Event Een ResMed-gegevensitem: "Trigger Cycle Event" Apnea Hypopnea Index (AHI) Apneu Hypopneu Index (AHI) Respiratory Disturbance Index (RDI) Ademhalings Verstorings Index (RDI) 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 Cheyne Stokes Respiration (CSR) Cheyne Stokes Ademhaling (CSR) tijdfractie Periodic Breathing (PB) Periodieke ademhaling (PB) tijdfractie Clear Airway (CA) Open luchtweg (Centrale Apneu) (CA) Obstructive Apnea (OA) Obstructieve Apneu (OA) Hypopnea (H) Hypopneu (H) An apnea that couldn't be determined as Central or Obstructive. Een apneu die niet als centraal of obstructief kon worden geclassificeerd. Unclassified Apnea (UA) Onbekende Apneu (UA) Apnea (A) Apneu (A) An apnea reportred by your CPAP device. Een apneu die door het apparaat is geregistreerd. A restriction in breathing from normal, causing a flattening of the flow waveform. Een abnormale beperking van de ademhaling, waardoor de luchtstroomsterktegolf afvlakte. Flow Limitation (FL) Luchtstroombeperking (FL) RERA (RE) RERA (RE) Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. Respiratory Effort Related Arousal: Een beperking van de ademhaling die ontwaken of verstoring van de slaap veroorzaakt. Vibratory Snore (VS) Vibrerend snurken (VS) Vibratory Snore (VS2) Vibrerend snurken (VS2) A vibratory snore as detected by a System One device System One detecteert vibrerend snurken 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 A partially obstructed airway Een gedeeltelijk afgesloten luchtweg UA UA A vibratory snore Een snurk 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 LL LL A type of respiratory event that won't respond to a pressure increase. Een ademhalings-incident dat niet door drukverhoging wordt beinvloed. 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. 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 A sudden (user definable) change in heart rate Een plotselinge verandering in hartritme (instelbaar) 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 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 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 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) 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) Min EEPAP %1 Max EEPAP %2 PDIFF %3-%4 (%5) Min EEPAP %1 Max EEPAP %2 PDIFF %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 Selection Length Tijdsduur in selectie 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 Device auto starts by breathing Apparaat start automatisch Response Reactie Device auto stops by breathing Apparaat stopt automatisch Patient View Patiënt weergave RiseEnable Stijgtijd vrijgegeven RiseTime Stijgtijd Cycle cyclus Trigger TiMax TiMin Your ResMed CPAP device (Model %1) has not been tested yet. Uw ResMed apparaat (Model %1) is nog niet getest. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device'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. SmartStart Autostart Smart Start Automatisch starten Humid. Status Bevocht. status Humidifier Enabled Status Status bevochtiger ingeschakeld 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 Smart Stop Automatisch stoppen Simple Eenvoudig Advanced Geavanceerd 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 ... Permissive Mode Vrije mode Total disabled sessions: %1, found in %2 days Totaal aantal uitgeschakelde sessies: %1, gevonden in %2 dagen Total disabled sessions: %1 Totaal aantal uitgeschakelde sessies: %1 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. Duur van langste uitgeschakelde sessie: %1 minuten. Totale duur van alle uitgeschakelde sessies: %2 minuten. Updating Statistics cache Bijwerken statistische gegevens Usage Statistics Gebruiks-statistieken 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 Löwenstein Löwenstein Prisma Smart Prisma Smart Resvent/Hoffrichter Resvent/Hoffrichter iBreeze/Point3 iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings Lay-out instellingen beheren Add Toevoegen Add Feature inhibited. The maximum number of Items has been exceeded. Toevoegen onmogelijk. Het maximum aantal items is overschreden. creates new copy of current settings. maakt een nieuwe kopie van de huidige instellingen. Restore Herstel Restores saved settings from selection. Herstelt de opgeslagen instellingen van de selectie. Rename Hernoemen Renames the selection. Must edit existing name then press enter. Hernoemt de selectie. Bewerk de bestaande naam en druk vervolgens op enter. Update Bijwerken Updates the selection with current settings. Werkt de selectie bij met de huidige instellingen. Delete Verwijder Deletes the selection. Verwijdert de selectie. Expanded Help menu. Uitgebreid Help menu. Exits the Layout menu. Sluit het Layout menu af. <h4>Help Menu - Manage Layout Settings</h4> <h4>Help Menu - Lay-out instellingen beheren</h4> Exits the help menu. Sluit het helpmenu af. Exits the dialog menu. Verlaat het dialoogmenu. This feature manages the saving and restoring of Layout Settings. Deze functie beheert het opslaan en herstellen van Lay-out instellingen. Layout Settings control the layout of a graph or chart. Lay-out Instellingen bepalen de lay-out van een grafiek of diagram. Different Layouts Settings can be saved and later restored. Verschillende lay-out instellingen kunnen worden opgeslagen en later hersteld. Button Knop Description Beschrijving Creates a copy of the current Layout Settings. Maakt een kopie van de huidige lay-outinstellingen. The default description is the current date. De standaardomschrijving is de huidige datum. The description may be changed. De beschrijving kan gewijzigd worden. The Add button will be greyed out when maximum number is reached. De knop Toevoegen wordt grijs als het maximale aantal is bereikt. Other Buttons Overige knoppen Greyed out when there are no selections Grijs als er geen selecties zijn Loads the Layout Settings from the selection. Automatically exits. io Laadt de lay-out instellingen van de selectie. Sluit automatisch af. Modify the description of the selection. Same as a double click.io Wijzig de beschrijving van de selectie. Dit is hetzelfde als dubbelklikken. Saves the current Layout Settings to the selection. Slaat de huidige Lay-out instellingen op in de selectie. Prompts for confirmation. Vraagt om bevestiging. Deletes the selecton. Verwijdert de selectie. Control Instellen Exit Afsluiten (Red circle with a white "X".) Returns to OSCAR menu. (Rode cirkel met een witte "X".) Hiermee keert u terug naar het OSCAR-menu. Return Terug Next to Exit icon. Only in Help Menu. Returns to Layout menu. Naast het pictogram Afsluiten. Alleen in menu Help. Terug naar menu Lay-out. Escape Key Escape toets Exit the Help or Layout menu. Sluit het menu Help of Layout af. Layout Settings Lay-out instellingen * Name * Naam * Pinning * Vastmaken * Plots Enabled * Grafieken ingeschakeld * Height * Hoogte * Order * Volgorde * Event Flags * Gebeurtenis vlaggen * Dotted Lines * Stippellijnen * Height Options * Hoogte opties General Information Algemene informatie Maximum description size = 80 characters. Maximale grootte beschrijving = 80 tekens. Maximum Saved Layout Settings = 30. Maximaal aantal opgeslagen lay-outinstellingen = 30. Saved Layout Settings can be accessed by all profiles. Opgeslagen lay-out instellingen zijn toegankelijk voor alle profielen. Layout Settings only control the layout of a graph or chart. Lay-out instellingen bepalen alleen de lay-out van een grafiek of diagram. They do not contain any other data. Deze bevatten geen andere gegevens. They do not control if a graph is displayed or not. Ze bepalen niet of een grafiek al dan niet wordt weergegeven. Layout Settings for daily and overview are managed independantly. Lay-out instellingen voor dag en overzicht worden onafhankelijk beheerd. <p style="color:black;"> This feature manages the saving and restoring of Layout Settings. <br> Layout Settings control the layout of a graph or chart. <br> Different Layouts Settings can be saved and later restored. <br> </p> <table width="100%"> <tr><td><b>Button</b></td> <td><b>Description</b></td></tr> <tr><td valign="top">Add</td> <td>Creates a copy of the current Layout Settings. <br> The default description is the current date. <br> The description may be changed. <br> The Add button will be greyed out when maximum number is reached.</td></tr> <br> <tr><td><i><u>Other Buttons</u> </i></td> <td>Greyed out when there are no selections</td></tr> <tr><td>Restore</td> <td>Loads the Layout Settings from the selection. Automatically exits. </td></tr> <tr><td>Rename </td> <td>Modify the description of the selection. Same as a double click.</td></tr> <tr><td valign="top">Update</td><td> Saves the current Layout Settings to the selection.<br> Prompts for confirmation.</td></tr> <tr><td valign="top">Delete</td> <td>Deletes the selecton. <br> Prompts for confirmation.</td></tr> <tr><td><i><u>Control</u> </i></td> <td></td></tr> <tr><td>Exit </td> <td>(Red circle with a white "X".) Returns to OSCAR menu.</td></tr> <tr><td>Return</td> <td>Next to Exit icon. Only in Help Menu. Returns to Layout menu.</td></tr> <tr><td>Escape Key</td> <td>Exit the Help or Layout menu.</td></tr> </table> <p><b>Layout Settings</b></p> <table width="100%"> <tr> <td>* Name</td> <td>* Pinning</td> <td>* Plots Enabled </td> <td>* Height</td> </tr> <tr> <td>* Order</td> <td>* Event Flags</td> <td>* Dotted Lines</td> <td>* Height Options</td> </tr> </table> <p><b>General Information</b></p> <ul style=margin-left="20"; > <li> Maximum description size = 80 characters. </li> <li> Maximum Saved Layout Settings = 30. </li> <li> Saved Layout Settings can be accessed by all profiles. <li> Layout Settings only control the layout of a graph or chart. <br> They do not contain any other data. <br> They do not control if a graph is displayed or not. </li> <li> Layout Settings for daily and overview are managed independantly. </li> </ul> <p style="color:black;"> Deze functie beheert het opslaan en herstellen van lay-outinstellingen. <br> Lay-out Instellingen bepalen de lay-out van een grafiek of diagram. <br> Diverse Lay-out instellingen kunnen worden opgeslagen en later hersteld. <br> </p> <table width="100%"> <tr><td><b>Button</b></td> <td><b>Beschrijving</b></td></tr> <tr><td valign="top">Toevoegen</td> <td>Maakt een kopie van de huidige lay-outinstellingen. <br> De standaardomschrijving is de huidige datum. <br> De omschrijving kan worden gewijzigd. <br> De knop Toevoegen wordt grijs als het maximale aantal is bereikt.</td></tr> <br> <tr><td><i><u>Andere knoppen</u> </i></td> <td>Grijs als er geen selecties zijn</td></tr> <tr><td>Herstel</td> <td>Laadt de lay-outinstellingen van de selectie. Sluit automatisch af. </td></tr> <tr><td>Hernoemen </td> <td>Wijzigt de beschrijving van de selectie. Hetzelfde als een dubbelklik.</td></tr> <tr><td valign="top">Bijwerken</td><td> Slaat de huidige lay-outinstellingen op in de selectie.<br> Vraagt om bevestiging.</td></tr> <tr><td valign="top">Verwijder</td> <td>Verwijdert de selectie. <br> Vraagt om bevestiging.</td></tr> <tr><td><i><u>Beheer</u> </i></td> <td></td></tr> <tr><td>Afsluiten </td> <td>(Rode cirkel met een witte "X".) Gaat terug naar het OSCAR menu.</td></tr> <tr><td>Ga terug</td> <td>Naast Afsluiten. Alleen in het menu Help. Keert terug naar het Lay-out menu.</td></tr> <tr><td>Escape toets</td> <td>Verlaat het menu Help of Layout.</td></tr> </table> <p><b>Lay-out instellingen</b></p> <table width="100%"> <tr> <td>* Naam</td> <td>* Vastpinnen</td> <td>* Plots ingeschakeld </td> <td>* Hoogte</td> </tr> <tr> <td>* Volgorde</td> <td>* Gebeurtenis markeringen</td> <td>* Stippellijnen</td> <td>* Hoogte-opties</td> </tr> </table> <p><b>Algemene informatie</b></p> <ul style=margin-left="20"; > <li> Maximumomvang beschrijving = 80 tekens. </li> <li> Maximum opgeslagen lay-outinstellingen = 30. </li> <li> Opgeslagen lay-outinstellingen zijn toegankelijk voor alle profielen. <li> Lay-outinstellingen regelen alleen de lay-out van een grafiek of diagram. <br> Ze bevatten geen andere gegevens. <br> Zij bepalen niet of een grafiek al dan niet wordt weergegeven. </li> <li> Lay-outinstellingen voor Dagrapport en Overzicht worden onafhankelijk beheerd. </li> </ul> Maximum number of Items exceeded. Maximum aantal items overschreden. No Item Selected Geen item geselecteerd Ok to Update? Ok om bij te werken? Ok To Delete? Ok om te verwijderen? SessionBar %1h %2m %1u: %2m No Sessions Present Geen sessies gevonden SleepStyleLoader Import Error Importfout This device 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 Warning: Disabled session data is excluded in this report Waarschuwing: Uitgeschakelde sessiegegevens zijn niet opgenomen in dit rapport 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: Device Information Apparaat-informatie Changes to Device Settings Wijzigingen in de instellingen van het apparaat Oscar has no data to report :( OSCAR heeft geen gegevens om te laten zien :( Database has No %1 data available. De database heeft geen %1 gegevens beschikbaar. Database has %1 day of %2 Data on %3 De database heeft %1 dag van %2 gegevens op %3 Database has %1 days of %2 Data, between %3 and %4 De database heeft %1 dagen van %2 gegevens, tussen %3 en %4 Total Days: %1 Totaal aantal dagen: %1 Days Not Used: %1 Dagen niet gebruikt: %1 Days Used: %1 Dagen gebruikt: %1 Days %1 %2 Hours: %3 Dagen %1 %2 Uren: %3 Best Device Setting Beste apparaatinstelling Worst Device Setting Slechtste apparaatinstelling 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) 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 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 device.</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 device 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 today vandaag %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 <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 pressure was under %1 %2 for %3% of the time. Uw druk was %3% van de tijd beneden %1 %2 (mediaan). 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. 1 day ago één dag geleden Your device was on for %1. Uw apparaat stond aan gedurende %1. Your CPAP device used a constant %1 %2 of air Uw CPAP blies met een constante %1 %2 luchtdruk Your device used a constant %1-%2 %3 of air. Uw CPAP gebruikte een constante luchtdruk van %1-%2 %3. Your device was under %1-%2 %3 for %4% of the time. Uw CPAP was beneden %1-%2 %3 gedurende %4% van de tijd. Your EEPAP pressure was under %1 %2 for %3% of the time. Uw EEPAP-druk was onder %1 %2 gedurende %3% van de tijd. 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 Double click Y-axis: Return to AUTO-FIT Scaling Dubbelklik op Y-as: Keer terug naar Automatisch passend Double click Y-axis: Return to DEFAULT Scaling Dubbelklik op de Y-as: Keer terug naar Standaard schaalverdeling Double click Y-axis: Return to OVERRIDE Scaling Dubbelklik op de Y-as: Keer terug naar Ingestelde schaalverdeling Double click Y-axis: For Dynamic Scaling Dubbelklik op de Y-as: Voor Dynamische aanpassing Double click Y-axis: Select DEFAULT Scaling Dubbelklik op de Y-as: Kies Standaard schaalverdeling Double click Y-axis: Select AUTO-FIT Scaling Dubbelklik op Y-as: Kies Automatisch passend %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.5.1/Translations/Norsk.no.ts000066400000000000000000015726731450332542600202540ustar00rootroot00000000000000 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. 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 i 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 Search Søk Layout Save and Restore Graph Layout Settings Show/hide available graphs. Vis/skjul tilgjengelige grafer. Breakdown Brutt ned events hendelser UF1 UF1 UF2 UF2 Time at Pressure Tid på trykk Clinical Mode Disabling Sessions requires the Permissive Mode 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.) no data :( Sorry, this device only provides compliance data. 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 Model %1 - %2 Modell %1 - %2 PAP Mode: %1 PAP-modus: %1%1 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 "Nothing's here!" "Ingenting her!" No data is available for this day. Ingen data tilgjengelig for denne dagen. 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 Device Settings <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 This CPAP device does NOT record detailed data 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?? Complain to your Equipment Provider! Klag til din fabrikant av ustyret! Pick a Colour Velg en farge Bookmark at %1 Bokmerke på %1 Hide All Events Show All Events Hide All Graphs Show All Graphs DailySearchTab Clear Bookmark Jumps to Date's Bookmark Start Search DATE Jumps to Date Match Notes Notater Notes containing Bookmarks Bokmerker Bookmarks containing AHI Daily Duration Session Duration Days Skipped Disabled Sessions Number of Sessions Click HERE to close Help Help Hjelp No Data Jumps to Date's Details Number Disabled Session Jumps to Date's Details Note Jumps to Date's Notes AHI Jumps to Date's Details EventsPerHour Session Duration Jumps to Date's Details Minutes Number of Sessions Jumps to Date's Details Sessions Daily Duration Jumps to Date's Details Hours Number of events Jumps to Date's Events Events Automatic start Continue Search End of Search No Matches Skip:%1 %1/%2%3 days %1 dager {1/%2%3 ?} Found %1 Finds days that match specified criteria. Searches from last day to first day. First click on Match Button then select topic. Then click on the operation to modify it. or update the value Topics without operations will automatically start. Compare Operations: numberic or character. Numberic Operations: Character Operations: Summary Line Left:Summary - Number of Day searched Center:Number of Items Found Right:Minimum/Maximum for item searched Result Table Column One: Date of match. Click selects date. Column two: Information. Click selects date. Then Jumps the appropiate tab. Wildcard Pattern Matching: Wildcards use 3 characters: Asterisk Question Mark Backslash. Asterisk matches any number of characters. Question Mark matches a single character. Backslash matches next character. DateErrorDisplay ERROR The start date MUST be before the end date The entered start date %1 is after the end date %2 Hint: Change the end date first The entered end date %1 is before the start date %1 Hint: Change the start date first 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 device Record cannot be imported in this profile. 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 Show Standard Report Standard Standard Show Monthly Report Monthly Månedlig Show Range Report Date Range Dato fra til Select Report Date Report Date 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 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 Standard - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> Advanced - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> Purge Current Selected Day &CPAP &CPAP &Oximetry &Oksimetri &Sleep Stage &Position &All except Notes All including &Notes Show &Line Cursor Vis &linjemarkør Purge ALL Device Data 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 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 Couldn't find any valid Device Data at %1 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. 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) Are you sure you want to rebuild all CPAP data for the following device: For some reason, OSCAR does not have any backups for the following device: 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. Note as a precaution, the backup folder will be left in place. Merk som en forholdsregel at sikkerhetskopimappen blir liggende igjen. 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 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 No supported data was found 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. Would you like to import from your own backups now? (you will have no data visible for this device until you do) OSCAR does not have any backups for this device! Unless you have made <i>your <b>own</b> backups for ALL of your data for this device</i>, <font size=+2>you will lose this device's data <b>permanently</b>!</font> You are about to <font size=+2>obliterate</font> OSCAR's device database for the following device:</p> A file permission error caused the purge process to fail; you will have to delete the following folder manually: There was a problem opening MSeries block File: Det var et problem med å åpne MSeries blokkfil: MSeries Import complete MSeries Import ferdig You must select and open the profile you wish to modify 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 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. This software is being designed to assist you in reviewing the data produced by your CPAP Devices and related equipment. 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 Layout Save and Restore Graph Layout Settings 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) Hide All Graphs Show All Graphs 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> 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. <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 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 I started this oximeter recording at (or near) the same time as a session on my CPAP device. <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 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 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é. Duration of airflow restriction Varighet av luftstrømningsbegrensning s s Event Duration Hendelsesvarighet 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 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><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></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>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 <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></p></body></html> 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.) <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 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 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. Clinical Clinical Settings Select Oscar Operating Mode Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. Clinical Mode permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. Permissive Mode Hours <!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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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> 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 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> Oximetry Settings Oximetry-innstlinnger <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</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) This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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>Provide an alert when importing data from any device model that has not yet been tested by OSCAR developers.</p></body></html> Warn when importing data from an untested device This calculation requires Total Leaks data to be provided by the CPAP device. (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. Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device missed. This option must be enabled before import, otherwise a purge is required. This experimental option attempts to use OSCAR's event flagging system to improve device detected event positioning. Resync Device Detected Events (Experimental) Allow duplicates near device events. Show flags for device detected events that haven't been identified yet. <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices do not support changing these settings.</p></body></html> Whether to include device serial number on device settings changes report 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) For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. Enables SessionBar in Event Flags Graph 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. Overview Oversikt No CPAP devices detected Will you be using a ResMed brand device? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, days will <b>split at noon</b> like in ResMed's commercial software.</p> 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å? ResMed S9 devices routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). 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 Clinical Mode: Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. Basically replicates the reports and data stored on the devices data card. This includes pap devices, oximeters, etc. Compliance reports fall under this mode. Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. Permissive Mode: Allows user to select which data sets/ sessions to be used for calculations and display. Additional charts and calculations may be available that are not available from the vendor data. Changing the Oscar Operating Mode: Requires a reload of the user's profile. Data will be saved and restored. Minor Flag Mindre flagg Span Spenn Always Minor Alltid mindre Never Aldri This may not be a good idea Dette er kanskje ikke en god idé 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 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 Length: %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 milliSeconds 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 EEPAP Min EEPAP Max EEPAP 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 Device No Data Available Ingen data tilgjengelig App key: Operating system: Built with Qt %1 on %2 Graphics Engine: Graphics Engine type: Compiler: Software Engine ANGLE / OpenGLES Desktop OpenGL m m cm cm in kg l/min Only Settings and Compliance Data Available Summary Data Only Bookmarks Bokmerker Mode Model Modell Brand Merke Serial Series 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 device's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Non Data Capable Device Your %1 CPAP Device (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 device. Device Untested Your %1 CPAP Device (Model %2) has not been tested yet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Device Unsupported Sorry, your %1 CPAP Device (%2) is not supported yet. The developers need a .zip copy of this device's SD card and matching clinician .pdf reports to make it work with OSCAR. Getting Ready... Scanning Files... Importing Sessions... UNKNOWN APAP (std) APAP (dyn) Auto S Auto S/T AcSV SoftPAP Mode Pressure relief during exhalation Slight Softstart pressure Pressure during soft start period PSoft Softstart minimum pressure Minimum pressure during soft start period PSoftMin Auto start Automatically turn on the device by breathing Softstart time Lenght of soft start period Soft start maximum time Maximum lenght of soft start period Soft start max. time Soft start pressure Higher End Expiratory Pressure Humidifier level Tube type Obstruction level Obstruction level in percentage rRMVFluctuation Relative respiratory minute volume fluctuation Relative respiratory minute volume Measured pressure Full flow Artefact Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) Epoch (2 mins) with Flow Limitation Deep Sleep Deep sleep, stable respiration Timed breath BiSoft Mode BiSoft 1 BiSoft 2 TriLevel PMaxOA EEPAPMin Lower End Expiratory Pressure EEPAPMax rRMV ART CriticalLeak Mask leakage is above a critical treshold CL eMO Epoch (2 mins) with Mild Obstruction eSO Epoch (2 mins) with Severe Obstruction eS Epoch (2 mins) with Snoring eFL DS Finishing up... 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 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 A few breaths automatically starts device Device automatically switches off Whether or not device allows Mask checking. 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 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 A period during a session where the device could not detect flow. 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 Auto Off Auto på Mask Alert Show AHI Vis AHI Whether or not device shows AHI via built-in display. The number of days in the Auto-CPAP trial period, after which the device will revert to CPAP Breathing Not Detected BND BND Timed Breath Machine Initiated Breath TB TB 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 device 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 device 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? Device Database Changes Sorry, the purge operation failed, which means this version of OSCAR can't start. The device data folder needs to be removed manually. 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. Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. 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 End 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 Cheyne Stokes Respiration (CSR) Periodic Breathing (PB) Clear Airway (CA) An apnea caused by airway obstruction A partially obstructed airway UA A vibratory snore Pressure Pulse A pulse of pressure 'pinged' to detect a closed airway. A type of respiratory event that won't respond to a pressure increase. Intellipap event where you breathe out your mouth. SensAwake feature will reduce pressure when waking is detected. Heart rate in beats per minute Blood-oxygen saturation percentage Plethysomogram An optical Photo-plethysomogram showing heart rhythm A sudden (user definable) change in heart rate A sudden (user definable) drop in blood oxygen saturation SD SD Breathing flow rate waveform 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 Ratio between Inspiratory and Expiratory time ratio Pressure Min Pressure Max Pressure Set Pressure Setting IPAP Set IPAP Setting EPAP Set EPAP Setting An abnormal period of Cheyne Stokes Respiration CSR An abnormal period of Periodic Breathing An apnea that couldn't be determined as Central or Obstructive. An apnea reported by your CPAP device. A restriction in breathing from normal, causing a flattening of the flow waveform. A vibratory snore as detected by a System One device 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 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 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. Obstructive Apnea (OA) Hypopnea (H) Unclassified Apnea (UA) Apnea (A) Flow Limitation (FL) RERA (RE) Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. Vibratory Snore (VS) Leak Flag (LF) A large mask leak affecting device performance. Large Leak (LL) Non Responding Event (NR) Expiratory Puff (EP) SensAwake (SA) User Flag #1 (UF1) User Flag #2 (UF2) User Flag #3 (UF3) Pulse Change (PC) SpO2 Drop (SD) I/E Value Apnea Hypopnea Index (AHI) Respiratory Disturbance Index (RDI) 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? OSCAR Reminder Don't forget to place your datacard back in your CPAP device 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 oximetry 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 (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 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) Min EEPAP %1 Max EEPAP %2 PDIFF %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 Selection Length 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 Device auto starts by breathing Response Device auto stops by breathing Patient View RiseEnable RiseTime Cycle Trigger TiMax TiMin Your ResMed CPAP device (Model %1) has not been tested yet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card to make sure it works with OSCAR. SmartStart 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 Smart Stop Simple Advanced Avansert 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... Loading summaries Permissive Mode Total disabled sessions: %1, found in %2 days Total disabled sessions: %1 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. 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 Löwenstein Prisma Smart Resvent/Hoffrichter iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings Add Add Feature inhibited. The maximum number of Items has been exceeded. creates new copy of current settings. Restore Restores saved settings from selection. Rename Renames the selection. Must edit existing name then press enter. Update Updates the selection with current settings. Delete Deletes the selection. Expanded Help menu. Exits the Layout menu. <h4>Help Menu - Manage Layout Settings</h4> Exits the help menu. Exits the dialog menu. This feature manages the saving and restoring of Layout Settings. Layout Settings control the layout of a graph or chart. Different Layouts Settings can be saved and later restored. Button Description Creates a copy of the current Layout Settings. The default description is the current date. The description may be changed. The Add button will be greyed out when maximum number is reached. Other Buttons Greyed out when there are no selections Loads the Layout Settings from the selection. Automatically exits. io Modify the description of the selection. Same as a double click.io Saves the current Layout Settings to the selection. Prompts for confirmation. Deletes the selecton. Control Exit Avslutt (Red circle with a white "X".) Returns to OSCAR menu. Return Next to Exit icon. Only in Help Menu. Returns to Layout menu. Escape Key Exit the Help or Layout menu. Layout Settings * Name * Pinning * Plots Enabled * Height * Order * Event Flags * Dotted Lines * Height Options General Information Maximum description size = 80 characters. Maximum Saved Layout Settings = 30. Saved Layout Settings can be accessed by all profiles. Layout Settings only control the layout of a graph or chart. They do not contain any other data. They do not control if a graph is displayed or not. Layout Settings for daily and overview are managed independantly. Maximum number of Items exceeded. No Item Selected Ok to Update? Ok To Delete? SessionBar %1h %2m %1h %2m No Sessions Present Ingen-økter tilstede SleepStyleLoader Import Error Viktig feilmelding This device Record cannot be imported in this profile. 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 Device Information Changes to Device Settings Database has No %1 data available. Database has %1 day of %2 Data on %3 Database has %1 days of %2 Data, between %3 and %4 Total Days: %1 Days Not Used: %1 Days Used: %1 Dager brukt: %1 Days %1 %2 Hours: %3 Best Device Setting Worst Device Setting 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 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 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 device.</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 device is detected 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 today %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 <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 pressure was under %1 %2 for %3% of the time. Trykket var under %1 %2 for %3 av tiden. 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. 1 day ago 1 dag siden Your device was on for %1. Your CPAP device used a constant %1 %2 of air Your device used a constant %1-%2 %3 of air. Your device was under %1-%2 %3 for %4% of the time. Your EEPAP pressure was under %1 %2 for %3% of the time. 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 Double click Y-axis: Return to AUTO-FIT Scaling Double click Y-axis: Return to DEFAULT Scaling Double click Y-axis: Return to OVERRIDE Scaling Double click Y-axis: For Dynamic Scaling Double click Y-axis: Select DEFAULT Scaling Double click Y-axis: Select AUTO-FIT Scaling %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.5.1/Translations/Polski.pl.ts000066400000000000000000016605261450332542600204130ustar00rootroot00000000000000 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 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 i i 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ę Search Wyszukaj Layout Save and Restore Graph Layout Settings 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 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ń "Nothing's here!" "Tu nic nie ma!" 10 of 10 Graphs 10 z 10 wykresów Oximeter Information Informacje pulsoksymetru Details Szczegóły Clinical Mode Disabling Sessions requires the Permissive Mode 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 Device Settings Ustawienia aparatu 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ń This CPAP device does NOT record detailed data To urządzenie CPAP NIE rejestruje szczegółowych danych 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?? 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. no data :( Brak danych Sorry, this device only provides compliance data. Niestety ten aparat dostarcza tylko danych o zgodności. 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.) Hide All Events Ukryj wszystkie zdarzenia Show All Events Pokaż wszystkie zdarzenia Hide All Graphs Show All Graphs DailySearchTab Match Clear DATE Jumps to Date Notes Notatki Notes containing Bookmarks Zakładki Bookmarks containing AHI Daily Duration Session Duration Days Skipped Disabled Sessions Number of Sessions Click HERE to close Help Help Pomoc No Data Jumps to Date's Details Number Disabled Session Jumps to Date's Details Note Jumps to Date's Notes Bookmark Jumps to Date's Bookmark AHI Jumps to Date's Details EventsPerHour Session Duration Jumps to Date's Details Minutes Number of Sessions Jumps to Date's Details Sessions Daily Duration Jumps to Date's Details Hours Number of events Jumps to Date's Events Events Automatic start Continue Search End of Search No Matches Start Search Skip:%1 %1/%2%3 days %1 dni {1/%2%3 ?} Found %1 Finds days that match specified criteria. Searches from last day to first day. First click on Match Button then select topic. Then click on the operation to modify it. or update the value Topics without operations will automatically start. Compare Operations: numberic or character. Numberic Operations: Character Operations: Summary Line Left:Summary - Number of Day searched Center:Number of Items Found Right:Minimum/Maximum for item searched Result Table Column One: Date of match. Click selects date. Column two: Information. Click selects date. Then Jumps the appropiate tab. Wildcard Pattern Matching: Wildcards use 3 characters: Asterisk Question Mark Backslash. Asterisk matches any number of characters. Question Mark matches a single character. Backslash matches next character. DateErrorDisplay ERROR The start date MUST be before the end date BŁĄD Data rozpoczęcia MUSI przypadać przed datą zakończenia The entered start date %1 is after the end date %2 Wprowadzona data rozpoczęcia %1 jest późniejsza niż data zakończenia %2 Hint: Change the end date first Wskazówka: najpierw zmień datę zakończenia The entered end date %1 Wprowadzona data zakończenia %1 is before the start date %1 jest przed datą początkową %1 Hint: Change the start date first Wskazówka: najpierw zmień datę rozpoczęcia 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 device 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 Show Standard Report Standard Standard Show Monthly Report Monthly Miesięcznie Show Range Report Date Range Zakres dat Select Report Date Report Date Statistics Statystyki Daily Dziennie Overview Przegląd Oximetry Pulsoksymetria Import Import Help Pomoc &File &Plik &View &Widok &Help &Pomoc &Data &Dane &Advanced &Zaawansowane Purge ALL Device Data Wyczyść wszystkie dane aparatu 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. You must select and open the profile you wish to modify 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. 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 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? Couldn't find any valid Device Data at %1 Nie mogę znaleźć odpowiednich danych w %1 Specify Sprecyzuj No supported data was found 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" Are you sure you want to rebuild all CPAP data for the following device: Na pewno chcesz przebudować wszystkie dane dla : For some reason, OSCAR does not have any backups for the following device: Z pewnych powodów OSCAR nie ma żadnych kopii zapasowych dla: Would you like to import from your own backups now? (you will have no data visible for this device 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) OSCAR does not have any backups for this device! 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 device</i>, <font size=+2>you will lose this device'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> You are about to <font size=+2>obliterate</font> OSCAR's device database for the following device:</p> Usuwasz <font size=+2>bezpowrotnie</font> bazę danych dla aparatu:</p> A file permission error caused the purge process to fail; you will have to delete the following folder manually: 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 &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) 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 Standard - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> Advanced - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> 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 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. This software is being designed to assist you in reviewing the data produced by your CPAP Devices and related equipment. Ten program jest przeznaczony do pomocy w ocenie danych z aparatu CPAP i akcesoriów. 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 Layout Save and Restore Graph Layout Settings 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 Hide All Graphs Show All Graphs 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. <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 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ę I started this oximeter recording at (or near) the same time as a session on my CPAP device. Wystartowałem pulsoksymetr o niemal tym samym czasie co aparat CPAP. <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.) <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 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) This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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) <html><head/><body><p>Provide an alert when importing data from any device 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 device Ostrzegaj przed importem danych z nie testowanego aparatu &CPAP &CPAP This calculation requires Total Leaks data to be provided by the CPAP device. (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ć. Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device 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. This experimental option attempts to use OSCAR's event flagging system to improve device detected event positioning. Ta eksperymentalna opcja próbuje użyć systemu flagowania OSCAR by poprawić pozycjonowanie zdarzeń wykrytych przez aparat. Resync Device Detected Events (Experimental) Resynchronizuj zdarzenia wykryte przez aparat (eksperymentalne) Allow duplicates near device events. Pozwalaj na duplikaty zdarzeń bliskich . Show flags for device detected events that haven't been identified yet. Pokaż flagi zdarzeń wykrytych przez aparat które dotąd nie zostały zidentyfikowane. 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 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. Duration of airflow restriction Czas ograniczenia przepływu powietrza s s Event Duration Czas trwania zdarzenia 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ń 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 <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> 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) 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 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> Clinical Clinical Settings Select Oscar Operating Mode Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. Clinical Mode permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. Permissive Mode Hours Oximetry Settings Ustawienia pulsoksymetru <!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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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> 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) <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">śesje trwające krócej nie będą wyświetlane</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Flagowanie przez użytkownika jest eksperymentalną metodą wykrywania zdarzeń pominiętych przez urządzenie.Są one </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> włączone do AHI.</span></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices 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> <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:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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><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:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Synchronizowanie danych oksymetrii i CPAP</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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 (from .spoR files) lub serial import </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ą właściwego znacznika czasowego potrzebnego do synchronizacji.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Tryb Live view (z użyciem kabla serial) jest jedynym sposobem do uzyskania właściwej synchronizacji pulsoksymetrów CMS50 oximeters, ale nie uwzględnia niedokładności zegara urządzenia CPAP.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Jeżeli właczysz pulsoksymetr </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">równocześnie </span><span style=" font-family:'Sans'; font-size:10pt;">z aparatem CPAP, to możesz uzyskać synchronizację. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Import przez usb pobiera czas startu z ostatniej sesji - pamiętaj, najpierw wgraj dane z karty SD!</span></p></body></html> 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. Whether to include device serial number on device settings changes report Czy zaimportować nr seryjny urządzenia w raporcie zmian ustawień urządzenia For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. Enables SessionBar in Event Flags Graph 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 ResMed S9 devices 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). 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 Clinical Mode: Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. Basically replicates the reports and data stored on the devices data card. This includes pap devices, oximeters, etc. Compliance reports fall under this mode. Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. Permissive Mode: Allows user to select which data sets/ sessions to be used for calculations and display. Additional charts and calculations may be available that are not available from the vendor data. Changing the Oscar Operating Mode: Requires a reload of the user's profile. Data will be saved and restored. Minor Flag Mała flaga Span Odstęp Always Minor Zawsze mniejsze Never Nigdy 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? No CPAP devices detected Nie wykryto aparatu CPAP Will you be using a ResMed brand device? Czy będziesz używał aparatu ResMed? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, 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> This may not be a good idea To słaby pomysł 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 Include Serial Number Dołącz numer seryjny <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 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 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 Length: %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 milliSeconds 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 EEPAP Min EEPAP Max EEPAP 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 Compiler: Software Engine Silnik oprogramowania ANGLE / OpenGLES ANGLE / OpenGLES Desktop OpenGL Desktop OpenGL m m cm cm in cal kg kg l/min l/min 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 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 device's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Dane zaimportowanie mogą nie być dokładne dlatego deweloperzy chcieliby kopię .zip Twoich plików danych z karty SD, dla pewności, ze OSCAR prawidłowo obsługuje dane. Non Data Capable Device Aparat nie zbierający danych Your %1 CPAP Device (Model %2) is unfortunately not a data capable model. Twoje urządzenier CPAP %1 (model %2) nie udostępnia zgodnych danych. I'm sorry to report that OSCAR can only track hours of use and very basic settings for this device. Niestety OSCAR może śledzić tylko czas działania i podstawowe ustawienia tego aparatu. Device Untested Aparat nie testowany Your %1 CPAP Device (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 devices that it might work, but the developers would like a .zip copy of this device'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. Device Unsupported Aparat nie wspierany Sorry, your %1 CPAP Device (%2) is not supported yet. Przepraszamy, Twój aparat %1 (%2) nie jest jeszcze wspierany. The developers need a .zip copy of this device's SD card and matching clinician .pdf reports to make it work with OSCAR. Deweloperzy potrzebują kopii .zip karty SD oraz odpowiadającej kopii .pdf badań lekarskich, aby mogły one współpracować z OSCARem. 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ę... Scanning Files... Skanuję pliki... Importing Sessions... Importuję sesje... UNKNOWN APAP (std) APAP (dyn) Auto S Auto S/T AcSV SoftPAP Mode Pressure relief during exhalation Slight Softstart pressure Pressure during soft start period PSoft Softstart minimum pressure Minimum pressure during soft start period PSoftMin Auto start Automatically turn on the device by breathing Softstart time Lenght of soft start period Soft start maximum time Maximum lenght of soft start period Soft start max. time Soft start pressure PMaxOA EEPAPMin Lower End Expiratory Pressure EEPAPMax Higher End Expiratory Pressure Humidifier level Tube type Obstruction level Obstruction level in percentage rRMVFluctuation Relative respiratory minute volume fluctuation rRMV Relative respiratory minute volume Measured pressure Full flow Artefact Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) ART CriticalLeak Mask leakage is above a critical treshold CL eMO Epoch (2 mins) with Mild Obstruction eSO Epoch (2 mins) with Severe Obstruction eS Epoch (2 mins) with Snoring eFL Epoch (2 mins) with Flow Limitation Deep Sleep Deep sleep, stable respiration DS Timed breath BiSoft Mode BiSoft 1 BiSoft 2 TriLevel Finishing up... Kończę... Hose Diameter Średnica węża Diameter of primary CPAP hose Średnica węża podstawowego CPAP Auto On Auto włączanie Auto Off Auto wyłączanie Mask Alert Alarm maski Show AHI Pokaż AHI Breathing Not Detected Nie wykryto oddechu BND BND Timed Breath Czasowy oddech Machine Initiated Breath Oddech inicjowany przez aparat TB TB Windows User Użytkownik Windows 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> <i>Your old device 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> 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 device data again afterwards from your own backups or data card. To oznacza, że potrzebujesz ponownie zaimportować dane urządzenia z zapasu lub karty danych. 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? Device Database Changes Zmiany bazy danych aparatów 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ć. The device data folder needs to be removed manually. Folder danych urządzenia musi być usunięty ręcznie. 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. 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 End Expiratory Pressure 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 Cheyne Stokes Respiration (CSR) Oddech Cheyne-Stokes'a (CSR) Periodic Breathing (PB) Oddychanie okresowe (PB) Clear Airway (CA) Centralne (CA) An apnea caused by airway obstruction Bezdech spowodowany obturacją dróg oddechowych A partially obstructed airway Częściowo zamknięte drogi oddechowe UA UA A vibratory snore Chrapanie z wibracją 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 type of respiratory event that won't respond to a pressure increase. Rodzaj zdarzenia oddechowego nie odpowiadającego na zwiększenie ciśnienia. 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. 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 A sudden (user definable) change in heart rate Nagła (zdefiniowana przez użytkownika) zmiana częstosci akcji serca 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 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 CSR CSR An abnormal period of Cheyne Stokes Respiration Nienormalny okres oddechów Cheyne-Stokes'a An abnormal period of Periodic Breathing Nienormalny czas oddychania okresowego 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. A vibratory snore as detected by a System One device Chrapanie wibracyjne wykryte przez urządzenie System One 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 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 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 Obstructive Apnea (OA) Bezdech obturacyjny (OA) Hypopnea (H) Spłycony oddech (H) Unclassified Apnea (UA) Bezdech niesklasyfikowany (UA) Apnea (A) bezdech (a) An apnea reportred by your CPAP device. Bezdech zaraportowany przez aparat. Flow Limitation (FL) Ograniczenie przepływu (FL) RERA (RE) RERA (RE) Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. Vibratory Snore (VS) Chrapanie z wibracją (VS) Leak Flag (LF) Flaga wycieku (LF) A large mask leak affecting device performance. Duży wyciek wpływający na efektywność działania aparatu. Large Leak (LL) Duży wyciek (LL) Non Responding Event (NR) Zdarzenie nie odpowiadające (NR) Expiratory Puff (EP) Pufnięcie wydechowe (EP) SensAwake (SA) Przebudzenie sensoryczne (SA) User Flag #1 (UF1) Flaga użytkownika #1 (UF1) User Flag #2 (UF2) Flaga użytkownika #2 (UF2) User Flag #3 (UF3) Flaga użytkownika #3(UF3) Pulse Change (PC) Zmiany pulsu (PC) SpO2 Drop (SD) Spadek SpO2 (SD) I/E Value Apnea Hypopnea Index (AHI) Indeks bezdechów i spłycenia oddechu (AHI) Respiratory Disturbance Index (RDI) Wskaźnik zaburzeń oddychania (RDI) 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? OSCAR Reminder OSCAR przypomina Don't forget to place your datacard back in your CPAP device Nie zapomnij włożyć swojej karty SD ponownie do aparatu CPAP 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 oximetry settings to defaults? 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) Min EEPAP %1 Max EEPAP %2 PDIFF %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 Selection Length 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 RiseEnable RiseTime Cycle Trigger TiMax TiMin Your ResMed CPAP device (Model %1) has not been tested yet. Twój aparat ResMed (model %1) nie był dotąd testowany. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device'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. 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. An apnea reported by your CPAP device. 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: Data directory: Folder danych: Permissive Mode Total disabled sessions: %1, found in %2 days Total disabled sessions: %1 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. 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 Device Urządzenie 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 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 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 A few breaths automatically starts device Kilka oddechów automatycznie włącza aparat Device automatically switches off Aparat wyłącza się automatycznie Whether or not device allows Mask checking. Czy aparat pokazuje alarm maski. Whether or not device shows AHI via built-in display. Czy aparat pokazuje AHI. The number of days in the Auto-CPAP trial period, after which the device will revert to CPAP Ilość dni w okresie próbnym auto-CPAP, po którym aparat wróci do trybu CPAP A period during a session where the device could not detect flow. Przez pewien czas sesji aparat nie wykrył przepływu. 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 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 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 Device auto starts by breathing Aparat sam startuje przez rozpoczęcie oddychania SmartStop SmartStop Smart Stop Smart Stop Device auto stops by breathing Aparat wyłącza się sam przez oddychanie Patient View Widok pacjenta Simple Prosty Advanced Zaawansowany SensAwake level poziom czułości wybudzania Expiratory Relief ulga wydechowa Expiratory Relief Level poziom ulgi wydechowej Humidity wilgotność SleepStyle styl snu 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ń Löwenstein Prisma Smart Resvent/Hoffrichter iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings Add Add Feature inhibited. The maximum number of Items has been exceeded. creates new copy of current settings. Restore Restores saved settings from selection. Rename Renames the selection. Must edit existing name then press enter. Update Updates the selection with current settings. Delete Deletes the selection. Expanded Help menu. Exits the Layout menu. <h4>Help Menu - Manage Layout Settings</h4> Exits the help menu. Exits the dialog menu. This feature manages the saving and restoring of Layout Settings. Layout Settings control the layout of a graph or chart. Different Layouts Settings can be saved and later restored. Button Description Creates a copy of the current Layout Settings. The default description is the current date. The description may be changed. The Add button will be greyed out when maximum number is reached. Other Buttons Greyed out when there are no selections Loads the Layout Settings from the selection. Automatically exits. io Modify the description of the selection. Same as a double click.io Saves the current Layout Settings to the selection. Prompts for confirmation. Deletes the selecton. Control Exit Wyjście (Red circle with a white "X".) Returns to OSCAR menu. Return Next to Exit icon. Only in Help Menu. Returns to Layout menu. Escape Key Exit the Help or Layout menu. Layout Settings * Name * Pinning * Plots Enabled * Height * Order * Event Flags * Dotted Lines * Height Options General Information Maximum description size = 80 characters. Maximum Saved Layout Settings = 30. Saved Layout Settings can be accessed by all profiles. Layout Settings only control the layout of a graph or chart. They do not contain any other data. They do not control if a graph is displayed or not. Layout Settings for daily and overview are managed independantly. Maximum number of Items exceeded. No Item Selected Ok to Update? Ok To Delete? SessionBar %1h %2m %1h %2m No Sessions Present Brak sesji SleepStyleLoader Import Error Błąd importu This device 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: Device Information Informacje o aparacie Changes to Device Settings Zmiany ustawień urządzenia Database has No %1 data available. Database has %1 day of %2 Data on %3 Database has %1 days of %2 Data, between %3 and %4 Total Days: %1 Days Not Used: %1 Days Used: %1 Dni użycia: %1 Worst Device Setting 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 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ń) No data found?!? Nie znaleziono danych? Days %1 %2 Hours: %3 Best Device Setting 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 <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 device.</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> 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 device 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 today %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 device was on for %1. Aparat działał przez %1. Your CPAP device used a constant %1 %2 of air Twój aparat podawał stałe ciśnienie %1 %2 Your device used a constant %1-%2 %3 of air. Ten aparat podawał stałe %1-%2 %3 powietrza. Your device was under %1-%2 %3 for %4% of the time. Aparat był poniżej %1-%2 %3 przez %4% czasu. <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 EEPAP pressure was under %1 %2 for %3% of the time. 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 pressure was under %1 %2 for %3% of the time. Ciśnienie było poniżej %1 %2 przez %3% czasu. 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. 1 day ago 1 dzień wcześniej gGraph Double click Y-axis: Return to AUTO-FIT Scaling Kliknij dwukrotnie oś Y: aby powrócić do skalowania AUTO-FIT Double click Y-axis: Return to DEFAULT Scaling Kliknij dwukrotnie oś Y: aby powrócićt do skalowania DOMYŚLNEGO Double click Y-axis: Return to OVERRIDE Scaling Dwukrotne kliknięcie osi Y: aby powrócić do opcji ZMIEŃ skalowanie Double click Y-axis: For Dynamic Scaling Kliknij dwukrotnie oś Y: dla dynamicznego skalowania Double click Y-axis: Select DEFAULT Scaling Kliknij dwukrotnie oś Y: Wybierz skalowanie DOMYŚLNE Double click Y-axis: Select AUTO-FIT Scaling Kliknij dwukrotnie oś Y: Wybierz skalowanie AUTO-FIT %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.5.1/Translations/Portugues.pt.ts000066400000000000000000017202701450332542600211500ustar00rootroot00000000000000 AboutDialog &About &Sobre Release Notes Notas da Versão Credits Créditos GPL License Licença GPL Close Fechar Show data folder Mostrar pasta de dados About OSCAR %1 Sobre OSCAR %1 Sorry, could not locate About file. Desculpe, não consegui localizar o ficheiro Sobre. Sorry, could not locate Credits file. Desculpe, não consegui localizar o ficheiro Créditos. Sorry, could not locate Release Notes. Desculpe, não consegui localizar as Notas de Versão. 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. Uma vez que esta é uma versão pré-lançamento, recomenda-se que faça <b>o back-up manualmente da sua pasta de dados</b> antes de prosseguir, porque tentar voltar mais tarde pode quebrar as coisas. 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: Não consegui encontrar o ficheiro de oximetro: Could not open the oximeter file: Não consegui abrir o ficheiro de oximetro: CMS50Loader Could not get data transmission from oximeter. Não consegui a transmissão de dados do oximetro. Please ensure you select 'upload' from the oximeter devices menu. Por favor certifique-se de que seleciona 'upload' do menu do dispositivo oximetro. Could not find the oximeter file: Não consegui encontrar o ficheiro de oximetro: Could not open the oximeter file: Não consegui abrir o ficheiro de oximetro: CheckUpdates Checking for newer OSCAR versions A buscar versões mais recentes 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 i i Small Pequeno Medium Médio Big Grande I'm feeling ... Estou a sentir-me... If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value Se a altura for maior do que zero no Diálogo de Preferências, configurar o peso aqui mostrará o valor do Índice de Massa Corporal (IMC) Search Pesquisar Layout Layout Save and Restore Graph Layout Settings Salvar e Restaurar as configurações de Layout de Gráfico 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 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 Horários de início da sessão Session End Times Horários finais da sessão Duration Duração Position Sensor Sessions Sessões de Sensor de Posição Details Detalhes Time at Pressure Tempo na Pressão Clinical Mode Disabling Sessions requires the Permissive Mode Unknown Session Sessão Desconhecida Click to %1 this session. Clique em %1 para 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. <b>Nota:</b> Todas as definições apresentadas abaixo baseiam-se em pressupostos de que nada mudou desde os dias anteriores. PAP Mode: %1 Modo PAP: %1 (Mode and Pressure settings missing; yesterday's shown.) (Definições de modo e pressão em falta; ontem mostrada.) 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 Não é possível exibir gráfico de tortas (Pizza) neste sistema Sessions all off! Sessões todas desativadas! Sessions exist for this day but are switched off. As sessões existem para este dia, mas estão desligadas. Impossibly short session Sessão impossivelmente curta Zero hours?? Zero horas?? Complain to your Equipment Provider! Reclama para seu fornecedor do equipamento! This bookmark is in a currently disabled area.. Este marcador encontra-se numa área atualmente desativada.. 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 Session Information Informações da Sessão Disable Warning Desativar Aviso Disabling a session will remove this session data from all graphs, reports and statistics. The Search tab can find disabled sessions Continue ? A desativação de uma sessão removerá os dados desta sessão de todos os gráficos, relatórios e estatísticas. A guia Pesquisar pode encontrar sessões desabilitadas Continuar ? CPAP Sessions Sessões CPAP Oximetry Sessions Sessões de Oxímetro Sleep Stage Sessions Sessões de Estátio de Sono Device Settings Definições do Dispositivo 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 This CPAP device does NOT record detailed data Este dispositivo CPAP NÃO regista dados detalhados no data :( sem dados :( Sorry, this device only provides compliance data. Desculpe, este dispositivo só fornece dados de conformidade. "Nothing's here!" "Não há nada aqui!" No data is available for this day. Não há dados disponíveis para este dia. Pick a Colour Escolha uma Cor Bookmark at %1 Favorito em %1 Hide All Events Ocultar Todos os Eventos Show All Events Mostrar Todos os Eventos Hide All Graphs Ocultar Todos os Gráficos Show All Graphs Mostrar Todos os Gráficos DailySearchTab Match: Correspondência: Select Match Selecione Correspondência Clear Limpar Bookmark Jumps to Date's Bookmark Start Search Iniciar Busca DATE Jumps to Date DATA Salta para a Data Match Notes Notas Notes containing Notas contendo Bookmarks Favoritos Bookmarks containing Favoritos contendo AHI IHA Daily Duration Duração Diária Session Duration Duração Sessão Days Skipped Dias Ignorados Disabled Sessions Sessões Desabilitadas Number of Sessions Número de Sessões Click HERE to close Help Help Ajuda No Data Jumps to Date's Details Sem Dados Salta para os Detalhes da Data Number Disabled Session Jumps to Date's Details Número de Sessões Desativadas Salta para os Detalhes da Data Note Jumps to Date's Notes Nota Salta para as Notas da Data Jumps to Date's Bookmark Salta para o Marcador da Data AHI Jumps to Date's Details IHA Salta para os Detalhes da Data EventsPerHour Session Duration Jumps to Date's Details Duração da Sessão Salta para os Detalhes da Data Minutes Number of Sessions Jumps to Date's Details Número de Sessões Salta para os Detalhes da Data Sessions Daily Duration Jumps to Date's Details Duração Diária Salta para os Detalhes da Data Hours Number of events Jumps to Date's Events Número de Eventos Salta para os Detalhes da Data Events Automatic start Início Automático More to Search Mais para Pesquisar Continue Search Continuar Pesquisa End of Search Fim da Pesquisa No Matches Sem Correspondências Skip:%1 Saltar:%1 %1/%2%3 days. %1/%2%3 dias. %1/%2%3 days %1 dias {1/%2%3 ?} Found %1 Encontrado %1. {1 ?} Finds days that match specified criteria. Searches from last day to first day. First click on Match Button then select topic. Then click on the operation to modify it. or update the value Topics without operations will automatically start. Compare Operations: numberic or character. Numberic Operations: Character Operations: Summary Line Left:Summary - Number of Day searched Center:Number of Items Found Right:Minimum/Maximum for item searched Result Table Column One: Date of match. Click selects date. Column two: Information. Click selects date. Then Jumps the appropiate tab. Wildcard Pattern Matching: Wildcards use 3 characters: Asterisk Question Mark Backslash. Asterisk matches any number of characters. Question Mark matches a single character. Backslash matches next character. Found %1. Encontrado %1. DateErrorDisplay ERROR The start date MUST be before the end date ERRO A data de início DEVE ser antes da data de fim The entered start date %1 is after the end date %2 A data de início inserida %1 é após a data final %2 Hint: Change the end date first Dica: Mude primeiro a data de fim The entered end date %1 A data de fim inserida %1 is before the start date %1 é antes da data de início %1 Hint: Change the start date first Dica: Mude primeiro a data de início 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 device Record cannot be imported in this profile. O Registo deste dispositivo não pode ser importado neste perfil. The Day records overlap with already existing content. Os registos do Dia sobrepõem-se ao conteúdo já 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. Os Ficheiros de Ajuda ainda não estão disponíveis para %1 e serão exibidos em %2. Help files do not appear to be present. Os ficheiros de ajuda não parecem estar presentes. 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 Sem documentação disponível Please wait a bit.. Indexing still in progress Por favor, espere um pouco. Indexação ainda em curso No Não %1 result(s) for "%2" %1 resultado(s) para "%2" clear limpar MD300W1Loader Could not find the oximeter file: Não consegui encontrar o ficheiro do oximetro: Could not open the oximeter file: Não consegui abrir o ficheiro do oximetro: MainWindow &Statistics E&statísticas Report Mode Modo Relatório Show Standard Report Standard Padrão Show Monthly Report Monthly Mensal Show Range Report Date Range Faixa de Data Select Report Date Report Date Statistics Estatísticas Daily Diariamente Overview Visão Geral Oximetry Oximetria Import Importar Help Ajuda &File &Arquivo &View &Exibir &Reset Graphs &Redifinir Gráficos &Help A&juda Troubleshooting Resolução de problemas &Data &Dados &Advanced A&vançado Rebuild CPAP Data Recompilar Dados CPAP &Import CPAP Card Data &Importação de Dados do Cartão CPAP Show Daily view Mostrar vista Diária Show Overview view Mostrar vista Geral &Maximize Toggle Alternar &Maximizar Maximize window Maximizar janela Reset Graph &Heights Redifinir &Altura do Gráfico Reset sizes of graphs Redifinir o tamanho do gráfico Show Right Sidebar Mostrar barra lateral direita Show Statistics view Mostrar vista estatística Import &Dreem Data Importar Dados &Dreem Show &Line Cursor Mostrar Cursor &Linha Show Daily Left Sidebar Mostrar barra lateral esquerda Diária Show Daily Calendar Mostrar Calendário Diário Create zip of CPAP data card Criar zip do cartão de dados CPAP Create zip of OSCAR diagnostic logs Criar zip dos registos de diagnóstico do OSCAR Create zip of all OSCAR data Criar zip de todos os dados do OSCAR Report an Issue Reportar um problema System Information Informação do Sistema Show &Pie Chart Mostrar Gráfico de &Torta (Pizza) Show Pie Chart on Daily page Mostrar Gráfico Torta na página Diária Standard - CPAP, APAP Padrão - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> <html><head/><body><p>Ordem gráfica padrão, boa para CPAP, APAP, BPAP Básico</p></body></html> Advanced - BPAP, ASV Avançado - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> <html><head/><body><p>Ordem gráfica avançada, boa para BPAP c/BU, ASV, AVAPS, IVAPS</p></body></html> Show Personal Data Mostrar Dados Pessoais Check For &Updates Buscar Por At&ualizações Purge Current Selected Day Apagar Dia Atualmente Selecionado &CPAP &CPAP &Oximetry &Oximetria &Sleep Stage E&stágio de Sono &Position &Posição &All except Notes &Tudo exeto Notas All including &Notes Tudo incluindo &Notas &Preferences &Preferências &Profiles &Perfis &About OSCAR &Sobre o 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 Importar Dados &Viatom/Wellue 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 Purge ALL Device Data Apagar TODOS os Dados do Dispositivo 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 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 Are you sure you want to rebuild all CPAP data for the following device: Tem a certeza de que pretende reconstruir todos os dados do CPAP para o seguinte dispositivo: Please note, that this could result in loss of data if OSCAR's backups have been disabled. Por favor, note que isto pode resultar em perda de dados se as cópias de segurança do OSCAR tiverem sido desativadas. There was a problem opening %1 Data File: %2 Havia um problema ao abrir %1 Ficheiro de Dados: %2 %1 Data Import of %2 file(s) complete %1 Importação de dados do ficheiro %2 completo %1 Import Partial Success %1 Importação Parcial com Sucesso %1 Data Import complete %1 Importação de Dados completa %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 Tem certeza de que deseja eliminar dados de oximetria para %1 <b>Please be aware you can not undo this operation!</b> <b>Por favor esteja ciente de que 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? 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' For some reason, OSCAR does not have any backups for the following device: Por alguma razão, o OSCAR não dispõe de cópias de segurança para o seguinte dispositivo: 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 tenha feito <i>as suas <b>próprias</b> cópias de segurança para todos os seus dados CPAP</i>, ainda pode concluir esta operação, mas terá de restaurar manualmente a partir das suas cópias de segurança. 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, 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) %1 (Perfil: %2) Import Success Sucesso na Importação Already up to date with CPAP data at %1 Up to date Atualizado Couldn't find any valid Device Data at %1 Não consegui encontrar nenhum dado de dispositivo válido em %1 No profile has been selected for Import. Nenhum perfil foi selecionado para a Importação. Please remember to select the root folder or drive letter of your data card, and not a folder inside it. Lembre-se de selecionar a pasta raiz ou a letra de unidade do seu cartão de dados e não uma pasta no seu interior. Find your CPAP data card Encontre o seu cartão de dados CPAP No supported data was found Dados não suportados foram encontrados Choose where to save screenshot Escolha onde guardar a imagem Image files (*.png) Arquivos de Imagens (*.png) The User's Guide will open in your default browser O Guia do Utilizador será aberto no seu navegador predefinido The FAQ is not yet implemented O FAQ ainda não está implementado If you can read this, the restart command didn't work. You will have to do it yourself manually. Se conseguires ler isto, o comando de reinício não funcionou. Terá que fazê-lo manualmente. A file permission error caused the purge process to fail; you will have to delete the following folder manually: Um erro de permissão de arquivo fez com que o processo de limpeza falhasse; você terá que excluir a seguinte pasta manualmente: The Glossary will open in your default browser O Glossário abrirá no seu navegador padrão You must select and open the profile you wish to modify Você deve selecionar e abrir o perfil que deseja modificar Export review is not yet implemented A revisão das exportações ainda não está implementada Would you like to zip this card? Gostaria de zipar este cartão? Choose where to save zip Escolha onde guardar o zip ZIP files (*.zip) Arquivos ZIP (*.zip) Creating zip... Criando zip... Calculating size... Calculando o tamanho... Reporting issues is not yet implemented Informar problemas ainda não esta 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. Would you like to import from your own backups now? (you will have no data visible for this device until you do) Gostaria de importar dos seus próprios backups agora? (não terá dados visíveis para este dispositivo até que o faça) OSCAR does not have any backups for this device! O OSCAR não tem quaisquer backups para este dispositivo! Unless you have made <i>your <b>own</b> backups for ALL of your data for this device</i>, <font size=+2>you will lose this device's data <b>permanently</b>!</font> A menos que tenha feito <i>as suas <b>próprias</b> cópias de segurança para todos os seus dados para este dispositivo</i>, <font size=+2>se perderá <b>permanentemente</b> os dados deste dispositivo!</font> You are about to <font size=+2>obliterate</font> OSCAR's device database for the following device:</p> Está prestes a <font size=+2>obliter a base de</font> dados de dispositivos da OSCAR para o seguinte dispositivo:</p> Are you <b>absolutely sure</b> you want to proceed? Tem <b>absoluta certeza</b> de que deseja prosseguir? No help is available. Não há ajuda disponível. 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 Informação do 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-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 desejar. 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. Proteção de senha muito fraca 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 Registro do Paciente &Cancel &Cancel &Back &Voltar &Next &Próximo Select Country Selecione o País 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 OSCAR é copyright &copy;2011-2018 Mark Watkins e porções &copy;2019-2022 A Equipe OSCAR Welcome to the Open Source CPAP Analysis Reporter Bem-vindo ao Repórter de Análise CPAP de Código Aberto This software is being designed to assist you in reviewing the data produced by your CPAP Devices and related equipment. Este software está a ser concebido para o ajudar a rever os dados produzidos pelos seus Dispositivos CPAP e equipamentos relacionados. 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. O OSCAR foi liberado livremente sob a <a href='qrc:/COPYING'> Licença Pública GNU v3</a>, e vem sem garantia, e sem qualquer reclamação de aptidão para qualquer fim. OSCAR is intended merely as a data viewer, and definitely not a substitute for competent medical guidance from your Doctor. O OSCAR destina-se apenas a um visualizador de dados, e definitivamente não substitui a orientação médica competente do seu Doutor. 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 com o uso ou uso indevido deste 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 Instantâneo Start: Início: End: Fim: Reset view to selected date range Redefinir visualização para o intervalo de datas selecionado Layout Layout Save and Restore Graph Layout Settings Salvar eRrestaurar Configurações de Layout de 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) Hide All Graphs Oculta Todos os Gráficos Show All Graphs Exibe 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;">Nota: </span><span style=" font-style:italic;">Selecione primeiro o seu tipo de oxímetro correto a partir do menu pull-down abaixo.</span></p></body></html> Where would you like to import from? De onde 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 o seu Oxímetro a partir destes grupos:</span></p></body></html> CMS50E/F users, when importing directly, please don't select upload on your device until OSCAR prompts you to. Os utilizadores CMS50E/F, quando importarem diretamente, não selecionem o upload do seu dispositivo até que o OSCAR o indique. <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 estiver ativado, o OSCAR reiniciará automaticamente o relógio interno do CMS50 utilizando o horário atual dos seus computadores.</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 pode introduzir um nome de 7 caracteres para este oximetro.</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>Esta opção apagará a sessão importada do seu oximetro após a conclusão da importação. </p><p>Tenha cuidado, porque se algo correr mal antes que o OSCAR salve a sua sessão, não poderá recuperá-la.</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>Esta opção permite-lhe importar (via cabo) das gravações internas dos seus oximetros.</p><p>Depois de selecionar esta opção, os oximetros antigos Contec exigirão que utilize o menu do dispositivo para iniciar o 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 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 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 pode ler isso, provavelmente 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 o seu dispositivo oximetro, ligue-o 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. <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: Pode ajustar manualmente o tempo aqui se necessário: HH:mm:ssap HH:mm:ss &Cancel &Cancelar &Information Page Página de &Informação 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;">Lembrou-se de importar as sessões do CPAP primeiro?<br/></span>Se esqueceu, 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 quer importar para o OSCAR Day recording (normally would have) started A gravação do dia (normalmente teria) começado I started this oximeter recording at (or near) the same time as a session on my CPAP device. Iniciei esta gravação de oximetro ao mesmo tempo (ou quase) que uma sessão no meu dispositivo CPAP. <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 um tempo de partida para saber para onde salvar esta sessão de oximetria.</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 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. 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. A OSCAR dá-lhe a capacidade de rastrear os dados da oximetria juntamente com os dados da sessão do CPAP, o que pode dar uma visão valiosa sobre a eficácia do tratamento CPAP. Também trabalhará autónomo com o seu Pulse Oximeter, permitindo-lhe armazenar, rastrear e rever os seus dados gravados. 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) A OSCAR é atualmente compatível com Contec CMS50D+, CMS50E, CMS50F e os oximetros da série CMS50I .<br/>(Nota: A importação direta de modelos bluetooth <span style=" font-weight:600;">provavelmente ainda não</span> é possível) If you are trying to sync oximetry and CPAP data, please make sure you imported your CPAP sessions first before proceeding! Se estiver a tentar sincronizar os dados da oximetria e do CPAP, certifique-se de que importou as suas sessões de CPAP primeiro 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 possa localizar e ler diretamente a partir do seu dispositivo Oximeter, tem de garantir os controladores corretos do dispositivo (por exemplo. USB para Serial UART) foram instalados no seu computador. Para mais informações sobre isto, %1click 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 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. 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 não possui uma sessão de CPAP para sincronizar a gravação, 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/aa HH:mm:ss R&eset R&edefinir Pulse Pulso &Open .spo/R File Abrir ficheiro .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 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 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. Duration of airflow restriction Duração da restrição de fluxo de ar s s Event Duration Duração de Evento 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 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.. Compacte cópias de segurança ResMed (EDF) para poupar espaço no disco. Os ficheiros EDF suportados são armazenados no formato .gz, que é comum nas plataformas Mac & Linux.. O OSCAR pode importar deste diretório de reserva comprimido de forma nativa.. Para usá-lo com o ResScan exigirá que os ficheiros .gz sejam descomprimidos primeiro.. <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Sessões de duração mais curta do que esta não serão exibidas</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> 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 a quantidade de utilização do espaço de disco do OSCAR e têm um efeito sobre o tempo de importação. 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. Isto faz com que os dados do OSCAR levem cerca de metade do espaço. Mas faz com que a importação e a mudança de dia levem mais tempo.. Se tiver um computador novo com um pequeno disco de estado sólido, esta é uma boa opção. Compress Session Data (makes OSCAR data smaller, but day changing slower.) Comprimir dados da sessão (torna os dados OSCAR menores, mas a mudança de dia mais devagar.) <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 com antecedência, o que acelera a navegação geral e alguns outros cálculos mais tarde. </p><p>Se tiver uma grande quantidade de dados, pode valer a pena manter isto desligado, mas se normalmente gosta de ver<span style=" font-style:italic;">tudo</span>em visão geral, todos os dados do resumo ainda têm que ser carregados de qualquer maneira. </p><p>Note que esta definição não afeta os dados da forma de onda e do evento, que é sempre carregada sob demanda conforme necessário.</p></body></html> Custom CPAP User Event Flagging Marcação de Evento Personalizada pelo Utilizador de CPAP <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">A sinalização personalizada é um método experimental de deteção de eventos perdidos pelo dispositivo. </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">não</span><span style=" font-family:'Sans'; font-size:10pt;">estão incluídos noa IAH.</span></p></body></html> Show Remove Card reminder notification on OSCAR shutdown Mostrar notificação de lembrete de remover o cartão no encerramento do 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.) Quero ser notificado das versões de teste. (Utilizadores avançados apenas por favor.) &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>Que separador abrir ao carregar um perfil. (Nota: Será padrão no Perfil se o OSCAR não estiver definido para não abrir um perfil no arranque)</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 isto a partir da definição predefinida (Desktop OpenGL) se tiver problemas de renderização com 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 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 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 ti 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 mantiver seu cartão SD inserido todas as noites, e importar pelo menos uma vez por semana, não verás problemas com isso com grande frequência..</p></body></html> 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. 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 compressão de backup SD não recomprime automaticamente os dados de 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 that is somehow different from anything previously seen by OSCAR developers.</p></body></html> <html><head/><body><p>Forneça um alerta ao importar dados que de alguma forma são diferentes de qualquer coisa anteriormente vista pelos desenvolvedores da OSCAR.</p></body></html> Warn when previously unseen data is encountered Atenção quando os dados nunca vistos são encontrados Your masks vent rate at 20 cmH2O pressure A sua taxa de ventilação das máscaras a pressão de 20 cmH2O Your masks vent rate at 4 cmH2O pressure A sua taxa de ventilação das máscaras a pressão de 4 cmH2O l/min l/min <html><head/><body><p>Cumulative Indices</p></body></html> <html><head/><body><p>Índices Cumulativos</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> html><head/><body><p>Marcador SpO<span style=" vertical-align:sub;">2</span> Desaturações Abaixo</p></body></html> <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:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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><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:12px; margin-bottom:12px; 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 de Oximetria e CPAP</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Dados CMS50 importados do SpO2Review (a partir de ficheiros .spoR) ou do método de importação em série </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">não</span><span style=" font-family:'Sans'; font-size:10pt;"> ter a marcação de tempo correta necessária para sincronizar.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">O modo de visualização ao vivo (utilizando um cabo de série) é uma forma de obter uma sincronização precisa nos oximetros CMS50, mas não contraria a deriva do relógio CPAP.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Se iniciar o seu modo de gravação de Oximeters em </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exatamente </span><span style=" font-family:'Sans'; font-size:10pt;">ao mesmo tempo que iniciar o seu dispositivo CPAP, pode agora também conseguir sincronização. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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 leva o tempo de partida das últimas noites na primeira sessão do CPAP. (Lembre-se de importar primeiro os seus dados CPAP!)</span></p></body></html> Always save screenshots in the OSCAR Data folder Guarde sempre imagens na pasta de dados do OSCAR Check For Updates Verificar Por 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. Está a usar uma versão de teste do OSCAR. As versões de teste verificam automaticamente as atualizações pelo menos uma vez a cada sete dias. Pode definir o intervalo para menos de sete dias. Automatically check for updates Automaticamente busca por atualizações How often OSCAR should check for updates. Com que frequencia o OSCAR deve buscar por atualizações. If you are interested in helping test new features and bugfixes early, click here. Se estiver interessado em ajudar a testar novas funcionalidades e bugfixs mais cedo, 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 quiser ajudar a testar as primeiras versões do OSCAR, consulte a página da Wiki sobre o teste do OSCAR. Damos as boas-vindas a todos os que gostariam de testar o OSCAR, ajudar a desenvolver o OSCAR e ajudar com traduções para línguas existentes ou novas. 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 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 Include Serial Number Inclui Número de Série Graphics Engine (Requires Restart) Motor Gráfico (Exige Reinício) This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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 uma cópia de segurança dos dados do cartão SD para dispositivos ResMed, Os dispositivos da série ResMed S9 eliminam dados de alta resolução com mais de 7 dias, e dados gráficos com mais de 30 dias.. O OSCAR pode ficar com uma cópia destes dados se alguma vez precisar de reinstalar. (Altamente recomendado, a menos que o seu espaço curto em disco ou não se importe com os dados do gráfico) <html><head/><body><p>Provide an alert when importing data from any device model that has not yet been tested by OSCAR developers.</p></body></html> <html><head/><body><p>Forneça um alerta ao importar dados de qualquer modelo de dispositivo que ainda não tenha sido testado pelos desenvolvedores do OSCAR.</p></body></html> Warn when importing data from an untested device Alerte ao importar dados de um dispositivo não testado This calculation requires Total Leaks data to be provided by the CPAP device. (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 que os dados dos Vazamentos Totais sejam fornecidos pelo dispositivo CPAP. (Por exemplo, PRS1, mas não ResMed, que já os tem) Os cálculos de fuga não intencional usados aqui são lineares, não modelam a curva de ventilação da máscara. Se utilizar algumas máscaras diferentes, escolha valores médios. Ainda deve estar perto o suficiente. Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device missed. This option must be enabled before import, otherwise a purge is required. Ativar/desativar melhorias de sinalização de eventos experimentais. Permite detetar eventos limítrotes, e alguns do dispositivo falharam. Esta opção deve ser ativada antes da importação, caso contrário é necessária um purge. This experimental option attempts to use OSCAR's event flagging system to improve device detected event positioning. Esta opção experimental tenta utilizar o sistema de sinalização de eventos do OSCAR para melhorar o posicionamento do evento detetado do dispositivo. Resync Device Detected Events (Experimental) Eventos Detetados por Dispositivos Resync (Experimental) Allow duplicates near device events. Permitir duplicações perto de eventos do dispositivos. Show flags for device detected events that haven't been identified yet. Mostra bandeiras para eventos detetados que ainda não foram identificados. <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices do not support changing these settings.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Nota: </span>Devido a limitações de desenho dos resumos, os dispositivos ResMed não suportam a alteração destas definições.</p></body></html> Clinical Clinical Settings Select Oscar Operating Mode Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. Clinical Mode permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. Permissive Mode Hours <!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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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 device serial number on device settings changes report Se incluir o número de série do dispositivo nas definições do dispositivo altera relatório 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 (monocromo) For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. Enables SessionBar in Event Flags Graph 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. Overview Visão-Geral No CPAP devices detected Nenhum dispositivo CPAP detectado Will you be using a ResMed brand device? Vai usar um dispositivo da marca ResMed? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, days will <b>split at noon</b> like in ResMed's commercial software.</p> <p><b>Por Favor Note:</b>As capacidades avançadas de divisão da sessão da OSCAR não são possíveis com dispositivos <b>ResMed</b> devido a uma limitação na forma como as suas definições e dados sumários são armazenados, pelo que foram desativados para este perfil.</p><p>Nos dispositivos ResMed, os dias <b>vão dividir-se ao meio-dia</b>, como no software comercial da ResMed.</p> 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 podes 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 podes 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 podes 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. Gostaria de fazer isso agora? ResMed S9 devices routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). Os dispositivos ResMed S9 eliminam rotineiramente certos dados do seu cartão SD com mais de 7 e 30 dias (dependendo da resolução). If you ever need to reimport this data again (whether in OSCAR or ResScan) this data won't come back. Se alguma vez precisar de reimportar estes dados novamente (seja no OSCAR ou ResScan) estes dados não voltarão. If you need to conserve disk space, please remember to carry out manual backups. Se precisar de conservar o espaço do disco, lembre-se de efetuar cópias de segurança manuais. Are you sure you want to disable these backups? Tem a certeza de que pretende desativar estas cópias de segurança? Switching off backups is not a good idea, because OSCAR needs these to rebuild the database if errors are found. Desligar as cópias de segurança não é uma boa ideia, porque o OSCAR precisa destes para reconstruir a base de dados se forem encontrados erros. Are you really sure you want to do this? Tem certeza mesmo de que deseja fazer isso? Flag Marcação Clinical Mode: Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. Basically replicates the reports and data stored on the devices data card. This includes pap devices, oximeters, etc. Compliance reports fall under this mode. Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. Permissive Mode: Allows user to select which data sets/ sessions to be used for calculations and display. Additional charts and calculations may be available that are not available from the vendor data. Changing the Oscar Operating Mode: Requires a reload of the user's profile. Data will be saved and restored. Minor Flag Pequena Marcação Span Período Always Minor Sempre Pequeno Never Nunca This may not be a good idea Isso pode não ser uma boa ideia ProfileSelector Filter: Filtro: Reset filter to see all profiles Redifinir filtro para ver todos os perfis 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 Tem de criar um perfil Enter Password for %1 Introduza palavra-passe para %1 You entered an incorrect password Introduziu uma palavra-passe incorreta Forgot your password? Esqueceu sua palavra-passe? 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 selecionado 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 estiver a tentar apagar porque se esqueceu da palavra-passe, tem de a redefinir ou de apagar manualmente a pasta de perfil. You are about to destroy profile '<b>%1</b>'. 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. Tem de inserir 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, precisa removê-la manualmente. Profile '%1' was succesfully deleted Perfil '%1' foi deletado com sucesso Bytes 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 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 Length: %1 Comprimento: %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 EEPAP EEPAP Min EEPAP Max EEPAP 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 Device Dispositivo Min IPAP IPAP Mín App key: Chave App: Operating system: Sistema Operativo: Built with Qt %1 on %2 Construido com Qt %1 em %2 Graphics Engine: Motor Gráfico: Graphics Engine type: Tipo de Motor Gráfico: Compiler: Software Engine Motor de Software ANGLE / OpenGLES ANGLE / OpenGLES Desktop OpenGL OpenGL Desktop m m cm cm in pol kg kg Minutes Minutos Seconds Segundos milliSeconds h h m m s s ms ms Events/hr Eventos/hr Hz Hz l/min l/min Litres Litros ml ml Breaths/min Respirações/min Degrees Graus Information Informação Busy Ocupado Only Settings and Compliance Data Available Apenas Configurações e Dados de Conformidade disponíveis Summary Data Only Somente Dados Resumidos Max IPAP IPAP Máx SA Apneia do Sono AS PB Respiração Periódica RP IE IE Insp. Time Ends with an abreviation @RISTRAUS Tempo de Insp. Exp. Time Ends with an abreviation @RISTRAUS Tempo de Exp. Resp. Event Ends with an abreviation @RISTRAUS 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. Ends with no abreviation @RISTRAUS Vent Alvo Minute Vent. Ends with no abreviation @RISTRAUS Vent. Minuto Tidal Volume Volume Tidal Resp. Rate Ends with an abreviation @RISTRAUS Taxa de Resp. Snore Ressonar 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 Ends with an abreviation @RISTRAUS Alívio de Pr. No Data Available Nenhum Dado Disponível Bookmarks Favoritos Mode Modo Model Modelo Brand Marca Serial Serial Series Série Channel Canal Settings Configurações Motion Movimento Name Nome DOB Ends with an abreviation @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 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. Os seus dados gerados por %1 %2 (%3) que a OSCAR nunca tinha visto antes. The imported data may not be entirely accurate, so the developers would like a .zip copy of this device's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Os dados importados podem não ser inteiramente precisos, pelo que os desenvolvedores gostariam de uma cópia zip do cartão SD deste dispositivodos e reltórios clínicos correspondentes .pdf para garantir que o OSCAR está a tratar os dados corretamente. Non Data Capable Device Dispositivo Não Compatível Your %1 CPAP Device (Model %2) is unfortunately not a data capable model. Seu Dispositivo CPAP %1 (Modelo %2) infelizmente não é compatível com dados. I'm sorry to report that OSCAR can only track hours of use and very basic settings for this device. Lamento informar que o OSCAR só pode acompanhar horas de utilização e configurações muito básicas para este dispositivo. Device Untested Dispositvo não Testado Your %1 CPAP Device (Model %2) has not been tested yet. O seu dispositivo CPAP %1 (Modelo %2) ainda não foi testado. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Parece semelhante a outros dispositivos que podem funcionar, mas os desenvolvedores gostariam de uma cópia .zip do cartão SD deste dispositivo e relatórios de .pdf clínico correspondentes para garantir que funciona com o OSCAR. Device Unsupported Dispositvo não Suportado Sorry, your %1 CPAP Device (%2) is not supported yet. O seu dispositivo CPAP %1 (Modelo %2) ainda não é suportado. The developers need a .zip copy of this device'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 deste dispositivo e relatórios clínicos correspondentes .pdf para que funcione com o OSCAR. Scanning Files... Vasculhando Arquivos... Importing Sessions... Importando Sessões... UNKNOWN DEsCONHECIDO APAP (std) APAP (padrão) APAP (dyn) APAP (din) Auto S Auto S Auto S/T Auto S/T AcSV AcSV SoftPAP Mode Modo SoftPAP Pressure relief during exhalation Slight Ligeira Softstart pressure Pressure during soft start period PSoft PSoft Softstart minimum pressure Minimum pressure during soft start period PSoftMin PSoftMin Auto start Automatically turn on the device by breathing Softstart time Lenght of soft start period Soft start maximum time Maximum lenght of soft start period Soft start max. time Soft start pressure Higher End Expiratory Pressure Humidifier level Tube type Obstruction level Obstruction level in percentage rRMVFluctuation Relative respiratory minute volume fluctuation Relative respiratory minute volume Measured pressure Full flow Artefact Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) Epoch (2 mins) with Flow Limitation Deep Sleep Deep sleep, stable respiration Timed breath BiSoft Mode BiSoft 1 BiSoft 2 TriLevel AutoStart InicioAuto Softstart_Time Tempo_Softstart Softstart_TimeMax TempoMax_Softstart Softstart_Pressure Pressão_Softstart PMaxOA PMaxOA EEPAPMin EEPAPMin Lower End Expiratory Pressure EEPAPMax EEPAPMax HumidifierLevel NívelUmidificador TubeType TipoTubo ObstructLevel NívelObstrução Obstruction Level Nível Obstrução rMVFluctuation FlutuaçãorMVF rRMV rRMV PressureMeasured PressãoMedida FlowFull FluxoTotal SPRStatus StatusSPR Artifact Artefato ART ART CriticalLeak VazamentoCrítico Mask leakage is above a critical treshold CL VC eMO eMO Epoch (2 mins) with Mild Obstruction eSO eSO Epoch (2 mins) with Severe Obstruction eS eS Epoch (2 mins) with Snoring eFL eFL DeepSleep SonoProfundo DS SP TimedBreath RepsiraçãoCronometrada Finishing up... Finalizando... Untested Data Dados Não Testados 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 Trava Flex Whether Flex settings are available to you. Se as definições Flex estão disponíveis para si. Amount of time it takes to transition from EPAP to IPAP, the higher the number the slower the transition Quantidade de tempo que leva para a transição de EPAP para IPAP, quanto maior o número, mais lenta a transição Rise Time Lock Bloqueio de tempo de ascensão Whether Rise Time settings are available to you. Se as definições de Tempo de Ascensão estão disponíveis para si. Rise Lock Trava Tempo de Ascensão Passover Passover Target Time Objetivo Tempo PRS1 Humidifier Target Time PRSQ Tempo Objetivo Umidificador Hum. Tgt Time Ends with an abreviation @RISTRAUS Tempo Obj. Umid. Mask Resistance Setting Configuração Resistência da Máscara Mask Resist. Resist. Másc. Hose Diam. Ends with no abreviation @RISTRAUS Diam. Tubo 15mm 15mm Tubing Type Lock Trava Tipo de Tubo Whether tubing type settings are available to you. Se as definições de Tipo de Tubo estão disponíveis para si. Tube Lock Trava Tubo Mask Resistance Lock Trava Resistência de Máscara Whether mask resistance settings are available to you. Se as definições de Resistência de máscara estão disponíveis para si. Mask Res. Lock Trava Resistência de Máscara A few breaths automatically starts device Algumas poucas respirações automaticamente inicia o Dispositivo Device automatically switches off O dispositivo automaticamente desliga Whether or not device allows Mask checking. Se o dispositivo permite verificar a Máscara. Ramp Type Tipo Rampa Type of ramp curve to use. Tipo de curva de rampa a utilizar. Linear Linear SmartRamp SmartRamp Ramp+ Rampa+ Backup Breath Mode Mode 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: nenhum (desligado), automático ou fixo Breath Rate Taxa de Respiração Fixed Fixo Fixed Backup Breath BPM Minimum breaths per minute (BPM) below which a timed breath will be initiated Respirações mínimas por minuto (RMM) abaixo das quais uma respiração cronometrada será iniciada Breath BPM BPM Respiração Timed Inspiration Inspiração Cronometrada The time that a timed breath will provide IPAP before transitioning to EPAP O tempo que uma respiração cronometrada fornecerá IPAP antes da transição para a EPAP Timed Insp. Inspiração Cronom. Auto-Trial Duration Duração Auto-Trial Auto-Trial Dur. Dur. Auto-Trial EZ-Start Início EZ Whether or not EZ-Start is enabled Se o EZ-Start 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 fluxo inspiratório máximo A period during a session where the device could not detect flow. Um período durante uma sessão em que o dispositivo não conseguiu detetar o fluxo. Peak Flow Fluxo Máximo Peak flow during a 2-minute interval Fluxo máximo durante um intervalo de 2 minutos 22mm 22mm Backing Up Files... Guardando ficheiros... model %1 modelo %1 unknown model modelo desconhecido 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 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 Modo Umidificador PRS1 Humidification Mode Modo de Umidificação PRS1 Humid. Mode Modo Umid. Fixed (Classic) Fixo (Clássico) Adaptive (System One) Adaptivo (System One) Heated Tube Tubo Aquecido Tube Temperature Temperatura do Tubo PRS1 Heated Tube Temperature PRS1 Temperatura do Tubo Aquecido Tube Temp. Ends with no abreviation @RISTRAUS Temp. Tubo PRS1 Humidifier Setting PRS1 Configuração do Umidificador 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 Auto Off Auto Desligar Mask Alert Alerta de Máscara Show AHI Mostrar IAH Whether or not device shows AHI via built-in display. Se o dispositivo mostra ou não IAH através de um ecrã incorporado. The number of days in the Auto-CPAP trial period, after which the device will revert to CPAP O número de dias no período experimental Auto-CPAP, após o qual o dispositivo reverterá para CPAP Breathing Not Detected Respiração Não Detectada 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 Utilização , found SleepyHead - , Encontrado SleepyHead - You must run the OSCAR Migration Tool Deve executar a Ferramenta de Migração do OSCAR 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>A OSCAR mantém uma cópia de segurança do cartão de dados dos dispositivos que utiliza para este fim.</b> <i>Your old device data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> <i>Os dados antigos do dispositivo devem ser regenerados desde que esta funcionalidade de backup não tenha sido desativada nas preferências durante uma importação de dados anteriores.</i> OSCAR does not yet have any automatic card backups stored for this device. O OSCAR ainda não possui quaisquer cópias de segurança automáticas armazenadas para este dispositivo. Important: Importante: If you are concerned, click No to exit, and backup your profile manually, before starting OSCAR again. Se estiver preocupado, clique em "Não" para sair e faça uma cópia de segurança manualmente do seu perfil, antes de recomeçar o OSCAR. Are you ready to upgrade, so you can run the new version of OSCAR? Está pronto para fazer upgrade, para poder executar a nova versão do OSCAR? Device Database Changes Mudanças Banco de Dados do Dispositivo Sorry, the purge operation failed, which means this version of OSCAR can't start. Desculpe, a operação de purga falhou, o que significa que esta versão do OSCAR não pode começar. The device data folder needs to be removed manually. A pasta de dados do dispositivo deve ser removida manualmente. 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? Gostaria de ligar as cópias de segurança automáticas, por isso, da próxima vez que uma nova versão do OSCAR precisar de o fazer, pode reconstruir a partir destes? OSCAR will now start the import wizard so you can reinstall your %1 data. O OSCAR iniciará agora o assistente de importação para que possa reinstalar os seus dados %1. OSCAR will now exit, then (attempt to) launch your computers file manager so you can manually back your profile up: O OSCAR irá agora sair, em seguida (tentar) lançar o seu gestor de ficheiros de computadores para que possa fazer um backup manual do 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 seu gestor de ficheiros para fazer uma cópia do seu diretório de perfil, em seguida, reinicie o OSCAR e complete o processo de atualização. OSCAR %1 needs to upgrade its database for %2 %3 %4 O OSCAR %1 precisa atualizar seu banco de dados para %2 %3 %4 This means you will need to import this device data again afterwards from your own backups or data card. Isto significa que terá de importar novamente estes dados do dispositivo a partir das suas próprias cópias de segurança ou cartão de dados. Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. Uma vez atualizado, <font size=+1>não</font> poderá mais utilizar este perfil com a versão anterior. 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: Não pode utilizar esta pasta: Migrating A migrar files ficheiros from De to para OSCAR crashed due to an incompatibility with your graphics hardware. A OSCAR despenhou-se devido a uma incompatibilidade com o hardware gráfico. To resolve this, OSCAR has reverted to a slower but more compatible method of drawing. Para resolver isto, o OSCAR reverteu para um método de desenho mais lento mas mais compatível. OSCAR will set up a folder for your data. O OSCAR irá configurar uma pasta para os seus dados. We suggest you use this folder: Sugerimos utilizar esta pasta: Click Ok to accept this, or No if you want to use a different folder. Clique em Ok para aceitar isto, ou Não se quiser utilizar uma pasta diferente. Next time you run OSCAR, you will be asked again. Da próxima vez que executar o OSCAR 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 No ecrã seguinte, o OSCAR irá pedir-lhe para selecionar uma pasta com dados 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. Clique em [OK] para ir ao ecrã seguinte ou [Não] se não quiser utilizar quaisquer dados sleepyhead ou OSCAR. Unable to create the OSCAR data folder at Incapaz de criar a pasta de dados do OSCAR em Unable to write to OSCAR data directory Incapaz de escrever para o 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á a sair. 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. Incapaz de escrever para depurar o log. Ainda pode utilizar a viga de depuração (Ajuda/Depuração/Mostrar Painel Depuração), mas o registo de depuração não será escrito para o 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 está a executar (%1) é mais antiga do que a utilizada para criar estes dados (%2). Choose or create a new folder for OSCAR data Escolher ou criar uma nova pasta para os dados do OSCAR Choose the SleepyHead or OSCAR data folder to migrate Escolha a pasta de dados SleepyHead ou OSCAR para migrar The folder you chose does not contain valid SleepyHead or OSCAR data. A pasta que escolheu não contém dados válidos Sleepyhead ou OSCAR. If you have been using SleepyHead or an older version of OSCAR, Se tem usado 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 posteriormente. As you did not select a data folder, OSCAR will exit. Como não selecionou uma pasta de dados, o OSCAR sairá. The folder you chose is not empty, nor does it already contain valid OSCAR data. A pasta que escolheu não está vazia, nem já contém dados OSCAR válidos. Data directory: Diretório 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? OSCAR Reminder Lembrete OSCAR Don't forget to place your datacard back in your CPAP device Não se esqueça de colocar o seu cartão de dados de volta no seu dispositivo CPAP You can only work with one instance of an individual OSCAR profile at a time. Só pode trabalhar com um perfil OSCAR individual de cada vez. If you are using cloud storage, make sure OSCAR is closed and syncing has completed first on the other computer before proceeding. Se estiver a utilizar o armazenamento em nuvem, certifique-se de que o OSCAR está fechado e que a sincronização foi concluída primeiro no outro computador antes de prosseguir. Loading profile "%1"... Carregando perfil "%1"... Chromebook file system detected, but no removable device found Sistema de ficheiros Chromebook detetado, mas nenhum dispositivo amovível encontrado You must share your SD card with Linux using the ChromeOS Files program Tem de partilhar o seu cartão SD com o Linux utilizando o programa Ficheiros ChromeOS Recompressing Session Files Recompressão de Ficheiros de Sessão Please select a location for your zip other than the data card itself! Por favor, selecione um local para o seu zip diferente do próprio cartão de dados! Unable to create zip! Incapaz de criar 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 oximetry settings to defaults? Tem certeza de que deseja redefinir todas as suas configurações de oximetria 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 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) Ressonar Vibratório (VS2) A vibratory snore as detected by a System One device Um ressonar vibratório detetado por um dispositivo System One A ResMed data item: Trigger Cycle Event Um item de dados ResMed: Evento de ciclo de desencadeamento 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 Cheyne Stokes Respiration (CSR) Respiração Cheyne Stokes (RCS) Periodic Breathing (PB) Respiração Periódica (RP) Clear Airway (CA) Vias Aéreas limpas (VAL) An apnea caused by airway obstruction Uma apneia causada por uma bostrução de via aérea A partially obstructed airway Uma via aérea parcialmente obstruída UA AI A vibratory snore Um ressonar vibratório 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 LL GV 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. Intellipap event where you breathe out your mouth. Evento Intellipap no qual expiras pela boca. SensAwake feature will reduce pressure when waking is detected. Recursos SensAwake reduzirá a pressão quando caminhar é detectado. 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 A sudden (user definable) change in heart rate Uma mudança brusca (definível pelo utilizador) na taxa cardíaca 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 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 ressonar 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 Config Pressure Setting Configuração de Pressão IPAP Set Config. IPAP IPAP Setting Configuração IPAP EPAP Set Config. EPAP EPAP Setting Configurar EPAP An abnormal period of Cheyne Stokes Respiration Um período anormal de respiração Cheyne Stokes CSR RCS An abnormal period of Periodic Breathing Um período anormal de respiração An apnea that couldn't be determined as Central or Obstructive. Uma apneia que não pode ser determinada como Central ou Obstrutiva. An apnea reported by your CPAP device. A restriction in breathing from normal, causing a flattening of the flow waveform. Uma restrição na respiração do normal, causando um achatamento da forma de onda de fluxo. LF MV A user definable event detected by OSCAR's flow waveform processor. Um evento definível do utilizador detetado 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 de 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 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 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. End Expiratory Pressure Fim Pressão Expiratória Obstructive Apnea (OA) Apneia Obstrutiva (AO) Hypopnea (H) Hipoapneia (H) Unclassified Apnea (UA) Apneia Não Classificada (ANC) Apnea (A) Apneia (A) An apnea reportred by your CPAP device. Uma apneia reportada pelo seu dispositvio CPAP. Flow Limitation (FL) Limitação de Fluxo (LF) RERA (RE) RERA (RE) Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. Excitação Relacionada ao Esforço Respiratório: Uma restrição na respiração que causa despertar ou distúrbios do sono. Vibratory Snore (VS) Ressonar Vibratório (RV) Leak Flag (LF) Marca de Vazamento (MV) A large mask leak affecting device performance. Um grande vazamento de máscara que afeta o desempenho do dispositivo. Large Leak (LL) Grande Vazamento (GV) Non Responding Event (NR) Evento Sem Reposta (SR) Expiratory Puff (EP) Puff Expiratório (PE) SensAwake (SA) SensAwake (SA) User Flag #1 (UF1) Marcador do utilizador #1 (MU1) User Flag #2 (UF2) Marcador do utilizador #2 (MU2) User Flag #3 (UF3) Marcador do utilizador #3 (MU3) Pulse Change (PC) Mudançã de Pulso (MP) SpO2 Drop (SD) Queda SpO2 (QS) I/E Value Apnea Hypopnea Index (AHI) Índice Hipoapneia Apneia (IHA) Respiratory Disturbance Index (RDI) Índice Disturbio Respiratório (IDR) 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 Depurando Canal #1 Test #1 Teste #1 For internal use only Somente para uso interno Debugging channel #2 Depurando Canal #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) 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) Min EEPAP %1 Max EEPAP %2 PDIFF %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> 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 Selection Length Seleção Comprimento 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 popout está cheia. Deve capturar a existente janela popout , apagá-lo e, em seguida, gerar este gráfico novamente. Your machine doesn't record data to graph in Daily View A sua máquina não grava dados para gráfico na Visão Diária There is no data to graph Não há dados para o gráfico 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: A OSCAR encontrou uma antiga pasta do Jornal, mas parece que foi renomeada: OSCAR will not touch this folder, and will create a new one instead. O OSCAR não tocará nesta pasta e criará uma nova. Please be careful when playing in OSCAR's profile folders :-P Tenha cuidado ao reproduzir as 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 encontrou um registo de objetos de diário no seu perfil, mas encontrou várias pastas de dados do Journal. OSCAR picked only the first one of these, and will use it in future: A OSCAR escolheu apenas a primeira, e vai usá-la 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... Guardando ficheiros... Reading data files... Ler ficheiros 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 Device auto starts by breathing Dispositivo inicia automaticamente ao respirar Response Resposta Device auto stops by breathing Dispositivo para automaticamente ao respirar Patient View Visão do Paciente RiseEnable RiseTime Cycle Trigger TiMax TiMin Your ResMed CPAP device (Model %1) has not been tested yet. O seu dispositivo CPAP ResMed (Modelo %1) ainda não foi testado. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card to make sure it works with OSCAR. Parece semelhante a outros dispositivos que podem funcionar, mas os desenvolvedores gostariam de uma cópia .zip do cartão SD deste dispositivo para garantir que funciona com o OSCAR. SmartStart SmartStart Smart Start Smart Start Humid. Status Ends with an abreviation @RISTRAUS Estado do Umidif. Humidifier Enabled Status Estado de Umidificador Ativo Humid. Level Ends with an abreviation @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 Ends with an abreviation @RISTRAUS Acesso Pac. Essentials Essenciais Plus Mais Climate Control Controle Climático Manual Manual Soft Leve Standard Padrão BiPAP-T BiPAP-T BiPAP-S BiPAP-S BiPAP-S/T BiPAP-S/T SmartStop SmartStop Smart Stop Smart Stop Simple Simples Advanced Avançado Parsing STR.edf records... A analisar os registos da 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... Permissive Mode Total disabled sessions: %1, found in %2 days Total disabled sessions: %1 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. Updating Statistics cache Atualização da cache de estatísticas Usage Statistics Estatísticas de Uso Loading summaries Carregando resumos Dreem Sonho Your Viatom device generated data that OSCAR has never seen before. O seu dispositivo Viatom gerou dados que o OSCAR nunca tinha visto antes. 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 ser inteiramente precisos, pelo que os desenvolvedores gostariam de uma cópia dos seus ficheiros Viatom para garantir que a OSCAR está a tratar os dados corretamente. Viatom Viatom Viatom Software Software Viatom New versions file improperly formed Novo ficheiro de versões indevidamente formado 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 teste You are running the latest %1 of OSCAR Estás a executar a mais recente %1 do OSCAR You are running OSCAR %1 Estás a executar o 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 de teste mais recente %1 estão disponíveis em <a href='%2'>%2</a> Check for OSCAR Updates Verificar por atualizações do OSCAR Unable to check for updates. Please try again later. Impossível buscar por atualizações. Por favor tente novamente mais tarde. SensAwake level Nível SensAwake Expiratory Relief Alívio Espiratório Expiratory Relief Level Nível de Alívio Expiratório Humidity Umidade SleepStyle TipoSono This page in other languages: Esta página em outros idiomas: %1 Graphs %1 Gráficos %1 of %2 Graphs %1 de %2 Gráficos %1 Event Types %1 Tipos de Eventos %1 of %2 Event Types %1 de %2 Tipos de Eventos Löwenstein Löwenstein Prisma Smart Prisma Inteligente Resvent/Hoffrichter iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings Gerenciar Configurações de Salvamento de Layout Add Somar Add Feature inhibited. The maximum number of Items has been exceeded. Adicionar Recurso inibido. O número máximo de itens foi excedido. creates new copy of current settings. cria uma nova cópia das configurações atuais. Restore Restaurar Restores saved settings from selection. Restaura as configurações salvas da seleção. Rename Renomear Renames the selection. Must edit existing name then press enter. Renomeia a seleção. Deve editar o nome existente e pressione enter. Update Atualizar Updates the selection with current settings. Atualiza a seleção com as configurações atuais. Delete Apagar Deletes the selection. Apaga a seleção. Expanded Help menu. Menu de Ajuda Expandido. Exits the Layout menu. Sair do menu de Layout. <h4>Help Menu - Manage Layout Settings</h4> <h4>Menu Ajuda - Gerenciar configurações de layout</h4> Exits the help menu. Sair menu de ajuda. Exits the dialog menu. Sair menu de diálogo. This feature manages the saving and restoring of Layout Settings. Layout Settings control the layout of a graph or chart. Different Layouts Settings can be saved and later restored. Button Description Creates a copy of the current Layout Settings. The default description is the current date. The description may be changed. The Add button will be greyed out when maximum number is reached. Other Buttons Greyed out when there are no selections Loads the Layout Settings from the selection. Automatically exits. io Modify the description of the selection. Same as a double click.io Saves the current Layout Settings to the selection. Prompts for confirmation. Deletes the selecton. Control Exit Sair (Red circle with a white "X".) Returns to OSCAR menu. Return Next to Exit icon. Only in Help Menu. Returns to Layout menu. Escape Key Exit the Help or Layout menu. Layout Settings * Name * Pinning * Plots Enabled * Height * Order * Event Flags * Dotted Lines * Height Options General Information Maximum description size = 80 characters. Maximum Saved Layout Settings = 30. Saved Layout Settings can be accessed by all profiles. Layout Settings only control the layout of a graph or chart. They do not contain any other data. They do not control if a graph is displayed or not. Layout Settings for daily and overview are managed independantly. <p style="color:black;"> This feature manages the saving and restoring of Layout Settings. <br> Layout Settings control the layout of a graph or chart. <br> Different Layouts Settings can be saved and later restored. <br> </p> <table width="100%"> <tr><td><b>Button</b></td> <td><b>Description</b></td></tr> <tr><td valign="top">Add</td> <td>Creates a copy of the current Layout Settings. <br> The default description is the current date. <br> The description may be changed. <br> The Add button will be greyed out when maximum number is reached.</td></tr> <br> <tr><td><i><u>Other Buttons</u> </i></td> <td>Greyed out when there are no selections</td></tr> <tr><td>Restore</td> <td>Loads the Layout Settings from the selection. Automatically exits. </td></tr> <tr><td>Rename </td> <td>Modify the description of the selection. Same as a double click.</td></tr> <tr><td valign="top">Update</td><td> Saves the current Layout Settings to the selection.<br> Prompts for confirmation.</td></tr> <tr><td valign="top">Delete</td> <td>Deletes the selecton. <br> Prompts for confirmation.</td></tr> <tr><td><i><u>Control</u> </i></td> <td></td></tr> <tr><td>Exit </td> <td>(Red circle with a white "X".) Returns to OSCAR menu.</td></tr> <tr><td>Return</td> <td>Next to Exit icon. Only in Help Menu. Returns to Layout menu.</td></tr> <tr><td>Escape Key</td> <td>Exit the Help or Layout menu.</td></tr> </table> <p><b>Layout Settings</b></p> <table width="100%"> <tr> <td>* Name</td> <td>* Pinning</td> <td>* Plots Enabled </td> <td>* Height</td> </tr> <tr> <td>* Order</td> <td>* Event Flags</td> <td>* Dotted Lines</td> <td>* Height Options</td> </tr> </table> <p><b>General Information</b></p> <ul style=margin-left="20"; > <li> Maximum description size = 80 characters. </li> <li> Maximum Saved Layout Settings = 30. </li> <li> Saved Layout Settings can be accessed by all profiles. <li> Layout Settings only control the layout of a graph or chart. <br> They do not contain any other data. <br> They do not control if a graph is displayed or not. </li> <li> Layout Settings for daily and overview are managed independantly. </li> </ul> <p style="color:black;"> Este recurso gerencia o salvamento e a restauração das Configurações de Layout. <br> Configurações de layout controlam o layout de um gráfico ou gráfico. <br> Diferentes configurações de layouts podem ser salvas e restauradas posteriormente. <br> </p> <table width=="100%"> <tr><td><b>Botão</b></td> <td><b>Descrição</b></td></tr> <tr><td valign="top">Add</td> <td>Cria uma cópia das Configurações de Layout atuais. <br> A descrição padrão é a data atual. <br> A descrição pode ser alterada. <br> O botão Adicionar ficará acinzentado quando o número máximo for atingido.</td></tr> <br> <tr><td><i><u>Outros Botões</u> </i></td> <td>Cinzento quando não há seleções</td></tr> <tr><td>Restaurar</td> <td>Carrega as Configurações de layout da seleção. Sai automaticamente. </td></tr> <tr><td>Renomear </td> <td>Modificar a descrição da seleção. O mesmo que um duplo clique.</td></tr> <tr><td valign="top">Atualiza</td><td> Salva as Configurações de layout atuais na seleção.<br> Solicita confirmação.</td></tr> <tr><td valign="top">Apagar</td> <td>Exclui a seleção. <br> Solicita confirmação.</td></tr> <tr><td><i><u>Controle</u> </i></td> <td></td></tr> <tr><td>Saída </td> <td>(Círculo vermelho com um "X" branco.) Regressa ao menu do OSCAR.</td></tr> <tr><td>Voltar</td> <td>Ao lado do ícone Sair. Apenas no Menu Ajuda. Retorna ao menu Layout.</td></tr> <tr><td>Tecla Escape</td> <td>Saia do menu Ajuda ou Layout.</td></tr> </table> <p><b>Configurações de layout</b></p> <table width="100%"> <tr> <td>* Nome</td> <td>* Fixação</td> <td>* Plotar Ativado </td> <td>* Altura</td> </tr> <tr> <td>* Ordem</td> <td>* Sinalizadores de eventos</td> <td>* Linhas Pontilhadas</td> <td>* Opções Altura</td> </tr> </table> <p><b> Informação Geral</b></p> <ul style=margin-left="20"; > <li> Tamanho máximo da descrição = 80 caracteres. </li> <li> Configurações de layout máximo salvo = 30. </li> <li> As Configurações de layout salvas podem ser acessadas por todos os perfis. <li> As configurações de layout controlam apenas o layout de um gráfico ou gráfico. <br> Não contêm quaisquer outros dados. <br> Eles não controlam se um gráfico é exibido ou não. </li> <li> As configurações de layout diárias e gerais são gerenciadas de forma independente. </li> </ul> Maximum number of Items exceeded. Número máximo de Itens excedido. No Item Selected Nenhum Item Selecionado Ok to Update? Ok para Atualizar? Ok To Delete? Ok para Apagar? SessionBar %1h %2m %1h %2m No Sessions Present Nenhuma Sessão Presente SleepStyleLoader Import Error Erro de Importação This device Record cannot be imported in this profile. O Registo deste dispositivo não pode ser importado neste 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) Conformidade (%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: This report was prepared on %1 by OSCAR %2 Este relatório foi elaborado em %1 por OSCAR %2 Device Information Informação de Dispositivo Changes to Device Settings Alterações nas Definições do Dispositivo Oscar has no data to report :( O Oscar não tem dados para reportar :( Database has No %1 data available. Database has %1 day of %2 Data on %3 Database has %1 days of %2 Data, between %3 and %4 Total Days: %1 Days Not Used: %1 Days Used: %1 Dias de Uso: %1 Days %1 %2 Hours: %3 Best Device Setting Worst Device Setting Low Use Days: %1 Dias de Pouco Uso: %1 Compliance: %1% Conformidade: %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. O OSCAR necessita de todos os dados resumidos carregados para calcular os melhores/piores dados para os dias individuais. 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 IAH: %1 Total Hours: %1 Total de Horas: %1 Worst RX Setting Pior Configuração RX Last Week Última Semana 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 é software de relatório CPAP de código aberto gratuito Days Dias Pressure Relief Alívio de Pressão Pressure Settings Configurações de Pressão First Use Primeiro Uso Last Use Último Uso Welcome Welcome to the Open Source CPAP Analysis Reporter Bem-vindo ao Repórter de Análise CPAP de Código Aberto What would you like to do? O que gostarias 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 device.</span></p></body></html> <span style=" font-weight:600;">Atenção: </span><span style=" color:#ff0000;">SDCards ResMed S9 precisam de ser bloqueados </span><span style=" font-weight:600; color:#ff0000;">antes de inserir no seu computador.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Alguns sistemas operativos escrevem ficheiros de índice no cartão sem pedir, o que pode tornar o seu cartão ilegível pelo seu dispositivo cpap.</span></p></body></html> It would be a good idea to check File->Preferences first, Seria uma boa ideia verificar o Ficheiro->Preferencias primeiro, as there are some options that affect import. uma vez que existem algumas opções que afetam a importação. Note that some preferences are forced when a ResMed device is detected Note que algumas preferências são forçadas quando um dispositivo ResMed é detetado First import can take a few minutes. A primeira importação pode levar alguns minutos. The last time you used your %1... A última vez que usaste o teu %1... last night noite passada today hoje %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 <font color = red>You only had the mask on for %1.</font> <font color = red>Só tinha a máscara vestida 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. Teve um IAH de %1, que é %2 sua média de %3 dias de %4. Your pressure was under %1 %2 for %3% of the time. Sua pressão ficou abaixo de %1 %2, %3% do tempo. 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. 1 day ago 1 dia atrás Your device was on for %1. O seu dispositivo estava ligado por %1. Your CPAP device used a constant %1 %2 of air O seu dispositivo CPAP utilizou uma constante %1 %2 de ar Your device used a constant %1-%2 %3 of air. O seu dispositivo utilizou uma constante %1 %2 %3 de ar. Your device was under %1-%2 %3 for %4% of the time. O seu dispositivo estava abaixo de %1-%2 %3 por %4% do tempo. Your EEPAP pressure was under %1 %2 for %3% of the time. 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 Double click Y-axis: Return to AUTO-FIT Scaling Duplo clique no eixo Y: Volta ao escalonamento AUTO-FIT Double click Y-axis: Return to DEFAULT Scaling Duplo clique no eixo Y: Volta ao escalonamento PADRÃO Double click Y-axis: Return to OVERRIDE Scaling Duplo clique no eixo Y: Volta ao escalonamento SOBREPOSTO Double click Y-axis: For Dynamic Scaling Duplo clique no eixo Y: Para o escalonamento Dinâmico Double click Y-axis: Select DEFAULT Scaling Duplo clique no eixo Y: Seleciona o escalonamento PADRÃO Double click Y-axis: Select AUTO-FIT Scaling Duplo clique no eixo Y: Seleciona o escalonamento AUTO-FIT %1 days %1 dias gGraphView 100% zoom level 100% de zoom Restore X-axis zoom to 100% to view entire selected period. Restaurar o zoom do eixo X a 100% para visualizar todo o período selecionado. Restore X-axis zoom to 100% to view entire day's data. Restaurar o zoom do eixo X a 100% para ver os dados do dia inteiro. 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 reencomendar gráficos Remove Clone Remover Clone Clone %1 Graph Clonar Gráfico %1 OSCAR-code-v1.5.1/Translations/Portugues.pt_BR.ts000066400000000000000000017322031450332542600215320ustar00rootroot00000000000000 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. 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 i i 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) Search Busca Layout Layout Save and Restore Graph Layout Settings Salva e Restaura as configurações gráficas 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 Clinical Mode Disabling Sessions requires the Permissive Mode 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) 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 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?? 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.. 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 Session Information Informações da Sessão Disable Warning Desabilita Alertas Disabling a session will remove this session data from all graphs, reports and statistics. The Search tab can find disabled sessions Continue ? Desativar uma sessão removerá os dados desta sessão de todos os gráficos, relatórios e estatísticas. A guia Pesquisar pode encontrar sessões desativadas Continuar ? CPAP Sessions Sessões CPAP Oximetry Sessions Sessões de Oxímetro Sleep Stage Sessions Sessões de Estátio de Sono Device Settings Configurações do Dispositivo 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 This CPAP device does NOT record detailed data Este dispositivo CPAP NÃO grava dados detalhados no data :( sem dados :( Sorry, this device only provides compliance data. Desculpe, este dispositivo fornece apenas dados de conformidade. "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 Hide All Events Esconder Todos Eventos Show All Events Mostrar Todos Eventos Hide All Graphs Ocultar Todos os Gráficos Show All Graphs Mostrar Todos os Gráficos DailySearchTab Match: Corresponder: Select Match Selecionar Correspondência Clear Limpar Bookmark Jumps to Date's Bookmark Start Search Iniciar Busca DATE Jumps to Date DATA Pular para Data Match Notes Notas Notes containing Bookmarks Favoritos Bookmarks containing Conteúdo favoritos AHI IAH Daily Duration Duração Diária Session Duration Duração da Sessão Days Skipped Dias Ignorados Disabled Sessions Sessões Desabilitadas Number of Sessions Número de Sessões Click HERE to close Help Help Ajuda No Data Jumps to Date's Details Sem Dados Salta para os detalhes da data Number Disabled Session Jumps to Date's Details Número Sessão Desativada Salta para os detalhes da data Note Jumps to Date's Notes Nota Salta para a Notas da Data Jumps to Date's Bookmark Salta para o marcador de data AHI Jumps to Date's Details IHA Salta para os Detalhes da Data EventsPerHour Session Duration Jumps to Date's Details Duração da Sessão Slata para os Detalhes da Data Minutes Number of Sessions Jumps to Date's Details Número de Sessões Salta para os Detalhes da Data Sessions Daily Duration Jumps to Date's Details Duração Diária Salta para os Detalhes da Data Hours Number of events Jumps to Date's Events Número de Eventos Salta para o eventos da Data Events Automatic start Início Automático More to Search Mais para Pesquisar Continue Search Continue Pesquisando End of Search Fim da Pesquisa No Matches Sem Combinações Skip:%1 Pular:%1 %1/%2%3 days. %1/%2%3 dias. %1/%2%3 days %1 dias {1/%2%3 ?} Found %1 Encontrado %1. {1 ?} Finds days that match specified criteria. Searches from last day to first day. First click on Match Button then select topic. Then click on the operation to modify it. or update the value Topics without operations will automatically start. Compare Operations: numberic or character. Numberic Operations: Character Operations: Summary Line Left:Summary - Number of Day searched Center:Number of Items Found Right:Minimum/Maximum for item searched Result Table Column One: Date of match. Click selects date. Column two: Information. Click selects date. Then Jumps the appropiate tab. Wildcard Pattern Matching: Wildcards use 3 characters: Asterisk Question Mark Backslash. Asterisk matches any number of characters. Question Mark matches a single character. Backslash matches next character. Found %1. Encontrado %1. DateErrorDisplay ERROR The start date MUST be before the end date ERRO A data de início DEVE ser anterior a data de fim The entered start date %1 is after the end date %2 A data inicial digitada %1 é posterior a data final %2 Hint: Change the end date first Dica: Mude a data final primeiro The entered end date %1 A data final digitada %1 is before the start date %1 é anterior a data inicial %1 Hint: Change the start date first Dica: Mude a data incial primeiro 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 device Record cannot be imported in this profile. Este registro de dispositivo não pode ser importado neste 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 Show Standard Report Standard Padrão Show Monthly Report Monthly Mensal Show Range Report Date Range Faixa de Data Select Report Date Report Date 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 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 Standard - CPAP, APAP Padrão - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> <html><head/><body><p>Ordem do gráfico padrão, boa para CPAP, APAP, BPAP básico</p></body></html> Advanced - BPAP, ASV Avançado - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> <html><head/><body><p>Ordem gráfica avançada, boa para BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> 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 Purge ALL Device Data Limpar TODOS os dados do dispositivo 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 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 &Glossá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 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 Are you sure you want to rebuild all CPAP data for the following device: Tem certeza de que deseja reconstruir todos os dados do CPAP para o seguinte dispositivo: 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. 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' For some reason, OSCAR does not have any backups for the following device: Por algum motivo, o OSCAR não possui backups para o seguinte dispositivo: 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 Couldn't find any valid Device Data at %1 Não foi possível encontrar dados de dispositivo válidos em %1 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 No supported data was found Dados não suportados foram encontrados 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. A file permission error caused the purge process to fail; you will have to delete the following folder manually: Um erro de permissão de arquivo causou falha no processo de limpeza; você terá que excluir a seguinte pasta manualmente: The Glossary will open in your default browser O Glossário será aberto no seu navegador padrão You must select and open the profile you wish to modify Você deve selecionar e abrir o perfil que deseja modificar 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. Would you like to import from your own backups now? (you will have no data visible for this device until you do) Você gostaria de importar de seus próprios backups agora? (você não terá dados visíveis para este dispositivo até que o faça) OSCAR does not have any backups for this device! OSCAR não possui backups para este dispositivo! Unless you have made <i>your <b>own</b> backups for ALL of your data for this device</i>, <font size=+2>you will lose this device's data <b>permanently</b>!</font> A menos que você tenha feito <i>seus <b>próprios</b> backups de TODOS os seus dados para este dispositivo</i>, <font size=+2>você perderá os dados deste dispositivo <b>permanentemente</b >!</font> You are about to <font size=+2>obliterate</font> OSCAR's device database for the following device:</p> Você está prestes a <font size=+2>eliminar</font> o banco de dados de dispositivos do OSCAR para o seguinte dispositivo:</p> Are you <b>absolutely sure</b> you want to proceed? Você tem <b>absoluta certeza</b> de que deseja prosseguir? 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 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 This software is being designed to assist you in reviewing the data produced by your CPAP Devices and related equipment. Este software está sendo projetado para ajudá-lo a revisar os dados produzidos por seus dispositivos CPAP e equipamentos relacionados. 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 Layout Layout Save and Restore Graph Layout Settings Salvar e restaurar as configurações de layout 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) Hide All Graphs Ocultar Todos os Gráficos Show All Graphs Mostrar 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> 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. <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 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 I started this oximeter recording at (or near) the same time as a session on my CPAP device. Comecei esta gravação do oxímetro no (ou próximo) ao mesmo tempo de uma sessão no meu dispositivo CPAP. <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 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 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. Duration of airflow restriction Duração da restrição de fluxo de ar s s Event Duration Duração de Evento 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 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> <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Sessões com duração menor que essa não serão exibidas</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></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 <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">A sinalização personalizada é um método experimental de detecção de eventos perdidos pelo dispositivo. Eles </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">não</span><span style=" font-family:'Sans'; font-size:10pt;"> estão inclusos no IAH.</span></p></body></html> 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) <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 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 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> 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. 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 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 Clinical Clinical Settings Select Oscar Operating Mode Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. Clinical Mode permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. Permissive Mode Hours Oximetry Settings Opções de oximetria <!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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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 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 Include Serial Number Inclui Número de Série Graphics Engine (Requires Restart) Motor Gráfico (Exige Reinício) This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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) Isso mantém um backup dos dados do cartão SD para dispositivos ResMed, Os dispositivos da série ResMed S9 excluem dados de alta resolução com mais de 7 dias, e gráficos de dados com mais de 30 dias.. OSCAR pode manter uma cópia desses dados se você precisar reinstalar. (Altamente recomendado, a menos que você tenha pouco espaço em disco ou não se importe com os dados do gráfico) <html><head/><body><p>Provide an alert when importing data from any device model that has not yet been tested by OSCAR developers.</p></body></html> <html><head/><body><p>Forneçe um alerta ao importar dados de qualquer modelo de dispositivo que ainda não tenha sido testado pelos desenvolvedores do OSCAR.</p></body></html> <html><head/><body><p>Provide an alert when importing data from any device model that has not yet been tested by OSCAR developers.</p></body></html> Warn when importing data from an untested device Avisar ao importar dados de um dispositivo não testado This calculation requires Total Leaks data to be provided by the CPAP device. (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 que os dados de vazamentos totais sejam fornecidos pelo dispositivo CPAP. (Por exemplo, PRS1, mas não ResMed, que já os possui) Os cálculos de vazamento não intencional 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. Ainda deve estar próximo o suficiente. Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device missed. This option must be enabled before import, otherwise a purge is required. This experimental option attempts to use OSCAR's event flagging system to improve device detected event positioning. Esta opção experimental tenta usar o sistema de sinalização de eventos do OSCAR para melhorar o posicionamento de eventos detectados pelo dispositivo. Resync Device Detected Events (Experimental) Eventos detectados de dispositivo de ressincronização (Experimental) Allow duplicates near device events. Permite duplicatas perto de eventos do dispositivo. Show flags for device detected events that haven't been identified yet. Mostrar sinalizadores para eventos detectados pelo dispositivo que ainda não foram identificados. <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices do not support changing these settings.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Observação: </span>Devido a limitações de design de resumo, os dispositivos ResMed não suportam a alteração dessas configurações.</p ></body></html> <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:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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><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:12px; margin-bottom:12px; 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 de oximetria e CPAP</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Os dados CMS50 importados de SpO2Review (de arquivos .spoR) ou o método de importação serial </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">não</span><span style=" font-family:'Sans'; font-size:10pt;"> tem a data/hora corretoa necessários para sincronizar.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">O modo de visualizaçã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 do CPAP.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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 Oxímetro em </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exatamente </span><span style=" font -family:'Sans'; font-size:10pt;">ao mesmo tempo em que você inicia seu dispositivo CPAP, você também poderá sincronizar agora. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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> Whether to include device serial number on device settings changes report Se o número de série do dispositivo deve ser incluído no relatório de alterações de configurações do dispositivo 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) For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. Enables SessionBar in Event Flags Graph 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. Overview Visão-Geral No CPAP devices detected Nenhum dispositivo CPAP detectado Will you be using a ResMed brand device? Você usará um dispositivo da marca ResMed? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, days will <b>split at noon</b> like in ResMed's commercial software.</p> <p><b>Observação:</b> os recursos avançados de divisão de sessão do OSCAR não são possíveis com dispositivos <b>ResMed</b> devido a uma limitação na maneira como suas configurações e dados de resumo são armazenados e, portanto, eles foram desativados para este perfil.</p><p>Em dispositivos ResMed, os dias serão <b>divididos ao meio-dia</b> como no software comercial da ResMed.</p> 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? ResMed S9 devices routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). Os dispositivos ResMed S9 excluem rotineiramente determinados dados do cartão SD com mais de 7 e 30 dias (dependendo da resolução). 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 Clinical Mode: Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. Basically replicates the reports and data stored on the devices data card. This includes pap devices, oximeters, etc. Compliance reports fall under this mode. Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. Permissive Mode: Allows user to select which data sets/ sessions to be used for calculations and display. Additional charts and calculations may be available that are not available from the vendor data. Changing the Oscar Operating Mode: Requires a reload of the user's profile. Data will be saved and restored. Minor Flag Pequena Marcação Span Período Always Minor Sempre Pequeno Never Nunca This may not be a good idea Isso pode não ser uma boa ideia 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 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 Length: %1 Comprimento: %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 EEPAP EEPAP Min EEPAP Max EEPAP 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 Device Dispositivo 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: Compiler: Software Engine Motor de Software ANGLE / OpenGLES ANGLE / OpenGLES Desktop OpenGL OpenGL Desktop m m cm cm in pol kg kg Minutes Minutos Seconds Segundos milliSeconds h h m m s s ms ms Events/hr Eventos/hr Hz Hz l/min l/min 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 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 device'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, portanto, os desenvolvedores gostariam de uma cópia .zip do cartão SD deste dispositivo e relatórios .pdf do médico correspondentes para garantir que o OSCAR esteja lidando com os dados corretamente. Non Data Capable Device Dispositivo sem capacidade de dados Your %1 CPAP Device (Model %2) is unfortunately not a data capable model. Infelizmente, seu dispositivo CPAP %1 (Modelo %2) não é um modelo compatível com dados. I'm sorry to report that OSCAR can only track hours of use and very basic settings for this device. Lamento informar que o OSCAR só pode rastrear horas de uso e configurações muito básicas para este dispositivo. Device Untested Dispositivo Não Testado Your %1 CPAP Device (Model %2) has not been tested yet. Seu dispositivo CPAP %1 (Modelo %2) ainda não foi testado. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Parece semelhante o suficiente a outros dispositivos para que funcione, mas os desenvolvedores gostariam de uma cópia .zip do cartão SD deste dispositivo e relatórios .pdf do médico correspondentes para garantir que funcione com o OSCAR. Device Unsupported Dispositivo Não Suportado Sorry, your %1 CPAP Device (%2) is not supported yet. Desculpe, seu dispositivo CPAP %1 (%2) ainda não é suportado. The developers need a .zip copy of this device'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 deste dispositivo e dos relatórios .pdf do médico correspondentes para que funcione com o OSCAR. Scanning Files... Vasculhando Arquivos... Importing Sessions... Importando Sessões... UNKNOWN DESCONHECIDO APAP (std) APAP (padrão) APAP (dyn) APAP (dinâmico) Auto S Auto I Auto S/T Auto I/T AcSV AcSV SoftPAP Mode Modo SoftPAP Pressure relief during exhalation Slight Pouco Softstart pressure Pressure during soft start period PSoft PSoft Softstart minimum pressure Minimum pressure during soft start period PSoftMin PSoftMin Auto start Automatically turn on the device by breathing Softstart time Lenght of soft start period Soft start maximum time Maximum lenght of soft start period Soft start max. time Soft start pressure Higher End Expiratory Pressure Humidifier level Tube type Obstruction level Obstruction level in percentage rRMVFluctuation Relative respiratory minute volume fluctuation Relative respiratory minute volume Measured pressure Full flow Artefact Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) Epoch (2 mins) with Flow Limitation Deep Sleep Deep sleep, stable respiration Timed breath BiSoft Mode BiSoft 1 BiSoft 2 TriLevel AutoStart InicioAutomático Softstart_Time Tempo_Softstart Softstart_TimeMax TempoMax_Softstart Softstart_Pressure Pressão_Softstart PMaxOA PMaxOA EEPAPMin PAEPMín Lower End Expiratory Pressure EEPAPMax PAEPMáx HumidifierLevel NívelUmidificador TubeType TipoTubo ObstructLevel NívelObstrução Obstruction Level Nível Obstrução rMVFluctuation FlutuaçãorMV rRMV rRMV PressureMeasured PressãoMedida FlowFull FluxoTotal SPRStatus StatusSPR Artifact Artefato ART ART CriticalLeak VazamentoCrítico Mask leakage is above a critical treshold CL CL eMO eMO Epoch (2 mins) with Mild Obstruction eSO eSO Epoch (2 mins) with Severe Obstruction eS eS Epoch (2 mins) with Snoring eFL eFL DeepSleep SonoProfundo DS DS TimedBreath RespiraçãoCronometrada Finishing up... Finalizando... Untested Data Dados não testados 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 A few breaths automatically starts device Algumas respirações iniciam automaticamente o aparelho Device automatically switches off O aparelho desliga automaticamente Whether or not device allows Mask checking. Se o dispositivo permite ou não a verificação de máscara. 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 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 A period during a session where the device could not detect flow. Um período durante uma sessão em que o aparelho não pôde detectar fluxo. 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 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 Auto Off Auto Desligar Mask Alert Alerta de Máscara Show AHI Mostrar IAH Whether or not device shows AHI via built-in display. Se o dispositivo mostra ou não o IAH via display integrado. The number of days in the Auto-CPAP trial period, after which the device will revert to CPAP O número de dias no período de teste do Auto-CPAP, após o qual o dispositivo reverterá para CPAP Breathing Not Detected Respiração Não Detectada 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 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> <i>Your old device data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> <i>Os dados do seu dispositivo antigo 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> 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. 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? Device Database Changes Alterações no banco de dados do dispositivo 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. The device data folder needs to be removed manually. A pasta de dados do dispositivo precisa ser removida manualmente. 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. 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 This means you will need to import this device data again afterwards from your own backups or data card. Isso significa que você precisará importar esses dados do dispositivo novamente depois de seus próprios backups ou cartão de dados. 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. 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? OSCAR Reminder Lembrete do OSCAR Don't forget to place your datacard back in your CPAP device Não se esqueça de colocar seu cartão de dados de volta no seu dispositivo CPAP 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 oximetry settings to defaults? Tem certeza de que deseja redefinir todas as configurações de oximetria 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 Cheyne Stokes Respiration (CSR) Respiração Cheyne Stokes (RCS) Periodic Breathing (PB) Respiração Periódica (RP) Clear Airway (CA) Via Aérea Desobstruída (VRD) An apnea caused by airway obstruction Uma apnéia causada por uma bostrução de via aérea A partially obstructed airway Uma via aérea parcialmente obstruída UA AI A vibratory snore Um ronco vibratório 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 LL Large Leak Grande Vazamento GV 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. 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 DespSense reduzirá a pressão quando caminhar é detectado. 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 A sudden (user definable) change in heart rate Uma mudança brusca (definível pelo usuário) na taxa cardíaca 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 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 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 An abnormal period of Periodic Breathing Um período anormal de respiração An apnea that couldn't be determined as Central or Obstructive. Uma apnéia que não pode ser determina como Central ou Obstrutiva. An apnea reported by your CPAP device. 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. A vibratory snore as detected by a System One device Um ronco vibratório detectado por um dispositivo System One 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 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 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. End Expiratory Pressure Pressão Expiratória Final Obstructive Apnea (OA) Apnéia Obstrutiva (AO) Hypopnea (H) Hipopneia (H) Unclassified Apnea (UA) Apneia não classificada (AU) Apnea (A) Apneia (A) An apnea reportred by your CPAP device. Uma apnéia relatada pelo seu dispositivo CPAP. Flow Limitation (FL) Limitação de Fluxo (LF) RERA (RE) Excitação Esforço Respiratório (EER) Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. Despertar Relacionado ao Esforço Respiratório: Uma restrição na respiração que causa o despertar ou distúrbios do sono. Vibratory Snore (VS) Ronco Vibratório (VS) Leak Flag (LF) Marcação Vazamento (MV) A large mask leak affecting device performance. Um grande vazamento de máscara afetando o desempenho do dispositivo. Large Leak (LL) Grande Vazamento (GV) Non Responding Event (NR) Evento sem resposta (SR) Expiratory Puff (EP) Sopro Expiratório (SE) SensAwake (SA) DespSense (DS) User Flag #1 (UF1) Sinalização do usuário #1 (SU1) User Flag #2 (UF2) Sinalização do usuário #2 (SU2) User Flag #3 (UF3) Sinalização do usuário #3 (SU3) Pulse Change (PC) Mudança de Pulso (MP) SpO2 Drop (SD) Queda de SpO2 (QS) I/E Value Apnea Hypopnea Index (AHI) Índice de apneia e hipopneia (IAH) Respiratory Disturbance Index (RDI) Índice de Distúrbio Respiratório (IDR) 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) 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) Min EEPAP %1 Max EEPAP %2 PDIFF %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 Selection Length Comprimento da Seleção 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 Device auto starts by breathing O dispositivo inicia automaticamente ao respirar Response Resposta Device auto stops by breathing O dispositivo pára automaticamente ao respirar Patient View Visão do Paciente RiseEnable RiseTime Cycle Trigger TiMax TiMin Your ResMed CPAP device (Model %1) has not been tested yet. Seu dispositivo CPAP ResMed (Modelo %1) ainda não foi testado. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card to make sure it works with OSCAR. Parece semelhante o suficiente a outros dispositivos para que funcione, mas os desenvolvedores gostariam de uma cópia .zip do cartão SD deste dispositivo para garantir que funcione com o OSCAR. SmartStart SmartStart 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 Smart Stop Parada Inteligente Simple Simples Advanced Avançado 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... Permissive Mode Total disabled sessions: %1, found in %2 days Total disabled sessions: %1 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. Updating Statistics cache Atualizando cache de Estatísticas Usage Statistics Estatísticas de Uso 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 Löwenstein no translation available Löwenstein Prisma Smart Smart Prisma Resvent/Hoffrichter iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings Gerenciar Salvar Configurações de Layout Add Adicionar Add Feature inhibited. The maximum number of Items has been exceeded. Adicionar Recurso inibido. O número máximo de itens foi excedido. creates new copy of current settings. cria uma nova cópia das configurações atuais. Restore Restaurar Restores saved settings from selection. Restaura as configurações salvas da seleção. Rename Renomear Renames the selection. Must edit existing name then press enter. Renomeia a seleção. Deve editar o nome existente e pressionar enter. Update Atualizar Updates the selection with current settings. Atualiza a seleção com as configurações atuais. Delete Apagar Deletes the selection. Apaga a seleção. Expanded Help menu. Menu de Ajuda Expandido. Exits the Layout menu. Sai do menu de Layout. <h4>Help Menu - Manage Layout Settings</h4> <h4>Menu Ajuda - Gerenciar configurações de layout</h4> Exits the help menu. Sai do menu de ajuda. Exits the dialog menu. Sai do menu de diálogo. This feature manages the saving and restoring of Layout Settings. Layout Settings control the layout of a graph or chart. Different Layouts Settings can be saved and later restored. Button Description Creates a copy of the current Layout Settings. The default description is the current date. The description may be changed. The Add button will be greyed out when maximum number is reached. Other Buttons Greyed out when there are no selections Loads the Layout Settings from the selection. Automatically exits. io Modify the description of the selection. Same as a double click.io Saves the current Layout Settings to the selection. Prompts for confirmation. Deletes the selecton. Control Exit Sair (Red circle with a white "X".) Returns to OSCAR menu. Return Next to Exit icon. Only in Help Menu. Returns to Layout menu. Escape Key Exit the Help or Layout menu. Layout Settings * Name * Pinning * Plots Enabled * Height * Order * Event Flags * Dotted Lines * Height Options General Information Maximum description size = 80 characters. Maximum Saved Layout Settings = 30. Saved Layout Settings can be accessed by all profiles. Layout Settings only control the layout of a graph or chart. They do not contain any other data. They do not control if a graph is displayed or not. Layout Settings for daily and overview are managed independantly. <p style="color:black;"> This feature manages the saving and restoring of Layout Settings. <br> Layout Settings control the layout of a graph or chart. <br> Different Layouts Settings can be saved and later restored. <br> </p> <table width="100%"> <tr><td><b>Button</b></td> <td><b>Description</b></td></tr> <tr><td valign="top">Add</td> <td>Creates a copy of the current Layout Settings. <br> The default description is the current date. <br> The description may be changed. <br> The Add button will be greyed out when maximum number is reached.</td></tr> <br> <tr><td><i><u>Other Buttons</u> </i></td> <td>Greyed out when there are no selections</td></tr> <tr><td>Restore</td> <td>Loads the Layout Settings from the selection. Automatically exits. </td></tr> <tr><td>Rename </td> <td>Modify the description of the selection. Same as a double click.</td></tr> <tr><td valign="top">Update</td><td> Saves the current Layout Settings to the selection.<br> Prompts for confirmation.</td></tr> <tr><td valign="top">Delete</td> <td>Deletes the selecton. <br> Prompts for confirmation.</td></tr> <tr><td><i><u>Control</u> </i></td> <td></td></tr> <tr><td>Exit </td> <td>(Red circle with a white "X".) Returns to OSCAR menu.</td></tr> <tr><td>Return</td> <td>Next to Exit icon. Only in Help Menu. Returns to Layout menu.</td></tr> <tr><td>Escape Key</td> <td>Exit the Help or Layout menu.</td></tr> </table> <p><b>Layout Settings</b></p> <table width="100%"> <tr> <td>* Name</td> <td>* Pinning</td> <td>* Plots Enabled </td> <td>* Height</td> </tr> <tr> <td>* Order</td> <td>* Event Flags</td> <td>* Dotted Lines</td> <td>* Height Options</td> </tr> </table> <p><b>General Information</b></p> <ul style=margin-left="20"; > <li> Maximum description size = 80 characters. </li> <li> Maximum Saved Layout Settings = 30. </li> <li> Saved Layout Settings can be accessed by all profiles. <li> Layout Settings only control the layout of a graph or chart. <br> They do not contain any other data. <br> They do not control if a graph is displayed or not. </li> <li> Layout Settings for daily and overview are managed independantly. </li> </ul> <p style="color:black;"> Este recurso gerencia o salvamento e a restauração das configurações de layout. <br> As configurações de layout controlam o layout de um gráfico ou tabela. <br> Diferentes configurações de layouts podem ser salvas e posteriormente restauradas. <br> </p> <table width="100%"> <tr><td><b>Botão</b></td> <td><b>Descrição</b></td></tr> <tr><td valign="top">Adicionar</td> <td>Cria uma cópia das configurações de layout atuais. <br> A descrição padrão é a data atual. <br> A descrição pode ser alterada. <br> O botão Adicionar ficará acinzentado quando o número máximo for atingido.</td></tr> <br> <tr><td><i><u>Outros Botões</u> </i></td> <td>Acinzentado quando não há seleções</td></tr> <tr><td>Restore</td> <td>Carrega as configurações de layout da seleção. Sai automaticamente. </td></tr> <tr><td>Renomear </td> <td>Modifique a descrição da seleção. O mesmo que um clique duplo.</td></tr> <tr><td valign="top">Atualizar</td><td> Salva as configurações de layout atuais na seleção.<br> Solicita confirmação.</td></tr> <tr><td valign="top">Apagar</td> <td>Apaga a seleção. <br> Solicita confirmação.</td></tr> <tr><td><i><u>Control</u> </i></td> <td></td></tr> <tr><td>Exit </td> <td>(Red circle with a white "X".) Retorna ao menu do OSCAR.</td></tr> <tr><td>Retorna</td> <td>Ao lado do ícone Sair. Somente no Menu Ajuda. Retorna ao menu Layout.</td></tr> <tr><td>Tecla de Escape</td> <td>Saia do menu Ajuda ou Layout.</td></tr> </table> <p><b>Configurações de Layout</b></p> <table width="100%"> <tr> <td>* Nome</td> <td>* Fixando</td> <td>* Gráficos ativados</td> <td>* Altura</td> </tr> <tr> <td>* Ordem</td> <td>* Sinalizadores de evento</td> <td>* Linhas Pontilhadas</td> <td>* Height Options</td> </tr> </table> <p><b>General Information</b></p> <ul style=margin-left="20"; > <li> Tamanho máximo da descrição = 80 caracteres. </li> <li> Configurações máximas de layout salvas = 30. </li> <li> As configurações de layout salvas podem ser acessadas por todos os perfis. <li> As configurações de layout controlam apenas o layout de um gráfico ou mapa. <br> Eles não contêm quaisquer outros dados. <br> Eles não controlam se um gráfico é exibido ou não. </li> <li> As configurações de layout para diário e visão geral são gerenciadas de forma independente. </li> </ul> Maximum number of Items exceeded. Excedeu o número máximo de Itens. No Item Selected Nenhum Item Selecionado Ok to Update? Ok para atualizar? Ok To Delete? Ok para apagar? SessionBar %1h %2m %1h %2m No Sessions Present Nenhuma Sessão Presente SleepStyleLoader Import Error Erro de Importação This device Record cannot be imported in this profile. Este registro de dispositivo não pode ser importado neste 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: Device Information Informação de Dispositivo Changes to Device Settings Alterações nas configurações do dispositivo Oscar has no data to report :( OSCAR não possui dados para relatar :( Database has No %1 data available. Database has %1 day of %2 Data on %3 Database has %1 days of %2 Data, between %3 and %4 Total Days: %1 Days Not Used: %1 Days Used: %1 Dias de Uso: %1 Days %1 %2 Hours: %3 Best Device Setting Worst Device Setting 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 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 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 device.</span></p></body></html> <span style=" font-weight:600;">Aviso: </span><span style=" color:#ff0000;">Os cartões SD ResMed S9 precisam ser bloqueados </span><span style=" font-weight :600; color:#ff0000;">antes de inserir no seu computador.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Alguns sistemas operacionais gravam arquivos de índice no cartão sem perguntar, o que pode tornar seu cartão ilegível pelo seu dispositivo cpap.</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 device is detected Observe que algumas preferências são forçadas quando um dispositivo 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 today hoje %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 <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 pressure was under %1 %2 for %3% of the time. Sua pressão ficou abaixo de %1 %2, %3% do tempo. 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. 1 day ago 1 dia atrás Your device was on for %1. Seu dispositivo estava ligado por %1. Your CPAP device used a constant %1 %2 of air O seu dispositivo CPAP usou uma constante %1 %2 de ar Your device used a constant %1-%2 %3 of air. Seu dispositivo usou uma constante %1-%2 %3 de ar. Your device was under %1-%2 %3 for %4% of the time. Seu dispositivo estava abaixo de %1-%2 %3 por %4% do tempo. Your EEPAP pressure was under %1 %2 for %3% of the time. 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 Double click Y-axis: Return to AUTO-FIT Scaling Duplo clique eixo Y: Enter para AUTO AJUSTAR à Escala Double click Y-axis: Return to DEFAULT Scaling Duplo clique eixo Y: Enter para Escala PADRÃO Double click Y-axis: Return to OVERRIDE Scaling Duplo clique eixo Y: Enter para SOBREPOR à Escala Double click Y-axis: For Dynamic Scaling Duplo clique eixo Y: Para Escala Dinâmica Double click Y-axis: Select DEFAULT Scaling Duplo clique eixo Y: Seleciona Escala Padrão Double click Y-axis: Select AUTO-FIT Scaling Duplo clique eixo Y:Selecione AUTO AJUSTAR à Escala %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.5.1/Translations/Romanian.ro.ts000066400000000000000000017220601450332542600207130ustar00rootroot00000000000000 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. 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 i i 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 Search Cauta Layout Aspect Save and Restore Graph Layout Settings Salveaza si Restaureaza aspectul graficului Show/hide available graphs. Arata/ascunde graficele disponibile. Breakdown Detaliere events evenimente UF1 UF1 UF2 UF2 Time at Pressure Timp la Presiunea Clinical Mode Disabling Sessions requires the Permissive Mode 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) no data :( nu exista date :( Sorry, this device only provides compliance data. Ne pare rău, acest dispozitiv oferă doar date de conformitate. 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 Model %1 - %2 Model %1 - %2 PAP Mode: %1 Mod PAP: %1 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 "Nothing's here!" "Nu e nimic aici!" No data is available for this day. Nu exista date pentru aceasta zi. Oximeter Information Informatii Pulsoximetru Click to %1 this session. Click pentru a %1 acesta sesiune. Disable Warning Dezactiveaza avertismentul Disabling a session will remove this session data from all graphs, reports and statistics. The Search tab can find disabled sessions Continue ? Dezactivarea unei sesiuni va elimina datele acestei sesiuni din toate graficele, rapoartele și statisticile. Fila Căutare poate găsi sesiuni dezactivate Continuati ? disable dezactiveaza enable activeaza %1 Session #%2 %1 Sesiune #%2 %1h %2m %3s %1h %2m %3s Device Settings Setari dispozitiv <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 This CPAP device does NOT record detailed data Acest dispozitiv CPAP NU înregistrează date detaliate 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?? 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 Hide All Events Ascunde toate evenimentele Show All Events Arata toate Evenimentele Hide All Graphs Ascunde Toate Graficele Show All Graphs Arata Toate Graficele DailySearchTab Match: Potrivire: Select Match Seloecteaza potrivire: Clear Curata Bookmark Jumps to Date's Bookmark Start Search Incepe Cautarea DATE Jumps to Date DATA Sari la Data Match Notes Note Notes containing Note care contin Bookmarks Semne de carte Bookmarks containing Semne de carte care contin AHI AHI Daily Duration Durata Zilnica Session Duration Durata Sesiunii Days Skipped Zile lipsa Disabled Sessions Sesiuni Dezactivate Number of Sessions Numar de sesiuni Click HERE to close Help Click AICI pentru a inchide Help Help Ajutor No Data Jumps to Date's Details Nu sunt date Sari la detaliile datei Number Disabled Session Jumps to Date's Details Numar de Sesiuni Dezactivate Sari la detaliile Datei Note Jumps to Date's Notes Nota Sari la notele Datei Jumps to Date's Bookmark Sari la semnul de carte al Datei AHI Jumps to Date's Details AHI Sari la detaliile Datei EventsPerHour Session Duration Jumps to Date's Details Durata Sesiunii Sari la detaliile Datei Minutes Number of Sessions Jumps to Date's Details Numar de sesiuni Sari la Detaliile Datei Sessions Daily Duration Jumps to Date's Details Durata zilnica Sari la Detaliile Datei Hours Number of events Jumps to Date's Events Numar de evenimente Sari la evenimentele Datei Events Automatic start Start automat More to Search Cauta mai mult Continue Search Continua cautarea End of Search Sfarsitul cautarii No Matches Nu am gasit nimic Skip:%1 Sari:%1 %1/%2%3 days. %1/%2%3 zile. %1/%2%3 days %1 zile {1/%2%3 ?} Found %1 Am gasit %1. {1 ?} Finds days that match specified criteria. Gaseste zile dupa criteriile specificate. Searches from last day to first day. Cauta de la ultima pana la prima zi. First click on Match Button then select topic. Intai apasati butonul Potrivire apoi alegeti subiectul. Then click on the operation to modify it. Apoi faceti click pe operatie pentru a o modifica. or update the value sau actualizati valoarea Topics without operations will automatically start. Subiectele fara operatii atasate vor porni automat. Compare Operations: numberic or character. Compara Operatii: numeric sau caractere. Numberic Operations: Operatii Numerice: Character Operations: Operatii cu Caractere: Summary Line Rezumat Left:Summary - Number of Day searched Stanga: Rezumat - Numarul zilei cautate Center:Number of Items Found Centru:Numarul de inregistrari Gasite Right:Minimum/Maximum for item searched Dreaptra:Minimum/Maximum elemente cautate Result Table Tabel Rezultate Column One: Date of match. Click selects date. Coloana 1: Data potrivirii. Click pentru a selecta data. Column two: Information. Click selects date. Coloana2: Informatii. Click pentru a selecta data. Then Jumps the appropiate tab. Apoi Sare la tab-ul potrivit. Wildcard Pattern Matching: Potrivire dupa WildCard: Wildcards use 3 characters: WildCard-urile utilizeaza 3 caractere: Asterisk Asterisk Question Mark Semn de intrebare Backslash. Backslash (\) Asterisk matches any number of characters. Asterisk inlocuieste orice numar de caractere. Question Mark matches a single character. Semnul de intrebare inlocuieste un singur caracater. Backslash matches next character. Backslash inlocuieste caracterul urmator. Found %1. Am gasit %1. DateErrorDisplay ERROR The start date MUST be before the end date EROARE Data de început TREBUIE să fie anterioară datei de încheiere The entered start date %1 is after the end date %2 Data de început %1 introdusă este după data de încheiere %2 Hint: Change the end date first Sugestie: mai întâi modificați data de încheiere The entered end date %1 Data de încheiere introdusă %1 is before the start date %1 este înainte de data de începere %1 Hint: Change the start date first Sugestie: mai întâi modificați data de început 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 device Record cannot be imported in this profile. Înregistrarea acestui dispozitiv nu poate fi importată în 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 Show Standard Report Standard Standard Show Monthly Report Monthly Lunar Show Range Report Date Range Interval Date Select Report Date Report 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 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 Standard - CPAP, APAP Standard - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> <html><head/><body><p>Grafic Standard, bun pentru CPAP, APAP, Basic BPAP</p></body></html> Advanced - BPAP, ASV Advanced - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> <html><head/><body><p>Grafic avansat, bun pentru BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> 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 Purge ALL Device Data Ștergeți TOATE datele dispozitivului 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ă 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 Couldn't find any valid Device Data at %1 Nu s-au putut găsi date valide ale dispozitivului la %1 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. 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) No supported data was found Nu am gasit date utilizabile 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) Are you sure you want to rebuild all CPAP data for the following device: Sunteți sigur că doriți să reconstruiți toate datele CPAP pentru următorul dispozitive: For some reason, OSCAR does not have any backups for the following device: Din anumite motive, OSCAR nu are copii de rezervă pentru următorul dispozitiv: 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. Note as a precaution, the backup folder will be left in place. Nota: Ca precautie, dosarul de backup va fi lasat la locul lui. Are you <b>absolutely sure</b> you want to proceed? Sunteti <b>absolut sigur</b> ca doriti ca continuati? A file permission error caused the purge process to fail; you will have to delete the following folder manually: O eroare de permisiune a fișierului a cauzat eșecul procesului de curățare; va trebui să ștergeți manual următorul folder: 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. You must select and open the profile you wish to modify Trebuie să selectați și să deschideți profilul pe care doriți să îl modificați 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 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. Would you like to import from your own backups now? (you will have no data visible for this device until you do) Doriți să importați din propriile copii de rezervă acum? (nu veți avea date vizibile pentru acest dispozitiv până când nu o faceți) OSCAR does not have any backups for this device! OSCAR nu are copii de rezervă pentru acest dispozitiv! Unless you have made <i>your <b>own</b> backups for ALL of your data for this device</i>, <font size=+2>you will lose this device's data <b>permanently</b>!</font> Cu excepția cazului în care v-ați făcut <i><b>propriile copii de siguranță</b> pentru TOATE datele pentru acest dispozitiv</i>, <font size=+2>veți pierde <b>permanent</b > datele acestui dispozitiv>!</font> You are about to <font size=+2>obliterate</font> OSCAR's device database for the following device:</p> Sunteți pe cale să <font size=+2>ștergeți</font> baza de date OSCAR pentru următorul dispozitiv:</p> 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 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. This software is being designed to assist you in reviewing the data produced by your CPAP Devices and related equipment. Acest software este conceput pentru a vă ajuta în vizualizarea datelor înregistrate de dispozitivele dumneavoastră CPAP și echipamentele aferente. 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 Layout Aspect Save and Restore Graph Layout Settings Salveasa si Restaureaza aspectul Graficului 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) Hide All Graphs Ascunde toate graficele Show All Graphs Arata 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> <html><head/><body><p><span style=" font-weight:600; font-style:italic;">Rețineți: </span><span style=" font-style:italic; „>Mai întâi selectați tipul corect de oximetru din meniul derulant de mai jos.</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> <html><head/><body><p><span style=" font-size:12pt; font-weight:700;">ÎNTÂI Selectați oximetrul dvs. din aceste grupuri:</span></p></p></p> corp></html> 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. <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 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 Ziua în care (în mod normal ar fi început) înregistrarea I started this oximeter recording at (or near) the same time as a session on my CPAP device. Am început înregistrarea acestui oximetru la (sau aproape) în același timp cu o sesiune pe dispozitivul meu CPAP. <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 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) This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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 cardului SD pentru dispozitivele ResMed, Dispozitivele din seria ResMed S9 șterg datele 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ă trebuie vreodată să le reinstalați. (Recomandat, cu excepția cazului în care aveți prea puțin spațiu pe disc sau nu vă pasă de datele din grafic) <html><head/><body><p>Provide an alert when importing data from any device model that has not yet been tested by OSCAR developers.</p></body></html> <html><head/><body><p>Vă alertează când importați date de pe orice model de dispozitiv care nu a fost încă testat de dezvoltatorii OSCAR.</p></body></html> Warn when importing data from an untested device Vă avertizează când importați date de pe un dispozitiv netestat &CPAP &CPAP This calculation requires Total Leaks data to be provided by the CPAP device. (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 necesită ca datele Total Leaks să fie furnizate de dispozitivul CPAP. (De exemplu, PRS1, dar nu ResMed, care le are deja) Calculele de scurgeri neintenționate din mască utilizate aici sunt liniare, nu modelează curba de aerisire a măștii. Dacă utilizați câteva măști diferite, alegeți în schimb valori medii. Ar trebui să fie încă suficient de aproape. Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device missed. This option must be enabled before import, otherwise a purge is required. Activați/dezactivați îmbunătățirile de semnalare a evenimentelor experimentale. Permite detectarea evenimentelor limită și a unora pe care dispozitivul le-a ratat. Această opțiune trebuie să fie activată înainte de import, altfel este necesară o curățare. This experimental option attempts to use OSCAR's event flagging system to improve device 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 dispozitiv. Resync Device Detected Events (Experimental) Resincronizarea evenimentelor detectate de dispozitiv (experimental) Allow duplicates near device events. Permite duplicate în apropierea evenimentelor de pe dispozitiv. Show flags for device detected events that haven't been identified yet. Afișați semnalizatoare pentru evenimentele detectate de dispozitiv care nu au fost identificate încă. 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 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. Duration of airflow restriction Durata restrictiei fluxului de aer s s Event Duration Durata eveniment 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 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 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><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Sesiunile cu o durată mai scurtă decât aceasta nu vor fi afișate</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></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>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 <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Semnalizarea personalizată este o metodă experimentală de detectare a evenimentelor ratate de dispozitiv. </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">Nu</span><span style=" font-family:'Sans' ; font-size:10pt;"> sunt incluse în AHI.</span></p></body></html> 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 <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices do not support changing these settings.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Notă: </span>Din cauza limitărilor de design, dispozitivele ResMed nu acceptă modificarea acestor setări.</p ></body></html> <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:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { white-space: pre -infasurare; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style= " font-family:'Sans'; font-size:10pt; font-weight:600;">Sincronizarea datelor de oximetrie și CPAP</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;" ><span style=" font-family:'Sans'; font-size:10pt;">Datele CMS50 importate din SpO2Review (din fișiere .spoR) sau metoda de import în serie </span><span style=" font- family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">nu</span><span style=" font-family:'Sans'; font-size:10pt; „> au marcajul de timp corect necesar pentru sincronizare.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;" ><span style=" font-family:'Sans'; font-size:10pt;">Modul de vizualizare live (folosind un cablu serial) este o modalitate de a obține o sincronizare precisă pe oximetrele CMS50, dar nu contracarează ceasul CPAP deriva.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;" ><span style=" font-family:'Sans'; font-size:10pt;">Dacă porniți modul de înregistrare a Oximetrelor </span><span style=" font-family:'Sans'; font-size :10pt; font-style:italic;">exact </span><span style=" font-family:'Sans'; font-size:10pt;">în același timp în care porniți dispozitivul CPAP, acum puteți realiza sincronizarea. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;" ><span style=" font-family:'Sans'; font-size:10pt;">Procesul de import în serie are timp de pornire de la prima sesiune CPAP de noaptea trecută. (Nu uitați să importați mai întâi datele CPAP!)</span></p></body></html> 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) <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 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 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 Clinical Clinical Settings Select Oscar Operating Mode Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. Clinical Mode permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. Permissive Mode Hours Oximetry Settings Setari Oximetrie <!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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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 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 Whether to include device serial number on device settings changes report Se include numărul de serie al dispozitivului în raportul de modificări ale setărilor dispozitivului Graphics Engine (Requires Restart) Graphics Engine (necesita Restart) 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>Evidențiază Desaturările SpO<span style=" vertical-align:sub;">2</span> sub</p></body></html> 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) For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. Enables SessionBar in Event Flags Graph 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 No CPAP devices detected Nu au fost detectate dispozitive CPAP Will you be using a ResMed brand device? Veți folosi un dispozitiv marca ResMed? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, days will <b>split at noon</b> like in ResMed's commercial software.</p> <p><b>Rețineți:</b> capabilitățile avansate ale OSCAR de împărțire a sesiunilor nu sunt posibile cu dispozitivele <b>ResMed</b> din cauza unei limitări în modul în care sunt stocate setările și datele rezumate ale acestora și, prin urmare, a fost dezactivată pentru acest profil.</p><p>Pe dispozitivele ResMed, zilele se vor <b>împărți la prânz</b>, ca în software-ul comercial 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? ResMed S9 devices routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). Dispozitivele ResMed S9 șterg în mod obișnuit anumite date de pe cardul SD mai vechi de 7 și 30 de zile (în funcție de rezoluție). 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? Flag Atenționare Clinical Mode: Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. Basically replicates the reports and data stored on the devices data card. This includes pap devices, oximeters, etc. Compliance reports fall under this mode. Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. Permissive Mode: Allows user to select which data sets/ sessions to be used for calculations and display. Additional charts and calculations may be available that are not available from the vendor data. Changing the Oscar Operating Mode: Requires a reload of the user's profile. Data will be saved and restored. Minor Flag Atenționare minoră Span Interval Always Minor Intotdeauna Minor Never Niciodata This may not be a good idea S-ar putea sa nu fi e o idee asa buna 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 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 Length: %1 Lungime: %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 milliSeconds 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 EEPAP EEPAP Min EEPAP Max EEPAP 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 Device Dispozitiv 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: Compiler: Compiler: 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 in kg kg l/min l/min 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 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 device'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 complet exacte, așa că dezvoltatorii ar dori o copie .zip a cardului SD al acestui dispozitiv și rapoarte .pdf ale clinicienilor pentru a se asigura că OSCAR gestionează datele corect. Non Data Capable Device Dispozitiv incapabil de a înregistra date Your %1 CPAP Device (Model %2) is unfortunately not a data capable model. Dispozitivul dvs. CPAP %1 (modelul %2) nu este, din păcate, un model capabil de a înregistra date. I'm sorry to report that OSCAR can only track hours of use and very basic settings for this device. Îmi pare rău să raportez că OSCAR poate urmări doar orele de utilizare și setările de bază pentru acest dispozitiv. Device Untested Dispozitiv Netestat Your %1 CPAP Device (Model %2) has not been tested yet. Dispozitivul dvs. CPAP %1 (model %2) nu a fost testat încă. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Pare suficient de asemănător cu alte dispozitive încât ar putea funcționa, dar dezvoltatorii ar dori o copie .zip a cardului SD al acestui dispozitiv și rapoarte .pdf ale clinicienilor pentru a se asigura că funcționează cu OSCAR. Device Unsupported Dispozitiv Incompatibil Sorry, your %1 CPAP Device (%2) is not supported yet. Ne pare rău, dispozitivul dvs. CPAP %1 (%2) nu este încă acceptat. The developers need a .zip copy of this device'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 dispozitiv și de rapoarte .pdf ale clinicienilor pentru ca acesta să funcționeze cu OSCAR. Getting Ready... Pregatesc... Scanning Files... Scanez fisierele... Importing Sessions... Import Sesiunile... UNKNOWN Necunoscut APAP (std) APAP (std) APAP (dyn) APAP (dyn) Auto S Auto S Auto S/T Auto S/T AcSV AcSV SoftPAP Mode Mod SoftPAP Pressure relief during exhalation Slight Usor Softstart pressure Pressure during soft start period PSoft PSoft Softstart minimum pressure Minimum pressure during soft start period PSoftMin PSoftMin Auto start Automatically turn on the device by breathing Softstart time Lenght of soft start period Soft start maximum time Maximum lenght of soft start period Soft start max. time Soft start pressure Higher End Expiratory Pressure Humidifier level Tube type Obstruction level Obstruction level in percentage rRMVFluctuation Relative respiratory minute volume fluctuation Relative respiratory minute volume Measured pressure Full flow Artefact Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) Epoch (2 mins) with Flow Limitation Deep Sleep Deep sleep, stable respiration Timed breath BiSoft Mode BiSoft 1 BiSoft 2 TriLevel AutoStart AutoStart Softstart_Time Softstart_Time Softstart_TimeMax Softstart_TimeMax Softstart_Pressure Softstart_Presiune PMaxOA PMaxOA EEPAPMin EEPAPMin Lower End Expiratory Pressure EEPAPMax EEPAPMax HumidifierLevel Nivel Umiditate TubeType Tip furtun ObstructLevel NIvel Obstructie Obstruction Level Nivelul Obstructiei rMVFluctuation rMVFluctuation rRMV rRMV PressureMeasured PresiuneMasurata FlowFull Debit SPRStatus SPRStatus Artifact Artefact ART ART CriticalLeak Scurgere Critica Mask leakage is above a critical treshold CL CL eMO eMO Epoch (2 mins) with Mild Obstruction eSO eSO Epoch (2 mins) with Severe Obstruction eS eS Epoch (2 mins) with Snoring eFL eFL DeepSleep SomnProfund DS DS TimedBreath RespiratiiCronometrate Finishing up... Finalizare.... Untested Data Date netestate 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 A few breaths automatically starts device Câteva respirații pornesc automat dispozitivul Device automatically switches off Dispozitivul se oprește automat Whether or not device allows Mask checking. Dacă dispozitivul permite sau nu verificarea măștii. 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 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 A period during a session where the device could not detect flow. O perioadă în timpul unei sesiuni în care dispozitivul nu a putut detecta fluxul. 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 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 Auto Off Auto dezactivat Mask Alert Alerta Masca Show AHI Arată AHI Whether or not device shows AHI via built-in display. Dacă dispozitivul afișează sau nu AHI prin afișajul încorporat. The number of days in the Auto-CPAP trial period, after which the device will revert to CPAP Numărul de zile din perioada de probă Auto-CPAP, după care dispozitivul va reveni la CPAP Breathing Not Detected Respiratia Nu a fost Detectata 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 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> <i>Your old device data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> <i>Datele dvs. vechi ale dispozitivului ar trebui să fie regenerate, cu condiția ca această funcție de rezervă să nu fi fost dezactivată în preferințe în timpul unui import anterior de date.</i> 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 device data again afterwards from your own backups or data card. Aceasta înseamnă că va trebui să importați din nou datele acestui dispozitiv ulterior din propriile copii de rezervă sau card de date. 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? Device Database Changes Modificări la baza de date a dispozitivului 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. The device data folder needs to be removed manually. Dosarul de date al acestui dispozitiv trebuie eliminat manual. 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. 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ă. 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 Cheyne Stokes Respiration (CSR) Respirație Cheyne Stokes (CSR) Periodic Breathing (PB) Respirație Periodică (PB) Clear Airway (CA) Căi aeriene libere (CA) Obstructive Apnea (OA) Apnee Obstructivă (OA) Hypopnea (H) Hypopnee (H) An apnea that couldn't be determined as Central or Obstructive. Apnee care nu poate fi caracterizată ca fiind nici Centrală nici Obstructivă. Unclassified Apnea (UA) Apnee Neclassificată (UA) Apnea (A) Apnee (A) An apnea reportred by your CPAP device. O apnee raportată de dispozitivul dvs. CPAP. 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. Flow Limitation (FL) Limitare Flux (FL) RERA (RE) RERA (RE) Vibratory Snore (VS) Sforăit vibratoriu (VS) Vibratory Snore (VS2) Sforait vibrator (VS2) Leak Flag (LF) Avertizare Scurgeri (FL) A large mask leak affecting device performance. O scurgere mare din mască care afectează performanța dispozitivului. Large Leak (LL) Scurgeri Mari (LL) Non Responding Event (NR) Eveniment fără răspuns (NR) Expiratory Puff (EP) Puff Expirator (EP) SensAwake (SA) Simt trezire (SA) User Flag #1 (UF1) Avertizare utilizator #1 (UF1) User Flag #2 (UF2) Avertizare utilizator #2 (UF2) User Flag #3 (UF3) Avertizare utilizator #3 (UF3) Pulse Change (PC) Modificări Puls (PC) SpO2 Drop (SD) Scădere SpO2 (SD) A ResMed data item: Trigger Cycle Event O informație specifică ResMed: Trigger Cycle Event Apnea Hypopnea Index (AHI) Index Apnee Hypopnee (AHI) Respiratory Disturbance Index (RDI) Index Tulbulențe Respiratorii (RDI) 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 A partially obstructed airway Obstructie partiala a cailor aeriene UA UA A vibratory snore Un sforait vibrator 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 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. 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. 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 A sudden (user definable) change in heart rate O schimbare brusca (definibila de utilizator) in frecventa cardiaca 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 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 CSR Respiratie Cheyne Stokes RCS An abnormal period of Periodic Breathing O perioadă anormală de respirație periodică 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 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 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. End Expiratory Pressure Presiune la finalul expiratiei An apnea reported by your CPAP device. Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. Excitare legată de efortul respirator: O restricție a respirației care provoacă fie trezire, fie tulburări de somn. A vibratory snore as detected by a System One device Un sforăit vibratoriu detectat de un dispozitiv System One I/E Value 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? OSCAR Reminder Reamintire OSCAR Don't forget to place your datacard back in your CPAP device Nu uitați să puneți cardul de date înapoi în dispozitivul CPAP 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 oximetry settings to defaults? Sigur doriți să resetați toate setările de oximetrie la valorile 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) 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) Min EEPAP %1 Max EEPAP %2 PDIFF %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> 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 Selection Length Lungimea Selectiei 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 Device auto starts by breathing Dispozitivul pornește automat prin respirație Response Raspuns Device auto stops by breathing Dispozitivul se oprește automat prin respirație Patient View Vizualizare pacient RiseEnable RiseTime Cycle Trigger TiMax TiMin Your ResMed CPAP device (Model %1) has not been tested yet. Dispozitivul dvs. ResMed CPAP (model %1) nu a fost testat încă. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card to make sure it works with OSCAR. Pare suficient de asemănător cu alte dispozitive încât ar putea funcționa cu OSCAR, dar dezvoltatorii ar dori o copie .zip a cardului SD al acestui dispozitiv pentru a se asigura că funcționează cu OSCAR. SmartStart SmartStart 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 Smart Stop Smart Stop Simple Simplu Advanced Avansat 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 Loading summaries Încarc rezumatele Permissive Mode Total disabled sessions: %1, found in %2 days Total disabled sessions: %1 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. 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 Löwenstein Löwenstein Prisma Smart Prisma Smart Resvent/Hoffrichter iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings Gestioneaza setarile de salvare a aspectului Add Adauga Add Feature inhibited. The maximum number of Items has been exceeded. Nu mai puteti adauga, numărul maxim de articole a fost depășit. creates new copy of current settings. creaza o noua copie a setarilor curente. Restore Restaureaza Restores saved settings from selection. Restaureaza setarile salvate din selectie. Rename Redenumire Renames the selection. Must edit existing name then press enter. Redenumeste selectia. Editati numele existent si apasati Enter. Update Actualizare Updates the selection with current settings. Actualizeaza selectia cu setarile curente. Delete Sterge Deletes the selection. Sterge selectia. Expanded Help menu. Meniu HELP extins. Exits the Layout menu. Iesire din meniul Aspect. <h4>Help Menu - Manage Layout Settings</h4> <h4>Meniu Help - Gestioneaza Setari Aspect</h4> Exits the help menu. Iesire din Meniu HELP. Exits the dialog menu. Iesire din Meniul de dialog. This feature manages the saving and restoring of Layout Settings. Layout Settings control the layout of a graph or chart. Different Layouts Settings can be saved and later restored. Button Description Creates a copy of the current Layout Settings. The default description is the current date. The description may be changed. The Add button will be greyed out when maximum number is reached. Other Buttons Greyed out when there are no selections Loads the Layout Settings from the selection. Automatically exits. io Modify the description of the selection. Same as a double click.io Saves the current Layout Settings to the selection. Prompts for confirmation. Deletes the selecton. Control Exit Exit (Red circle with a white "X".) Returns to OSCAR menu. Return Next to Exit icon. Only in Help Menu. Returns to Layout menu. Escape Key Exit the Help or Layout menu. Layout Settings * Name * Pinning * Plots Enabled * Height * Order * Event Flags * Dotted Lines * Height Options General Information Maximum description size = 80 characters. Maximum Saved Layout Settings = 30. Saved Layout Settings can be accessed by all profiles. Layout Settings only control the layout of a graph or chart. They do not contain any other data. They do not control if a graph is displayed or not. Layout Settings for daily and overview are managed independantly. <p style="color:black;"> This feature manages the saving and restoring of Layout Settings. <br> Layout Settings control the layout of a graph or chart. <br> Different Layouts Settings can be saved and later restored. <br> </p> <table width="100%"> <tr><td><b>Button</b></td> <td><b>Description</b></td></tr> <tr><td valign="top">Add</td> <td>Creates a copy of the current Layout Settings. <br> The default description is the current date. <br> The description may be changed. <br> The Add button will be greyed out when maximum number is reached.</td></tr> <br> <tr><td><i><u>Other Buttons</u> </i></td> <td>Greyed out when there are no selections</td></tr> <tr><td>Restore</td> <td>Loads the Layout Settings from the selection. Automatically exits. </td></tr> <tr><td>Rename </td> <td>Modify the description of the selection. Same as a double click.</td></tr> <tr><td valign="top">Update</td><td> Saves the current Layout Settings to the selection.<br> Prompts for confirmation.</td></tr> <tr><td valign="top">Delete</td> <td>Deletes the selecton. <br> Prompts for confirmation.</td></tr> <tr><td><i><u>Control</u> </i></td> <td></td></tr> <tr><td>Exit </td> <td>(Red circle with a white "X".) Returns to OSCAR menu.</td></tr> <tr><td>Return</td> <td>Next to Exit icon. Only in Help Menu. Returns to Layout menu.</td></tr> <tr><td>Escape Key</td> <td>Exit the Help or Layout menu.</td></tr> </table> <p><b>Layout Settings</b></p> <table width="100%"> <tr> <td>* Name</td> <td>* Pinning</td> <td>* Plots Enabled </td> <td>* Height</td> </tr> <tr> <td>* Order</td> <td>* Event Flags</td> <td>* Dotted Lines</td> <td>* Height Options</td> </tr> </table> <p><b>General Information</b></p> <ul style=margin-left="20"; > <li> Maximum description size = 80 characters. </li> <li> Maximum Saved Layout Settings = 30. </li> <li> Saved Layout Settings can be accessed by all profiles. <li> Layout Settings only control the layout of a graph or chart. <br> They do not contain any other data. <br> They do not control if a graph is displayed or not. </li> <li> Layout Settings for daily and overview are managed independantly. </li> </ul> <p style="color:black;"> Această funcție gestionează salvarea și restabilirea setărilor de aspect. <br> Setările de aspect controlează aspectul unui grafic sau diagramă. <br> Setările diferitelor aspecte pot fi salvate și ulterior restaurate. <br> </p> <table width="100%"> <tr><td><b>Buton</b></td> <td><b>Descriere</b></td></tr> <tr><td valign="top">Adaugare</td> <td>Creează o copie a setărilor actuale de aspect. <br> Descrierea implicită este data curentă. <br> Descrierea poate fi modificată. <br> Butonul Adaugă va fi inactiv când se atinge numărul maxim.</td></tr> <br> <tr><td><i><u>Alte Buttoane</u> </i></td> <td>Dezactivat când nu există selecții</td></tr> <tr><td>Restaurare</td> <td>Încarcă setările de aspect din selecție. Iese automat. </td></tr> <tr><td>Redenumire </td> <td>Modificați descrierea selecției. La fel ca un dublu clic.</td></tr> <tr><td valign="top">Actualizare</td><td> Salvează setările actuale de aspect din selecție.<br> E nevoie de confirmare.</td></tr> <tr><td valign="top">Sterge</td> <td>Sterge selectia. <br> E nevoie de confirmare.</td></tr> <tr><td><i><u>Control</u> </i></td> <td></td></tr> <tr><td>Exit </td> <td>(Cerc rosu cu un "X" alb.) Inapoi la meniul OSCAR.</td></tr> <tr><td>Intoarcere</td> <td>Lângă pictograma Ieșire. Doar în meniul Ajutor. Revine la meniul Aspect.</td></tr> <tr><td>Tasta ESC</td> <td>Iesire din meniul HELP sau Aspect</td></tr> </table> <p><b>Setari Aspect</b></p> <table width="100%"> <tr> <td>* Nume</td> <td>* Pinning</td> <td>* Plots activate </td> <td>* Inaltime</td> </tr> <tr> <td>* Ordine</td> <td>* Repere</td> <td>* Linie intrerupta</td> <td>* Optiuni Inaltime</td> </tr> </table> <p><b>Informatii Generale</b></p> <ul style=margin-left="20"; > <li> Dimensiunea maxima descriere = 80 caractere. </li> <li> Numar maxim de salvari ale Aspectului= 30. </li> <li> Setările de aspect salvate pot fi accesate de toate profilurile. <li> Setările de aspect controlează doar aspectul unui grafic sau diagramă. <br> Nu contin nicio alta informatie sau date. <br> Ei nu controlează dacă un grafic este afișat sau nu. </li> <li> Setările de aspect pentru zi de zi și prezentarea generală sunt gestionate independent. </li> </ul> Maximum number of Items exceeded. A fost depasit numarul maxim de elemente. No Item Selected Niciun element selectat Ok to Update? Pornesc actualizarea? Ok To Delete? Sterg? SessionBar %1h %2m %1h %2m No Sessions Present Nu am gasit sesiuni SleepStyleLoader Import Error Eroare la Importare This device Record cannot be imported in this profile. Înregistrările acestui dispozitiv nu pot fi importate în 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: Device Information Informații Dispozitiv Changes to Device Settings Modificări în Informații Dispozitiv Database has No %1 data available. Database has %1 day of %2 Data on %3 Database has %1 days of %2 Data, between %3 and %4 Total Days: %1 Days Not Used: %1 Days Used: %1 Zile de utilizare: %1 Days %1 %2 Hours: %3 Best Device Setting Worst Device Setting 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 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 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 device.</span></p></body></html> <span style=" font-weight:600;">Avertisment: </span><span style=" color:#ff0000;">SDCard-urile ResMed S9 trebuie blocate 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>Unele sisteme de operare scriu fișiere index pe card fără a întreba, ceea ce poate face cardul dvs. imposibil de citit de dispozitivul dvs. cpap.</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 device is detected Rețineți că unele preferințe sunt forțate atunci când este detectat un dispozitiv 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 today astazi %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 <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 pressure was under %1 %2 for %3% of the time. Presiunea dvs a fost sub %1 %2 pentru %3% din timp. 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. 1 day ago o zi in urma Your device was on for %1. Dispozitivul dvs. a fost pornit pentru %1. Your CPAP device used a constant %1 %2 of air Dispozitivul dvs. CPAP a folosit o cantitate constantă de %1 %2 de aer Your device used a constant %1-%2 %3 of air. Dispozitivul dvs. a folosit constant %1-%2 %3 de aer. Your device was under %1-%2 %3 for %4% of the time. Dispozitivul dvs. a fost sub %1-%2 %3 pentru %4% din timp. Your EEPAP pressure was under %1 %2 for %3% of the time. 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 Double click Y-axis: Return to AUTO-FIT Scaling Dublu clic pe axa Y: Reveniți la scalarea AUTO-FIT Double click Y-axis: Return to DEFAULT Scaling Dublu clic pe axa Y: Reveniți la scalarea DEFAULT Double click Y-axis: Return to OVERRIDE Scaling Dublu clic pe axa Y: Reveniți la scalarea OVERRIDE Double click Y-axis: For Dynamic Scaling Dublu clic pe axa Y: Pentru scalare dinamică Double click Y-axis: Select DEFAULT Scaling Dublu clic pe axa Y: Setați scalarea DEFAULT Double click Y-axis: Select AUTO-FIT Scaling Dublu clic pe axa Y: Setați scalarea AUTO-FIT %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.5.1/Translations/Russkiy.ru.ts000066400000000000000000020274101450332542600206240ustar00rootroot00000000000000 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. Не удалось найти файл "Примечания к этой версии". 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 Дневник i i 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 Удалить закладку Search Поиск Layout Настройки Save and Restore Graph Layout Settings Сохранить или загрузить настройки графиков Show/hide available graphs. Показать доступные графики. Breakdown Разбор events события UF1 UF1 UF2 UF2 Time at Pressure Время терапии Clinical Mode Disabling Sessions requires the Permissive Mode 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.) (Нет настроек режима и давления; показаны вчерашние) no data :( Нет данных Sorry, this device only provides compliance data. К сожалению, этот аппарат предоставляет только общие данные. This bookmark is in a currently disabled area.. Закладка в недоступной сейчас области. CPAP Sessions Сеансы CPAP Details Подробности Sleep Stage Sessions Сеансы стадий сна Position Sensor Sessions Сеансы датчика положения Unknown Session Неизвестный сеанс 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 Время вне разгона Start Начало End Конец Unable to display Pie Chart on this system В этой системе невозможно отобразить круговую диаграмму "Nothing's here!" "Здесь ничего нет!" No data is available for this day. Данных за этот день нет. Oximeter Information Информация об оксиметре Click to %1 this session. Нажмите, чтобы %1 этот сеанс. Disable Warning Отключить предупреждение Disabling a session will remove this session data from all graphs, reports and statistics. The Search tab can find disabled sessions Continue ? Отключение сессии удалит все данные сессии со всех графиков, отчетов и статистики. Найти отключенные сессии можно на вкладке Поиск Продолжить? disable отключить enable включить %1 Session #%2 %1: сеанс #%2 %1h %2m %3s %1ч %2м %3с Device Settings Настройки аппарата <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 Разбор событий This CPAP device does NOT record detailed data Этот аппарат не записывает подробные данные 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 Закладка на %1 Hide All Events Скрыть все события Show All Events Показать все события Hide All Graphs Скрыть все графики Show All Graphs Показать все графики DailySearchTab Match: Искать: Select Match Выберите совпадение Clear Очистить Bookmark Jumps to Date's Bookmark Start Search Искать DATE Jumps to Date ДАТА Переход к дате Match Notes Заметки Notes containing Заметки содержащие Bookmarks Закладки Bookmarks containing Закладки содержащие AHI AHI Daily Duration Дневная длительность Session Duration Длительность сеанса Days Skipped Дней пропущено Disabled Sessions Отключено сеансов Number of Sessions Число сеансов Click HERE to close Help Нажмите здесь чтобы закрыть Помощь Help Помощь No Data Jumps to Date's Details Нет данных Переход к Подробностям дня Number Disabled Session Jumps to Date's Details Переход к Подробностям Note Jumps to Date's Notes Заметки Переход к Заметкам Jumps to Date's Bookmark Переход к Закладке AHI Jumps to Date's Details AHI Переход к Подробностям EventsPerHour Session Duration Jumps to Date's Details Длительность сеанса Переход к Подробностям Minutes Number of Sessions Jumps to Date's Details Число сеансов Переход к Подробностям Sessions Daily Duration Jumps to Date's Details Дневная продолжительность Переход к Подробностям Hours Number of events Jumps to Date's Events Число событий Переход к Событиям Events Automatic start Автостарт More to Search Больше Continue Search Продолжить поиск End of Search Прекратить поиск No Matches Ничего не нашлось Skip:%1 Пропустить %1 %1/%2%3 days. %1/%2%3 дней. %1/%2%3 days %1 дней {1/%2%3 ?} Found %1 Найдено %1. {1 ?} Finds days that match specified criteria. Находит дни, подходящие под условия. Searches from last day to first day. Ищет от последного до первого дня. First click on Match Button then select topic. Сначала нажмите на Поиск, потом выберите тему. Then click on the operation to modify it. Затем выберите операцию, которую нужно изменить. or update the value или изменить значение Topics without operations will automatically start. Темы без операций запускаются автоматически. Compare Operations: numberic or character. Операции сравнения: числовые и символьные. Numberic Operations: Числовые операции: Character Operations: Символьные операции: Summary Line Итоги Left:Summary - Number of Day searched Слева: Итоги - число дней поиска Center:Number of Items Found Центр: число результатов Right:Minimum/Maximum for item searched Справа: минимум и максимум Result Table Таблица результатов Column One: Date of match. Click selects date. Первый столбец: Дата совпадения. Нажатие выбирает дату. Column two: Information. Click selects date. Второй столбец: Информация. Нажатие выбирает дату. Then Jumps the appropiate tab. Затем переходит в нужную вкладку. Wildcard Pattern Matching: Поиск по шаблону: Wildcards use 3 characters: Шаблоны используют 3 типа символов: Asterisk Звездочка Question Mark Знак вопроса Backslash. Бекслеш. Asterisk matches any number of characters. Звездочка соответствует любому числу символов. Question Mark matches a single character. Знак вопроса соответствует одному символу. Backslash matches next character. Бекслеш соответствует следующему символу. Found %1. Найдено %1. DateErrorDisplay ERROR The start date MUST be before the end date ОШИБКА Дата начала должна быть раньше конечной даты The entered start date %1 is after the end date %2 Выбранная дата начала %1 позже конечной даты %2 Hint: Change the end date first Совет: сначала выберите конечную дату The entered end date %1 Выбранная конечная дата %1 is before the start date %1 раньше даты начала %1 Hint: Change the start date first Совет: сначала выберите начальную дату 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 device 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 Режим отчета Show Standard Report Standard Стандартный Show Monthly Report Monthly Месячный Show Range Report Date Range Период Select Report Date Report Date Statistics Статистика Daily День Overview Сводка Oximetry Оксиметрия Import Импорт Help Помощь &File &Файл &View &Вид &Reset Graphs &Сбросить графики &Help &Помощь Troubleshooting Решение проблем &Data &Данные &Advanced Д&ополнительно 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 Standard - CPAP, APAP Стандартный - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> <html><head/><body><p>Стандартный набор графиков, подходит для CPAP, APAP, базового BPAP</p></body></html> Advanced - BPAP, ASV Расширенный - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> <html><head/><body><p>Расширенный набор графиков, подходит для BPAP с BU, ASV, AVAPS, IVAPS</p></body></html> Purge Current Selected Day Очистить выбранный день &CPAP &CPAP &Oximetry &Оксиметрия &Sleep Stage &Стадии сна &Position &Положение &All except Notes &Все кроме примечаний All including &Notes Все включая &примечания Show &Line Cursor Показать &линию курсора Purge ALL Device Data Очистить ВСЕ данные аппарата 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 Показать круговую диаграмму на дневной странице 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 Проблема импорта данных Couldn't find any valid Device Data at %1 Не удалось найти корректные данные: %1 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. Если вы видите это, перезапуск не сработал. Перезапустите приложение вручную. No help is available. Справка недоступна. You must select and open the profile you wish to modify Нужно выбрать и открыть профиль для редактирования %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. Выбирайте корневую папку или диск вашей карты памяти, а не папку внутри нее. No supported data was found Подходящие данные не найдены Please open a profile first. Сначала откройте профиль. Check for updates not implemented Проверка обновлений не реализована Are you sure you want to rebuild all CPAP data for the following device: Подтвердите полную перестройку данных CPAP для данного аппарата: For some reason, OSCAR does not have any backups for the following device: По какой-то причине OSCAR не содержит резервных копий для данного аппарата: 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. Поскольку нет внутренних резервных копий, используйте свои копии для восстановления. Note as a precaution, the backup folder will be left in place. Предупреждение: папка с архивом будет сохранена. OSCAR does not have any backups for this device! OSCAR не содержит резервных копий для этого аппарата! Unless you have made <i>your <b>own</b> backups for ALL of your data for this device</i>, <font size=+2>you will lose this device'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 device database for the following device:</p> Вы собираетесь <font size=+2>удалить</font> базу данных OSCAR для этого аппарата:</p> Are you <b>absolutely sure</b> you want to proceed? Вы <b>абсолютно уверены</b> что хотите продолжить? A file permission error caused the purge process to fail; you will have to delete the following folder manually: Не хватило прав доступа для очистки, придется удалить этот каталог вручную: 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 Обновление 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 было отключено. Would you like to import from your own backups now? (you will have no data visible for this device until you do) Хотите импортировать собственные резервные копии? (У вас не будет данных, видимых для этого аппарата, пока вы этого не сделаете) 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 данных 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> и поставляется без гарантии и без каких-либо претензий на соответствие любого рода. This software is being designed to assist you in reviewing the data produced by your CPAP Devices and related equipment. Это программное обеспечение предназначено для помощи в анализе данных, сгенерированных аппаратами CPAP и другим оборудованием. 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 Показать выбранный период Layout Настройки Save and Restore Graph Layout Settings Сохранение и загрузка настроек графиков 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) Hide All Graphs Скрыть все графики Show 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> 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. Использовать время из встроенных часов оксиметра. <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 &Информационная страница 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 День начала записи I started this oximeter recording at (or near) the same time as a session on my CPAP device. Запись оксиметра начата одновременно (или почти) с началом сеанса на аппарате CPAP. <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 Игнорировать короткие сеансы 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-карт (первый импорт медленнее, зато резервные копии меньше) This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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>Provide an alert when importing data from any device 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 device Предупреждать при импорте данных из непроверенного аппарата &CPAP &CPAP This calculation requires Total Leaks data to be provided by the CPAP device. (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, который сам по себе показывает эти данные) Расчеты непреднамеренных утечек, используемые здесь, являются линейными, они не учитывают вентиляционную кривую маски. Если вы используете разные маски, выберите средние значения. Этого должно быть достаточно. Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device missed. This option must be enabled before import, otherwise a purge is required. Включить/выключить экспериментальные метки событий. Это позволяет обнаруживать граничные события, которые некоторые аппараты пропускают. Опция должна быть включена перед импортом, в противном случае требуется очистка. This experimental option attempts to use OSCAR's event flagging system to improve device detected event positioning. Эта экспериментальная настройка пытается использовать систему отметок Oscar, чтобы улучшить позиционирование событий, обнаруженных аппаратом. Resync Device Detected Events (Experimental) Пересинхронизировать события аппарата (экспериментальная опция) Allow duplicates near device events. Разрешить дублирование событий аппарата. Show flags for device detected events that haven't been identified yet. Показать отметки для событий, обнаруженных аппаратом, которые еще не были идентифицированы. Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. Рассматривать дни с временем использования менее данного значения, как "несоответствующие". Четырех часов обычно достаточно. hours час Flow Restriction Ограничение потока Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. Отклонение ограничения потока от медианного значения. Установка в 20% достаточно хорошо определяет апноэ. Duration of airflow restriction Длительность перекрытия воздушного потока s с Event Duration Длительность события 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 Показывать в диаграмме событий 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 Импортировать без запроса о подтверждении 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><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Сессии короче этого периода не будут показаны.</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></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>Примечание: не предназначено для исправления часового пояса! Убедитесь, что время и часовой пояс операционной системы установлены правильно.</p></body></html> Hours Часы <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</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:'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> 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 событий <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices do not support changing these settings.</p></body></html> <html><head/><head/><body><p><span style=" font-weight: 600;">Примечание: </span>из-за ограничений дизайна, аппараты ResMed не поддерживают изменение этих настроек.</p></body></html> <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:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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> 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, но замедляет переключение между днями) <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 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 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 Clinical Clinical Settings Select Oscar Operating Mode Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. Clinical Mode permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. Permissive Mode Hours Oximetry Settings Настройки оксиметрии <!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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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. Вы используете тестовую версию 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 device serial number on device 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> Print reports in black and white, which can be more legible on non-color printers Печатать монохромные отчеты, которые могу быть более различимы на нецветных принтерах Print reports in black and white (monochrome) Печатать монохромные отчеты For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. Enables SessionBar in Event Flags Graph 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 Сводка No CPAP devices detected CPAP аппараты не найдены Will you be using a ResMed brand device? Будете ли вы использовать аппарат ResMed? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, 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? Сделанные вами изменения требуют перезапуска приложения, чтобы эти изменения вступили в силу. Хотите сделать это сейчас? ResMed S9 devices routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). Аппараты ResMed S9 регулярно удаляют с SD-карты данные, записанные больше 7 и 30 дней назад (в зависимости от точности). 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 Событие Clinical Mode: Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. Basically replicates the reports and data stored on the devices data card. This includes pap devices, oximeters, etc. Compliance reports fall under this mode. Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. Permissive Mode: Allows user to select which data sets/ sessions to be used for calculations and display. Additional charts and calculations may be available that are not available from the vendor data. Changing the Oscar Operating Mode: Requires a reload of the user's profile. Data will be saved and restored. Minor Flag Незначительное событие Span Период Always Minor Всегда незначительное событие Never Никогда This may not be a good idea Скорей всего это плохая идея 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 унц 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 Length: %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 секунды milliSeconds 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 EEPAP EEPAP Min EEPAP Max EEPAP 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 Ослабление давления Device Аппарат No Data Available Нет данных App key: Программа: Operating system: Операционная система: Built with Qt %1 on %2 Собрано с Qt %1 для %2 Graphics Engine: Графический движок: Graphics Engine type: Тип графического движка: Compiler: Компилятор: Software Engine Программный ANGLE / OpenGLES ANGLE / OpenGLES Desktop OpenGL Desktop OpenGL m м cm см in " kg кг l/min л/мин Only Settings and Compliance Data Available Доступны только настройки и данные соответствия Summary Data Only Только итоговые данные Bookmarks Закладки Mode Режим Model Модель Brand Марка Serial Номер Series Серия 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 device's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Загруженные данные могут быть неточными, поэтому мы бы хотели получить .zip архив данных вашего аппарата и соответствующие врачебные отчеты .pdf, для улучшения обработки данных. Non Data Capable Device Аппарат не поддерживает сбор данных Your %1 CPAP Device (Model %2) is unfortunately not a data capable model. Ваш аппарат %1 (модель %2) не поддерживает передачу данных. I'm sorry to report that OSCAR can only track hours of use and very basic settings for this device. К сожалению, OSCAR может отследить только время использования и самые основные настройки этого аппарата. Device Untested Аппарат не проверен Your %1 CPAP Device (Model %2) has not been tested yet. Ваш аппарат %1 (модель %2) еще не был протестирован. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Выглядит достаточно похоже на другие аппараты, чтобы работать с OSCAR, но мы бы хотели получить .zip архив данных вашего аппарата и соответствующие врачебные отчеты .pdf, чтобы это подтвердить. Device Unsupported Аппарат не поддерживается Sorry, your %1 CPAP Device (%2) is not supported yet. К сожалению, ваш аппарат %1 (%2) еще не поддерживается. The developers need a .zip copy of this device's SD card and matching clinician .pdf reports to make it work with OSCAR. Нам необходим .zip архив данных вашего аппарата и соответствующие врачебные отчеты .pdf, чтобы OSCAR мог с ними работать. Getting Ready... Подготовка... Scanning Files... Сканирование файлов... Importing Sessions... Импорт сеансов... UNKNOWN Неизвестно APAP (std) APAP (стд) APAP (dyn) APAP (дин) Auto S Авто S Auto S/T Авто S/T AcSV AcSV SoftPAP Mode Режим SoftPAP Pressure relief during exhalation Slight Slight Softstart pressure Pressure during soft start period PSoft PSoft Softstart minimum pressure Minimum pressure during soft start period PSoftMin PSoftMin Auto start Automatically turn on the device by breathing Softstart time Lenght of soft start period Soft start maximum time Maximum lenght of soft start period Soft start max. time Soft start pressure Higher End Expiratory Pressure Humidifier level Tube type Obstruction level Obstruction level in percentage rRMVFluctuation Relative respiratory minute volume fluctuation Relative respiratory minute volume Measured pressure Full flow Artefact Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) Epoch (2 mins) with Flow Limitation Deep Sleep Deep sleep, stable respiration Timed breath BiSoft Mode BiSoft 1 BiSoft 2 TriLevel AutoStart Автостарт Softstart_Time Время Softstart Softstart_TimeMax Макс время Softstart Softstart_Pressure Давление Softstart PMaxOA PMaxOA EEPAPMin EEPAPMin Lower End Expiratory Pressure EEPAPMax EEPAPMax HumidifierLevel Уровень увлажнителя TubeType Тип трубки ObstructLevel Уровень обструкции Obstruction Level Уровень обструкции rMVFluctuation rMVFluctuation rRMV rRMV PressureMeasured Измеренное давление FlowFull Полный поток SPRStatus Статус SPR Artifact Артифакт ART ART CriticalLeak Критическая утечка Mask leakage is above a critical treshold CL CL eMO eMO Epoch (2 mins) with Mild Obstruction eSO eSO Epoch (2 mins) with Severe Obstruction eS eS Epoch (2 mins) with Snoring eFL eFL DeepSleep Глубокий сон DS DS TimedBreath TimedBreath Finishing up... Завершение... 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 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 Блокировка сопр. маски A few breaths automatically starts device Аппарат включается после нескольких вдохов Device automatically switches off Аппарат автоматически выключается Whether or not device allows Mask checking. Доступна ли проверка маски. 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 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 НЕ ПОДТВЕРЖДЕНО: вероятно, переменное дыхание - промежутки значительного отклонения от обычных показателей дыхания A period during a session where the device could not detect flow. Промежуток, в течение которого аппарат не может определить дыхание. 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 Авто включение Auto Off Авто выключение Mask Alert Сигнализация маски Show AHI Показывать ИАГ Whether or not device shows AHI via built-in display. Отображение ИАГ на встроенном дисплее. The number of days in the Auto-CPAP trial period, after which the device will revert to CPAP Число дней пробного периода Auto-CPAP, после которого аппарат вернется к режиму CPAP Breathing Not Detected Нет дыхания (BND) 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 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> <i>Your old device data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> <i>Данные аппарата нужно перестроить, если резервное копирование не было отключено в настройках во время предыдущей загрузки.</i> OSCAR does not yet have any automatic card backups stored for this device. Автоматические резервные копии для этого аппарата еще не делались. This means you will need to import this device 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? Device Database Changes Изменение базы данных аппарата Sorry, the purge operation failed, which means this version of OSCAR can't start. К сожалению, операция сброса не удалась, и данная версия OSCAR не будет работать. The device data folder needs to be removed manually. Нужно удалить папку с данными аппарата вручную. 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 и завершите обновление. Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. После обновления, <font size=+1>невозможно</font> будет использовать данный профиль с предущей версией. 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 Аномальный период дыхания Чейна-Стокса Cheyne Stokes Respiration (CSR) Дыхание Чейна-Стокса (CSR) Periodic Breathing (PB) Периодическое дыхание Clear Airway (CA) Свободные дыхательные пути Obstructive Apnea (OA) Обструктивное апноэ Hypopnea (H) Гипопноэ An apnea that couldn't be determined as Central or Obstructive. Апноэ, которое нельзя отнести к обструктивному или центральному. Unclassified Apnea (UA) Непонятное апноэ (UA) Apnea (A) Апноэ (A) An apnea reportred by your CPAP device. Апноэ по данным вашего аппарата. A restriction in breathing from normal, causing a flattening of the flow waveform. Нарушение дыхания, меняющее форму графика потока. Flow Limitation (FL) Ограничение потока (FL) RERA (RE) RERA (RE) Vibratory Snore (VS) Храп (VS) Vibratory Snore (VS2) Храп (VS2) Leak Flag (LF) Флаг утечки (LF) A large mask leak affecting device performance. Серьезная утечка из маски, влияющая на работу аппарата. Large Leak (LL) Значительная утечка (LL) Non Responding Event (NR) Событие без реакции (NR) Expiratory Puff (EP) Утечка выдоха (EP) SensAwake (SA) Обнаружение пробуждения (SA) User Flag #1 (UF1) Пользовательский флаг #1 (UF1) User Flag #2 (UF2) Пользовательский флаг #2 (UF2) User Flag #3 (UF3) Пользовательский флаг #3 (UF3) Pulse Change (PC) Изменение пульса (PC) SpO2 Drop (SD) Падение SpO2 (SD) A ResMed data item: Trigger Cycle Event Данные ResMed: событие запуска цикла Apnea Hypopnea Index (AHI) Индекс апноэ-гипоапноэ (AHI) Respiratory Disturbance Index (RDI) Индекс нарушения дыхания (RDI) 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 Апноэ, вызванное перекрытием дыхательных путей A partially obstructed airway Частично перекрытые дыхательные пути UA UA A vibratory snore Храп Pressure Pulse Пульсация давления A pulse of pressure 'pinged' to detect a closed airway. Пульсация давления для определения перекрытых дыхательных путей. A type of respiratory event that won't respond to a pressure increase. Дыхательное событие, не реагирующее на увеличение давления. Intellipap event where you breathe out your mouth. Событие Intellipap при выдохе ртом. SensAwake feature will reduce pressure when waking is detected. SensAwake уменьшает давление, когда обнаруживает пробуждение. Heart rate in beats per minute Пульс в ударах в минуту Blood-oxygen saturation percentage Оксигенация крови в процентах Plethysomogram Плетизмограмма An optical Photo-plethysomogram showing heart rhythm Оптическая плетизмограмма сердечного ритма A sudden (user definable) change in heart rate Внезапное (задается пользователем) изменение сердечного ритма A sudden (user definable) drop in blood oxygen saturation Внезапное (задается пользователем) падение сатурации крови SD SD Breathing flow rate waveform Кривая изменения потока дыхания 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 CSR CSR An abnormal period of Periodic Breathing Аномальный промежуток периодического дыхания 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 Макс утечки 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 Медианные утечки 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, приложение будет закрыто. End Expiratory Pressure Конечное давление выдоха An apnea reported by your CPAP device. Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. Respiratory Effort Related Arousal: затруднение дыхания, вызывающее пробуждение или нарушение сна. A vibratory snore as detected by a System One device Вибрирующий храп по данным System One I/E Value 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? Точно хотите использовать эту папку? OSCAR Reminder Напоминание OSCAR Don't forget to place your datacard back in your CPAP device Не забудьте вставить карту памяти обратно в CPAP аппарат 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 oximetry 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) 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) Min EEPAP %1 Max EEPAP %2 PDIFF %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 Selection Length Длинна выборки 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 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 Мягкий RiseEnable RiseTime Cycle Trigger TiMax TiMin Your ResMed CPAP device (Model %1) has not been tested yet. Ваш аппарат ResMed (модель %1) еще не был протестирован. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card to make sure it works with OSCAR. Выглядит достаточно похоже на другие аппараты, чтобы работать с OSCAR, но мы бы хотели получить zip архив карты памяти аппарата, чтобы это подтвердить. Standard Стандартный BiPAP-T BiPAP-T BiPAP-S BiPAP-S BiPAP-S/T BiPAP-S/T Device auto starts by breathing Включает аппарат при появлении дыхания SmartStop SmartStop Smart Stop Smart Stop Device auto stops by breathing Автоматическое отключение по дыханию Patient View Экран пациента Simple Простой Advanced Расширенный 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... Подождите... Permissive Mode Total disabled sessions: %1, found in %2 days Total disabled sessions: %1 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. Updating Statistics cache Обновление кеша статистики Usage Statistics Статистика использования 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 типов событий Löwenstein Löwenstein Prisma Smart Prisma Smart Resvent/Hoffrichter iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings Настройки сохранения Add Сохранить Add Feature inhibited. The maximum number of Items has been exceeded. Невозможно сохранить. Достигнуто максимальное количество. creates new copy of current settings. Сохранить текущие настройки. Restore Загрузить Restores saved settings from selection. Загрузить сохраненные настройки. Rename Переименовать Renames the selection. Must edit existing name then press enter. Поменять название настроек (Enter для сохранения). Update Обновить Updates the selection with current settings. Сохранить текущие настройки в выбранные. Delete Удалить Deletes the selection. Удалить выбранные настройки. Expanded Help menu. Помощь. Exits the Layout menu. Закрыть. <h4>Help Menu - Manage Layout Settings</h4> <h4>Помощь - Управление настройками</h4> Exits the help menu. Закрыть окно помощи. Exits the dialog menu. Закрыть окно настроек. This feature manages the saving and restoring of Layout Settings. Layout Settings control the layout of a graph or chart. Different Layouts Settings can be saved and later restored. Button Description Creates a copy of the current Layout Settings. The default description is the current date. The description may be changed. The Add button will be greyed out when maximum number is reached. Other Buttons Greyed out when there are no selections Loads the Layout Settings from the selection. Automatically exits. io Modify the description of the selection. Same as a double click.io Saves the current Layout Settings to the selection. Prompts for confirmation. Deletes the selecton. Control Exit Выход (Red circle with a white "X".) Returns to OSCAR menu. Return Next to Exit icon. Only in Help Menu. Returns to Layout menu. Escape Key Exit the Help or Layout menu. Layout Settings * Name * Pinning * Plots Enabled * Height * Order * Event Flags * Dotted Lines * Height Options General Information Maximum description size = 80 characters. Maximum Saved Layout Settings = 30. Saved Layout Settings can be accessed by all profiles. Layout Settings only control the layout of a graph or chart. They do not contain any other data. They do not control if a graph is displayed or not. Layout Settings for daily and overview are managed independantly. <p style="color:black;"> This feature manages the saving and restoring of Layout Settings. <br> Layout Settings control the layout of a graph or chart. <br> Different Layouts Settings can be saved and later restored. <br> </p> <table width="100%"> <tr><td><b>Button</b></td> <td><b>Description</b></td></tr> <tr><td valign="top">Add</td> <td>Creates a copy of the current Layout Settings. <br> The default description is the current date. <br> The description may be changed. <br> The Add button will be greyed out when maximum number is reached.</td></tr> <br> <tr><td><i><u>Other Buttons</u> </i></td> <td>Greyed out when there are no selections</td></tr> <tr><td>Restore</td> <td>Loads the Layout Settings from the selection. Automatically exits. </td></tr> <tr><td>Rename </td> <td>Modify the description of the selection. Same as a double click.</td></tr> <tr><td valign="top">Update</td><td> Saves the current Layout Settings to the selection.<br> Prompts for confirmation.</td></tr> <tr><td valign="top">Delete</td> <td>Deletes the selecton. <br> Prompts for confirmation.</td></tr> <tr><td><i><u>Control</u> </i></td> <td></td></tr> <tr><td>Exit </td> <td>(Red circle with a white "X".) Returns to OSCAR menu.</td></tr> <tr><td>Return</td> <td>Next to Exit icon. Only in Help Menu. Returns to Layout menu.</td></tr> <tr><td>Escape Key</td> <td>Exit the Help or Layout menu.</td></tr> </table> <p><b>Layout Settings</b></p> <table width="100%"> <tr> <td>* Name</td> <td>* Pinning</td> <td>* Plots Enabled </td> <td>* Height</td> </tr> <tr> <td>* Order</td> <td>* Event Flags</td> <td>* Dotted Lines</td> <td>* Height Options</td> </tr> </table> <p><b>General Information</b></p> <ul style=margin-left="20"; > <li> Maximum description size = 80 characters. </li> <li> Maximum Saved Layout Settings = 30. </li> <li> Saved Layout Settings can be accessed by all profiles. <li> Layout Settings only control the layout of a graph or chart. <br> They do not contain any other data. <br> They do not control if a graph is displayed or not. </li> <li> Layout Settings for daily and overview are managed independantly. </li> </ul> <p style="color:black;"> Сохранение и загрузка настроек. <br> Настройки управляют оформлением графиков. <br> Можно сохранить разные варианты настроек и загрузить их. <br> </p> <table width="100%"> <tr><td><b>Кнопка</b></td> <td><b>Назначение</b></td></tr> <tr><td valign="top">Сохранить</td> <td>Создает копию текущих настроек. <br> Текущая дата по умолчанию. <br> Описание можно изменить. <br> Кнопка Сохранить будет отключена при достижении максимального числа настроек.</td></tr> <br> <tr><td><i><u>Остальные кнопки</u> </i></td> <td>Отключены, когда ничего не выбрано</td></tr> <tr><td>Загрузить</td> <td>Загружает выбранные настройки и закрывается. </td></tr> <tr><td>Переименовать </td> <td>Изменить название. Доступно по двойному клику.</td></tr> <tr><td valign="top">Обновить</td><td> Сохраняет текущие настройки в выбранные.<br> Требуется подтверждение.</td></tr> <tr><td valign="top">Удалить</td> <td>Удалить выбранное. <br> Требуется подтверждение.</td></tr> <tr><td><i><u>Элемент</u> </i></td> <td></td></tr> <tr><td>Выход </td> <td>(Круг с белым "X".) Выход в OSCAR.</td></tr> <tr><td>Возврат</td> <td>Рядом с Выход. Только в меню Помощи. Возврат в окно настроек.</td></tr> <tr><td>Escape</td> <td>Выход.</td></tr> </table> <p><b>Настройки</b></p> <table width="100%"> <tr> <td>* Название</td> <td>* Закрепление</td> <td>* Отображать </td> <td>* Высота</td> </tr> <tr> <td>* Порядок</td> <td>* Флаги событий</td> <td>* Пунктир</td> <td>* Параметры высоты</td> </tr> </table> <p><b>Общая информация</b></p> <ul style=margin-left="20"; > <li> Максимум 80 символов. </li> <li> Максимум 30 сохраненных настроек. </li> <li> Сохраненные настройки доступны из любого профиля. <li> Эти настройки управляют только отображением графиков. <br> Других данных в них нет. <br> Они не управляют выводом графиков. </li> <li> Дневные и общие графики настраиваются отдельно. </li> </ul> Maximum number of Items exceeded. Достигнуто максимальное значение. No Item Selected Ничего не выбрано Ok to Update? Обновить? Ok To Delete? Удалить? SessionBar %1h %2m %1 ч %2 м No Sessions Present Нет сеансов SleepStyleLoader Import Error Ошибка импорта This device 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 Device Information Информация об аппарате Changes to Device Settings Изменения настроек аппарата Database has No %1 data available. Database has %1 day of %2 Data on %3 Database has %1 days of %2 Data, between %3 and %4 Total Days: %1 Days Not Used: %1 Days Used: %1 Дней использования: %1 Days %1 %2 Hours: %3 Best Device Setting Worst Device Setting 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 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 Установки давления 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 device.</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 device is detected Обратите внимание, что некоторые настройки активируются при обнаружении аппарата ResMed First import can take a few minutes. Первый импорт данных может занять несколько минут. The last time you used your %1... Последний раз, когда вы использовали %1... last night вчера today сегодня %2 days ago %2 дней назад was %1 (on %2) был %1 (%2) %1 hours, %2 minutes and %3 seconds %1 ч, %2 мин и %3 сек <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 pressure was under %1 %2 for %3% of the time. Давление было ниже %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% времени. 1 day ago 1 день назад Your device was on for %1. Ваш аппарат был включен в течении %1. Your CPAP device used a constant %1 %2 of air Ваш CPAP аппарат использовал постоянные %1 %2 воздуха Your device used a constant %1-%2 %3 of air. Аппарат использовал постоянное %1-%2 %3 воздуха. Your device was under %1-%2 %3 for %4% of the time. Ваш аппарат был менее %1-%2 %3 %4% времени. Your EEPAP pressure was under %1 %2 for %3% of the time. 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 Double click Y-axis: Return to AUTO-FIT Scaling Двойной клик по оси Y: возврат к автоматическому масштабированию Double click Y-axis: Return to DEFAULT Scaling Двойной клик по оси Y: возврат к масштабированию по умолчанию Double click Y-axis: Return to OVERRIDE Scaling Двойной клик по оси Y: возврат к переопределенному масштабированию Double click Y-axis: For Dynamic Scaling Двойной клик по оси Y: включить динамическое масштабирование Double click Y-axis: Select DEFAULT Scaling Двойной клик по оси Y: включить масштабирование по умолчанию Double click Y-axis: Select AUTO-FIT Scaling Двойной клик по оси Y: включить автоматическое масштабирование %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.5.1/Translations/Suomi.fi.ts000066400000000000000000017206051450332542600202240ustar00rootroot00000000000000 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. 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 i i 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 Search Etsi Layout Asettelu Save and Restore Graph Layout Settings Tallenna ja palauta kaavioiden asetteluasetukset Show/hide available graphs. Näytä/piilota graafit. Breakdown Erittely events tapahtumat UF1 UF1 UF2 UF2 Time at Pressure Aika paineen alla Clinical Mode Kliininen tila Disabling Sessions requires the Permissive Mode Käyttökertojen poistaminen käytöstä edellyttää sallivaa tilaa 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.) no data :( ei tietoja :( Sorry, this device only provides compliance data. Valitettavasti tämä laite tarjoaa vain yhteensopivuustietoja. 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 Model %1 - %2 Malli %1 - %2 PAP Mode: %1 PAP toimintatapa: %1 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ä "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. Oximeter Information Oksimetrin tiedot Details Yksityiskohdat Disable Warning Estä varoitukset Disabling a session will remove this session data from all graphs, reports and statistics. The Search tab can find disabled sessions Continue ? Käyttökerran poistaminen käytöstä poistaa tämän käyttökerran tiedot kaikista kaavioista, raporteista ja tilastoista. Haku-välilehti löytää käytöstä poistetut istunnot Jatketaanko ? 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 Device Settings Laitteen asetukset <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 This CPAP device does NOT record detailed data Tämä CPAP-laite EI tallenna yksityiskohtaisia tietoja 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?? Complain to your Equipment Provider! Reklamoi laitteesi edustajalle! Pick a Colour Valitse väri Bookmark at %1 Kirjanmerkki paikassa %1 Hide All Events Piilota kaikki tapahtumat Show All Events Näytä kaikki tapahtumat Hide All Graphs Piilota kaikki kaaviot Show All Graphs Näytä kaikki kaaviot DailySearchTab Match: Osuma: Select Match Valitse osuma Clear Poista Bookmark Jumps to Date's Bookmark Siirtyy päivämäärän kirjanmerkkiin Start Search Aloita etsintä DATE Jumps to Date PÄIVÄYS Mene päivään Match Sopii yhteen Notes Huom Notes containing Huomautukset Bookmarks Kirjanmerkit Bookmarks containing Kirjanmerkit AHI AHI Daily Duration Päivittäinen kesto Session Duration Käyttökerran kesto Days Skipped Ohitetut päivät Disabled Sessions Estetyt käyttökerrat Number of Sessions Käyttökertojen lukumäärä Click HERE to close Help Napsauta TÄTÄ sulkeaksesi helpin Help Apua No Data Jumps to Date's Details Ei tietoja Menee päivän yksityiskohtiin Number Disabled Session Jumps to Date's Details Estettyjen käyttökertojen lukumäärä Menee päivän yksityiskohtiin Note Jumps to Date's Notes Huomautus Menee päivän huomautuksiin Jumps to Date's Bookmark Menee päivän kirjanmerkkeihin AHI Jumps to Date's Details AHI Menee päivän yksityiskohtiin EventsPerHour Tapahtumaa tunnissa Session Duration Jumps to Date's Details Käyttöjakson kesto Menee päivän yksityiskohtiin Minutes Minuuttia Number of Sessions Jumps to Date's Details Käyttöjaksojen lukumäärä Menee päivän yksityiskohtiin Sessions Käyttökertaa Daily Duration Jumps to Date's Details Päivittäinen kesto Menee päivän yksityiskohtiin Hours Tuntia Number of events Jumps to Date's Events Tapahtumien lukumäärä Menee päivän tapahtumien yksityiskohtiin Events Tapahtumaa Automatic start Automaattinen aloitus More to Search Hae edelleen Continue Search Jatka hakua End of Search Haun loppu No Matches Ei osumia Skip:%1 Ohita:%1 %1/%2%3 days. %1/%2%3 päivää. %1/%2%3 days %1/%2%3 päivää Found %1 Löytyi %1 Finds days that match specified criteria. Etsii päivät, jotka vastaavat määritettyjä ehtoja. Searches from last day to first day. Haut viimeisestä päivästä ensimmäiseen päivään. First click on Match Button then select topic. Napsauta ensin Match-painiketta ja valitse sitten aihe. Then click on the operation to modify it. Napsauta sitten toimintoa muokataksesi sitä. or update the value tai päivitä arvo Topics without operations will automatically start. Aiheet ilman toimintoja käynnistyvät automaattisesti. Compare Operations: numberic or character. Vertaa toiminnot: numero tai merkki. Numberic Operations: Numeeriset operaatiot: Character Operations: Merkkioperaatiot: Summary Line Summarivi Left:Summary - Number of Day searched Jäljellä:Summa - etsittyjen päivien lukumäärä Center:Number of Items Found Keskellä:Löydettyjen kohteiden lukumäärä Right:Minimum/Maximum for item searched Oikealla:Minimi/Maksimi etsittäville kohteille Result Table Tulostaulu Column One: Date of match. Click selects date. Sarake 1: Päivä. Napsautus valitsee päivän. Column two: Information. Click selects date. Sarake 2: Tiedot. Napsautus valitsee päivän. Then Jumps the appropiate tab. Sitten hyppää sopivaan välilehteen. Wildcard Pattern Matching: Jokerimerkkien sovitus: Wildcards use 3 characters: Jokerimerkit käytä 3 merkkiä: Asterisk Tähti Question Mark Kysymysmerkki Backslash. Takakenoviiva. Asterisk matches any number of characters. Tähti vastaa mitä tahansa määrää merkkejä. Question Mark matches a single character. Kysymysmerkki vastaa yhtä merkkiä. Backslash matches next character. Takakenoviiva vastaa seuraavaa merkkiä. Found %1. Löytyi %1. DateErrorDisplay ERROR The start date MUST be before the end date VIRHE Aloituspäivä PITÄÄ olla ennen lopetuspäivää The entered start date %1 is after the end date %2 Annettu aloituspäivä %1 on lopetuspäivän %2 jälkeen Hint: Change the end date first Vihje: Muuta lopetuspäivä ensin The entered end date %1 Annettu lopetuspäivä %1 is before the start date %1 on ennen aloituspäivämäärää %1 Hint: Change the start date first Vihje: Muuta aloituspäivä ensin 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 device Record cannot be imported in this profile. Tätä laitetietuetta ei voi 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 Show Standard Report Näytä standardiraportti Standard Standardi Show Monthly Report Näytä kuukausiraportti Monthly Kuukausittain Show Range Report Näytä aikaväliraportti Date Range Päivien väli Select Report Date Valitse raportin päivä Report Date Raportoi päivä 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ää Purge ALL Device Data Tyhjennä KAIKKI laitetiedot 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 Standard - CPAP, APAP Standardi - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> <html><head/><body><p>Standardi kaavio, Hyvä CPAPille, APAPille, normaalille BPAPille</p></body></html> Advanced - BPAP, ASV Edistyneelle - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> <html><head/><body><p>Tarkennettu kaaviojärjestys; hyvä BPAPile ilman BU, ASV, AVAPS, IVAPS</p></body></html> 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 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 &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 Couldn't find any valid Device Data at %1 Ei löytynyt kelvollisia laitetietoja lähteessä %1 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 No supported data was found Tuettuja tietoja ei löytynyt Importing Data Tuodaan tietoja The User's Guide will open in your default browser Käyttöopas avautuu oletusselaimessa Are you sure you want to rebuild all CPAP data for the following device: Haluatko varmasti rakentaa uudelleen kaikki seuraavan laitteen CPAP-tiedot: For some reason, OSCAR does not have any backups for the following device: Jostain syystä OSCARilla ei ole varmuuskopioita seuraavalle laitteelle: Would you like to import from your own backups now? (you will have no data visible for this device until you do) Haluatko nyt tuoda omia varmuuskopioitasi? (sinulla ei näy tämän laitteen tietoja ennen kuin teet ne) OSCAR does not have any backups for this device! OSCARilla ei ole varmuuskopioita tälle laitteelle! Unless you have made <i>your <b>own</b> backups for ALL of your data for this device</i>, <font size=+2>you will lose this device's data <b>permanently</b>!</font> Ellet ole tehnyt <i><b>omaa</b> varmuuskopiota KAIKISTA tämän laitteen tiedoistasi</i>, <font size=+2>menetät tämän laitteen tiedot <b>pysyvästi</b>!</font> You are about to <font size=+2>obliterate</font> OSCAR's device database for the following device:</p> Olet <font size=+2>poistamassa</font> OSCARin laitetietokantaa seuraavalle laitteelle:</p> A file permission error caused the purge process to fail; you will have to delete the following folder manually: Tiedoston käyttöoikeusvirhe aiheutti tyhjennysprosessin epäonnistumisen; sinun on poistettava seuraava kansio käsin: 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 You must select and open the profile you wish to modify Sinun on valittava ja avattava profiili, jota haluat muokata %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. Note as a precaution, the backup folder will be left in place. Esivaroitus: varmuuskopiokansion sijainti muuttuu. Are you <b>absolutely sure</b> you want to proceed? Oletko <b>absoluuttisesti varma</b>, että haluat tehdä tämän? 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 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. 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 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 This software is being designed to assist you in reviewing the data produced by your CPAP Devices and related equipment. Tämä ohjelmisto on suunniteltu auttamaan sinua CPAP-laitteiden ja niihin liittyvien laitteiden tuottamien tietojen arvioimisessa. 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ö Layout Asettelu Save and Restore Graph Layout Settings Tallenna ja palauta kaavioiden asettelut 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) Hide All Graphs Piilota kaikki kaaviot Show All Graphs Näytä 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> 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. <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 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) I started this oximeter recording at (or near) the same time as a session on my CPAP device. Aloitin tämän oksimetrin tallennuksen samaan aikaan (tai lähes) samaan aikaan kuin CPAP-laitteen käytö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 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 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ä) This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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ä ylläpitää varmuuskopiota SD-kortin tiedoista ResMed-laitteille, ResMed S9 -sarjan laitteet poistavat korkearesoluutioisia yli 7 päivää vanhoja tietoja, ja kuvaajatiedot, jotka ovat vanhoja yli 30 päivää.. OSCAR voi säilyttää kopion näistä tiedoista, jos sinun on joskus asennettava ne uudelleen. (Erittäin suositeltavaa, paitsi jos sinulla on vähän levytilaa tai et välitä kaavion tiedoista) <html><head/><body><p>Provide an alert when importing data from any device model that has not yet been tested by OSCAR developers.</p></body></html> <html><head/><body><p>Anna ilmoitus, kun tuot tietoja mistä tahansa laitemallista, jota OSCAR-kehittäjät eivät ole vielä testaaneet.</p></body></html> Warn when importing data from an untested device Varoita, kun tietoja tuodaan testaamattomasta laitteesta &CPAP &CPAP This calculation requires Total Leaks data to be provided by the CPAP device. (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. Tämä laskelma edellyttää, että CPAP-laite toimittaa Total Leaks -tiedot. (Esim. PRS1, mutta ei ResMed, jolla on nämä jo)) Tässä käytetyt tahattoman vuodon laskelmat ovat lineaarisia, ne eivät mallinna maskin tuuletuskäyrää. Jos käytät muutamaa eri maskia, valitse sen sijaan keskiarvot. Sen pitäisi silti olla tarpeeksi lähellä. Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device missed. This option must be enabled before import, otherwise a purge is required. Ota käyttöön tai poista käytöstä kokeelliset tapahtuman ilmoittamisen parannukset. Se mahdollistaa rajatapahtumien havaitsemisen ja osan laitteelta jää huomaamatta. Tämä vaihtoehto on otettava käyttöön ennen tuontia, muuten vaaditaan tyhjennys. This experimental option attempts to use OSCAR's event flagging system to improve device detected event positioning. Tämä kokeellinen vaihtoehto yrittää käyttää OSCARin tapahtumailmoitusjärjestelmää parantaakseen laitteen havaitsemien tapahtumien paikannusta. Resync Device Detected Events (Experimental) Synkronoi laitteen havaitut tapahtumat uudelleen (kokeellinen) Allow duplicates near device events. Salli kopiot laitteen tapahtumien lähellä. Show flags for device detected events that haven't been identified yet. Näytä liput laitteen havaitsemille tapahtumille, joita ei ole vielä tunnistettu. 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 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. Duration of airflow restriction Ilmavirran rajoituksen kesto s s Event Duration Tapahtuman kesto 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ä 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> <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Tätä lyhyempiä istuntoja ei näytetä</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></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 <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Muokattu merkintä on kokeellinen menetelmä laitteen ohittamien tapahtumien havaitsemiseksi. Ne ovat </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">ei</span><span style=" font-family:'Sans' ; font-size:10pt;"> sisältyy AHI:hen.</span></p></body></html> 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.) <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 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 <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices do not support changing these settings.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Huomaa: </span>yhteenvetosuunnittelun rajoitusten vuoksi ResMed-laitteet eivät tue näiden asetusten muuttamista.</p ></body></html> <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:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> p, li { välilyönti: pre -kääre; } </style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style= " font-family:'Sans'; font-size:10pt; font-weight:600;">Oksimetrian ja CPAP-tietojen synkronointi</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;" ><span style=" font-family:'Sans'; font-size:10pt;">SpO2Reviewsta (.spoR-tiedostoista) tai sarjatuontimenetelmällä tuodut CMS50-tiedot </span><span style=" font- family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">ei</span><span style=" font-family:'Sans'; font-size:10pt; "> on oikea synkronointia varten tarvittava aikaleima.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;" ><span style=" font-family:'Sans'; font-size:10pt;">Live-näkymätila (käyttämällä sarjakaapelia) on yksi tapa saavuttaa tarkka synkronointi CMS50-oksimetreissä, mutta se ei laske CPAP-kelloa ajautuminen.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;" ><span style=" font-family:'Sans'; font-size:10pt;">Jos käynnistät oksimetrien tallennustilan kohdasta </span><span style=" font-family:'Sans'; font-size :10pt; font-style:italic;">täsmälleen </span><span style=" font-family:'Sans'; font-size:10pt;">samalla kun käynnistät CPAP-laitteen, voit nyt myös saavuttaa synkronointi. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;" ><span style=" font-family:'Sans'; font-size:10pt;">Sarjamuotoinen tuontiprosessi alkaa viime yön ensimmäisestä CPAP-istunnosta. (Muista tuoda ensin CPAP-tietosi!)</span></p></body></html> 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 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. 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 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 Clinical Kliininen Clinical Settings Kliiniset asetukset Select Oscar Operating Mode Aseta Oscarin toimintamoodi Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. Kliininen tila ei salli käytöstä poistettuja istuntoja.\nPoistettuja istuntoja ei käytetä kaavioiden tai tilastojen luomiseen. Clinical Mode Kliininen tila permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. Salliva tila sallii käytöstä poistetut istunnot.\nPoistettua istuntoa käytetään kuvaamiseen ja tilastoihin. Permissive Mode Salliva tila Hours Tuntia Oximetry Settings Oksimetrin asetukset <!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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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 oksimetrin ja CPAP-laitteen tiedot</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50-tiedot tuotu SpO2Reviewistä (tiedostoista .spoR) tai sarjatuontitavassa </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">ei</span><span style=" font-family:'Sans'; font-size:10pt;"> ole oikeita aikaleimoja tietojen synkronointiin.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Reaaliaikainen näyttötila (käyttää sarjaportin kaapelia) on yksi tapa saada täsmällinen synkronointi CMS50.oksimetreihin, mutta ei ota huomioon CPAP-laitteen kellon poikkeamaa.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Jos käynnistät oksimetrin tallennuistilassa </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 kuin käynnistät CPAP-laitteen, voit saada tietojen synkronoinnin. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Sarjamuotoinen tietojen tuonti ottaa aloitusajan viime yön ensimmäisestä CPAP-käyttökerrasta. (Muista tuoda ensin CPAP-laitteen unitiedot!)</span></p></body></html> 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 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> 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. Whether to include device serial number on device settings changes report Sisällytetäänkö laitteen sarjanumero laiteasetusten muutosraporttiin 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 For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. Useita käyttökertoja varten näyttää ohuen harmaan viivan kullekin käyttökerralle Tapahtumalippu-kaavion yläosassa. Enables SessionBar in Event Flags Graph Ottaa käyttöön käyttökertapalkin tapahtumalippujen kaaviossa 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. Overview Yleiskatsaus No CPAP devices detected CPAP-laitteita ei havaittu Will you be using a ResMed brand device? Käytätkö ResMed-merkkistä laitetta? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, days will <b>split at noon</b> like in ResMed's commercial software.</p> <p><b>Huomaa:</b> OSCARin edistyneet istunnon jakamisominaisuudet eivät ole mahdollisia <b>ResMed</b>-laitteiden kanssa, koska niiden asetusten ja yhteenvetotietojen tallennustapa on rajoitettu. poistettu käytöstä tässä profiilissa.</p><p>ResMedin laitteissa päivät <b>jaetaan keskipäivällä</b> kuten ResMedin kaupallisessa ohjelmistossa.</p> 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 ResMed S9 devices routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). ResMed S9 -laitteet poistavat rutiininomaisesti tietyt tiedot SD-kortiltasi, jotka ovat vanhempia kuin 7 ja 30 päivää (resoluutiosta riippuen). 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 Clinical Mode: Kliininen tila: Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. Raportoi, mitä datakortilla on, kaikki mukaan lukien kaikki tiedot, joiden valinta on poistettu sallitussa tilassa. Basically replicates the reports and data stored on the devices data card. Toistaa periaatteessa laitteen tietokortille tallennetut raportit ja tiedot. This includes pap devices, oximeters, etc. Compliance reports fall under this mode. Tämä sisältää pap-laitteet, oksimetrit jne. Vaatimustenmukaisuusraportit kuuluvat tähän tilaan. Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. Vaatimustenmukaisuusraportit sisältävät aina kaikki tiedot valitulta noudattamisjaksolta, vaikka ne muutoin olisi poistettu. Permissive Mode: Salliva tila: Allows user to select which data sets/ sessions to be used for calculations and display. Antaa käyttäjän valita, mitä tietojoukkoja/käyttökertoja otetaan laskelmiin ja näyttöön. Additional charts and calculations may be available that are not available from the vendor data. Saatavilla voi olla muita kaavioita ja laskelmia, jotka eivät ole saatavissa toimittajan tiedoista. Changing the Oscar Operating Mode: Oscar-käyttötilan vaihtaminen: Requires a reload of the user's profile. Data will be saved and restored. Edellyttää käyttäjän profiilin uudelleenlatausta. Tiedot tallennetaan ja palautetaan. Minor Flag Pieni lippu Span Kattaa Always Minor Aina pieni 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 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 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 Length: %1 Pituus: %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 milliSeconds millisekuntia 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 EEPAP EEPAP Min EEPAP Minimi EEPAP Max EEPAP Maksimi EEPAP 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. Device Laite 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: Compiler: Kääntäjä: Software Engine Ohjelmistotuote ANGLE / OpenGLES ANGLE / OpenGLES Desktop OpenGL Työpöydän OpenGL m m cm cm in tuumaa kg kg l/min l/min 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 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 device'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 laitteen SD-kortista ja vastaavat lääkärin .pdf-raportit varmistaakseen, että OSCAR käsittelee tietoja oikein. Non Data Capable Device Ei dataa tukeva laite Your %1 CPAP Device (Model %2) is unfortunately not a data capable model. %1 CPAP-laitteesi (malli %2) ei valitettavasti ole dataa tukeva malli. I'm sorry to report that OSCAR can only track hours of use and very basic settings for this device. Ikävää, että OSCAR voi seurata vain tämän laitteen käyttötunteja ja perusasetuksia. Device Untested Laitetta ei ole testattu Your %1 CPAP Device (Model %2) has not been tested yet. %1 CPAP-laitettasi (malli %2) ei ole vielä testattu. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Se näyttää riittävän samanlaiselta kuin muut laitteet, jotta se voisi toimia. Kehittäjät haluaisivat .zip-kopion tämän laitteen SD-kortista ja vastaavat lääkärin .pdf-raportit varmistaakseen, että se toimii OSCAR:n kanssa. Device Unsupported Laitetta ei tueta Sorry, your %1 CPAP Device (%2) is not supported yet. Valitettavasti %1 CPAP-laitettasi (%2) ei tueta vielä. The developers need a .zip copy of this device's SD card and matching clinician .pdf reports to make it work with OSCAR. Kehittäjät tarvitsevat .zip-kopion tämän laitteen SD-kortista ja vastaavat lääkärin .pdf-raportit, jotta se toimisi OSCAR:n kanssa. Getting Ready... Valmistautuu... Scanning Files... Skannaa tiedostoja... Importing Sessions... Tuo käyttötietoja... UNKNOWN TUNTEMATON APAP (std) APAP (standardi) APAP (dyn) APAP (dynaaminen) Auto S Automaattinen S Auto S/T Automaattinen S/T AcSV AcSV SoftPAP Mode SoftPAP-moodi Pressure relief during exhalation Paineen helpotus uloshengityksen aikana Slight Lievä Softstart pressure Pehmeäkäynnistyspaine Pressure during soft start period Paine pehmeän käynnistyksen aikana PSoft Pehmeä Softstart minimum pressure Pehmeän käynnistyksen minimipaine Minimum pressure during soft start period Minimipaine pehmeän käynnistyksen aikana PSoftMin Pehmeäminimi Auto start Automaattinen käynnistys Automatically turn on the device by breathing Käynnistä laite automaattisesti hengittämällä Softstart time Pehmeä käynnistyksen aika Lenght of soft start period Pehmeän käynnistysjakson pituus Soft start maximum time Pehmeän käynnistyksen maksimiaika Maximum lenght of soft start period Pehmeän käynnistysjakson enimmäispituus Soft start max. time Pehmeä käynnistys maksimiaika Soft start pressure Pehmeän käynnistyksen paine Higher End Expiratory Pressure Korkeampi uloshengityspaine Humidifier level Ilmankostuttimen taso Tube type Letkun tyyppi Obstruction level Estetaso Obstruction level in percentage Esteaste prosentteina rRMVFluctuation rRMV vaihtelu Relative respiratory minute volume fluctuation Suhteellinen hengitysminuuttitilavuuden vaihtelu Relative respiratory minute volume Suhteellinen hengitysminuuttitilavuus Measured pressure Mitattu paine Full flow Täysi virtaus Artefact Artifakti Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) Mittaustietojen epäsäännöllisyys, joka ei edusta hengitystapahtumaa (esim. nieleminen, yskiminen tai puhuminen) Epoch (2 mins) with Flow Limitation Epokki (2 min) virtausrajoituksella Deep Sleep Syvä uni Deep sleep, stable respiration Syvä uni, vakaa hengitys Timed breath Ajastettu hengitys BiSoft Mode BiSoft-tila BiSoft 1 BiSoft 1 BiSoft 2 BiSoft 2 TriLevel Kolmitaso AutoStart Automaattinen käynnistys Softstart_Time Automaattisen käynnistyksen aika Softstart_TimeMax Automaattisen käynnistyksen maksimiaika Softstart_Pressure Pehmeän käynnistyksen paine PMaxOA PMaxOA EEPAPMin EEPAPMin Lower End Expiratory Pressure Uloshengityksen alempi paine EEPAPMax EEPAPMax HumidifierLevel Kostuttimen taso TubeType Letkutyyppi ObstructLevel Obstruktiotaso Obstruction Level Obstruktion taso rMVFluctuation rMVFluctuation rRMV rRMV PressureMeasured Arvioitu paine FlowFull Täysi virtaus SPRStatus SPR-status Artifact Esine ART ART CriticalLeak Kriittinen vuoto Mask leakage is above a critical treshold Maskin vuoto ylittää kriittisen tason CL CL eMO eMO Epoch (2 mins) with Mild Obstruction Epokki (2 min) lievällä tukkeella eSO eSO Epoch (2 mins) with Severe Obstruction Epokki (2 min) vakavalla esteellä eS eS Epoch (2 mins) with Snoring Epokki (2 min) kuorsauksella eFL eFL DeepSleep Syvä uni DS DS TimedBreath Hengityksen aika Finishing up... Lopettelee... Untested Data Testaamattomat tiedot 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 A few breaths automatically starts device Muutama hengitys käynnistää laitteen automaattisesti Device automatically switches off Laite sammuu automaattisesti Whether or not device allows Mask checking. Salliiko laite maskin tarkistuksen. 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 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ä A period during a session where the device could not detect flow. Ajanjakso käytön aikana, jolloin laite ei pystynyt havaitsemaan virtausta. 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 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 Auto Off Automaatti pois Mask Alert Maskihälytys Show AHI Näytä AHI Whether or not device shows AHI via built-in display. Näyttääkö laite AHI:n sisäänrakennetun näytön kautta. The number of days in the Auto-CPAP trial period, after which the device will revert to CPAP Auto-CPAP -kokeilujakson päivien lukumäärä, jonka jälkeen laite palaa CPAP-tilaan Breathing Not Detected Hengitystä ei löydy 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) 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> <i>Your old device data should be regenerated provided this backup feature has not been disabled in preferences during a previous data import.</i> <i>Vanhat laitteesi tiedot tulee luoda uudelleen, jos tätä varmuuskopiointiominaisuutta ei ole poistettu käytöstä aiemman tietojen tuonnin aikana.</i> 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. 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? Device Database Changes Laitetietokannan muutokset 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ää. The device data folder needs to be removed manually. Laitteen tietokansio on poistettava käsin. 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. OSCAR %1 needs to upgrade its database for %2 %3 %4 Oscar %1 pitää päivittää tietokanta kohteille %2 %3 %4 This means you will need to import this device data again afterwards from your own backups or data card. Tämä tarkoittaa, että sinun on tuotava tämän laitteen tiedot uudelleen jälkeenpäin omasta varmuuskopiostasi tai tietokortistasi. 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. 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 Cheyne Stokes Respiration (CSR) Cheyne Stokes -hengitys (CSR) Periodic Breathing (PB) Jaksottainen hengitys (PB) Clear Airway (CA) Aukioleva hengitystie (CA) An apnea caused by airway obstruction Ilmavirtauksen tukoksen aiheuttama apnea A partially obstructed airway Osittain tukkeutunut ilmavirta UA UA A vibratory snore 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 type of respiratory event that won't respond to a pressure increase. Hengitystapahtuma, johon ei paineen nousu vaikuta. 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. 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 A sudden (user definable) change in heart rate Äkillinen (määriteltävissä) muutos sydämen lyöntitiheyteen 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 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 An abnormal period of Cheyne Stokes Respiration Poikkeava jaksoittainen Cheyne Stokes hengitys CSR CSR An abnormal period of Periodic Breathing Poikkeava jaksoittainen hengitys 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. End Expiratory Pressure Uloshengityspaineen loppu 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 An apnea reported by your CPAP device. CPAP-laitteesi ilmoittama apnea. A restriction in breathing from normal, causing a flattening of the flow waveform. Rajoitettu hengitys normaalista, mikä aiheuttaa virtausaaltomuodon litistymisen. A vibratory snore as detected by a System One device System One -laitteen havaitsema värisevä kuorsaus 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 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 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 Obstructive Apnea (OA) Asentoriippuvainen katkos (OA) Hypopnea (H) Hypopnea (H) Unclassified Apnea (UA) Tunnistamaton katkos (UA) Apnea (A) Katkos (A) An apnea reportred by your CPAP device. CPAP-laitteesi ilmoittama katkos. Flow Limitation (FL) Virtauksen rajoite (FL) RERA (RE) RERA (RE) Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. Hengitysponnistuksiin liittyvä kiihotus: Hengityksen rajoitus, joka aiheuttaa joko heräämistä tai unihäiriöitä. Vibratory Snore (VS) Tärisevä kuorsaus (VS) Leak Flag (LF) Ohivuoto (LF) A large mask leak affecting device performance. Suuri maskivuoto, joka vaikuttaa laitteen suorituskykyyn. Large Leak (LL) Suuri vuoto (LL) Non Responding Event (NR) Ei-vastaava tapahtuma (NR) Expiratory Puff (EP) Uloshengityksen puhallus (EP) SensAwake (SA) hengitysyritykseen liittyvä havahtuminen (SA) User Flag #1 (UF1) Käyttäjätapahtuma #1 (UF1) User Flag #2 (UF2) Käyttäjätapahtuma #2 (UF2) User Flag #3 (UF3) Käyttäjätapahtuma #3 (UF3) Pulse Change (PC) Pulssin muutos (PC) SpO2 Drop (SD) SpO2 putoaminen (SD) I/E Value I/E-arvo Apnea Hypopnea Index (AHI) Katkosten hypopnea Index (AHI) Respiratory Disturbance Index (RDI) Hengityshäiriöindeksi (RDI) 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? OSCAR Reminder Oscar-muistutin Don't forget to place your datacard back in your CPAP device Älä unohda laittaa datakorttia takaisin CPAP-laitteeseen 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 oximetry settings to defaults? Haluatko varmasti palauttaa kaikki oksimetriasetuksesi 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) 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) Min EEPAP %1 Max EEPAP %2 PDIFF %3-%4 (%5) Minimi EEPAP %1 Maksimi EEPAP %2 PDIFF %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 Selection Length Valinnan pituus 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 Device auto starts by breathing Laite käynnistyy automaattisesti hengittämällä Response Vaste Device auto stops by breathing Laite pysähtyy automaattisesti hengittämällä Patient View Käyttäjän näkymä RiseEnable Nousu käyttöön RiseTime Nousun aika Cycle Kierto Trigger Liipaisin TiMax Maksimi Ti TiMin Minimi Ti Your ResMed CPAP device (Model %1) has not been tested yet. ResMed CPAP -laitettasi (malli %1) ei ole vielä testattu. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card to make sure it works with OSCAR. Se näyttää riittävän samanlaiselta kuin muut laitteet, jotta se voisi toimia. Kehittäjät haluaisivat .zip-kopion tämän laitteen SD-kortista varmistaakseen, että se toimii OSCARin kanssa. SmartStart Älykäs käynnistys 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 Smart Stop Älykäs pysäytys Simple Yksinkertainen Advanced Monipuolinen 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... Permissive Mode Salliva tila Total disabled sessions: %1, found in %2 days Pois käytöstä poistettuja käyttökertoja yhteensä: %1, löydetty %2 päivässä Total disabled sessions: %1 Käytöstä poistettuja käyttökertoja yhteensä: %1 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. Pisimmän käytöstä poistetun käyttökerran kesto: %1 minuuttia, Kaikkien käytöstä poistettujen käyttökertojen kokonaiskesto: %2 minuuttia. Updating Statistics cache Päivittää tilastojen välimuistia Usage Statistics Käyttötilastot 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ä Löwenstein Löwenstein Prisma Smart Prisma Smart Resvent/Hoffrichter Resvent/Hoffrichter iBreeze/Point3 iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings Hallinnoi Tallennuksen asetteluja Add Lisää Add Feature inhibited. The maximum number of Items has been exceeded. Ominaisuuden lisääminen estetty. Kohteiden enimmäismäärä on ylitetty. creates new copy of current settings. Luo uuden kopion nykyisistä asetuksista. Restore Palauta Restores saved settings from selection. Palauttaa valittujen talletetut asetukset. Rename Nimeä uudelleen Renames the selection. Must edit existing name then press enter. Muuttaa valittujen nimiä. Valitse muokattavien olemassaolevien nimiä ja sitten paina enter. Update Päivitä Updates the selection with current settings. Päivittää valitut nykyisillä asetuksilla. Delete Poista Deletes the selection. Poistaa valitut. Expanded Help menu. Laajennettu apua-valikko. Exits the Layout menu. Poistuu asettelu-valikosta. <h4>Help Menu - Manage Layout Settings</h4> <h4>Apua-valikko - Hallinnoi asetteluja</h4> Exits the help menu. Poistuu ohjevalikosta. Exits the dialog menu. Poistuu dialogivalikosta. This feature manages the saving and restoring of Layout Settings. Tämä ominaisuus hallitsee asetteluasetusten tallentamista ja palauttamista. Layout Settings control the layout of a graph or chart. Asetteluasetukset ohjaavat kaavion tai kaavion asettelua. Different Layouts Settings can be saved and later restored. Erilaiset asetteluasetukset voidaan tallentaa ja palauttaa myöhemmin. Button Painike Description Kuvaus Creates a copy of the current Layout Settings. Luo kopion nykyisistä asetteluasetuksista. The default description is the current date. Oletuskuvaus on nykyinen päivämäärä. The description may be changed. Kuvaus voi muuttua. The Add button will be greyed out when maximum number is reached. Lisää-painike näkyy harmaana, kun enimmäismäärä on saavutettu. Other Buttons Muut painikkeet Greyed out when there are no selections Harmaana, kun valintoja ei ole Loads the Layout Settings from the selection. Automatically exits. io Lataa asetteluasetukset valinnasta. Poistuu automaattisesti. io Modify the description of the selection. Same as a double click.io Muokkaa valinnan kuvausta. Sama kuin kaksoisnapsautus.io Saves the current Layout Settings to the selection. Tallentaa nykyiset asetteluasetukset valintaan. Prompts for confirmation. Pyydä vahvistusta. Deletes the selecton. Poistaa valinnan. Control Kontrolli Exit Poistu (Red circle with a white "X".) Returns to OSCAR menu. (Punainen ympyrä, jossa valkoinen "X".) Palaa OSCAR-valikkoon. Return Palaa Next to Exit icon. Only in Help Menu. Returns to Layout menu. Poistu-kuvakkeen vieressä. Vain Ohje-valikossa. Palaa Asettelu-valikkoon. Escape Key Escape-painike Exit the Help or Layout menu. Poistu Ohje- tai Asettelu-valikosta. Layout Settings Asetteluasetukset * Name * Nimi * Pinning * Kiinnittäminen * Plots Enabled * Plots käytössä * Height * Korkeus * Order * Järjestys * Event Flags * Tapahtumaliput * Dotted Lines * Pisteviivat * Height Options * Korkeusvalinnat General Information Yleistiedot Maximum description size = 80 characters. Kuvauksen enimmäiskoko = 80 merkkiä. Maximum Saved Layout Settings = 30. Tallennettujen asetteluasetusten enimmäismäärä = 30. Saved Layout Settings can be accessed by all profiles. Kaikki profiilit voivat käyttää tallennettuja asetteluasetuksia. Layout Settings only control the layout of a graph or chart. Asetteluasetukset hallitsevat vain kaavion tai kaavion asettelua. They do not contain any other data. Ne eivät sisällä muita tietoja. They do not control if a graph is displayed or not. Ne eivät hallitse, näytetäänkö kaavio vai ei. Layout Settings for daily and overview are managed independantly. Päivittäisiä ja yleiskatsauksen asetteluasetuksia hallitaan itsenäisesti. <p style="color:black;"> This feature manages the saving and restoring of Layout Settings. <br> Layout Settings control the layout of a graph or chart. <br> Different Layouts Settings can be saved and later restored. <br> </p> <table width="100%"> <tr><td><b>Button</b></td> <td><b>Description</b></td></tr> <tr><td valign="top">Add</td> <td>Creates a copy of the current Layout Settings. <br> The default description is the current date. <br> The description may be changed. <br> The Add button will be greyed out when maximum number is reached.</td></tr> <br> <tr><td><i><u>Other Buttons</u> </i></td> <td>Greyed out when there are no selections</td></tr> <tr><td>Restore</td> <td>Loads the Layout Settings from the selection. Automatically exits. </td></tr> <tr><td>Rename </td> <td>Modify the description of the selection. Same as a double click.</td></tr> <tr><td valign="top">Update</td><td> Saves the current Layout Settings to the selection.<br> Prompts for confirmation.</td></tr> <tr><td valign="top">Delete</td> <td>Deletes the selecton. <br> Prompts for confirmation.</td></tr> <tr><td><i><u>Control</u> </i></td> <td></td></tr> <tr><td>Exit </td> <td>(Red circle with a white "X".) Returns to OSCAR menu.</td></tr> <tr><td>Return</td> <td>Next to Exit icon. Only in Help Menu. Returns to Layout menu.</td></tr> <tr><td>Escape Key</td> <td>Exit the Help or Layout menu.</td></tr> </table> <p><b>Layout Settings</b></p> <table width="100%"> <tr> <td>* Name</td> <td>* Pinning</td> <td>* Plots Enabled </td> <td>* Height</td> </tr> <tr> <td>* Order</td> <td>* Event Flags</td> <td>* Dotted Lines</td> <td>* Height Options</td> </tr> </table> <p><b>General Information</b></p> <ul style=margin-left="20"; > <li> Maximum description size = 80 characters. </li> <li> Maximum Saved Layout Settings = 30. </li> <li> Saved Layout Settings can be accessed by all profiles. <li> Layout Settings only control the layout of a graph or chart. <br> They do not contain any other data. <br> They do not control if a graph is displayed or not. </li> <li> Layout Settings for daily and overview are managed independantly. </li> </ul> <p style="color:black;"> Tämä ominaisuus hallitsee asettelujen asetusten tallentamista ja palauttamista. <br> Asetteluasetukset ohjaavat kaavion tai kaavion asettelua. <br> Erilaiset asetteluasetukset voidaan tallentaa ja palauttaa myöhemmin. <br> </p> <table width="100%"> <tr><td><b>Painike</b></td> <td><b>Kuvaus</b></td></tr> <tr><td valign="top">Lisää</td> <td>Luo kopion nykyisistä asetteluasetuksista. <br> Oletuskuvaus on nykyinen päivämäärä. <br> Kuvaus voi muuttua. <br> Lisää-painike näkyy harmaana, kun enimmäismäärä on saavutettu.</td></tr> <br> <tr><td><i><u>Muut painikkeet</u> </i></td> <td>Harmaana kun valintoja ei ole</td></tr> <tr><td>Palauta</td> <td>Lataa asetteluasetukset valinnasta. Poistuu automaattisesti. </td></tr> <tr><td>Nimeä uudelleen </td> <td>Muokkaa valinnan kuvausta. Sama kuin kaksoisnapsautus.</td></tr> <tr><td valign="top">Päivitä</td><td> Tallentaa nykyiset asetteluasetukset valintaan.<br> Kysyy vahvistusta.</td></tr> <tr><td valign="top">Poista</td> <td>Poistaa valitut. <br> Kysyy vahvistusta.</td></tr> <tr><td><i><u>Hallinnoi</u> </i></td> <td></td></tr> <tr><td>Poistu </td> <td>(Punainen ympyrä ja valkoinen "X".) Palaa OSCAR-valikkoon.</td></tr> <tr><td>Palaa</td> <td>Poistu-kuvakkeen vieressä. Vain Ohje-valikossa. Palaa Asettelu-valikkoon.</td></tr> <tr><td>Esc-näppäin</td> <td>Poistu Ohje- tai Asettelu-valikosta.</td></tr> </table> <p><b>Asetteluasetukset</b></p> <table width="100%"> <tr> <td>* Nimi</td> <td>* Kiinnitys</td> <td>* Plots käytössä </td> <td>* Korkeus</td> </tr> <tr> <td>* Järjestys</td> <td>* Tapahtumat</td> <td>* Pisterivit</td> <td>* Korkeusvalinnat</td> </tr> </table> <p><b>Yleistiedot</b></p> <ul style=margin-left="20"; > <li> Suurin kuvauksen koko = 80 merkkiä. </li> <li> Suurin asetteluasetusten määrä = 30. </li> <li> Kaikki profiilit voivat käyttää tallennettuja asetteluasetuksia. <li> Asetteluasetukset hallitsevat vain kaavion tai kaavion asettelua. <br> Ne eivät sisällä muita tietoja. <br> Ne eivät hallitse, näytetäänkö kaavio vai ei. </li> <li> Päivittäisiä ja yleiskatsauksen asetteluasetuksia hallitaan itsenäisesti. </li> </ul> Maximum number of Items exceeded. Maksimimäärä ylitetty. No Item Selected Kohdetta ei ole valittu Ok to Update? Päivitetäänkö? Ok To Delete? Poistetaanko? SessionBar %1h %2m %1h %2m No Sessions Present Ei käyttöjaksoja SleepStyleLoader Import Error Tuontivirhe This device Record cannot be imported in this profile. Tätä laitetietuetta ei voi 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: Device Information Laitteen tiedot Changes to Device Settings Muutokset laiteasetuksiin Oscar has no data to report :( Oscarilla ei ole mitään tietoja raportoitavaksi :( Database has No %1 data available. Tietokannassa ei ole %1 tietoa saatavilla. Database has %1 day of %2 Data on %3 Tietokannassa on %1 päivää %2:stä %3 Database has %1 days of %2 Data, between %3 and %4 Tietokannassa on %1 päivää %2 tietoa, välillä %3 ja %4 Total Days: %1 Päiviä kaikkiaan: %1 Days Not Used: %1 Ei käytettyjä päiviä: %1 Days Used: %1 Päivää käytössä: %1 Days %1 %2 Hours: %3 Päiviä %1 %2 Tuntia: %3 Best Device Setting Parhaimmat laiteasetukset Worst Device Setting Huonoimmat laiteasetukset 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ä) 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 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 device.</span></p></body></html> <span style=" font-weight:600;">Varoitus: </span><span style=" color:#ff0000;">ResMed S9 SDC -kortit on lukittava </span><span style=" font-weight :600; color:#ff0000;">ennen asettamista tietokoneeseen.&nbsp;&nbsp;&nbsp;</span><span style=" color:#000000;"><br>Jotkin käyttöjärjestelmät kirjoittavat hakemistotiedostoja kortille kysymättä, mikä voi tehdä korttisi lukukelvottomaksi 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 device is detected Huomaa, että jotkin asetukset pakotetaan, kun ResMed-laite havaitaan 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ä today tänää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 <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 pressure was under %1 %2 for %3% of the time. Hoitopaineesi oli %3% ajasta alle %1 %2. 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. 1 day ago 1 päivä sitten Your device was on for %1. Laitteesi oli päällä %1. Your CPAP device used a constant %1 %2 of air CPAP-laitteesi käytti ilman vakiona %1 %2 Your device used a constant %1-%2 %3 of air. Laitteesi käytti ilman vakiona %1-%2 %3. Your device was under %1-%2 %3 for %4% of the time. Laitteesi oli alle %1-%2 %3 %4 % ajasta. Your EEPAP pressure was under %1 %2 for %3% of the time. EEPAP-paineesi oli alle %1 %2 ja se on %3 % ajasta. 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 Double click Y-axis: Return to AUTO-FIT Scaling Kaksoisnapsauta Y-akselia: Palauta AUTOMAATTINEN skaalaus Double click Y-axis: Return to DEFAULT Scaling Kaksoisnapsauta Y-akselia: Palauta OLETUSskaalaus Double click Y-axis: Return to OVERRIDE Scaling Kaksoinsnapsauta Y-akselia: Palauta OHITA skaalaus Double click Y-axis: For Dynamic Scaling Kaksoisnapsauta Y-akselia: DYNAAMINEN skaalaus Double click Y-axis: Select DEFAULT Scaling Kaksoisnapsauta Y-akselia: Valitse OLETUS skaalaus Double click Y-axis: Select AUTO-FIT Scaling Kaksoisnapsauta Y-akselia: Valitse AUTOMAATTINEN skaalaus %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.5.1/Translations/Svenska.sv.ts000066400000000000000000016546001450332542600205750ustar00rootroot00000000000000 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. 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 i i 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 Search Sök Layout Save and Restore Graph Layout Settings 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 Device Settings Maskininställningar 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. This CPAP device does NOT record detailed data Den här CPAP-maskinen sparar inga detaljerade data no data :( Ingen data :( Sorry, this device only provides compliance data. Tyvärr, den här maskinen sparar bara compliance-data. 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?? 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 Details Detaljer Clinical Mode Disabling Sessions requires the Permissive Mode 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.) 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 This bookmark is in a currently disabled area.. Detta bokmärke finns i ett för närvarande inaktiverat område.. "Nothing's here!" Ingenting här Pick a Colour Välj en färg Bookmark at %1 Bokmärke på %1 Hide All Events Dölj alla händelser Show All Events Visa alla händelser Hide All Graphs Show All Graphs DailySearchTab Clear Bookmark Jumps to Date's Bookmark Start Search DATE Jumps to Date Match Notes Notes containing Bookmarks Bokmärken Bookmarks containing AHI Daily Duration Session Duration Days Skipped Disabled Sessions Number of Sessions Click HERE to close Help Help Hjälp No Data Jumps to Date's Details Number Disabled Session Jumps to Date's Details Note Jumps to Date's Notes AHI Jumps to Date's Details EventsPerHour Session Duration Jumps to Date's Details Minutes Number of Sessions Jumps to Date's Details Sessions Daily Duration Jumps to Date's Details Hours Number of events Jumps to Date's Events Events Automatic start Continue Search End of Search No Matches Skip:%1 %1/%2%3 days %1 dagar {1/%2%3 ?} Found %1 Finds days that match specified criteria. Searches from last day to first day. First click on Match Button then select topic. Then click on the operation to modify it. or update the value Topics without operations will automatically start. Compare Operations: numberic or character. Numberic Operations: Character Operations: Summary Line Left:Summary - Number of Day searched Center:Number of Items Found Right:Minimum/Maximum for item searched Result Table Column One: Date of match. Click selects date. Column two: Information. Click selects date. Then Jumps the appropiate tab. Wildcard Pattern Matching: Wildcards use 3 characters: Asterisk Question Mark Backslash. Asterisk matches any number of characters. Question Mark matches a single character. Backslash matches next character. DateErrorDisplay ERROR The start date MUST be before the end date ERROR Startdatum MÅSTE vara före slutdatum The entered start date %1 is after the end date %2 Det inmatade startdatumet %1 är efter slutdatum %2 Hint: Change the end date first Tips: Ändra slutdatum först The entered end date %1 Det angivna slutdatumet %1 is before the start date %1 är före startdatumet %1 Hint: Change the start date first Tips: Ändra startdatumet först 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 device 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 Show Standard Report Standard Standard Show Monthly Report Monthly Månadsvis Show Range Report Date Range Datumintervall Select Report Date Report Date 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 Purge ALL Device Data Rensa ALLA Maskindata 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 Standard - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> Advanced - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> 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 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 &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 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 No supported data was found Importing Data Importerar data Are you sure you want to rebuild all CPAP data for the following device: Ä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 device: Av någon anledning, så har inte OSCAR någon säkerhetskopia för följande maskiner: Would you like to import from your own backups now? (you will have no data visible for this device 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å) OSCAR does not have any backups for this device! 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 device</i>, <font size=+2>you will lose this device'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 device database for the following device:</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 caused the purge process to fail; you will have to delete the following folder manually: 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 You must select and open the profile you wish to modify 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) Couldn't find any valid Device Data at %1 Hittade inga giltiga maskindata på %1 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. 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. 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 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 This software is being designed to assist you in reviewing the data produced by your CPAP Devices 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. 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 Layout Save and Restore Graph Layout Settings 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) Hide All Graphs Show All Graphs 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> 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. <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 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 I started this oximeter recording at (or near) the same time as a session on my CPAP device. Jag startade denna pulsoximeter-inspelning samtidigt (eller nära den tid) som jag startade min CPAP-maskin. <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 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 This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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) 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 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 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. Duration of airflow restriction Varaktighet luftflödesbegränsning s s Event Duration Varaktighet Händelse 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 <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices do not support changing these settings.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Notera: </span>.</p>På grund av begränsningar i ResMed's konstruktion så stöds inte ändringar av dessa inställningar </body></html> <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:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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><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:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Syncroniserar Oximeter och CPAP Data</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">CMS50 data importeerad från SpO2Review (från .spoR filer) eller serial import metoden gör </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;"> har korrekt tidsstämpel som behövs för att synca.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">"Live view mode" (via en seriell kabel) är en väg att uppnå korrekt synkning med CMS50 pulsoximeter, men räknar inte med cpap:ens tids-driftt.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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 pulsoxymeters inspelning på </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">exakt </span><span style=" font-family:'Sans'; font-size:10pt;">samma tid som du startar din CPAP, så kan du uppnå bra synkning. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Seriell-import metoden tar din starttid från sista nattens CPAP-data. (Kom ihåg att importera CPAP-data först.!)</span></p></body></html> 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. <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging är en experimentell metod för att detektera händelser missade av maskinen. Dom är </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">inte</span><span style=" font-family:'Sans'; font-size:10pt;"> inräknade i AHI.</span></p></body></html> 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> <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Sessioner med kortaretid än detta visas inte</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></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 <html><head/><body><p>Provide an alert when importing data from any device 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 device Varna när du importerar data från en otestad maskin This calculation requires Total Leaks data to be provided by the CPAP device. (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. Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device 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. This experimental option attempts to use OSCAR's event flagging system to improve device detected event positioning. Detta experimentella alternativ använder OSCAR's flaggningssystem för att förbättra programmets funktion. Resync Device Detected Events (Experimental) Återsynka detekterade händelser (Experimentell) Allow duplicates near device events. Tillåt dubbletter nära maskinhändelser. General CPAP and Related Settings Allmänna CPAP- och relaterade inställningar Show flags for device detected events that haven't been identified yet. Visa flagga för händelser som inte blivit identifierade än. 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.) <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 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. Whether to include device serial number on device settings changes report Huruvida man ska inkludera maskinens serienummer i rapporten om ändringar av maskininställningar 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 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. 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 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 Clinical Clinical Settings Select Oscar Operating Mode Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. Clinical Mode permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. Permissive Mode Hours Oximetry Settings Oximeter inställningar <!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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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 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 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> 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) For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. Enables SessionBar in Event Flags Graph 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 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. Overview Översikt No CPAP devices detected Ingen CPAP maskin hittades Will you be using a ResMed brand device? Kommer du att använda en ResMed maskin? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, 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 för denna profil.</p><p>På ResMed-maskiner kommer dagar <b>att delas vid middagstid dvs, kl.12.00</b>, precis som i ResMeds kommersiella programvara.</p> 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? ResMed S9 devices 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). 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è Clinical Mode: Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. Basically replicates the reports and data stored on the devices data card. This includes pap devices, oximeters, etc. Compliance reports fall under this mode. Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. Permissive Mode: Allows user to select which data sets/ sessions to be used for calculations and display. Additional charts and calculations may be available that are not available from the vendor data. Changing the Oscar Operating Mode: Requires a reload of the user's profile. Data will be saved and restored. 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 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 Length: %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 in i Compiler: kg kg milliSeconds l/min l/minut 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 EEPAP Min EEPAP Max EEPAP 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 Device Maskin 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 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 device'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 Device EJ data-kapabel maskin Your %1 CPAP Device (Model %2) is unfortunately not a data capable model. Din %1 CPAP maskin (Modell %2) är tyvärr inte kapabel till att spara data. I'm sorry to report that OSCAR can only track hours of use and very basic settings for this device. 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. Device Untested Denna maskin är otestad Your %1 CPAP Device (Model %2) has not been tested yet. Din %1 CPAP maskin (Modell %2) har inte blivit testad än. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device'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. Device Unsupported Maskinen stöds inte Sorry, your %1 CPAP Device (%2) is not supported yet. Ledsen, men din %1 CPAP maskin (%2) stöds inte än. The developers need a .zip copy of this device'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... Scanning Files... Skannar filer... Importing Sessions... Importerar inspelningar ... UNKNOWN APAP (std) APAP (dyn) Auto S Auto S/T AcSV SoftPAP Mode Pressure relief during exhalation Slight Softstart pressure Pressure during soft start period PSoft Softstart minimum pressure Minimum pressure during soft start period PSoftMin Auto start Automatically turn on the device by breathing Softstart time Lenght of soft start period Soft start maximum time Maximum lenght of soft start period Soft start max. time Soft start pressure Higher End Expiratory Pressure Humidifier level Tube type Obstruction level Obstruction level in percentage rRMVFluctuation Relative respiratory minute volume fluctuation Relative respiratory minute volume Measured pressure Full flow Artefact Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) Epoch (2 mins) with Flow Limitation Deep Sleep Deep sleep, stable respiration Timed breath BiSoft Mode BiSoft 1 BiSoft 2 TriLevel PMaxOA EEPAPMin Lower End Expiratory Pressure EEPAPMax rRMV ART CriticalLeak Mask leakage is above a critical treshold CL eMO Epoch (2 mins) with Mild Obstruction eSO Epoch (2 mins) with Severe Obstruction eS Epoch (2 mins) with Snoring eFL DS Finishing up... Avslutar... Untested Data Otestade data 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 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 A few breaths automatically starts device Några få andetag startar maskinen automatiskt Device automatically switches off Maskinen stängs av automatiskt Whether or not device allows Mask checking. Huruvida maskinen har mask-kontroll 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 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 A period during a session where the device could not detect flow. En period under en session då maskinen inte kunde detektera flöde. 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å Auto Off Auto Av Mask Alert Mask Varning Show AHI Visa AHI Whether or not device shows AHI via built-in display. Om maskinen visar AHI på displayen eller inte. The number of days in the Auto-CPAP trial period, after which the device will revert to CPAP Antalet dagar som maskinen är i Auto-Trial läge innan den återgår till CPAP Breathing Not Detected Andning ej detekterad 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. <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> <i>Your old device 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> 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. 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? Device Database Changes Maskindatabas Förändringar 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. The device data folder needs to be removed manually. Maskinens data-katalog måste raderas manuellt. 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. OSCAR %1 needs to upgrade its database for %2 %3 %4 OSCAR %1 måste uppgradera sin databas för %2 %3 %4 This means you will need to import this device 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 minneskort. 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. 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? OSCAR Reminder OSCAR Påminnelse Don't forget to place your datacard back in your CPAP device Kom ihåg att sätta tillbaka ditt minneskort i CPAP:en 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 oximetry settings to defaults? 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 End Expiratory Pressure 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 A partially obstructed airway En delvis blockerad luftväg UA UA CSR CSR An abnormal period of Periodic Breathing En onormal period av periodisk andning An apnea reported by your CPAP device. A vibratory snore En snarkning Vibratory Snore (VS2) Snarkning (VS2) A vibratory snore as detected by a System One device LF LF 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. 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. 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 % A sudden (user definable) change in heart rate En plötslig (användardefinierad) förändring av hjärtfrekvensen 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 Cheyne Stokes Respiration (CSR) Cheyne Stokes Andning (CSR) Periodic Breathing (PB) Periodisk andning (PB) Clear Airway (CA) Central Apne (CA) Obstructive Apnea (OA) Obstruktiv Apne (OA) Hypopnea (H) Hypopné (H) Unclassified Apnea (UA) Ospecifierat andningsuppehåll (UA) Apnea (A) Apne (A) An apnea reportred by your CPAP device. En apne rapporterad av din CPAP maskin. 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. Flow Limitation (FL) Flödesbegränsning (FL) RERA (RE) RERA (RE) Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. Vibratory Snore (VS) Snarkning (VS) Leak Flag (LF) Läckage-flagga (LF) A large mask leak affecting device performance. En stor mask läcka som påverkar maskinens prestanda. Large Leak (LL) Stor läcka (LL) Non Responding Event (NR) En händelse som inte reagerats på (NR) Expiratory Puff (EP) Utandningspuff (EP) SensAwake (SA) SensAwake (SA) 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. User Flag #1 (UF1) Användarflagga #1 (UF1) User Flag #2 (UF2) Användarflagga #2 (UF2) User Flag #3 (UF3) Användarflagga #3 (UF3) Pulse Change (PC) Pulsförändringar (PC) SpO2 Drop (SD) SpO2 nedgång (SD) Mask Pressure (High frequency) Masktryck (hög frekvens) I/E Value 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 Graph showing running AHI for the past hour Graf som visar rullande AHI den senaste timmen Apnea Hypopnea Index (AHI) Apnea Hypopnea Index (AHI) 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 Graph showing running RDI for the past hour Graf som visar rullande RDI den senaste timmen Respiratory Disturbance Index (RDI) Andningsstörningsindex (RDI) 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'. Selection Length 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) 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) Min EEPAP %1 Max EEPAP %2 PDIFF %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 Device auto starts by breathing Maskinen startar automatiskt då man andas Response Respons Device auto stops by breathing Maskinen autostannar av andningen Patient View Patientvy RiseEnable RiseTime Cycle Trigger TiMax TiMin Your ResMed CPAP device (Model %1) has not been tested yet. Din ResMed CPAP maskin (Modell %1) är inte testad än. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card to make sure it works with OSCAR. Det ser tillräckligt lika ut som från 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. SmartStart SmartStart 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 Smart Stop Smart Stop Simple Enkelt Advanced Avancerat 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... Permissive Mode Total disabled sessions: %1, found in %2 days Total disabled sessions: %1 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. Updating Statistics cache Uppdaterar cache för statistik Usage Statistics Användningsstatistik 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 Löwenstein Prisma Smart Resvent/Hoffrichter iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings Add Add Feature inhibited. The maximum number of Items has been exceeded. creates new copy of current settings. Restore Restores saved settings from selection. Rename Renames the selection. Must edit existing name then press enter. Update Updates the selection with current settings. Delete Deletes the selection. Expanded Help menu. Exits the Layout menu. <h4>Help Menu - Manage Layout Settings</h4> Exits the help menu. Exits the dialog menu. This feature manages the saving and restoring of Layout Settings. Layout Settings control the layout of a graph or chart. Different Layouts Settings can be saved and later restored. Button Description Creates a copy of the current Layout Settings. The default description is the current date. The description may be changed. The Add button will be greyed out when maximum number is reached. Other Buttons Greyed out when there are no selections Loads the Layout Settings from the selection. Automatically exits. io Modify the description of the selection. Same as a double click.io Saves the current Layout Settings to the selection. Prompts for confirmation. Deletes the selecton. Control Exit Avsluta (Red circle with a white "X".) Returns to OSCAR menu. Return Next to Exit icon. Only in Help Menu. Returns to Layout menu. Escape Key Exit the Help or Layout menu. Layout Settings * Name * Pinning * Plots Enabled * Height * Order * Event Flags * Dotted Lines * Height Options General Information Maximum description size = 80 characters. Maximum Saved Layout Settings = 30. Saved Layout Settings can be accessed by all profiles. Layout Settings only control the layout of a graph or chart. They do not contain any other data. They do not control if a graph is displayed or not. Layout Settings for daily and overview are managed independantly. Maximum number of Items exceeded. No Item Selected Ok to Update? Ok To Delete? SessionBar %1h %2m %1h %2m No Sessions Present Ingen period finns för närvarande SleepStyleLoader Import Error Import-fel This device 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: Device Information Maskininformation Changes to Device Settings Ändringar av maskininställningar Oscar has no data to report :( Oscar har ingen data att rapportera :( Database has No %1 data available. Database has %1 day of %2 Data on %3 Database has %1 days of %2 Data, between %3 and %4 Total Days: %1 Days Not Used: %1 Days Used: %1 Dagar använd: %1 Days %1 %2 Hours: %3 Best Device Setting Worst Device Setting 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) 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 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 device.</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 device 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 today %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 <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 pressure was under %1 %2 for %3% of the time. Ditt tryck var på eller under %1 %2 i %3% av tiden. 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. 1 day ago För 1 dag sedan Your device was on for %1. Din maskin var på i %1. Your CPAP device used a constant %1 %2 of air Din CPAP maskin använde %1 %2 i tryck Your device used a constant %1-%2 %3 of air. Din maskin använde %1-%2 %3 i tryck. Your device was under %1-%2 %3 for %4% of the time. Din maskin var under %1-%2 %3 i %4% av tiden. Your EEPAP pressure was under %1 %2 for %3% of the time. 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 Double click Y-axis: Return to AUTO-FIT Scaling Dubbelklicka på Y-axeln: Återgå till AUTO-FIT-skalning Double click Y-axis: Return to DEFAULT Scaling Dubbelklicka på Y-axeln: Återgå till STANDARD skalning Double click Y-axis: Return to OVERRIDE Scaling Dubbelklicka på Y-axeln: Återgå till OVERRIDE Scaling Double click Y-axis: For Dynamic Scaling Dubbelklicka på Y-axeln: För dynamisk skalning Double click Y-axis: Select DEFAULT Scaling Dubbelklicka på Y-axeln: Välj STANDARD skalning Double click Y-axis: Select AUTO-FIT Scaling Dubbelklicka på Y-axeln: Välj AUTO-FIT skalning %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.5.1/Translations/Thai.th.ts000066400000000000000000015453421450332542600200350ustar00rootroot00000000000000 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. 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 i 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 Search Layout Save and Restore Graph Layout Settings Show/hide available graphs. Breakdown events UF1 UF2 Time at Pressure Clinical Mode Disabling Sessions requires the Permissive Mode No %1 events are recorded this day %1 event %1 events Session Start Times Session End Times Session Information Oximetry Sessions Duration Device Settings (Mode and Pressure settings missing; yesterday's shown.) This CPAP device does NOT record detailed data no data :( Sorry, this device only provides compliance data. This bookmark is in a currently disabled area.. CPAP Sessions Details Sleep Stage Sessions Position Sensor Sessions Unknown Session 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 "Nothing's here!" No data is available for this day. 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 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 Hide All Events Show All Events Hide All Graphs Show All Graphs DailySearchTab Clear Bookmark Jumps to Date's Bookmark Start Search DATE Jumps to Date Match Notes Notes containing Bookmarks Bookmarks containing AHI Daily Duration Session Duration Days Skipped Disabled Sessions Number of Sessions Click HERE to close Help Help No Data Jumps to Date's Details Number Disabled Session Jumps to Date's Details Note Jumps to Date's Notes AHI Jumps to Date's Details EventsPerHour Session Duration Jumps to Date's Details Minutes Number of Sessions Jumps to Date's Details Sessions Daily Duration Jumps to Date's Details Hours Number of events Jumps to Date's Events Events Automatic start Continue Search End of Search No Matches Skip:%1 %1/%2%3 days Found %1 Finds days that match specified criteria. Searches from last day to first day. First click on Match Button then select topic. Then click on the operation to modify it. or update the value Topics without operations will automatically start. Compare Operations: numberic or character. Numberic Operations: Character Operations: Summary Line Left:Summary - Number of Day searched Center:Number of Items Found Right:Minimum/Maximum for item searched Result Table Column One: Date of match. Click selects date. Column two: Information. Click selects date. Then Jumps the appropiate tab. Wildcard Pattern Matching: Wildcards use 3 characters: Asterisk Question Mark Backslash. Asterisk matches any number of characters. Question Mark matches a single character. Backslash matches next character. DateErrorDisplay ERROR The start date MUST be before the end date The entered start date %1 is after the end date %2 Hint: Change the end date first The entered end date %1 is before the start date %1 Hint: Change the start date first 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 device 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 Show Standard Report Standard Show Monthly Report Monthly Show Range Report Date Range Select Report Date Report Date Statistics Daily Overview Oximetry Import Help &File &View &Reset Graphs &Help Troubleshooting &Data &Advanced 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 - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> Advanced - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> 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 Purge ALL Device 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 Couldn't find any valid Device Data at %1 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. No help is available. You must select and open the profile you wish to modify %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. No supported data was found Please open a profile first. Check for updates not implemented Are you sure you want to rebuild all CPAP data for the following device: For some reason, OSCAR does not have any backups for the following device: 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. Note as a precaution, the backup folder will be left in place. OSCAR does not have any backups for this device! Unless you have made <i>your <b>own</b> backups for ALL of your data for this device</i>, <font size=+2>you will lose this device's data <b>permanently</b>!</font> You are about to <font size=+2>obliterate</font> OSCAR's device database for the following device:</p> Are you <b>absolutely sure</b> you want to proceed? A file permission error caused the purge process to fail; you will have to delete the following folder manually: 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 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. Would you like to import from your own backups now? (you will have no data visible for this device until you do) 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 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. This software is being designed to assist you in reviewing the data produced by your CPAP Devices and related equipment. 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 Layout Save and Restore Graph Layout Settings 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) Hide All Graphs Show 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> 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. <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 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 I started this oximeter recording at (or near) the same time as a session on my CPAP device. <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 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. Flow Restriction Percentage of restriction in airflow from the median value. A value of 20% works well for detecting apneas. Duration of airflow restriction s Event Duration 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 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 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.) <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 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 <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> 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 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 <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></p></body></html> l/min <html><head/><body><p>Cumulative Indices</p></body></html> Oximetry Settings <html><head/><body><p>Flag SpO<span style=" vertical-align:sub;">2</span> Desaturations Below</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 Include Serial Number Graphics Engine (Requires Restart) This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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>Provide an alert when importing data from any device model that has not yet been tested by OSCAR developers.</p></body></html> Warn when importing data from an untested device This calculation requires Total Leaks data to be provided by the CPAP device. (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. Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device missed. This option must be enabled before import, otherwise a purge is required. This experimental option attempts to use OSCAR's event flagging system to improve device detected event positioning. Resync Device Detected Events (Experimental) Allow duplicates near device events. Show flags for device detected events that haven't been identified yet. <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices do not support changing these settings.</p></body></html> Clinical Clinical Settings Select Oscar Operating Mode Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. Clinical Mode permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. Permissive Mode Hours <!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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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 device serial number on device settings changes report Print reports in black and white, which can be more legible on non-color printers Print reports in black and white (monochrome) For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. Enables SessionBar in Event Flags Graph 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 No CPAP devices detected Will you be using a ResMed brand device? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, 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? ResMed S9 devices routinely delete certain data from your SD card older than 7 and 30 days (depending on resolution). 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 Clinical Mode: Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. Basically replicates the reports and data stored on the devices data card. This includes pap devices, oximeters, etc. Compliance reports fall under this mode. Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. Permissive Mode: Allows user to select which data sets/ sessions to be used for calculations and display. Additional charts and calculations may be available that are not available from the vendor data. Changing the Oscar Operating Mode: Requires a reload of the user's profile. Data will be saved and restored. Minor Flag Span Always Minor Never This may not be a good idea 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 cmH2O Med. Min: %1 Min: Max: Max: %1 %1 (%2 days): %1 (%2 day): % in %1 Hours Min %1 Length: %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 milliSeconds 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 EEPAP Min EEPAP Max EEPAP 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 Device No Data Available App key: Operating system: Built with Qt %1 on %2 Graphics Engine: Graphics Engine type: Compiler: Software Engine ANGLE / OpenGLES Desktop OpenGL m cm in kg l/min Only Settings and Compliance Data Available Summary Data Only Bookmarks Mode Model Brand Serial Series 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 device's SD card and matching clinician .pdf reports to make sure OSCAR is handling the data correctly. Non Data Capable Device Your %1 CPAP Device (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 device. Device Untested Your %1 CPAP Device (Model %2) has not been tested yet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card and matching clinician .pdf reports to make sure it works with OSCAR. Device Unsupported Sorry, your %1 CPAP Device (%2) is not supported yet. The developers need a .zip copy of this device's SD card and matching clinician .pdf reports to make it work with OSCAR. Getting Ready... Scanning Files... Importing Sessions... UNKNOWN APAP (std) APAP (dyn) Auto S Auto S/T AcSV SoftPAP Mode Pressure relief during exhalation Slight Softstart pressure Pressure during soft start period PSoft Softstart minimum pressure Minimum pressure during soft start period PSoftMin Auto start Automatically turn on the device by breathing Softstart time Lenght of soft start period Soft start maximum time Maximum lenght of soft start period Soft start max. time Soft start pressure Higher End Expiratory Pressure Humidifier level Tube type Obstruction level Obstruction level in percentage rRMVFluctuation Relative respiratory minute volume fluctuation Relative respiratory minute volume Measured pressure Full flow Artefact Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) Epoch (2 mins) with Flow Limitation Deep Sleep Deep sleep, stable respiration Timed breath BiSoft Mode BiSoft 1 BiSoft 2 TriLevel PMaxOA EEPAPMin Lower End Expiratory Pressure EEPAPMax rRMV ART CriticalLeak Mask leakage is above a critical treshold CL eMO Epoch (2 mins) with Mild Obstruction eSO Epoch (2 mins) with Severe Obstruction eS Epoch (2 mins) with Snoring eFL DS Finishing up... 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 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 A few breaths automatically starts device Device automatically switches off Whether or not device allows Mask checking. 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 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 A period during a session where the device could not detect flow. 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 Off Mask Alert Show AHI Whether or not device shows AHI via built-in display. The number of days in the Auto-CPAP trial period, after which the device will revert to CPAP Breathing Not Detected BND Timed Breath Machine Initiated Breath TB 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 device 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 device 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? Device Database Changes Sorry, the purge operation failed, which means this version of OSCAR can't start. The device data folder needs to be removed manually. 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. Once you upgrade, you <font size=+1>cannot</font> use this profile with the previous version anymore. 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 Cheyne Stokes Respiration (CSR) Periodic Breathing (PB) Clear Airway (CA) Obstructive Apnea (OA) Hypopnea (H) An apnea that couldn't be determined as Central or Obstructive. Unclassified Apnea (UA) Apnea (A) A restriction in breathing from normal, causing a flattening of the flow waveform. Flow Limitation (FL) RERA (RE) Vibratory Snore (VS) Vibratory Snore (VS2) Leak Flag (LF) A large mask leak affecting device performance. Large Leak (LL) Non Responding Event (NR) Expiratory Puff (EP) SensAwake (SA) User Flag #1 (UF1) User Flag #2 (UF2) User Flag #3 (UF3) Pulse Change (PC) SpO2 Drop (SD) A ResMed data item: Trigger Cycle Event Apnea Hypopnea Index (AHI) Respiratory Disturbance Index (RDI) Mask On Time Time started according to str.edf Summary Only An apnea where the airway is open An apnea caused by airway obstruction A partially obstructed airway UA A vibratory snore Pressure Pulse A pulse of pressure 'pinged' to detect a closed airway. A type of respiratory event that won't respond to a pressure increase. Intellipap event where you breathe out your mouth. SensAwake feature will reduce pressure when waking is detected. Heart rate in beats per minute Blood-oxygen saturation percentage Plethysomogram An optical Photo-plethysomogram showing heart rhythm A sudden (user definable) change in heart rate A sudden (user definable) drop in blood oxygen saturation SD Breathing flow rate waveform 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 Ratio between Inspiratory and Expiratory time ratio Pressure Min Pressure Max Pressure Set Pressure Setting IPAP Set IPAP Setting EPAP Set EPAP Setting CSR An abnormal period of Periodic Breathing 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 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 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. End Expiratory Pressure An apnea reported by your CPAP device. Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. A vibratory snore as detected by a System One device I/E Value 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? OSCAR Reminder Don't forget to place your datacard back in your CPAP device 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 oximetry 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 (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 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) Min EEPAP %1 Max EEPAP %2 PDIFF %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 Selection Length 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 Device auto starts by breathing Response Device auto stops by breathing Patient View RiseEnable RiseTime Cycle Trigger TiMax TiMin Your ResMed CPAP device (Model %1) has not been tested yet. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device's SD card to make sure it works with OSCAR. SmartStart 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 Smart Stop Simple Advanced 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... Permissive Mode Total disabled sessions: %1, found in %2 days Total disabled sessions: %1 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. Updating Statistics cache Usage Statistics 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 Löwenstein Prisma Smart Resvent/Hoffrichter iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings Add Add Feature inhibited. The maximum number of Items has been exceeded. creates new copy of current settings. Restore Restores saved settings from selection. Rename Renames the selection. Must edit existing name then press enter. Update Updates the selection with current settings. Delete Deletes the selection. Expanded Help menu. Exits the Layout menu. <h4>Help Menu - Manage Layout Settings</h4> Exits the help menu. Exits the dialog menu. This feature manages the saving and restoring of Layout Settings. Layout Settings control the layout of a graph or chart. Different Layouts Settings can be saved and later restored. Button Description Creates a copy of the current Layout Settings. The default description is the current date. The description may be changed. The Add button will be greyed out when maximum number is reached. Other Buttons Greyed out when there are no selections Loads the Layout Settings from the selection. Automatically exits. io Modify the description of the selection. Same as a double click.io Saves the current Layout Settings to the selection. Prompts for confirmation. Deletes the selecton. Control Exit (Red circle with a white "X".) Returns to OSCAR menu. Return Next to Exit icon. Only in Help Menu. Returns to Layout menu. Escape Key Exit the Help or Layout menu. Layout Settings * Name * Pinning * Plots Enabled * Height * Order * Event Flags * Dotted Lines * Height Options General Information Maximum description size = 80 characters. Maximum Saved Layout Settings = 30. Saved Layout Settings can be accessed by all profiles. Layout Settings only control the layout of a graph or chart. They do not contain any other data. They do not control if a graph is displayed or not. Layout Settings for daily and overview are managed independantly. Maximum number of Items exceeded. No Item Selected Ok to Update? Ok To Delete? SessionBar %1h %2m No Sessions Present SleepStyleLoader Import Error This device 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 Device Information Changes to Device Settings Database has No %1 data available. Database has %1 day of %2 Data on %3 Database has %1 days of %2 Data, between %3 and %4 Total Days: %1 Days Not Used: %1 Days Used: %1 Days %1 %2 Hours: %3 Best Device Setting Worst Device Setting 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. Date: %1 - %2 AHI: %1 Total Hours: %1 Most Recent Compliance (%1 hrs/day) OSCAR is free open-source CPAP report software No data found?!? Oscar has no data to report :( Last Week Last 30 Days Last 6 Months Last Year Last Session Details Days Pressure Relief Pressure Settings 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 device.</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 device is detected First import can take a few minutes. The last time you used your %1... last night today %2 days ago was %1 (on %2) %1 hours, %2 minutes and %3 seconds <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 pressure was under %1 %2 for %3% of the time. 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. 1 day ago Your device was on for %1. Your CPAP device used a constant %1 %2 of air Your device used a constant %1-%2 %3 of air. Your device was under %1-%2 %3 for %4% of the time. Your EEPAP pressure was under %1 %2 for %3% of the time. Your average leaks were %1 %2, which is %3 your %4 day average of %5. No CPAP data has been imported yet. gGraph Double click Y-axis: Return to AUTO-FIT Scaling Double click Y-axis: Return to DEFAULT Scaling Double click Y-axis: Return to OVERRIDE Scaling Double click Y-axis: For Dynamic Scaling Double click Y-axis: Select DEFAULT Scaling Double click Y-axis: Select AUTO-FIT Scaling %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.5.1/Translations/Turkish.tr.ts000066400000000000000000016743761450332542600206250ustar00rootroot00000000000000 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ı. 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 i i 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 Search Ara Layout Save and Restore Graph Layout Settings 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 Clinical Mode Disabling Sessions requires the Permissive Mode 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.) no data :( veri yok :( Sorry, this device only provides compliance data. Üzgünüz, bu cihaz sadece uyum verisini sunmaktadır. 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 Model %1 - %2 Model %1 - %2 PAP Mode: %1 PAP Modu: %1 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 "Nothing's here!" "Burada hiçbir şey yok!" No data is available for this day. Bu gün için veri mevcut değil. 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 Device Settings Cihaz Ayarları <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ü This CPAP device does NOT record detailed data Bu CPAP cihazı detaylı veri kaydı YAPMIYOR 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?? 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 Hide All Events Tüm Olayları Sakla Show All Events Tüm Olayları Göster Hide All Graphs Show All Graphs DailySearchTab Clear Bookmark Jumps to Date's Bookmark Start Search DATE Jumps to Date Match Notes Notlar Notes containing Bookmarks Yer İşaretleri Bookmarks containing AHI Daily Duration Session Duration Days Skipped Disabled Sessions Number of Sessions Click HERE to close Help Help Yardım No Data Jumps to Date's Details Number Disabled Session Jumps to Date's Details Note Jumps to Date's Notes AHI Jumps to Date's Details EventsPerHour Session Duration Jumps to Date's Details Minutes Number of Sessions Jumps to Date's Details Sessions Daily Duration Jumps to Date's Details Hours Number of events Jumps to Date's Events Events Automatic start Continue Search End of Search No Matches Skip:%1 %1/%2%3 days %1 gün {1/%2%3 ?} Found %1 Finds days that match specified criteria. Searches from last day to first day. First click on Match Button then select topic. Then click on the operation to modify it. or update the value Topics without operations will automatically start. Compare Operations: numberic or character. Numberic Operations: Character Operations: Summary Line Left:Summary - Number of Day searched Center:Number of Items Found Right:Minimum/Maximum for item searched Result Table Column One: Date of match. Click selects date. Column two: Information. Click selects date. Then Jumps the appropiate tab. Wildcard Pattern Matching: Wildcards use 3 characters: Asterisk Question Mark Backslash. Asterisk matches any number of characters. Question Mark matches a single character. Backslash matches next character. DateErrorDisplay ERROR The start date MUST be before the end date HATA Başlangıç tarihi bitiş tarihinden önce olmak ZORUNDADIR The entered start date %1 is after the end date %2 Başlangıç tariihi olarak girilen %1 bitiş tarihi olan %2 'den öncedir Hint: Change the end date first İp ucu: Önce bitiş tarihini değiştirin The entered end date %1 Girilmiş olan bitiş tarihi %1 is before the start date %1 başlangıç tarihi %1 den öncedir Hint: Change the start date first İp ucu: Önce başlangıç tarihini değiştirin 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 device 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 Show Standard Report Standard Standart Show Monthly Report Monthly Aylık Show Range Report Date Range Veri Aralığı Select Report Date Report Date 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 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 Standard - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> Advanced - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> 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 Purge ALL Device Data TÜM Cihaz Verisini Sil 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 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 Couldn't find any valid Device Data at %1 %1'de herhangi bir geçerli Cihaz Verisi bulunamadı 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. No help is available. Yardım mevcut değil. You must select and open the profile you wish to modify %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. No supported data was found Please open a profile first. Lütfen öncelikle bir profil açın. Check for updates not implemented Güncelleme kontrolü henüz eklenmedi Are you sure you want to rebuild all CPAP data for the following device: 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 device: Bir sebepten ötürü OSCAR'ın şu cihazlar için alınmış herhangi bir yedeklemesi mevcut değildir: 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. Note as a precaution, the backup folder will be left in place. Önlem olarak, yedekleme klasörü yerinde bırakılacaktır. OSCAR does not have any backups for this device! 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 device</i>, <font size=+2>you will lose this device'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 device database for the following device:</p> OSCAR'ın bu cihaz için olan veri tabanını <font size=+2> yok etmek</font> üzeresiniz:</p> Are you <b>absolutely sure</b> you want to proceed? Devam etmek istediğinizden <b>kesinlikle emin</b> misiniz? A file permission error caused the purge process to fail; you will have to delete the following folder manually: 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 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. Would you like to import from your own backups now? (you will have no data visible for this device 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) 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 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. This software is being designed to assist you in reviewing the data produced by your CPAP Devices 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. 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 Layout Save and Restore Graph Layout Settings 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) Hide All Graphs Show All Graphs 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> 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. <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ı 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 I started this oximeter recording at (or near) the same time as a session on my CPAP device. Bu oksimetre kaydını CPAP cihazımdaki bir seans ile aynı (veya ona yakın) zamanda başlattım. <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 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) This maintains a backup of SD-card data for ResMed devices, ResMed S9 series devices 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>Provide an alert when importing data from any device 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 device Test edilmemiş bir cihazdan veri içe aktarımı yaparken uyar &CPAP &CPAP This calculation requires Total Leaks data to be provided by the CPAP device. (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. Enable/disable experimental event flagging enhancements. It allows detecting borderline events, and some the device 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. This experimental option attempts to use OSCAR's event flagging system to improve device 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. Resync Device Detected Events (Experimental) Cihaz Tarafından Tespit Edilen Olayları Tekrar Senkronize Et (Deneysel) Allow duplicates near device events. Cihaz olaylarına yakın kopyalara izin ver. Show flags for device detected events that haven't been identified yet. Cihaz tarafından tespit edilen henüz tanımlanamamış olayları işaretle. 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 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. Duration of airflow restriction Hava akımı kısıtlanma süresi s s Event Duration Olay Süresi 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 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 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><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Bu süreden daha kısa süreli olan seanslar gösterilmeyecektir</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></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>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 <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></p></body></html> <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Özel işaretleme cihaz tarafından atlanmış olan olayların tesbit edilebilmeleri için oluşturulmuş deneysel bir yöntemdir. Bunlar </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">hiçbir zaman</span><span style=" font-family:'Sans'; font-size:10pt;"> AHI içinde yer almazlar.</span></p></body></html> 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 <html><head/><body><p><span style=" font-weight:600;">Note: </span>Due to summary design limitations, ResMed devices 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> <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:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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><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:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt; font-weight:600;">Oksmietri ve CPAP Verileri Senkronize Ediliyor</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">SpO2 Review'dan içe aktarılan CMS50 verisi (.spoR dosyalarından) veya seri bağlantı ile içe aktarma yöntemi </span><span style=" font-family:'Sans'; font-size:10pt; font-weight:600; text-decoration: underline;">hiçbir zaman</span><span style=" font-family:'Sans'; font-size:10pt;"> senkronizasyon için gereken doğru zaman bilgisine sahip değildir.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Live view modu (seri kablo kullanarak) CMS50 oksimetrelerinde hatasız senkronizasyon sağlama yollarından biridir ancak CPAP saatindeki kaymayı bertaraf edemez.</span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Eğer Oksimetrenizin kayıt modunu </span><span style=" font-family:'Sans'; font-size:10pt; font-style:italic;">tam olarak </span><span style=" font-family:'Sans'; font-size:10pt;">CPAP cihazınızı başlattığınız anda başlatırsanız senkronizasyon sağlayabilirsiniz. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans'; font-size:10pt;">Seri içe aktarım yöntemi başlangıç zamanını önceki gecenin CPAP seansının başlangıcından alır. (Önce CPAP verinizi içe aktarmayı unutmayın!)</span></p></body></html> 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.) <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 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 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ı Clinical Clinical Settings Select Oscar Operating Mode Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. Clinical Mode permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. Permissive Mode Hours Oximetry Settings Oksimetri Ayarları <!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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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 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 device serial number on device 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> 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) For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. Enables SessionBar in Event Flags Graph 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ış No CPAP devices detected CPAP cihazı tespit edilmedi Will you be using a ResMed brand device? ResMed marka bir cihaz kullanacak mısınız? <p><b>Please Note:</b> OSCAR's advanced session splitting capabilities are not possible with <b>ResMed</b> devices 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 devices, 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? ResMed S9 devices 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ı). 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? Flag İşaret Clinical Mode: Reports what is on the data card, all of it including any and all data deselected in the Permissive mode. Basically replicates the reports and data stored on the devices data card. This includes pap devices, oximeters, etc. Compliance reports fall under this mode. Compliance reports always include all data within the chosen Compliance period, even if otherwise deselected. Permissive Mode: Allows user to select which data sets/ sessions to be used for calculations and display. Additional charts and calculations may be available that are not available from the vendor data. Changing the Oscar Operating Mode: Requires a reload of the user's profile. Data will be saved and restored. Minor Flag Minör İşaret Span Süre Always Minor Her zaman Küçük Never Asla This may not be a good idea Bu iyi bir fikir olmayabilir 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 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 Length: %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 milliSeconds 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 EEPAP Min EEPAP Max EEPAP 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 Device Cihaz 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: Compiler: Software Engine Yazılım Motoru ANGLE / OpenGLES ANGLE / OpenGLES Desktop OpenGL Masaüstü OpenGL m m cm cm in inç kg kg l/min l/dk 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 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 device'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 Device Veri Özelliğine Sahip Olmayan Cihaz Your %1 CPAP Device (Model %2) is unfortunately not a data capable model. %1 CPAP cihazınız (Model %2) maalesef veri üretebilen bir model değildir. I'm sorry to report that OSCAR can only track hours of use and very basic settings for this device. OSCAR'ın bu cihaz için sadece kullanım süresini ve bazı çok basit ayarları takip edebileceğini üzülerek bildiririz. Device Untested Test Edilmemiş Cihaz Your %1 CPAP Device (Model %2) has not been tested yet. %1 CPAP cihazınız (Model %2) henüz test edilmdi. It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device'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. Device Unsupported Cihaz Desteklenmiyor Sorry, your %1 CPAP Device (%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 device'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... Scanning Files... Dosyalar Taranıyor... Importing Sessions... Seanslar İçe Aktarılıyor... UNKNOWN APAP (std) APAP (dyn) Auto S Auto S/T AcSV SoftPAP Mode Pressure relief during exhalation Slight Softstart pressure Pressure during soft start period PSoft Softstart minimum pressure Minimum pressure during soft start period PSoftMin Auto start Automatically turn on the device by breathing Softstart time Lenght of soft start period Soft start maximum time Maximum lenght of soft start period Soft start max. time Soft start pressure Higher End Expiratory Pressure Humidifier level Tube type Obstruction level Obstruction level in percentage rRMVFluctuation Relative respiratory minute volume fluctuation Relative respiratory minute volume Measured pressure Full flow Artefact Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking) Epoch (2 mins) with Flow Limitation Deep Sleep Deep sleep, stable respiration Timed breath BiSoft Mode BiSoft 1 BiSoft 2 TriLevel PMaxOA EEPAPMin Lower End Expiratory Pressure EEPAPMax rRMV ART CriticalLeak Mask leakage is above a critical treshold CL eMO Epoch (2 mins) with Mild Obstruction eSO Epoch (2 mins) with Severe Obstruction eS Epoch (2 mins) with Snoring eFL DS Finishing up... Bitiriliyor... 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 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 A few breaths automatically starts device Birkaç kez nefes alıp verme ile cihaz otomatik olarak çalışmaya başlar Device automatically switches off Cihaz otomatik olarak kapanır Whether or not device allows Mask checking. Cihazın Maske kontrolüne izin verip vermediğ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 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ığı A period during a session where the device could not detect flow. Seans esnasında cihazın akımı tespit edemediği bir dönem. 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 Auto Off Otomatik Kapanma Mask Alert Maske Uyarısı Show AHI AHI'yi göster Whether or not device shows AHI via built-in display. Cihazın AHI'yi dahili ekranı üzerinden gösterip göstermediği. The number of days in the Auto-CPAP trial period, after which the device 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ı Breathing Not Detected Solunum Tespit Edilemedi 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 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> <i>Your old device 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> 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 device 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? Device Database Changes Cihaz Veritabanı Değişiklikleri 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. The device data folder needs to be removed manually. Cihazın veri klasörünün manüel olarak silinmesi gereklidir. 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. 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>. 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 Cheyne Stokes Respiration (CSR) Cheyne Stokes Solunumu (CSR) Periodic Breathing (PB) Peiyodik Solunum (Periodic Breathing - PB) Clear Airway (CA) Açık Havayolu (Clear Airway - CA) Obstructive Apnea (OA) Tıkayıcı Apne (Obstructive Apnea - OA) Hypopnea (H) Hipopne (H) An apnea that couldn't be determined as Central or Obstructive. Santral veya Obstrüktif olarak tanımlanamayan bir apne. Unclassified Apnea (UA) Sınıflandırılamayan Apne (Unclassified Apnea - UA) Apnea (A) Apne (A) An apnea reportred by your CPAP device. CPAP cihazınız tarafından bildirilmiş olan 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. Flow Limitation (FL) Akım Kısıtlaması (Flow Limitation - FL) RERA (RE) RERA (RE) Vibratory Snore (VS) Titreşimli Horlama (Vibratory Snore-VS) Vibratory Snore (VS2) Titreşimli Horlama (Vibratory Snore-VS2) Leak Flag (LF) Kaçak İşareti (Leak Flag - LF) A large mask leak affecting device performance. Cihazın performansını etkileyecek seviyede bir maske kaçağı. Large Leak (LL) Büyük Kaçak (Large Leak - LL) Non Responding Event (NR) Cevap Vermeyen Olay (Non Responding Event - NR) Expiratory Puff (EP) Nefes Verici Üfleme (Expiratory Puff - EP) SensAwake (SA) SensAwake (SA) User Flag #1 (UF1) Kullanıcı İşareti #1 (UF1) User Flag #2 (UF2) Kullanıcı İşareti #2 (UF2) User Flag #3 (UF3) Kullanıcı İşareti #3 (UF3) Pulse Change (PC) Nabız Değişikliği (Pulse Change - PC) SpO2 Drop (SD) SpO2 Düşmesi (SD) A ResMed data item: Trigger Cycle Event Bir ResMed veri öğesi: Tetikleyici Döngü Olayı Apnea Hypopnea Index (AHI) Apne Hipopne Indeksi (AHI) Respiratory Disturbance Index (RDI) Solunum Bozukluğu İndeksi (Respiratory Disturbance Index - RDI) 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 A partially obstructed airway Kısmi olarak tıkanmış bir hava yolu UA UA A vibratory snore Titreşimli bir 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 type of respiratory event that won't respond to a pressure increase. Basınç artışına cevap vermeyen tipte bir solunumsal olay. 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. 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 A sudden (user definable) change in heart rate Kalp hızında ani (kullanıcı tarafından tarif edilebilen) değişiklik 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 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ı CSR CSR An abnormal period of Periodic Breathing Anormal bir Periyodik Solunum süreci 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 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 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. End Expiratory Pressure An apnea reported by your CPAP device. Respiratory Effort Related Arousal: A restriction in breathing that causes either awakening or sleep disturbance. A vibratory snore as detected by a System One device I/E Value 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? OSCAR Reminder OSCAR Hatırlatıcısı Don't forget to place your datacard back in your CPAP device Veri kartınızı CPAP cihazınıza geri koymayı unutmayın 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 oximetry settings to defaults? 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) 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) Min EEPAP %1 Max EEPAP %2 PDIFF %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 Selection Length 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) Device auto starts by breathing Cihaz nefes almayla birlikte otomatik olarak başlar Response Yanıt Device auto stops by breathing Cihaz nefes almaya göre otomatik olarak durur Patient View Hasta Görünümü RiseEnable RiseTime Cycle Trigger TiMax TiMin Your ResMed CPAP device (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 devices that it might work, but the developers would like a .zip copy of this device'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. SmartStart SmartStart 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) Smart Stop Smart Stop (Akıllı Sonlanma) Simple Basit Advanced Gelişmiş 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... Permissive Mode Total disabled sessions: %1, found in %2 days Total disabled sessions: %1 Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes. Updating Statistics cache İstatistik önbelleği güncelleniyor Usage Statistics Kullanım İstatistikleri 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 Löwenstein Prisma Smart Resvent/Hoffrichter iBreeze/Point3 SaveGraphLayoutSettings Manage Save Layout Settings Add Add Feature inhibited. The maximum number of Items has been exceeded. creates new copy of current settings. Restore Restores saved settings from selection. Rename Renames the selection. Must edit existing name then press enter. Update Updates the selection with current settings. Delete Deletes the selection. Expanded Help menu. Exits the Layout menu. <h4>Help Menu - Manage Layout Settings</h4> Exits the help menu. Exits the dialog menu. This feature manages the saving and restoring of Layout Settings. Layout Settings control the layout of a graph or chart. Different Layouts Settings can be saved and later restored. Button Description Creates a copy of the current Layout Settings. The default description is the current date. The description may be changed. The Add button will be greyed out when maximum number is reached. Other Buttons Greyed out when there are no selections Loads the Layout Settings from the selection. Automatically exits. io Modify the description of the selection. Same as a double click.io Saves the current Layout Settings to the selection. Prompts for confirmation. Deletes the selecton. Control Exit Çıkış (Red circle with a white "X".) Returns to OSCAR menu. Return Next to Exit icon. Only in Help Menu. Returns to Layout menu. Escape Key Exit the Help or Layout menu. Layout Settings * Name * Pinning * Plots Enabled * Height * Order * Event Flags * Dotted Lines * Height Options General Information Maximum description size = 80 characters. Maximum Saved Layout Settings = 30. Saved Layout Settings can be accessed by all profiles. Layout Settings only control the layout of a graph or chart. They do not contain any other data. They do not control if a graph is displayed or not. Layout Settings for daily and overview are managed independantly. Maximum number of Items exceeded. No Item Selected Ok to Update? Ok To Delete? SessionBar %1h %2m %1st %2dk No Sessions Present Seans Yok SleepStyleLoader Import Error İçe Aktarma Hatası This device 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 Device Information Cihaz Bilgisi Changes to Device Settings Cihaz Ayarlarındaki Değişiklikler Database has No %1 data available. Database has %1 day of %2 Data on %3 Database has %1 days of %2 Data, between %3 and %4 Total Days: %1 Days Not Used: %1 Days Used: %1 Kullanılan Gün Sayısı: %1 Days %1 %2 Hours: %3 Best Device Setting Worst Device Setting 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 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ı 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 device.</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 device 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 today %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 <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 pressure was under %1 %2 for %3% of the time. Basıncınız seansın %3%'ünde %1 %2'nin altındaydı. 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ı. 1 day ago 1 gün önce Your device was on for %1. Cihazınız %1 çalıştı. Your CPAP device used a constant %1 %2 of air CPAP cihazınız sabit olarak %1 %2 hava kullandı Your device used a constant %1-%2 %3 of air. CPAP cihazınız %1-%2 %3 sabit hava kullandı. Your device was under %1-%2 %3 for %4% of the time. Cihazınız seansın %4%'ünde %1-%2 %3'ün altındaydı. Your EEPAP pressure was under %1 %2 for %3% of the time. 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 Double click Y-axis: Return to AUTO-FIT Scaling Y-aksına çift tıklama: OTOMATİK SIĞDIRMA Ölçeklendirmesine Geri Dönüş Double click Y-axis: Return to DEFAULT Scaling Y-aksına çift tıklama: VARSAYILAN Ölçeklendirmeye Geri Dönüş Double click Y-axis: Return to OVERRIDE Scaling Y-aksına çift tıklama: GEÇERSİZ KILMA Ölçeklemesine Dönüş Double click Y-axis: For Dynamic Scaling Y-aksına çift tıklama: Dinamik Ölçeklendirme Double click Y-axis: Select DEFAULT Scaling Y-aksına çift tıklama: VARSAYILAN Ölçeklendirmeyi Seç Double click Y-axis: Select AUTO-FIT Scaling Y-aksına çift tıklama: OTOMATİK SIĞDIRMA Ölçeklendirmesini Seç %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.5.1/Translations/qt/000077500000000000000000000000001450332542600165745ustar00rootroot00000000000000OSCAR-code-v1.5.1/Translations/qt/oscar_qt_af.ts000066400000000000000000000044201450332542600214250ustar00rootroot00000000000000 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.5.1/Translations/qt/oscar_qt_ar.ts000066400000000000000000000045251450332542600214470ustar00rootroot00000000000000 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.5.1/Translations/qt/oscar_qt_bg.ts000066400000000000000000000047001450332542600214300ustar00rootroot00000000000000 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.5.1/Translations/qt/oscar_qt_da.ts000066400000000000000000000044221450332542600214250ustar00rootroot00000000000000 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.5.1/Translations/qt/oscar_qt_de.ts000066400000000000000000000044301450332542600214300ustar00rootroot00000000000000 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.5.1/Translations/qt/oscar_qt_el.ts000066400000000000000000000046731450332542600214510ustar00rootroot00000000000000 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.5.1/Translations/qt/oscar_qt_es.ts000066400000000000000000000044411450332542600214510ustar00rootroot00000000000000 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.5.1/Translations/qt/oscar_qt_fi.ts000066400000000000000000000044151450332542600214410ustar00rootroot00000000000000 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.5.1/Translations/qt/oscar_qt_fil.ts000066400000000000000000000044161450332542600216160ustar00rootroot00000000000000 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.5.1/Translations/qt/oscar_qt_fr.ts000066400000000000000000000044601450332542600214520ustar00rootroot00000000000000 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.5.1/Translations/qt/oscar_qt_he.ts000066400000000000000000000045061450332542600214400ustar00rootroot00000000000000 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.5.1/Translations/qt/oscar_qt_it.ts000066400000000000000000000044111450332542600214530ustar00rootroot00000000000000 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.5.1/Translations/qt/oscar_qt_ja.ts000066400000000000000000000046511450332542600214370ustar00rootroot00000000000000 QShortcut No いいえ Yes はい QPlatformTheme OK I believe this is a better fit. 了解 &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.5.1/Translations/qt/oscar_qt_ko.ts000066400000000000000000000044131450332542600214520ustar00rootroot00000000000000 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.5.1/Translations/qt/oscar_qt_nl.ts000066400000000000000000000044151450332542600214540ustar00rootroot00000000000000 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.5.1/Translations/qt/oscar_qt_no.ts000066400000000000000000000044221450332542600214550ustar00rootroot00000000000000 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.5.1/Translations/qt/oscar_qt_pl.ts000066400000000000000000000044311450332542600214540ustar00rootroot00000000000000 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.5.1/Translations/qt/oscar_qt_pt.ts000066400000000000000000000044161450332542600214670ustar00rootroot00000000000000 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.5.1/Translations/qt/oscar_qt_ro.ts000066400000000000000000000044311450332542600214610ustar00rootroot00000000000000 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.5.1/Translations/qt/oscar_qt_ru.ts000066400000000000000000000047251450332542600214750ustar00rootroot00000000000000 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.5.1/Translations/qt/oscar_qt_sv.ts000066400000000000000000000044411450332542600214720ustar00rootroot00000000000000 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.5.1/Translations/qt/oscar_qt_th.ts000066400000000000000000000050211450332542600214500ustar00rootroot00000000000000 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.5.1/Translations/qt/oscar_qt_tr.ts000066400000000000000000000044411450332542600214670ustar00rootroot00000000000000 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.5.1/Translations/qt/oscar_qt_zh.ts000066400000000000000000000043761450332542600214720ustar00rootroot00000000000000 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.5.1/anotDump.pro000066400000000000000000000012071450332542600160000ustar00rootroot00000000000000#------------------------------------------------- # # 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.5.1/anotDump/000077500000000000000000000000001450332542600152565ustar00rootroot00000000000000OSCAR-code-v1.5.1/anotDump/main.cpp000066400000000000000000000077341450332542600167210ustar00rootroot00000000000000/* 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.5.1/dumpSTR.pro000066400000000000000000000012021450332542600155420ustar00rootroot00000000000000#------------------------------------------------- # # 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.5.1/dumpSTR/000077500000000000000000000000001450332542600150255ustar00rootroot00000000000000OSCAR-code-v1.5.1/dumpSTR/SleepLib/000077500000000000000000000000001450332542600165245ustar00rootroot00000000000000OSCAR-code-v1.5.1/dumpSTR/SleepLib/common.h000066400000000000000000000006361450332542600201720ustar00rootroot00000000000000/* 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.5.1/dumpSTR/edfparser.cpp000077700000000000000000000000001450332542600302062../oscar/SleepLib/loader_plugins/edfparser.cppustar00rootroot00000000000000OSCAR-code-v1.5.1/dumpSTR/edfparser.h000077700000000000000000000000001450332542600273202../oscar/SleepLib/loader_plugins/edfparser.hustar00rootroot00000000000000OSCAR-code-v1.5.1/dumpSTR/main.cpp000066400000000000000000000141721450332542600164620ustar00rootroot00000000000000/* 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.5.1/history/000077500000000000000000000000001450332542600151705ustar00rootroot00000000000000OSCAR-code-v1.5.1/history/README000066400000000000000000000007021450332542600160470ustar00rootroot00000000000000This 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.5.1/history/cms50f/000077500000000000000000000000001450332542600162655ustar00rootroot00000000000000OSCAR-code-v1.5.1/history/cms50f/cms50f_duration_check.py000077500000000000000000000027431450332542600230070ustar00rootroot00000000000000#!/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.5.1/history/cms50f/dump_cms50f37.py000077500000000000000000000064271450332542600211470ustar00rootroot00000000000000#!/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.5.1/history/configure000077500000000000000000000010171450332542600170760ustar00rootroot00000000000000#!/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.5.1/history/cpap.py000066400000000000000000001506241450332542600164750ustar00rootroot00000000000000#!/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.5.1/history/migration.sh000077500000000000000000000013121450332542600175150ustar00rootroot00000000000000#! /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.5.1/makedoxy.bat000066400000000000000000000001231450332542600157740ustar00rootroot00000000000000setlocal set path="c:\Program Files (x86)\Graphviz2.38\bin";%path% doxygen endlocalOSCAR-code-v1.5.1/oscar/000077500000000000000000000000001450332542600145765ustar00rootroot00000000000000OSCAR-code-v1.5.1/oscar/Graphs/000077500000000000000000000000001450332542600160225ustar00rootroot00000000000000OSCAR-code-v1.5.1/oscar/Graphs/MinutesAtPressure.cpp000066400000000000000000001765701450332542600222100ustar00rootroot00000000000000/* 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 #endif // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< #if defined(ENABLE_MAP_DRAWING_DEBUG) && ( defined(ENABLE_TEST_CPAP) || defined(ENABLE_TEST_SAWTOOTH) || defined(ENABLE_TEST_SINGLE) || defined(ENABLE_TEST_NODATA) ) #define test_data(A,B,C,D,E,F,G,H) if (!testdata( A , B , C , D , E , F , G, H)) continue ; #else #define test_data(A,B,C,D,E,F,G,H) #endif #define TEST_MACROS_ENABLEDoff #include "test_macros.h" // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< #ifdef ENABLE_DISPLAY_FLAG_EVENTS_AS_TICKS static const int drawTickLength =12; #else static const int drawTickLength =0; #endif static const int tableSize = 1+(HIGHEST_POSSIBLE_PRESSURE * INTERVALS_PER_CCMH2O); static constexpr EventDataType sampleIntervalDiv2 = 1.0/EventDataType(INTERVALS_PER_CCMH2O*2); // interval pressure is Value-sampleInterval to value+sampleInterval #ifdef ENABLE_BUCKET_PRESSURE_AVERAGE // New definition of bucket Pressure-0.1 - Pressure0.1 //Bucket pressure is in the middle of the low high range static constexpr EventDataType sampleIntervalStart = sampleIntervalDiv2; static constexpr EventDataType sampleIntervalEnd = sampleIntervalDiv2; #else // Original bucket definition. Pressure - Pressure+0.2 // Bucket Presure is at the low end of the range. static constexpr EventDataType sampleIntervalStart = 0.0; static constexpr EventDataType sampleIntervalEnd = sampleIntervalDiv2*2; // interval pressure is Value-sampleInterval to value+sampleInterval #endif //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Module <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< int calculatePrecision(EventDataType value) { if (value>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) { qint64 d1,d2; qint64 minx=0,maxx=0; //qint64 prevSessDuration=info.totalDuration; EventDataType gain; EventStoreType data, lastdata; qint64 time, lasttime; int ELsize; bool first; if (info.eventLists.size()==0) return; // Find pressure channel // Loop through event lists EventList* EL=info.eventLists[0]; int idx=0; for (; idxcount(); 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]; } 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.5.1/oscar/Graphs/MinutesAtPressure.h000066400000000000000000000201641450332542600216400ustar00rootroot00000000000000/* 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; ~PressureInfo() {} ; 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.5.1/oscar/Graphs/gAHIChart.cpp000066400000000000000000000116451450332542600202670ustar00rootroot00000000000000/* gAHUChart 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. */ #define TEST_MACROS_ENABLEDoff #include "test_macros.h" #include #include #include #include "mainwindow.h" #include "SleepLib/profiles.h" #include "SleepLib/machine_common.h" #include "gAHIChart.h" #include "gYAxis.h" extern MainWindow * mainwin; //extern short SummaryCalcItem::midcalc; //////////////////////////////////////////////////////////////////////////// /// 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.5.1/oscar/Graphs/gAHIChart.h000066400000000000000000000037141450332542600177320ustar00rootroot00000000000000/* gAHIChart 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 GAHICHART_H #define GAHICHART_H #include "SleepLib/day.h" #include "SleepLib/profiles.h" #include "Graphs/gGraphView.h" #include "Graphs/gSummaryChart.h" 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 // GAHICHART_H OSCAR-code-v1.5.1/oscar/Graphs/gFlagsLine.cpp000066400000000000000000000341661450332542600205530ustar00rootroot00000000000000/* 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. */ #define TEST_MACROS_ENABLEDoff #include #define BAR_TITLE_BAR_DEBUGoff #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; } m_sessions = d->getSessions(MT_CPAP); m_start = d->first(MT_CPAP); m_duration = d->last(MT_CPAP) - m_start; if (m_duration<=0) m_duration = 1; // avoid divide by zero quint32 z = schema::FLAG | schema::SPAN | schema::MINOR_FLAG; 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(); 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::refreshConfiguration(gGraph* graph) { int numOn=0; for (const auto & flagsline : lvisible) { if (schema::channel[flagsline->code()].enabled()) numOn++; } if (numOn==0) numOn=1; // always have an area showing in graph. float barHeight = QFontMetrics(*defaultfont).capHeight() + QFontMetrics(*defaultfont).descent() ; int height (barHeight * numOn); height += sessionBarHeight(); setMinimumHeight (height); if (graph->height()setHeight (height); } int gFlagsGroup::sessionBarHeight() { static const int m_sessionHeight = 7; return (m_sessions.size()>1 ) ? m_sessionHeight : 0; }; void gFlagsGroup::paint(QPainter &painter, gGraph &g, const QRegion ®ion) { if (!m_visible) { return; } if (!m_day) { return; } 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(); QVector visflags; for (const auto & flagsline : lvisible) { if (schema::channel[flagsline->code()].enabled()) visflags.push_back(flagsline); } int sheight = (m_sessions.size()>1?sessionBarHeight():0); int vis = visflags.size(); m_barh = float(height-sheight) / float(vis); float linetop = top+sheight-2; qint64 minx,maxx,dur; g.graphView()->GetXBounds(minx,maxx); dur = maxx - minx; #if BAR_TITLE_BAR_DEBUG // debug for minimum size for event flags. adding required height for enabled events , number eventTypes , height of an event bar QString text= QString("%1 -> %2 %3: %4 H:%5 Vis:%6 barH:%7"). arg(QDateTime::fromMSecsSinceEpoch(minx).time().toString()). arg(QDateTime::fromMSecsSinceEpoch(maxx).time().toString()). arg(QObject::tr("Selection Length")). arg(QTime(0,0).addMSecs(dur).toString("H:mm:ss.zzz")) .arg(height) .arg(vis) .arg(m_barh) ; #else QString text= QString("%1 -> %2 %3: %4"). arg(QDateTime::fromMSecsSinceEpoch(minx).time().toString()). arg(QDateTime::fromMSecsSinceEpoch(maxx).time().toString()). arg(QObject::tr("Selection Length")). arg(QTime(0,0).addMSecs(dur).toString("H:mm:ss.zzz")) ; #endif g.renderText(text, left , top -5 ); 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; } // graph each session at top if ( p_profile->appearance->eventFlagSessionBar() && m_sessions.size()>1 ) { QRect sessBox(0,g.top,0,sessionBarHeight()); double adjustment = width/(double)m_duration; for (const auto & sess : m_sessions) { sessBox.setX(left + (sess->first()-m_start)*adjustment); sessBox.setWidth( sess->length() * adjustment); painter.fillRect(sessBox, QBrush(Qt::gray)); } } 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.5.1/oscar/Graphs/gFlagsLine.h000066400000000000000000000106621450332542600202130ustar00rootroot00000000000000/* 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); 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->m_lx = m_lx; layer->m_ly = m_ly; } protected: virtual bool mouseMoveEvent(QMouseEvent *event, gGraph *graph); bool m_always_visible; 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 a list of Visible gFlagsLine layers to draw QVector &visibleLayers() { return lvisible; } void alwaysVisible(ChannelID code) { m_alwaysvisible.push_back(code); } void refreshConfiguration(gGraph* graph) ; 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; layer->m_sessions = m_sessions; 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; QList m_sessions; qint64 m_start ; qint64 m_duration ; QVector lvisible; float m_barh; bool m_empty; bool m_rebuild_cpap; int sessionBarHeight(); }; #endif // GFLAGSLINE_H OSCAR-code-v1.5.1/oscar/Graphs/gFooBar.cpp000066400000000000000000000071531450332542600200530ustar00rootroot00000000000000/* 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.5.1/oscar/Graphs/gFooBar.h000066400000000000000000000025421450332542600175150ustar00rootroot00000000000000/* 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.5.1/oscar/Graphs/gGraph.cpp000066400000000000000000001260731450332542600177470ustar00rootroot00000000000000/* 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. */ #define TEST_MACROS_ENABLEDoff #include "test_macros.h" #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; #if 0 /* from qt 4.8 int QGraphicsSceneWheelEvent::delta() const Returns the distance that the wheel is rotated, in eighths (1/8s) of a degree. A positive value indicates that the wheel was rotated forwards away from the user; a negative value indicates that the wheel was rotated backwards toward the user. int QWheelEvent::delta () const Returns the distance that the wheel is rotated, in eighths of a degree. A positive value indicates that the wheel was rotated forwards away from the user; a negative value indicates that the wheel was rotated backwards toward the user. Most mouse types work in steps of 15 degrees, in which case the delta value is a multiple of 120; i.e., 120 units * 1/8 = 15 degrees. However, some mice have finer-resolution wheels and send delta values that are less than 120 units (less than 15 degrees). To support this possibility, you can either cumulatively add the delta values from events until the value of 120 is reached, then scroll the widget, or you can partially scroll the widget in response to each wheel event. Example: void MyWidget::wheelEvent(QWheelEvent *event) { int numDegrees = event->delta() / 8; int numSteps = numDegrees / 15; if ( isWheelEventHorizontal(event) ) { scrollHorizontally(numSteps); } else { scrollVertically(numSteps); } event->accept(); } from qt 5.15 Returns the relative amount that the wheel was rotated, in eighths of a degree. A positive value indicates that the wheel was rotated forwards away from the user; a negative value indicates that the wheel was rotated backwards toward the user. angleDelta().y() provides the angle through which the common vertical mouse wheel was rotated since the previous event. angleDelta().x() provides the angle through which the horizontal mouse wheel was rotated, if the mouse has a horizontal wheel; otherwise it stays at zero. Some mice allow the user to tilt the wheel to perform horizontal scrolling, and some touchpads support a horizontal scrolling gesture; that will also appear in angleDelta().x(). Most mouse types work in steps of 15 degrees, in which case the delta value is a multiple of 120; i.e., 120 units * 1/8 = 15 degrees. However, some mice have finer-resolution wheels and send delta values that are less than 120 units (less than 15 degrees). To support this possibility, you can either cumulatively add the delta values from events until the value of 120 is reached, then scroll the widget, or you can partially scroll the widget in response to each wheel event. But to provide a more native feel, you should prefer pixelDelta() on platforms where it's available. */ #endif // The qt5.15 obsolescence of hex requires this change. // this solution to QT's obsolescence is only used in debug statements #if QT_VERSION >= QT_VERSION_CHECK(5,15,0) #define wheelEventPos( id ) id ->position() #define wheelEventX( id ) id ->position().x() #define wheelEventY( id ) id ->position().y() #define wheelEventDelta( id ) id ->angleDelta().y() #define isWheelEventVertical( id ) id ->angleDelta().x()==0 #define isWheelEventHorizontal( id ) id ->angleDelta().y()==0 #else #define wheelEventPos( id ) id ->pos() #define wheelEventX( id ) id ->x() #define wheelEventY( id ) id ->y() #define wheelEventDelta( id ) id ->delta() #define isWheelEventVertical( id ) id ->orientation() == Qt::Vertical #define isWheelEventHorizontal( id ) id ->orientation() == Qt::Horizontal #endif // 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) { // DEBUGF Q(name) Q(title) QQ("UNITS",units) Q(height) Q(group); 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; // this is the graphs minimum height.- does not consider the graphs layer height requirements. m_defaultLayerMinHeight = 25; // this is the minimum requirements for the layer height. can be chnaged by a layer. // not changable 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 = ZS_AUTO_FIT; 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_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(ZoomyScaling zoomy) { m_zoomY = zoomy; dynamicScalingOn =false; timedRedraw(0); } void gGraph::mouseDoubleClickYAxis(QMouseEvent * ) { if ( isDynamicScalingEnabled() ) { dynamicScalingOn = !dynamicScalingOn; } else { dynamicScalingOn =false; if (m_zoomY == ZS_AUTO_FIT ) { m_zoomY = ZS_DEFAULT; } else if (m_zoomY == ZS_DEFAULT) { m_zoomY = ZS_AUTO_FIT ; } } 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); } bool gGraph::isDynamicScalingEnabled() { return ((m_lineChart_layer!=nullptr) && AppSetting->allowYAxisScaling() ); } QString gGraph::unitsTooltip() { if (isDynamicScalingEnabled()) { if(dynamicScalingOn) { if (zoomY() == ZS_AUTO_FIT ) { return QString("%1%2%3").arg(m_units).arg("\n").arg(tr("Double click Y-axis: Return to AUTO-FIT Scaling")); } else if (zoomY() == ZS_DEFAULT ) { return QString("%1%2%3").arg(m_units).arg("\n").arg(tr("Double click Y-axis: Return to DEFAULT Scaling")); } else { return QString("%1%2%3").arg(m_units).arg("\n").arg(tr("Double click Y-axis: Return to OVERRIDE Scaling")); } } else { return QString("%1%2%3").arg(m_units).arg("\n").arg(tr("Double click Y-axis: For Dynamic Scaling")); } } else { if (zoomY() == ZS_AUTO_FIT ) { return QString("%1%2%3").arg(m_units).arg("\n").arg(tr("Double click Y-axis: Select DEFAULT Scaling")); } else if (zoomY() == ZS_DEFAULT ) { return QString("%1%2%3").arg(m_units).arg("\n").arg(tr("Double click Y-axis: Select AUTO-FIT Scaling")); } } return m_units; } void gGraph::dynamicScaling(EventDataType &miny, EventDataType &maxy) { // Have new Dynamic mode; miny = m_lineChart_layer->actualMinY(); maxy = m_lineChart_layer->actualMaxY(); EventDataType diff= (maxy-miny); maxy += diff*0.08; // more space at top for event ticks. miny -= diff*0.04; if (m_saved_minY!=m_lineChart_layer->actualMinY() || m_saved_maxY!=m_lineChart_layer->actualMaxY() ) { // DEBUGF O(m_name) Q(m_saved_minY) QQ("==>",m_lineChart_layer->actualMinY() ) QQ("==>",miny) Q(m_saved_maxY) QQ("==>",m_lineChart_layer->actualMaxY() ) QQ("==>",maxy); m_saved_minY=m_lineChart_layer->actualMinY(); m_saved_maxY=m_lineChart_layer->actualMaxY(); } } // YAxis Autoscaling code void gGraph::roundY(EventDataType &miny, EventDataType &maxy) { if (dynamicScalingOn) { dynamicScaling(miny, maxy) ; if (maxy > miny) return; }; if (zoomY() == ZS_OVERRIDE) { // Have override mode // set min and max to override values. miny = rec_miny; maxy = rec_maxy; if (maxy > miny) return; // Not Autoscaling } else if (zoomY() ==ZS_DEFAULT) { // Have Default mode // set min and max to physical Min / max values. miny = physMinY(); maxy = physMaxY(); if (maxy > miny) return; // Not Autoscaling } 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); if (l->layerType()==LT_LineChart) { m_lineChart_layer = dynamic_cast(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=QString::asprintf("%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; } } } void gGraph::mouseReleaseEvent(QMouseEvent *event) { int y = event->pos().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)) { if ((event->modifiers() & Qt::ShiftModifier) != 0) { qint64 time = (qint64)screenToTime(x); qint64 period =qint64(p_profile->general->eventWindowSize())*60000L; // eventwindowsize units minutes qint64 small =period/10; qint64 start = time - period; qint64 end = time + small; m_graphview->SetXBounds(start, end); return; } // 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" << wheelEventX(event) << wheelEventY(event) << wheelEventDelta(event); //int y=event->pos().y(); if ( isWheelEventHorizontal(event) ) { return; } int x = wheelEventPos( event).x() - m_graphview->titleWidth; //(left+m_marginleft); if (wheelEventDelta(event) > 0) { ZoomX(0.75, x); } else { ZoomX(1.5, x); } int y = wheelEventPos(event).y(); x = wheelEventPos(event).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); } } } 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; 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() { if (m_lineChart_layer) return m_lineChart_layer; for (auto & layer : m_layers) { Layer *tmp = dynamic_cast(layer); if (tmp) m_lineChart_layer = tmp; } return nullptr; } int gGraph::minHeight() { // adjust graph height for centerLayer (ploting area) required height. int adjustment = top + bottom; //adjust graph minimun to layer minimum int minlayerheight = m_min_height - adjustment; for (const auto & layer : m_layers) { if (layer->position() != LayerCenter) continue; minlayerheight = max(max (m_defaultLayerMinHeight,layer->minimumHeight()),minlayerheight); } return minlayerheight + adjustment; // adjust layer min to graph minimum } 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.5.1/oscar/Graphs/gGraph.h000066400000000000000000000363041450332542600174110ustar00rootroot00000000000000/* 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; enum ZoomyScaling {ZS_AUTO_FIT =0 , ZS_DEFAULT =1 , ZS_OVERRIDE =2}; 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; } //! \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 Returns the measurement Units the Y scale is referring to plus tooltip. QString unitsTooltip() ; //! \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; } // returns if graph is a daily line chart with Dynamic Scaling mode enabled. bool isDynamicScalingEnabled(); short left, right, top, bottom; // dirty magin hacks.. Layer *getLineChart(); Layer *m_lineChart_layer =nullptr ; bool dynamicScalingOn =false; QTimer *timer; // This gets set to true to force a redraw of the xAxis tickers when graphs are resized. 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 ZoomyScaling zoomY() { return m_zoomY; } void setZoomY(ZoomyScaling zoomy); 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; } void mouseDoubleClickYAxis(QMouseEvent *event); 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 dynamicScaling(EventDataType &miny, EventDataType &maxy); 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; ZoomyScaling m_zoomY; bool m_block_select; QRect m_rect; int m_defaultLayerMinHeight; qint64 m_selectedDuration; double m_currentTime; qint64 m_clickTime; QString m_selDurString; bool m_snapshot; EventDataType m_saved_minY=0; EventDataType m_saved_maxY=0; 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.5.1/oscar/Graphs/gGraphView.cpp000066400000000000000000003451321450332542600206010ustar00rootroot00000000000000/* 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. */ #define TEST_MACROS_ENABLEDoff #include "test_macros.h" #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" #ifndef REMOVE_FITNESS #include "Graphs/gOverviewGraph.h" #endif #include "Graphs/gSummaryChart.h" #include "Graphs/gYAxis.h" #include "Graphs/gFlagsLine.h" #include "SleepLib/profiles.h" #include "overview.h" #if 0 /* from qt 4.8 int QGraphicsSceneWheelEvent::delta() const Returns the distance that the wheel is rotated, in eighths (1/8s) of a degree. A positive value indicates that the wheel was rotated forwards away from the user; a negative value indicates that the wheel was rotated backwards toward the user. int QWheelEvent::delta () const Returns the distance that the wheel is rotated, in eighths of a degree. A positive value indicates that the wheel was rotated forwards away from the user; a negative value indicates that the wheel was rotated backwards toward the user. Most mouse types work in steps of 15 degrees, in which case the delta value is a multiple of 120; i.e., 120 units * 1/8 = 15 degrees. However, some mice have finer-resolution wheels and send delta values that are less than 120 units (less than 15 degrees). To support this possibility, you can either cumulatively add the delta values from events until the value of 120 is reached, then scroll the widget, or you can partially scroll the widget in response to each wheel event. Example: void MyWidget::wheelEvent(QWheelEvent *event) { int numDegrees = event->delta() / 8; int numSteps = numDegrees / 15; if ( isEventHorizontal(event) ) { scrollHorizontally(numSteps); } else { scrollVertically(numSteps); } event->accept(); } from qt 5.15 Returns the relative amount that the wheel was rotated, in eighths of a degree. A positive value indicates that the wheel was rotated forwards away from the user; a negative value indicates that the wheel was rotated backwards toward the user. angleDelta().y() provides the angle through which the common vertical mouse wheel was rotated since the previous event. angleDelta().x() provides the angle through which the horizontal mouse wheel was rotated, if the mouse has a horizontal wheel; otherwise it stays at zero. Some mice allow the user to tilt the wheel to perform horizontal scrolling, and some touchpads support a horizontal scrolling gesture; that will also appear in angleDelta().x(). Most mouse types work in steps of 15 degrees, in which case the delta value is a multiple of 120; i.e., 120 units * 1/8 = 15 degrees. However, some mice have finer-resolution wheels and send delta values that are less than 120 units (less than 15 degrees). To support this possibility, you can either cumulatively add the delta values from events until the value of 120 is reached, then scroll the widget, or you can partially scroll the widget in response to each wheel event. But to provide a more native feel, you should prefer pixelDelta() on platforms where it's available. */ #endif // The qt5.15 obsolescence of hex requires this change. // this solution to QT's obsolescence is only used in debug statements #if QT_VERSION >= QT_VERSION_CHECK(5,15,0) #define wheelEventPos( id ) id ->position() #define wheelEventX( id ) id ->position().x() #define wheelEventY( id ) id ->position().y() #define wheelEventDelta( id ) id ->angleDelta().y() #define isWheelEventVertical( id ) id ->angleDelta().x()==0 #define isWheelEventHorizontal( id ) id ->angleDelta().y()==0 #else #define wheelEventPos( id ) id ->pos() #define wheelEventX( id ) id ->x() #define wheelEventY( id ) id ->y() #define wheelEventDelta( id ) id ->delta() #define isWheelEventVertical( id ) id ->orientation() == Qt::Vertical #define isWheelEventHorizontal( id ) id ->orientation() == Qt::Horizontal #endif 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,bool alwaysShow) { if (!alwaysShow && !AppSetting->graphTooltips()) { return; } if (timeout <= 0) { timeout = AppSetting->tooltipTimeout(); } m_alignment = align; m_text = text; // for testing add mouse position to tooltip. QString("%1:(%2,%3)").arg(text).arg(m_graphview->currentMousePos().x()).arg(m_graphview->currentMousePos().y()); 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); } void gToolTip::cancel() { m_visible = false; timer->stop(); } QRect gToolTip::calculateRect(QPainter &painter) { int x = m_pos.x(); int y = m_pos.y(); // calcualte size of tooltip QRect rect(x, y, 0, 0); painter.setFont(*m_font); rect = painter.boundingRect(rect, Qt::AlignCenter, m_text); // Set preffered locations rect.moveTo(m_pos); // Add borders arround text area // add space around rectangle horizontilally left & right sides. int w = rect.width() + m_spacer * 2; rect.setWidth(w); // add space around rectangle vertically int h = rect.height() + m_spacer * 2; rect.setHeight(h); /* now must verify that the tooltip must fit inti the display area. if part of the display can not be displayed (outside the bounding rectangle of the graph then the tool tip will be moved to fit. If the tooltip is too big . (does not fit) then preference is giver to the top and left sides. the following checks are executed in the order. 1) do right side 2) do left side 3) do bottom 4) do top. these checks are independant of alignment requirements. */ // get display area QRect displayRect = m_graphview->geometry(); int right = displayRect.right() -2; // allow tooltip border to be displayed int left = displayRect.left(); int top = displayRect.top(); int bottom = displayRect.bottom(); if (rect.right() > right ) { rect.moveRight(right); } if (rect.left() < left ) { rect.moveLeft(left); } if (rect.bottom() > bottom ) { rect.moveBottom(bottom); } if (rect.top() < top ) { rect.moveTop(top); } return rect; } void gToolTip::paint(QPainter &painter) //actually paints it. { if (!m_visible) { return; } QRect a_rect=calculateRect(painter); QBrush brush(QColor(255, 255, 128, 230)); brush.setStyle(Qt::SolidPattern); painter.setBrush(brush); painter.setPen(QColor(0, 0, 0, 255)); painter.drawRoundedRect(a_rect, 5, 5); painter.setBrush(Qt::black); painter.setFont(*m_font); painter.drawText(a_rect, Qt::AlignCenter, m_text); } void gToolTip::timerDone() { m_visible = false; m_graphview->redraw(); m_graphview->resetMouse(); } /* Parent tool tip Allow the parent (overview or daily) to add tooltip or short messages to the user. The basic problem is that the parent does not know the current dimensions of the graph view. the parent does have knowledge of the location of fixed widgets which makes it possible to locate tool tips in an appropiate location. */ gParentToolTip::gParentToolTip(gGraphView *graphview) : gToolTip(graphview) { m_parent_visible=false; } gParentToolTip::~gParentToolTip() { } void gParentToolTip::display(gGraphView* gv,QString text, int verticalOffset, int alignOffset, ToolTipAlignment align , int timeout ,QFont *font ) { m_text=text; m_verticalOffset=verticalOffset; m_alignOffset=alignOffset; m_alignment=align; m_timeout=timeout; m_font=font; m_parent_visible=true; gv->timedRedraw(0); }; QRect gParentToolTip::calculateRect(QPainter& painter ) { QRect rect(0, 0, 0, 0); painter.setFont(*m_font); rect = painter.boundingRect(rect, m_alignment, m_text); // update space arround text int space=2*m_spacer; rect.setHeight(space+rect.height()); rect.setWidth(space+rect.width()); rect.moveTo(m_alignOffset,m_height-(m_verticalOffset+rect.height())); // move rect accounding to alignment. default is left. if (m_alignment == TT_AlignRight) { // move rect left by width of rect. if <0 use 0; rect.moveLeft(rect.left()-rect.width()); } else if (m_alignment == TT_AlignCenter) { // left by 1/2 width of rect. if < 0 then use 0 rect.moveLeft(rect.left()-rect.width()/2); } if (rect.top()<0) {rect.setTop(0);}; if (rect.left()<0) {rect.setLeft(0);}; return rect; } void gParentToolTip::paint(QPainter &painter,int width,int height) { if (!m_parent_visible) {return ;}; m_width=width; m_height=height; gToolTip::display(m_text, 0, 0,m_alignment, m_timeout,true); gToolTip::paint(painter); }; void gParentToolTip::timerDone() { gToolTip::timerDone(); if (m_parent_visible) { m_graphview->timedRedraw(0); } m_parent_visible=false; }; void gParentToolTip::cancel() { gToolTip::cancel(); m_parent_visible=false; }; bool gParentToolTip::visible() { return gToolTip::visible() && m_parent_visible; }; #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); m_parent_tooltip = new gParentToolTip(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; delete m_parent_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 { #if (QT_VERSION >= QT_VERSION_CHECK(5,11,0)) w = painter.fontMetrics().horizontalAdvance(q.text); #else w = painter.fontMetrics().width(q.text); #endif 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 { #if (QT_VERSION >= QT_VERSION_CHECK(5,11,0)) w = painter.fontMetrics().horizontalAdvance(q.text); #else w = painter.fontMetrics().width(q.text); #endif 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); #if (QT_VERSION >= QT_VERSION_CHECK(5,11,0)) w = painter.fontMetrics().horizontalAdvance(q.text); #else w = painter.fontMetrics().width(q.text); #endif 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) { bool changed= (minx!=m_minx)||(maxx!=m_maxx); 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); } if (changed) emit XBoundsChanged(minx ,maxx); } 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); m_parent_tooltip->paint(painter,width(), height() ); #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; gGraph* graph = m_graphs[m_sizer_index]; int minHeight = graph-> minHeight(); if (h < minHeight) { h = minHeight; } // past minimum height - reset to to minimum if ((h > minHeight) || ( graph->height() > minHeight)) { graph->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() { ZoomyScaling tmp = graph->zoomY(); graph->setZoomY(ZS_AUTO_FIT); 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) { ZoomyScaling idx = static_cast(_idx) ; minbox->setEnabled(idx == ZS_OVERRIDE); maxbox->setEnabled(idx == ZS_OVERRIDE); reset->setEnabled(idx == ZS_OVERRIDE); graph->setZoomY(idx); if (idx == ZS_OVERRIDE) { 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"), ZS_AUTO_FIT); combobox->addItem(tr("Defaults"), ZS_DEFAULT); combobox->addItem(tr("Override"), ZS_OVERRIDE); 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)); #ifndef REMOVE_FITNESS gOverviewGraph * sc = dynamic_cast(findLayer(graph,LT_SummaryChart)); #endif gSummaryChart * stg = dynamic_cast(findLayer(graph,LT_Overview)); limits_menu->clear(); #ifndef REMOVE_FITNESS if (lc || sc || stg ) #else if (lc || stg ) #endif { 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; #if defined(ENABLE_ALWAYS_ON_ZERO_RED_LINE_FLOW_RATE) // if red line is always on then there is no need for the button to turn it on /off // skip creating UI to change value. or turn enabled off. //if (lc->code() == CPAP_FlowRate && dot.type == Calc_Zero) continue; if (lc->code() == CPAP_FlowRate) continue; #endif 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 (isWheelEventVertical(event)) { // 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 = wheelEventY(event); 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 = wheelEventDelta(event); 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; //'Aug(' const quint16 gVversion = 5; // version 5 has same format as 4, used to override settings in shg files on upgrade to version 5. QString gGraphView::settingsFilename (QString title,QString folderName, QString ext) { if (folderName.size()==0) { folderName = p_profile->Get("{DataFolder}/"); } return folderName+title.toLower()+ext; } /* This note is for the save and restore settings. * all versions prior to 4 were sleepyHead versions and have never been used. * The layouts (version 4 and 5) are identical * The rollback to a gVversion should always work. * So new addtions to the saved configuration must be placed at the end of the file. * the SHG file will then be * SHG FILE HEADER - Must never change * SHG VERSION 4(5) changes - for all graphs * SHG VERSION 6 changes - for all graphs * SHG VERSION 7 changes - for all graphs * ... */ void gGraphView::SaveSettings(QString title,QString folderName) { qDebug() << "Saving" << title << "settings"; QString filename=settingsFilename(title,folderName) ; 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 << (short)graph->zoomY(); // the return type of zoomY was changed from a short to an enum (int) so much type cast it here 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; } } #if 0 // add changes for additional settings for (auto & graph : m_graphs) { } #endif 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 folderName) { //qDebug() << "Loading" << title << "settings"; QString filename=settingsFilename (title,folderName) ; 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; //The first version of OSCAR 1.0.0-release started at gVversion 4. and the OSCAR 1.4.0 still uses gVversion 4 // This section of code is being simplified to remove dependances on lower version. //if (version < gVversion) { //qDebug() << "gGraphView" << title << "settings will be upgraded."; //} qint16 numGraphs; QString name; float hght; bool vis; EventDataType recminy, recmaxy; bool pinned; short zoomy = 0; QList neworder; QHash::iterator gi; in >> numGraphs; for (int i = 0; i < numGraphs; i++) { in >> name; in >> hght; in >> vis; in >> recminy; in >> recmaxy; //qDebug() << "Loading graph" << title << name; in >> zoomy; in >> pinned; QHash flags_enabled; QHash plots_enabled; QHash > dot_enabled; // Warning: Do not break the follow section up!!! quint32 layertype; in >> layertype; if (layertype == LT_LineChart) { in >> flags_enabled; in >> plots_enabled; in >> dot_enabled; } gGraph *g = nullptr; 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(static_cast(zoomy)); g->setPinned(pinned); 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); // the following check forces the flowRate graph to have a Zero dotted line enabled when the the current version changes from 4 to 5 // still allows the end user user to to remove the zero dotted line. // currently this would be executed on each graphview version (gVversion) change // could be changed. // This is a one time change. if (version==4 && gVversion>4) { lc->resetGraphViewSettings(); } } } } } // Do this for gVersion 5 #if 0 // Version 5 had no changes if (version>=gVversion) for (int i = 0; i < numGraphs; i++) { } #endif // Do this for gVersion 6 ... #if 0 // repeat this for each additional version change // this for the next additions to the saved information. if (version>=gVversion) for (int i = 0; i < numGraphs; i++) { } #endif 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.5.1/oscar/Graphs/gGraphView.h000066400000000000000000000563251450332542600202510ustar00rootroot00000000000000/* 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 #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; QElapsedTimer 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,bool alwaysShow=false); //! \brief Draw the tooltip virtual void paint(QPainter &paint); //actually paints it. //! \brief Close the tooltip early. virtual void cancel(); //! \brief Returns true if the tooltip is currently visible virtual bool visible() { return m_visible; } virtual QRect calculateRect(QPainter &painter); protected: gGraphView *m_graphview; QTimer *timer; QPoint m_pos; int tw, th; QString m_text; bool m_visible; int m_spacer; QImage m_image; ToolTipAlignment m_alignment; QFont* m_font=defaultfont; protected slots: //! \brief Timeout to hide tooltip, and redraw without it. virtual void timerDone(); }; class gParentToolTip : public gToolTip { Q_OBJECT public: gParentToolTip(gGraphView *graphview); ~gParentToolTip(); using gToolTip::display; virtual void display(gGraphView* gv,QString text,int verticalOffset, int alignOffset , ToolTipAlignment align = TT_AlignCenter, int timeout = 0,QFont *font = defaultfont) ; virtual void cancel(); virtual bool visible(); virtual QRect calculateRect(QPainter &painter); using gToolTip::paint; virtual void paint(QPainter &paint,int width,int height) ; private: int m_verticalOffset; int m_alignOffset; int m_width; int m_height; bool m_parent_visible; int m_timeout; protected slots: virtual 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); QString settingsFilename (QString title,QString folderName="" ,QString ext=".shg"); //! \brief Saves the current graph order, heights, min & Max Y values to disk void SaveSettings(QString title,QString folderName=""); //! \brief Loads the current graph order, heights, min & max Y values from disk bool LoadSettings(QString title,QString folderName=""); //! \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; gParentToolTip *m_parent_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; QElapsedTimer 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(); void XBoundsChanged(qint64 ,qint64); 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.5.1/oscar/Graphs/gLineChart.cpp000066400000000000000000001114611450332542600205520ustar00rootroot00000000000000/* 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. */ #define TEST_MACROS_ENABLEDoff #include "test_macros.h" #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 #if 0 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; } #endif 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()); } void gLineChart::resetGraphViewSettings() { #if !defined(ENABLE_ALWAYS_ON_ZERO_RED_LINE_FLOW_RATE) // always turn zero red line on as default value. if (m_code==CPAP_FlowRate) { m_dot_enabled[m_code][Calc_Zero] = true; } else #endif if (m_code==CPAP_Leak) { m_dot_enabled[m_code][Calc_UpperThresh] = true; } } 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; resetGraphViewSettings(); } 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]; if (code != CPAP_FlowRate) { 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_FlowRate) && (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)); #if defined(ENABLE_ALWAYS_ON_ZERO_RED_LINE_FLOW_RATE) //on set day force red line on. m_dot_enabled[m_code][Calc_Zero] = true; #endif } 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) { EventDataType actual_min_y, actual_max_y; 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; } actual_min_y = (EventDataType)INT_MAX; actual_max_y = -(EventDataType)INT_MAX; 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::asprintf("%.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; if (actual_min_y>data) { actual_min_y=data; } if (actual_max_y=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; if (actual_min_y>data) { actual_min_y=data; } if (actual_max_y= 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; if (actual_min_y>data) { actual_min_y=data; } if (actual_max_ydata) { actual_min_y=data; } if (actual_max_y xst + width) { px = xst + width; } } //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.. //} if (square_plot) { lines.append(QLine(lastpx, lastpy, px, lastpy)); lines.append(QLine(px, lastpy, px, py)); } else { 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 (Event flags) independant of line Cursor mode //The problem was that turning lineCUrsor mode off (or Control L) also stopped flag event on most daily graphs. // The user didn't know what trigger the problem. Best quess is that Control L was typed by mistable. // this fix allows flag events to be normally displayed when the line Cursor mode is off. //was if (m_day /*&& (AppSetting->lineCursorMode() || (m_codes[0]==CPAP_FlowRate))*/) if (m_day) { 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);// +" " if (linecursormode) txt+=lasttext; w.renderText(txt,left,top-5); } // painter.setRenderHint(QPainter::Antialiasing, false); if (actual_max_y>0) { m_actual_min_y=actual_min_y; m_actual_max_y=actual_max_y; } } OSCAR-code-v1.5.1/oscar/Graphs/gLineChart.h000066400000000000000000000150121450332542600202120ustar00rootroot00000000000000/* 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" #define ENABLE_ALWAYS_ON_ZERO_RED_LINE_FLOW_RATE 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 Minimum Y-axis value for this layer virtual EventDataType actualMinY() {return m_actual_min_y;}; //! \brief Returns Maximum Y-axis value for this layer virtual EventDataType actualMaxY() {return m_actual_max_y;}; //! \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; } virtual void resetGraphViewSettings(); 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; EventDataType m_actual_min_y=0,m_actual_max_y=0; //! \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.5.1/oscar/Graphs/gLineOverlay.cpp000066400000000000000000000310321450332542600211250ustar00rootroot00000000000000/* 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::asprintf("%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.5.1/oscar/Graphs/gLineOverlay.h000066400000000000000000000055421450332542600206010ustar00rootroot00000000000000/* 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.5.1/oscar/Graphs/gOverviewGraph.cpp000066400000000000000000001146451450332542600215000ustar00rootroot00000000000000/* gOverviewGraph 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. */ #define TEST_MACROS_ENABLEDoff #include "test_macros.h" #include #include #include #include "gYAxis.h" #include "gOverviewGraph.h" #ifndef REMOVE_FITNESS /* To enable this module change the REMOTE_FITNESS define in appsettings.h */ gOverviewGraph::gOverviewGraph(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; } gOverviewGraph::~gOverviewGraph() { } void gOverviewGraph::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, 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; 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; } } bool first = true; int suboffset; SummaryType type; // 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) { qint64 zt; EventDataType tmp2; // 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 gOverviewGraph::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; 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; 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++) { EventDataType 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; QSize size = QFontMetrics(*defaultfont).size(Qt::TextSingleLine ,"X"); int bw = size.width(); int bh = size.height() / 1.8; //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 = QString::asprintf("%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); //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::asprintf("%i:%02i:%02i%s", h, m, s, pm); } else { return QString::asprintf("%i:%02i%s", h, m, pm); } } bool gOverviewGraph::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 = QString::asprintf("%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 = QString::asprintf("%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 gOverviewGraph::mousePressEvent(QMouseEvent *event, gGraph *graph) { if (event->modifiers() & Qt::ShiftModifier) { //qDebug() << "Jump to daily view?"; return true; } Q_UNUSED(graph) return false; } bool gOverviewGraph::keyPressEvent(QKeyEvent *event, gGraph *graph) { Q_UNUSED(event) Q_UNUSED(graph) //qDebug() << "Summarychart Keypress"; return false; } #include "mainwindow.h" extern MainWindow *mainwin; bool gOverviewGraph::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; } #endif OSCAR-code-v1.5.1/oscar/Graphs/gOverviewGraph.h000066400000000000000000000124701450332542600211360ustar00rootroot00000000000000/* gOverviewGraph 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 GOVERVIEWGRAPH_H #define GOVERVIEWGRAPH_H #include #include "gGraphView.h" #include "gXAxis.h" #include "SleepLib/appsettings.h" #ifndef REMOVE_FITNESS /* BMI, Weight and Feeling graphs are are hard coded in Overview.cpp These graph require special handling in class gOverviewGraph. Currently there are 4 graphs types, one of which is not used. GT_BAR Used by CPAP graph to make bar graphs for each day GT_LINE ? Used for making a line ? GT_POINT ? Used to display points instead of lines ? GT_SESSION ?? NOT USED. BMI, Weight and Feeling graphs current use GT_LINE and not GT_BAR The Overview Linecharts preference allows points to be displayed instead of lines. */ /*! \enum GraphType \value GT_BAR Display as a BarGraph \value GT_LINE Display as a line plot */ enum GraphType { GT_BAR, GT_LINE, GT_POINTS , GT_SESSIONS }; /*! \class gOverviewGraph \brief The main overall chart type layer used in Overview page */ class gOverviewGraph: public Layer { public: //! \brief Constructs a gOverviewGraph with QString label, of GraphType type gOverviewGraph(QString label, GraphType type = GT_BAR); virtual ~gOverviewGraph(); //! \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 gOverviewGraph (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 gOverviewGraph is interested in void setMachineType(MachineType type) { m_machinetype = type; } //! \brief Returns the MachineType this gOverviewGraph is interested in MachineType machineType() { return m_machinetype; } virtual Layer * Clone() { gOverviewGraph * sc = new gOverviewGraph(m_label); Layer::CloneInto(sc); CloneInto(sc); return sc; } void CloneInto(gOverviewGraph * 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 #endif // GOVERVIEWGRAPH_H OSCAR-code-v1.5.1/oscar/Graphs/gPressureChart.cpp000066400000000000000000000170541450332542600214760ustar00rootroot00000000000000/* 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_TRILEVEL_AUTO_VARIABLE_PDIFF) { addSlice(CPAP_EEPAPLo); if (!day->summaryOnly()) { ChannelID eepap = CPAP_EEPAP; ChannelID ipap = CPAP_IPAP; addSlice(eepap, ST_MID); addSlice(eepap, 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.5.1/oscar/Graphs/gPressureChart.h000066400000000000000000000034251450332542600211400ustar00rootroot00000000000000/* 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 "gSummaryChart.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.5.1/oscar/Graphs/gSegmentChart.cpp000066400000000000000000000225261450332542600212700ustar00rootroot00000000000000/* 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.5.1/oscar/Graphs/gSegmentChart.h000066400000000000000000000045601450332542600207330ustar00rootroot00000000000000/* 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.5.1/oscar/Graphs/gSessionTimesChart.cpp000066400000000000000000000257371450332542600223220ustar00rootroot00000000000000/* 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. */ #define TEST_MACROS_ENABLEDoff #include "test_macros.h" #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; 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(durationInHoursToHhMmSs(calc.min)).arg(durationInHoursToHhMmSs(mid)).arg(durationInHoursToHhMmSs(calc.max)) .arg(durationInHoursToHhMmSs(calc2.min)).arg(durationInHoursToHhMmSs(midlongest)).arg(durationInHoursToHhMmSs(calc2.max)); 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; float s2_display = double(slice.end - slice.start) / 1000.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(durationInSecondsToHhMmSs(s2_display)); 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(durationInHoursToHhMmSs(s2)); 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) { // 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); } OSCAR-code-v1.5.1/oscar/Graphs/gSessionTimesChart.h000066400000000000000000000035001450332542600217470ustar00rootroot00000000000000/* 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 "Graphs/gGraphView.h" #include "Graphs/gSummaryChart.h" /*! \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; }; #endif // GSESSIONTIMESCHART_H OSCAR-code-v1.5.1/oscar/Graphs/gStatsLine.cpp000066400000000000000000000032131450332542600206020ustar00rootroot00000000000000/* 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.5.1/oscar/Graphs/gStatsLine.h000066400000000000000000000016501450332542600202520ustar00rootroot00000000000000/* 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.5.1/oscar/Graphs/gSummaryChart.cpp000066400000000000000000000474571450332542600213350ustar00rootroot00000000000000/* 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. */ #define TEST_MACROS_ENABLEDoff #include "test_macros.h" #include #include #include #include "mainwindow.h" #include "SleepLib/profiles.h" #include "SleepLib/machine_common.h" #include "gSummaryChart.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; m_emptyPrev = true; } 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); if ((hours==0) && (m_machtype != MT_CPAP)) hours = day->hours(); 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) { // 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; m_emptyPrev = true; graph.graphView()->updateScale(); emit summaryChartEmpty(this,m_minx,m_maxx,true); } else if (m_emptyPrev) { m_emptyPrev = false; emit summaryChartEmpty(this,m_minx,m_maxx,false); } } QString gSummaryChart::durationInHoursToHhMmSs(double duration) { return durationInSecondsToHhMmSs(duration * 3600); } QString gSummaryChart::durationInMinutesToHhMmSs(double duration) { return durationInSecondsToHhMmSs(duration * 60); } QString gSummaryChart::durationInSecondsToHhMmSs(double duration) { // ensure that a negative duration is supported (could potentially occur when start and end occur in different timezones without compensation) double duration_abs = abs(duration); int seconds_abs = static_cast(0.5 + duration_abs); int daily_hours_abs = seconds_abs / 3600; QString result; if (daily_hours_abs < 24) { result = QTime(0,0,0,0).addSecs(seconds_abs).toString("hh:mm:ss"); } else { result = QString::number(daily_hours_abs + seconds_abs % 86400 / 3600) + ":" + QTime(0, 0, 0, 0).addSecs(seconds_abs).toString("mm:ss"); } if (duration == duration_abs) { return result; } else { return "-" + result; } } OSCAR-code-v1.5.1/oscar/Graphs/gSummaryChart.h000066400000000000000000000161661450332542600207730ustar00rootroot00000000000000/* 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. */ #if 1 #ifndef GSUMMARYCHART_H #define GSUMMARYCHART_H #include "SleepLib/day.h" #include "SleepLib/profiles.h" #include "Graphs/gGraphView.h" #include "SleepLib/appsettings.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 QObject , public Layer { Q_OBJECT; 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; } //! \brief Allows chart to recalculate empty flag. void reCalculate() {m_empty=false;}; 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; } signals: void summaryChartEmpty(gSummaryChart*,qint64,qint64,bool); 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 durationInHoursToHhMmSs(double duration); QString durationInMinutesToHhMmSs(double duration); QString durationInSecondsToHhMmSs(double duration); QString m_label; MachineType m_machtype; bool m_empty; bool m_emptyPrev; 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; }; #endif // GSUMMARYCHART_H #endif OSCAR-code-v1.5.1/oscar/Graphs/gTTIAChart.cpp000066400000000000000000000054351450332542600204270ustar00rootroot00000000000000/* gTTIAChart 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. */ #define TEST_MACROS_ENABLEDoff #include "test_macros.h" #include #include #include #include "mainwindow.h" #include "SleepLib/profiles.h" #include "SleepLib/machine_common.h" #include "gTTIAChart.h" #include "gYAxis.h" extern MainWindow * mainwin; // short SummaryCalcItem::midcalc; //////////////////////////////////////////////////////////////////////////// /// 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(durationInMinutesToHhMmSs(calc.min)).arg(durationInMinutesToHhMmSs(mid)).arg(durationInMinutesToHhMmSs(calc.max))); } 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); slices.append(SummaryChartSlice(&calcitems[0], ttia / 60.0, ttia / 60.0, QObject::tr("\nTTIA: %1").arg(durationInSecondsToHhMmSs(ttia)), 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; } OSCAR-code-v1.5.1/oscar/Graphs/gTTIAChart.h000066400000000000000000000022241450332542600200650ustar00rootroot00000000000000/* gTTIAChart 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 GTTIACHART_H #define GTTIACHART_H #include "SleepLib/day.h" #include "SleepLib/profiles.h" #include "Graphs/gGraphView.h" #include "Graphs/gSummaryChart.h" 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: }; #endif // GTTIACHART_H OSCAR-code-v1.5.1/oscar/Graphs/gUsageChart.cpp000066400000000000000000000056651450332542600207370ustar00rootroot00000000000000/* gUsageChart 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. */ #if 1 #define TEST_MACROS_ENABLEDoff #include "test_macros.h" #include #include #include #include "mainwindow.h" #include "SleepLib/profiles.h" #include "SleepLib/machine_common.h" #include "gUsageChart.h" #include "gYAxis.h" extern MainWindow * mainwin; // short SummaryCalcItem::midcalc; QString gUsageChart::tooltipData(Day * day, int) { return QObject::tr("\nLength: %1").arg(durationInHoursToHhMmSs(day->hours(m_machtype))); } 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(durationInHoursToHhMmSs(calc.min)).arg(durationInHoursToHhMmSs(mid)).arg(durationInHoursToHhMmSs(calc.max)); graph.renderText(txt, rect.left(), rect.top()-5*graph.printScaleY(), 0); } } #endif OSCAR-code-v1.5.1/oscar/Graphs/gUsageChart.h000066400000000000000000000025121450332542600203700ustar00rootroot00000000000000/* gUsageChart 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. */ #if 1 #ifndef GUSAGECHART_H #define GUSAGECHART_H #include "SleepLib/day.h" #include "SleepLib/profiles.h" #include "Graphs/gGraphView.h" #include "Graphs/gSummaryChart.h" 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; }; #endif // GUSAGECHART_H #endif OSCAR-code-v1.5.1/oscar/Graphs/gXAxis.cpp000066400000000000000000000334441450332542600177410ustar00rootroot00000000000000/* 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.5.1/oscar/Graphs/gXAxis.h000066400000000000000000000130051450332542600173750ustar00rootroot00000000000000/* 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.5.1/oscar/Graphs/gYAxis.cpp000066400000000000000000000201321450332542600177300ustar00rootroot00000000000000/* 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. */ #define TEST_MACROS_ENABLEDoff #include #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->unitsTooltip(), x+10, y+10, TT_AlignLeft); // graph->redraw(); } return false; } bool gYAxis::mouseDoubleClickEvent(QMouseEvent *event, gGraph *graph) { if (graph) { graph->mouseDoubleClickYAxis(event); } return false; } const QString gYAxisTime::Format(EventDataType v, int dp) { // it seems that v is the total duration of the sleep from 12 noon 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 { h < 12 ? h+=12 : h-=12; pm[0] = 0; } if (dp > 2) { return QString::asprintf("%02i:%02i:%02i%s", h, m, s, pm) ; } return QString::asprintf("%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.5.1/oscar/Graphs/gYAxis.h000066400000000000000000000135751450332542600174120ustar00rootroot00000000000000/* 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.5.1/oscar/Graphs/gdailysummary.cpp000066400000000000000000000372601450332542600214250ustar00rootroot00000000000000/* 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.5.1/oscar/Graphs/gdailysummary.h000066400000000000000000000032151450332542600210630ustar00rootroot00000000000000/* 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.5.1/oscar/Graphs/glcommon.cpp000066400000000000000000000020171450332542600203410ustar00rootroot00000000000000/* 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.5.1/oscar/Graphs/glcommon.h000066400000000000000000000043261450332542600200130ustar00rootroot00000000000000/* 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.5.1/oscar/Graphs/graphdata.h000066400000000000000000000073511450332542600201340ustar00rootroot00000000000000/* -*- 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.5.1/oscar/Graphs/graphdata_custom.h000066400000000000000000000061461450332542600215270ustar00rootroot00000000000000/* -*- 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.5.1/oscar/Graphs/gspacer.cpp000066400000000000000000000006361450332542600201570ustar00rootroot00000000000000/* 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.5.1/oscar/Graphs/gspacer.h000066400000000000000000000014071450332542600176210ustar00rootroot00000000000000/* 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.5.1/oscar/Graphs/layer.cpp000066400000000000000000000147141450332542600176510ustar00rootroot00000000000000/* 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. */ #define TEST_MACROS_ENABLEDoff #include "test_macros.h" #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.5.1/oscar/Graphs/layer.h000066400000000000000000000245011450332542600173110ustar00rootroot00000000000000/* 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 void setMinimumHeight(int height) { m_minimumHeight=height; } //! \brief Override to set the minimum allowed height for this layer virtual int minimumHeight() { return m_minimumHeight; } //! \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; int m_minimumHeight=0; 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; } virtual EventDataType actualMinY() {return 0;}; virtual EventDataType actualMaxY() {return 0;}; }; /*! \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.5.1/oscar/Resources.qrc000066400000000000000000000053041450332542600172610ustar00rootroot00000000000000 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 icons/exit.png icons/plus.png icons/rename.png icons/restore.png icons/trash_can.png icons/update.png icons/cog.png icons/question_mark.png icons/checkmark.png icons/empty_box.png icons/resvent.png OSCAR-code-v1.5.1/oscar/STYLE000066400000000000000000000020251450332542600154200ustar00rootroot00000000000000# 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.5.1/oscar/SleepLib/000077500000000000000000000000001450332542600162755ustar00rootroot00000000000000OSCAR-code-v1.5.1/oscar/SleepLib/appsettings.cpp000066400000000000000000000060671450332542600213530ustar00rootroot00000000000000/* 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_EventFlagSessionBar, 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(); #ifndef REMOVE_FITNESS m_olm = (OverviewLinechartModes)initPref(STR_AS_OverviewLinechartMode, (int)OLC_Bartop).toInt(); #endif 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.5.1/oscar/SleepLib/appsettings.h000066400000000000000000000327401450332542600210150ustar00rootroot00000000000000/* 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; #define REMOVE_FITNESS_OFF /* valid values are REMOVE_FITNESS or REMOVE_FITNESS_OFF */ #ifndef REMOVE_FITNESS enum OverviewLinechartModes { OLC_Bartop, OLC_Lines }; #endif #define REMSTAR_M_SUPPORTdisabled // 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"; #ifndef REMOVE_FITNESS const QString STR_AS_OverviewLinechartMode = "OverviewLinechartMode"; #endif 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_EventFlagSessionBar = "EventFlagSessionBar"; 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; #ifndef REMOVE_FITNESS OverviewLinechartModes m_olm; #endif 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*/false; } //disables use pixmap caching without modifing any code. //! \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 device 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(); } //bool eventFlagSessionBar() const { return getPref(STR_AS_EventFlagSessionBar).toBool(); } //! \Allow disabling of sessions //! \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; } #ifndef REMOVE_FITNESS //! \brief Returns the display type of Overview pages linechart inline OverviewLinechartModes overviewLinechartMode() const { return m_olm; } #endif 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 device serial number on device 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); } // void setEventFlagSessionBar(bool b) { setPref(STR_AS_EventFlagSessionBar, 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) #ifndef REMOVE_FITNESS void setOverviewLinechartMode(OverviewLinechartModes olm) { setPref(STR_AS_OverviewLinechartMode, (int)(m_olm=olm)); } #endif //! \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.5.1/oscar/SleepLib/calcs.cpp000066400000000000000000001350551450332542600200770ustar00rootroot00000000000000/* 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 from 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 from 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 device 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 devices (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.5.1/oscar/SleepLib/calcs.h000066400000000000000000000122301450332542600175310ustar00rootroot00000000000000/* 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; } Filter& operator=(const Filter ©) = default; ~Filter() {}; 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 device 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.5.1/oscar/SleepLib/common.cpp000066400000000000000000001046441450332542600203020ustar00rootroot00000000000000/* 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; } QString getCompilerVersion() { #ifdef __clang_version__ return QString("clang++:%1").arg(__clang_version__); #elif defined(__MINGW64__) return QString("MINGW64:%1").arg(__VERSION__); #elif defined(__MINGW32__) return QString("MINGW32:%1").arg(__VERSION__); #elif defined (__GNUG__) or defined (__GNUC__) return QString("GNU C++:%1").arg(__VERSION__); #else return QString(); #endif } 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()); QString compiler = getCompilerVersion(); if (compiler.length() >0 ) buildInfo << (QObject::tr("Compiler:") + " " + compiler); 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)gram_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_milliSeconds; 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; // Device 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_EEPAP; // End Expiratory Positive Airway Pressure QString STR_TR_EEPAPLo; // End-Expiratory Positive Airway Pressure, Low QString STR_TR_EEPAPHi; // End-Expiratory Positive Airway Pressure, High 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 devices 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 devices 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_milliSeconds = QObject::tr("milliSeconds"); 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 = QString("?"); 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("Feeling"); 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"); // Device 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_EEPAP = QObject::tr("EEPAP"); // End-Expiratory Positive Airway Pressure STR_TR_EEPAPLo = QObject::tr("Min EEPAP"); // Lower End-Expiratory Positive Airway Pressure STR_TR_EEPAPHi = QObject::tr("Max EEPAP"); // Higher End-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 devices 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 devices 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("Device"); 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.5.1/oscar/SleepLib/common.h000066400000000000000000000327471450332542600177530ustar00rootroot00000000000000/* 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; ~ValueCount() {}; 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; const float gram_ounce_convert = 0.0352754; // ounces in a gram 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_MACH_Prisma = "Prisma"; 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_milliSeconds; 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; // Device 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_EEPAP; // Expiratory Positive Airway Pressure extern QString STR_TR_EEPAPLo; // End-Expiratory Positive Airway Pressure, Low extern QString STR_TR_EEPAPHi; // End-Expiratory Positive Airway Pressure, High 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 devices 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 devices 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.5.1/oscar/SleepLib/crypto.cpp000066400000000000000000000115301450332542600203210ustar00rootroot00000000000000/* SleepLib cryptography abstraction * * 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 "SleepLib/crypto.h" #include "SleepLib/thirdparty/botan_all.h" CryptoResult decrypt_aes256(const QByteArray & key, const QByteArray & ciphertext, QByteArray & plaintext) { CryptoResult result = OK; plaintext.clear(); try { const std::vector botan_key(key.begin(), key.end()); Botan::secure_vector botan_message(ciphertext.begin(), ciphertext.end()); std::unique_ptr dec = Botan::BlockCipher::create("AES-256"); dec->set_key(botan_key); dec->decrypt(botan_message); QByteArray message((char*) botan_message.data(), botan_message.size()); plaintext = message; } catch (std::exception& e) { // Make sure no Botan exceptions leak out and terminate the application. qWarning() << "Unexpected exception in decrypt_aes256:" << e.what(); result = UnknownError; } return result; } CryptoResult decrypt_aes256_gcm(const QByteArray & key, const QByteArray & iv, const QByteArray & ciphertext, const QByteArray & tag, QByteArray & plaintext) { CryptoResult result = OK; plaintext.clear(); try { const std::vector botan_key(key.begin(), key.end()); const std::vector botan_iv(iv.begin(), iv.end()); const std::vector botan_tag(tag.begin(), tag.end()); Botan::secure_vector botan_message(ciphertext.begin(), ciphertext.end()); botan_message += botan_tag; std::unique_ptr dec = Botan::Cipher_Mode::create("AES-256/GCM", Botan::DECRYPTION); dec->set_key(botan_key); dec->start(botan_iv); try { dec->finish(botan_message); //qDebug() << QString::fromStdString(Botan::hex_encode(message.data(), message.size())); QByteArray message((char*) botan_message.data(), botan_message.size()); plaintext = message; } catch (const Botan::Invalid_Authentication_Tag& e) { result = InvalidTag; } } catch (std::exception& e) { // Make sure no Botan exceptions leak out and terminate the application. qWarning() << "Unexpected exception in decrypt_aes256_gcm:" << e.what(); result = UnknownError; } return result; } CryptoResult encrypt_aes256_gcm(const QByteArray & key, const QByteArray & iv, const QByteArray & plaintext, QByteArray & ciphertext, QByteArray & tag) { CryptoResult result = OK; ciphertext.clear(); try { const std::vector botan_key(key.begin(), key.end()); const std::vector botan_iv(iv.begin(), iv.end()); Botan::secure_vector botan_message(plaintext.begin(), plaintext.end()); std::unique_ptr enc = Botan::Cipher_Mode::create("AES-256/GCM", Botan::ENCRYPTION); enc->set_key(botan_key); enc->start(botan_iv); enc->finish(botan_message); //qDebug() << QString::fromStdString(Botan::hex_encode(botan_message.data(), botan_message.size())); size_t tag_size = enc->tag_size(); QByteArray message((char*) botan_message.data(), botan_message.size()); tag = message.right(tag_size); ciphertext = message.left(message.size() - tag_size); } catch (std::exception& e) { // Make sure no Botan exceptions leak out and terminate the application. qWarning() << "Unexpected exception in encrypt_aes256_gcm:" << e.what(); result = UnknownError; } return result; } CryptoResult pbkdf2_sha256(const QByteArray & passphrase, const QByteArray & salt, int iterations, QByteArray & key) { CryptoResult result = OK; try { std::unique_ptr family = Botan::PasswordHashFamily::create("PBKDF2(SHA-256)"); std::unique_ptr kdf = family->from_params(iterations); Botan::secure_vector botan_key(key.size()); kdf->derive_key(botan_key.data(), botan_key.size(), (const char*) passphrase.data(), passphrase.size(), (const uint8_t*) salt.data(), salt.size()); QByteArray output((char*) botan_key.data(), botan_key.size()); key = output; } catch (std::exception& e) { // Make sure no Botan exceptions leak out and terminate the application. qWarning() << "Unexpected exception in pbkdf2_sha256:" << e.what(); result = UnknownError; key.clear(); } return result; } OSCAR-code-v1.5.1/oscar/SleepLib/crypto.h000066400000000000000000000020671450332542600177730ustar00rootroot00000000000000/* SleepLib cryptography abstraction * * 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 CRYPTO_H #define CRYPTO_H #include enum CryptoResult { OK = 0, UnknownError = -1, InvalidTag = 1, }; CryptoResult decrypt_aes256(const QByteArray & key, const QByteArray & ciphertext, QByteArray & plaintext); CryptoResult decrypt_aes256_gcm(const QByteArray & key, const QByteArray & iv, const QByteArray & ciphertext, const QByteArray & tag, QByteArray & plaintext); CryptoResult encrypt_aes256_gcm(const QByteArray & key, const QByteArray & iv, const QByteArray & plaintext, QByteArray & ciphertext, QByteArray & tag); CryptoResult pbkdf2_sha256(const QByteArray & passphrase, const QByteArray & salt, int iterations, QByteArray & key); #endif // CRYPTO_H OSCAR-code-v1.5.1/oscar/SleepLib/day.cpp000066400000000000000000001321551450332542600175650ustar00rootroot00000000000000/* 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. */ #define TEST_MACROS_ENABLEDoff #include #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() ? QString("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 device type 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 || cpapmode == MODE_TRILEVEL_AUTO_VARIABLE_PDIFF) { 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 device match 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 device match 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 device 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 device-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 device-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_TRILEVEL_AUTO_VARIABLE_PDIFF) { // return QObject::tr("Auto TriLevel (Variable PDIFF)"); // } 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 devices 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_TRILEVEL_AUTO_VARIABLE_PDIFF) { return QObject::tr("Min EEPAP %1 Max EEPAP %2 PDIFF %3-%4 (%5)").arg(validPressure(settings_min(CPAP_EEPAPLo))). arg(validPressure(settings_max(CPAP_EEPAPHi))). 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.5.1/oscar/SleepLib/day.h000066400000000000000000000276071450332542600172370ustar00rootroot00000000000000/* 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 device records per day */ class OneTypePerDay { }; class Machine; class Session; /*! \class Day \brief Contains a list of all Sessions for single date, for a single device */ class Day { public: Day(); ~Day(); //! \brief Add a new device to this day record bool addMachine(Machine *m); //! \brief Returns a device record if present of specified device type Machine *machine(MachineType type); //! \brief Returns a list of sessions for the specified device 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 device type inline bool hasMachine(MachineType mt) const { return machines.contains(mt); } //! \brief Returns true if Day has specific device record bool hasMachine(Machine * mach); //! \brief Returns true if any sessions have records matching specific device type bool searchMachine(MachineType mt); //! \brief Removes any lingering references to a specific device 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 device type for this day qint64 first(MachineType type); //! \brief Returns the last session time of this device 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 device type qint64 total_time(MachineType type); //! \brief Returns true if this day has enabled sessions for supplied device 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 device 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.5.1/oscar/SleepLib/deviceconnection.cpp000066400000000000000000000731131450332542600223250ustar00rootroot00000000000000/* 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.5.1/oscar/SleepLib/deviceconnection.h000066400000000000000000000272131450332542600217720ustar00rootroot00000000000000/* 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; SerialPortInfo& operator=(const SerialPortInfo & other) = default; protected: SerialPortInfo(const class QSerialPortInfo & other); QHash m_info; friend class DeviceConnectionManager; }; #endif // DEVICECONNECTION_H OSCAR-code-v1.5.1/oscar/SleepLib/event.cpp000066400000000000000000000176621450332542600201360ustar00rootroot00000000000000/* 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.5.1/oscar/SleepLib/event.h000066400000000000000000000156361450332542600176020ustar00rootroot00000000000000/* 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.5.1/oscar/SleepLib/importcontext.cpp000066400000000000000000000151051450332542600217220ustar00rootroot00000000000000/* 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. */ #define TEST_MACROS_ENABLEDoff #include #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 device // 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 device'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 Device"), QString(QObject::tr("Your %1 CPAP Device (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 device.")) ,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("Device Untested"), QObject::tr("Your %1 CPAP Device (Model %2) has not been tested yet.").arg(info.brand).arg(info.modelnumber) +"\n\n"+ QObject::tr("It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device'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("Device Unsupported"), QObject::tr("Sorry, your %1 CPAP Device (%2) is not supported yet.").arg(info.brand).arg(info.modelnumber) +"\n\n"+ QObject::tr("The developers need a .zip copy of this device's SD card and matching clinician .pdf reports to make it work with OSCAR.") ,QMessageBox::Ok); } OSCAR-code-v1.5.1/oscar/SleepLib/importcontext.h000066400000000000000000000077111450332542600213730ustar00rootroot00000000000000/* 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 device'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 devices. 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.5.1/oscar/SleepLib/journal.cpp000066400000000000000000000255631450332542600204660ustar00rootroot00000000000000/* 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. */ #define TEST_MACROS_ENABLEDoff #include #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 Device 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 device 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.5.1/oscar/SleepLib/journal.h000066400000000000000000000031421450332542600201200ustar00rootroot00000000000000/* 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& operator=(const Bookmark & other) = default; 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.5.1/oscar/SleepLib/loader_plugins/000077500000000000000000000000001450332542600213045ustar00rootroot00000000000000OSCAR-code-v1.5.1/oscar/SleepLib/loader_plugins/cms50_loader.cpp000066400000000000000000000500161450332542600242670ustar00rootroot00000000000000/* 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 // The qt5.15 obsolescence of hex requires this change. // this solution to QT's obsolescence is only used in debug statements #if QT_VERSION >= QT_VERSION_CHECK(5,15,0) #define QTHEX Qt::hex #define QTDEC Qt::dec #else #define QTHEX hex #define QTDEC dec #endif 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 << QTHEX << buffer.at(idx + 1) << buffer.at(idx + 2) << ":" << QTDEC << 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.5.1/oscar/SleepLib/loader_plugins/cms50_loader.h000066400000000000000000000041451450332542600237360ustar00rootroot00000000000000/* 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 #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; QElapsedTimer 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.5.1/oscar/SleepLib/loader_plugins/cms50f37_loader.cpp000066400000000000000000000711241450332542600246120ustar00rootroot00000000000000/* 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 // The qt5.15 obsolescence of hex requires this change. // this solution to QT's obsolescence is only used in debug statements #if QT_VERSION >= QT_VERSION_CHECK(5,15,0) #define QTHEX Qt::hex #define QTDEC Qt::dec #else #define QTHEX hex #define QTDEC dec #endif 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); QElapsedTimer 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); QElapsedTimer 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); QElapsedTimer 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); QElapsedTimer 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); QElapsedTimer 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); QElapsedTimer 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); QElapsedTimer 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); QElapsedTimer 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); QElapsedTimer 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::asprintf("%02i%02i",buffer.at(idx+4), buffer.at(idx+5)).toInt(); month = QString::asprintf("%02i", buffer.at(idx+6)).toInt(); day = QString::asprintf("%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::asprintf("%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?" << QTHEX << (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::asprintf("%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::asprintf("%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::asprintf("%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; QElapsedTimer 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::asprintf("%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(); QElapsedTimer 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"; } QElapsedTimer 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() << "Registering CMS50F37Loader"; RegisterLoader(new CMS50F37Loader()); cms50f37_initialized = true; } OSCAR-code-v1.5.1/oscar/SleepLib/loader_plugins/cms50f37_loader.h000066400000000000000000000065361450332542600242640ustar00rootroot00000000000000/* 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 #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; QElapsedTimer 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.5.1/oscar/SleepLib/loader_plugins/dreem_loader.cpp000066400000000000000000000222441450332542600244360ustar00rootroot00000000000000/* 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.5.1/oscar/SleepLib/loader_plugins/dreem_loader.h000066400000000000000000000034551450332542600241060ustar00rootroot00000000000000/* 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.5.1/oscar/SleepLib/loader_plugins/edfparser.cpp000066400000000000000000000326471450332542600237770ustar00rootroot00000000000000/* 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; } bool EDFInfo::Open(const QByteArray &data) { fileData = data; if (fileData.size() <= EDFHeaderSize) { fileData.clear();; qDebug() << "EDFInfo::Open() buffer too short"; return false; } // TODO AXT filename = "bytearray"; 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); // TODO AXT // 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; } bool ret = ParseSignalData(); fileData.clear(); return ret; } bool EDFInfo::ParseSignalData() { // 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.5.1/oscar/SleepLib/loader_plugins/edfparser.h000066400000000000000000000160231450332542600234320ustar00rootroot00000000000000/* 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; // EDFType is used by all the edf loaders - resmed and sleepstyle, so far enum EDFType { EDF_UNKNOWN, EDF_BRP, EDF_PLD, EDF_SAD, EDF_EVE, EDF_CSL, EDF_AEV, EDF_RT }; /*! \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 Open(const QByteArray &data); virtual bool Parse(); //! \brief Parse the EDF+ file into the EDFheaderQT. Must call Open(..) first. virtual bool ParseSignalData(); //! \brief Parse the signal data 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 protected: //! \brief This is the array of signal descriptors and values char *signalPtr; long filesize; long datasize; long pos; bool eof; //! \brief This is the array holding the EDF file data QByteArray fileData; //! \brief Read 16 bit word of data from the EDF+ data stream qint16 Read16(); 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 //! \brief The EDF+ files header structure, used as a place holder while processing the text data. EDFHeaderRaw *hdrPtr; }; #endif // EDFPARSER_H OSCAR-code-v1.5.1/oscar/SleepLib/loader_plugins/icon_loader.cpp000066400000000000000000000711021450332542600242670ustar00rootroot00000000000000/* 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" // The qt5.15 obsolescence of hex requires this change. // this solution to QT's obsolescence is only used in debug statements #if QT_VERSION >= QT_VERSION_CHECK(5,15,0) #define QTHEX Qt::hex #define QTDEC Qt::dec #else #define QTHEX hex #define QTDEC dec #endif 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 device 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 device 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 device 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 device 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 device 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 device information strings. // magic? 0201 // version 1.5.0 // serial number 12 digits // Device Series "ICON" // Device 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" << QTHEX << 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; Q_UNUSED( totalrecs ); 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.5.1/oscar/SleepLib/loader_plugins/icon_loader.h000066400000000000000000000073561450332542600237460ustar00rootroot00000000000000/* 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 device 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 device class name of this CPAP device, "FPIcon" virtual const QString & loaderName() { return fpicon_class_name; } // ! \brief Creates a device 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.5.1/oscar/SleepLib/loader_plugins/intellipap_loader.cpp000066400000000000000000003114641450332542600255100ustar00rootroot00000000000000/* 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 device mode"; } if (!info.serial.isEmpty()) { mach = p_profile->CreateMachine(info); } if (!mach) { qDebug() << "Couldn't get Intellipap device 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 device but we're // not sure how those measures relate to other device'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 devices? //////////////////////////////////////////////////////////////////////////// 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 device 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 device 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 device database record if it doesn't exist already mach = p_profile->CreateMachine(info); if (mach == nullptr) { qWarning() << "Could not create DV6 device 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 device database's info field with this device 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 device) 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.5.1/oscar/SleepLib/loader_plugins/intellipap_loader.h000066400000000000000000000070671450332542600251560ustar00rootroot00000000000000/* 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 device 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 device class name of this IntelliPap, "Intellipap" virtual const QString &loaderName() { return intellipap_class_name; } //! \brief Creates a device 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.5.1/oscar/SleepLib/loader_plugins/md300w1_loader.cpp000066400000000000000000000145001450332542600244310ustar00rootroot00000000000000/* 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::asprintf("%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.5.1/oscar/SleepLib/loader_plugins/md300w1_loader.h000066400000000000000000000041341450332542600241000ustar00rootroot00000000000000/* 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 #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; QElapsedTimer 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.5.1/oscar/SleepLib/loader_plugins/mseries_loader.cpp000066400000000000000000000350221450332542600250070ustar00rootroot00000000000000/* 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 "mseries_loader.h" #ifdef REMSTAR_M_SUPPORT #include #include // The qt5.15 obsolescence of hex requires this change. // this solution to QT's obsolescence is only used in debug statements #if QT_VERSION >= QT_VERSION_CHECK(5,15,0) #define QTHEX Qt::hex #define QTDEC Qt::dec #else #define QTHEX hex #define QTDEC dec #endif 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 << QTHEX << 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 << QTHEX << 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 << QTHEX << 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 << QTHEX << 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::asprintf("%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; } #endif // REMSTAR_M_SUPPORT OSCAR-code-v1.5.1/oscar/SleepLib/loader_plugins/mseries_loader.h000066400000000000000000000050641450332542600244570ustar00rootroot00000000000000/* -*- 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/appsettings.h" #ifdef REMSTAR_M_SUPPORT #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 device 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 device 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 // REMSTAR_M_SUPPORT #endif // MSERIES_LOADER_H OSCAR-code-v1.5.1/oscar/SleepLib/loader_plugins/prisma_loader.cpp000066400000000000000000001204351450332542600246360ustar00rootroot00000000000000/* SleepLib Prisma 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 #include "SleepLib/schema.h" #include "SleepLib/importcontext.h" #include "prisma_loader.h" #include "SleepLib/session.h" #include "SleepLib/calcs.h" #include "rawdata.h" #define MINIZ_NO_ZLIB_COMPATIBLE_NAMES #include "../thirdparty/miniz.h" #define PRISMA_SMART_CONFIG_FILE "config.pscfg" #define PRISMA_LINE_CONFIG_FILE "config.pcfg" #define PRISMA_LINE_THERAPY_FILE "therapy.pdat" //******************************************************************************************** /// IMPORTANT!!! //******************************************************************************************** // Please INCREMENT the prisma_data_version in prisma_loader.h when making changes to this // loader that change loader behaviour or modify channels. //******************************************************************************************** // parameters ChannelID Prisma_Mode = 0, Prisma_SoftPAP = 0, Prisma_BiSoft = 0, Prisma_PSoft = 0, Prisma_PSoft_Min = 0, Prisma_AutoStart = 0, Prisma_Softstart_Time = 0, Prisma_Softstart_TimeMax = 0, Prisma_Softstart_Pressure = 0, Prisma_TubeType = 0, Prisma_PMaxOA = 0, Prisma_HumidifierLevel = 0; // waveforms ChannelID Prisma_ObstructLevel = 0, Prisma_rMVFluctuation = 0, Prisma_rRMV= 0, Prisma_PressureMeasured = 0, Prisma_FlowFull = 0, Prisma_EEPAP = 0; // events ChannelID Prisma_Artifact = 0, Prisma_CriticalLeak = 0, Prisma_DeepSleep = 0, Prisma_TimedBreath = 0; // epoch events // The device evaluates 2 minute long epochs, and catagorizes them based on the events that occur during that epoch. // eSO: Severe Obstruction // eMO: Mild Obstruction // eS: Snore // eF: Flattening/Flow limitation ChannelID Prisma_eSO = 0, Prisma_eMO = 0, Prisma_eS = 0, Prisma_eF = 0; // this "virtual" config setting is used to indicate, if the selected Prisma Mode is not fully supported. ChannelID Prisma_Warning = 0; QString PrismaLoader::PresReliefLabel() { return QString("SoftPAP: "); } ChannelID PrismaLoader::PresReliefMode() { return Prisma_SoftPAP; } ChannelID PrismaLoader::CPAPModeChannel() { return Prisma_Mode; } //******************************************************************************************** bool WMEDFInfo::ParseSignalData() { int bytes = 0; for (auto & sig : edfsignals) { if (sig.reserved == "#1") { bytes += 1 * sig.sampleCnt; } else if (sig.reserved == "#2") { bytes += 2 * sig.sampleCnt; } } // allocate the arrays for the signal values edfHdr.num_data_records = (fileData.size() - edfHdr.num_header_bytes) / bytes; 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]; } for (int recNo = 0; recNo < edfHdr.num_data_records; recNo++) { for (auto & sig : edfsignals) { for (int j=0;j= 0) { sig.dataArray[recNo*sig.sampleCnt+j]=(qint16)Read8U(); } else { sig.dataArray[recNo*sig.sampleCnt+j]=(qint16)Read8S(); } } else if (sig.reserved == "#2") { qint16 t=Read16(); sig.dataArray[recNo*sig.sampleCnt+j]=t; } } } } return true; } qint8 WMEDFInfo::Read8S() { if ((pos + 1) > datasize) { eof = true; return 0; } qint8 res = *(qint8 *)&signalPtr[pos]; pos += 1; return res; } quint8 WMEDFInfo::Read8U() { if ((pos + 1) > datasize) { eof = true; return 0; } quint8 res = *(quint8 *)&signalPtr[pos]; pos += 1; return res; } //******************************************************************************************** void PrismaImport::run() { qDebug() << "PRISMA IMPORT" << sessionid; if (!wmedf.Open(signalData)) { qWarning() << "Signal file open failed"; return; } if (!wmedf.Parse()) { qWarning() << "Signal file parsing failed"; return; } eventFile = new PrismaEventFile(eventData); startdate = qint64(wmedf.edfHdr.startdate_orig.toTime_t()) * 1000L; enddate = startdate + wmedf.GetDuration() * qint64(wmedf.GetNumDataRecords()) * 1000; session = loader->context()->CreateSession(sessionid); session->really_set_first(startdate); session->really_set_last(enddate); // TODO AXT: set physical limits from a config file session->setPhysMax(CPAP_Pressure, 20); session->setPhysMin(CPAP_Pressure, 4); session->setPhysMax(CPAP_IPAP, 20); session->setPhysMin(CPAP_IPAP, 4); session->setPhysMax(CPAP_EPAP, 20); session->setPhysMin(CPAP_EPAP, 4); // set session parameters auto parameters = eventFile->getParameters(); // TODO AXT: extract if (parameters.contains(PRISMA_SMART_MODE)) { switch(parameters[PRISMA_SMART_MODE]) { case PRISMA_MODE_CPAP: session->settings[CPAP_Mode] = (int)MODE_CPAP; session->settings[Prisma_Mode] = (int)PRISMA_COMBINED_MODE_CPAP; break; case PRISMA_MODE_APAP: session->settings[CPAP_Mode] = (int)MODE_APAP; switch (parameters[PRISMA_SMART_APAP_DYNAMIC]) { case PRISMA_APAP_MODE_STANDARD: session->settings[Prisma_Mode] = (int)PRISMA_COMBINED_MODE_APAP_STD; break; case PRISMA_APAP_MODE_DYNAMIC: session->settings[Prisma_Mode] = (int)PRISMA_COMBINED_MODE_APAP_DYN; break; } break; } session->settings[CPAP_PressureMin] = parameters[PRISMA_SMART_PRESSURE] / 100; session->settings[CPAP_PressureMax] = parameters[PRISMA_SMART_PRESSURE_MAX] / 100; session->settings[Prisma_SoftPAP] = parameters[PRISMA_SMART_SOFTPAP]; session->settings[Prisma_PSoft] = parameters[PRISMA_SMART_PSOFT] / 100.0; session->settings[Prisma_PSoft_Min] = parameters[PRISMA_SMART_PSOFT_MIN] / 100; session->settings[Prisma_AutoStart] = parameters[PRISMA_SMART_AUTOSTART]; session->settings[Prisma_Softstart_Time] = parameters[PRISMA_SMART_SOFTSTART_TIME]; session->settings[Prisma_Softstart_TimeMax] = parameters[PRISMA_SMART_SOFTSTART_TIME_MAX]; if (parameters.contains(PRISMA_SMART_TUBE_TYPE)) { session->settings[Prisma_TubeType] = parameters[PRISMA_SMART_TUBE_TYPE] / 10.0; } session->settings[Prisma_PMaxOA] = parameters[PRISMA_SMART_PMAXOA] / 100; // TODO // session->settings[Prisma_HumidifierLevel] = parameters[PRISMA_SMART_HUMIDLEVEL]; } bool found = true; if (parameters.contains(PRISMA_LINE_MODE)) { if (parameters[PRISMA_LINE_MODE] == PRISMA_MODE_AUTO_ST || parameters[PRISMA_LINE_MODE] == PRISMA_MODE_AUTO_S) { if (parameters[PRISMA_LINE_EXTRA_OBSTRUCTION_PROTECTION] != 1) { if (parameters[PRISMA_LINE_AUTO_PDIFF] == 1) { session->settings[CPAP_Mode] = (int)MODE_BILEVEL_AUTO_VARIABLE_PS; }else{ session->settings[CPAP_Mode] = (int)MODE_BILEVEL_AUTO_FIXED_PS; } }else{ session->settings[CPAP_Mode] = (int)MODE_TRILEVEL_AUTO_VARIABLE_PDIFF; } session->settings[Prisma_BiSoft] = parameters[PRISMA_LINE_EXTRA_OBSTRUCTION_PROTECTION]; session->settings[CPAP_EEPAPLo] = parameters[PRISMA_LINE_EEPAP_MIN] / 100.0; session->settings[CPAP_EEPAPHi] = parameters[PRISMA_LINE_EEPAP_MAX] / 100.0; session->settings[CPAP_EPAP] = parameters[PRISMA_LINE_EPAP] / 100.0; session->settings[CPAP_IPAP] = parameters[PRISMA_LINE_IPAP] / 100.0; session->settings[CPAP_IPAPHi] = parameters[PRISMA_LINE_IPAP_MAX] / 100.0; session->settings[CPAP_PSMin] = parameters[PRISMA_LINE_PDIFF_NORM] / 100.0; session->settings[CPAP_PSMax] = parameters[PRISMA_LINE_PDIFF_MAX] / 100.0; session->settings[Prisma_Softstart_Pressure] = parameters[PRISMA_LINE_SOFT_START_PRESS] / 100.0; session->settings[Prisma_Softstart_Time] = parameters[PRISMA_LINE_SOFT_START_TIME]; session->settings[Prisma_AutoStart] = parameters[PRISMA_LINE_AUTOSTART]; if (parameters.contains(PRISMA_SMART_TUBE_TYPE)) { session->settings[Prisma_TubeType] = parameters[PRISMA_LINE_TUBE_TYPE] / 10.0; } // Indicate partial support session->settings[Prisma_Warning] = 2; } switch(parameters[PRISMA_LINE_MODE]) { case PRISMA_MODE_AUTO_ST: // TODO AXT // Was not sure which mode this should be mapped, maybe we need to intorudce new modes // Setting/parameter mapping should be reviewed and tested // session->settings[CPAP_Mode] = (int)MODE_BILEVEL_AUTO_VARIABLE_PS; ??? session->settings[Prisma_Mode] = (int)PRISMA_COMBINED_MODE_AUTO_ST; break; case PRISMA_MODE_AUTO_S: session->settings[Prisma_Mode] = (int)PRISMA_COMBINED_MODE_AUTO_S; break; case PRISMA_MODE_ACSV: // TODO AXT: its possible that based on PDIFF setting we should choose between MODE_ASV // and MODE_ASV_VARIABLE_EPAP here session->settings[CPAP_Mode] = (int)MODE_ASV; session->settings[Prisma_Mode] = (int)PRISMA_COMBINED_MODE_ACSV; session->settings[CPAP_EEPAPLo] = parameters[PRISMA_LINE_EEPAP_MIN] / 100.0; session->settings[CPAP_EEPAPHi] = parameters[PRISMA_LINE_EEPAP_MAX] / 100.0; session->settings[CPAP_EPAP] = parameters[PRISMA_LINE_EPAP] / 100.0; session->settings[CPAP_IPAP] = parameters[PRISMA_LINE_IPAP] / 100.0; session->settings[CPAP_IPAPHi] = parameters[PRISMA_LINE_IPAP_MAX] / 100.0; session->settings[CPAP_PSMin] = parameters[PRISMA_LINE_PDIFF_NORM] / 100.0; session->settings[CPAP_PSMax] = parameters[PRISMA_LINE_PDIFF_MAX] / 100.0; session->settings[Prisma_Softstart_Time] = parameters[PRISMA_LINE_SOFT_START_TIME]; session->settings[Prisma_Softstart_Pressure] = parameters[PRISMA_LINE_SOFT_START_PRESS] / 100.0; session->settings[Prisma_AutoStart] = parameters[PRISMA_LINE_AUTOSTART]; if (parameters.contains(PRISMA_SMART_TUBE_TYPE)) { session->settings[Prisma_TubeType] = parameters[PRISMA_LINE_TUBE_TYPE] / 10.0; } // Indicate partial support session->settings[Prisma_Warning] = 2; break; case PRISMA_MODE_APAP: session->settings[CPAP_Mode] = (int)MODE_APAP; switch (parameters[PRISMA_LINE_APAP_DYNAMIC]) { case PRISMA_APAP_MODE_STANDARD: session->settings[Prisma_Mode] = (int)PRISMA_COMBINED_MODE_APAP_STD; break; case PRISMA_APAP_MODE_DYNAMIC: session->settings[Prisma_Mode] = (int)PRISMA_COMBINED_MODE_APAP_DYN; break; } session->settings[CPAP_PressureMin] = parameters[PRISMA_LINE_EPAP] / 100.0; session->settings[CPAP_PressureMax] = parameters[PRISMA_LINE_IPAP] / 100.0; session->settings[Prisma_AutoStart] = parameters[PRISMA_LINE_AUTOSTART]; session->settings[Prisma_SoftPAP] = parameters[PRISMA_LINE_SOFT_PAP_LEVEL]; session->settings[Prisma_Softstart_Time] = parameters[PRISMA_LINE_SOFT_START_TIME]; session->settings[Prisma_Softstart_Pressure] = parameters[PRISMA_LINE_SOFT_START_PRESS] / 100.0; if (parameters.contains(PRISMA_SMART_TUBE_TYPE)) { session->settings[Prisma_TubeType] = parameters[PRISMA_LINE_TUBE_TYPE] / 10.0; } break; default: found = false; break; } if (!found) { // Indicate mode not supported session->settings[Prisma_Warning] = 1; } } // add waveforms // common waveforms, these exists on all prisma devices AddWaveform(CPAP_MaskPressure, QString("Pressure")); AddWaveform(CPAP_FlowRate, QString("RespFlow")); AddWaveform(CPAP_Leak, QString("LeakFlowBreath")); AddWaveform(Prisma_ObstructLevel, QString("ObstructLevel")); // prisma smart // waweforms specific for prisma smart / soft devices AddWaveform(CPAP_EPAP, QString("EPAP")); AddWaveform(CPAP_IPAP, QString("IPAP")); AddWaveform(Prisma_rMVFluctuation, QString("rMVFluctuation")); AddWaveform(Prisma_rRMV, QString("rRMV")); AddWaveform(Prisma_PressureMeasured, QString("PressureMeasured")); AddWaveform(Prisma_FlowFull, QString("FlowFull")); // The CPAPPressure exitst but is not used // AddWaveform(CPAP_Pressure, QString("CPAPPressure")); // prisma line AddWaveform(CPAP_EPAP, QString("EPAPsoll")); AddWaveform(CPAP_IPAP, QString("IPAPsoll")); AddWaveform(CPAP_EEPAP, QString("EEPAPsoll")); // Channels that exist on varous Prisma Line devices, but are not handled yet // AddWaveform(CPAP_RespRate, "BreathFrequency"); // AddWaveform(CPAP_TidalVolume, "BreathVolume"); // AddWaveform(CPAP_IE, "InspExpirRel"); // AddWaveform(OXI_Pulse, "HeartFrequency"); // AddWaveform(OXI_SPO2, "SpO2"); // AddWaveform(CPAP_LeakTotal, QString("TotalLeakage")); // 20A, 25ST // MV.txt // rAMV.txt // 25S: // rMVFluctuation.txt // RSBI.txt // TotalLeakage.txt // add signals AddEvents(CPAP_Obstructive, PRISMA_EVENT_OBSTRUCTIVE_APNEA); AddEvents(CPAP_ClearAirway, PRISMA_EVENT_CENTRAL_APNEA); AddEvents(CPAP_Apnea, { PRISMA_EVENT_APNEA_LEAKAGE, PRISMA_EVENT_APNEA_HIGH_PRESSURE, PRISMA_EVENT_APNEA_MOVEMENT}); AddEvents(CPAP_Hypopnea, { PRISMA_EVENT_OBSTRUCTIVE_HYPOPNEA, PRISMA_EVENT_CENTRAL_HYPOPNEA}); AddEvents(CPAP_RERA, PRISMA_EVENT_RERA); AddEvents(CPAP_VSnore, PRISMA_EVENT_SNORE); AddEvents(CPAP_CSR, PRISMA_EVENT_CS_RESPIRATION); AddEvents(CPAP_FlowLimit, PRISMA_EVENT_FLOW_LIMITATION); AddEvents(Prisma_Artifact, PRISMA_EVENT_ARTIFACT); AddEvents(Prisma_CriticalLeak, PRISMA_EVENT_CRITICAL_LEAKAGE); AddEvents(Prisma_eSO, PRISMA_EVENT_EPOCH_SEVERE_OBSTRUCTION); AddEvents(Prisma_eMO, PRISMA_EVENT_EPOCH_MILD_OBSTRUCTION); AddEvents(Prisma_eF, PRISMA_EVENT_EPOCH_FLOW_LIMITATION); AddEvents(Prisma_eS, PRISMA_EVENT_EPOCH_SNORE); AddEvents(Prisma_DeepSleep, PRISMA_EVENT_EPOCH_DEEPSLEEP); AddEvents(Prisma_TimedBreath, PRISMA_EVENT_TIMED_BREATH); session->SetChanged(true); loader->context()->AddSession(session); } void PrismaImport::AddWaveform(ChannelID code, QString edfLabel) { EDFSignal * es = wmedf.lookupLabel(edfLabel); if (es != nullptr) { qint64 duration = wmedf.GetNumDataRecords() * wmedf.GetDuration() * 1000L; long recs = es->sampleCnt * wmedf.GetNumDataRecords(); double rate = double(duration) / double(recs); EventList *a = session->AddEventList(code, EVL_Waveform, es->gain, es->offset, 0, 0, rate); a->setDimension(es->physical_dimension); a->AddWaveform(startdate, es->dataArray, recs, duration); session->setPhysMin(code, es->physical_minimum); session->setPhysMax(code, es->physical_maximum); } } void PrismaImport::AddEvents(ChannelID channel, QList eventTypes) { EventList *eventList = nullptr; for (auto eventType : eventTypes) { QList events = eventFile->getEvents(eventType); for (auto event: events) { if (eventList == nullptr) { eventList = session->AddEventList(channel, EVL_Event, 1.0, 0.0, 0.0, 0.0, 0.0, true); } eventList ->AddEvent(startdate + event.endTime(), event.duration(), event.strength()); } } session->AddEventList(channel, EVL_Event); } //******************************************************************************************** PrismaEventFile::PrismaEventFile(QByteArray &buffer) { QDomDocument dom; dom.setContent(buffer); QDomElement root = dom.documentElement(); QDomNodeList deviceEventNodelist = root.elementsByTagName("DeviceEvent"); for(int i=0; i < deviceEventNodelist.count(); i++) { QDomElement node=deviceEventNodelist.item(i).toElement(); int eventId = node.attribute("DeviceEventID").toInt(); if (eventId == 0) { int parameterId = node.attribute("ParameterID").toInt(); int value = node.attribute("NewValue").toInt(); m_parameters[parameterId] = value; } } QDomNodeList respEventNodelist = root.elementsByTagName("RespEvent"); for(int i=0; i < respEventNodelist.count(); i++) { QDomElement node=respEventNodelist.item(i).toElement(); int eventId = node.attribute("RespEventID").toInt(); const int time_quantum = 10; int endTime = node.attribute("EndTime").toInt() * 1000 / time_quantum; int duration = node.attribute("Duration").toInt() / time_quantum; int pressure = node.attribute("Pressure").toInt(); int strength = node.attribute("Strength").toInt(); m_events[eventId].append(PrismaEvent(endTime, duration, pressure, strength)); } qDebug() << "SIZE" << m_events.size(); } //******************************************************************************************** // NOTE: was created for PrismaSmart, should be extended to support PrismaLines as they stabilize struct PrismaTestedModel { QString deviceId; const char* name; }; static const PrismaTestedModel s_PrismaTestedModels[] = { { "0x92", "Prisma Smart" }, { "0x91", "Prisma Soft" }, { "", ""} }; PrismaModelInfo s_PrismaModelInfo; PrismaModelInfo::PrismaModelInfo () { for (int i = 0; !s_PrismaTestedModels[i].deviceId.isEmpty(); i++) { const PrismaTestedModel & model = s_PrismaTestedModels[i]; m_modelNames[model.deviceId] = model.name; } } bool PrismaModelInfo::IsTested(const QString & deviceId) const { return m_modelNames.contains(deviceId); }; const char* PrismaModelInfo::Name(const QString & deviceId) const { const char* name; if (m_modelNames.contains(deviceId)) { name = m_modelNames[deviceId]; } else { name = "Unknown Model"; } return name; }; //******************************************************************************************** PrismaLoader::PrismaLoader() { m_type = MT_CPAP; } PrismaLoader::~PrismaLoader() { } bool PrismaLoader::Detect(const QString & selectedPath) { QFile prismaSmartConfigFile(selectedPath + QDir::separator() + PRISMA_SMART_CONFIG_FILE); QFile prismaLineConfigFile(selectedPath + QDir::separator() + PRISMA_LINE_CONFIG_FILE); return prismaSmartConfigFile.exists() || prismaLineConfigFile.exists(); } int PrismaLoader::Open(const QString & selectedPath) { if (m_ctx == nullptr) { qWarning() << "PrismaLoader::Open() called without a valid m_ctx object present"; return 0; } Q_ASSERT(m_ctx); qDebug() << "Prisma opening" << selectedPath; QFile prismaSmartConfigFile(selectedPath + QDir::separator() + PRISMA_SMART_CONFIG_FILE); QFile prismaLineConfigFile(selectedPath + QDir::separator() + PRISMA_LINE_CONFIG_FILE); MachineInfo info = PeekInfoFromConfig(selectedPath); if (info.type == MT_UNKNOWN) { emit deviceIsUnsupported(info); return -1; } m_ctx->CreateMachineFromInfo(info); if (!s_PrismaModelInfo.IsTested(info.modelnumber)) { qDebug() << info.modelnumber << "untested"; emit deviceIsUntested(info); } m_abort = false; emit setProgressValue(0); emit updateMessage(QObject::tr("Getting Ready...")); QCoreApplication::processEvents(); emit updateMessage(QObject::tr("Backing Up Files...")); QCoreApplication::processEvents(); QString backupPath = context()->GetBackupPath() + selectedPath.section("/", -1); if (QDir::cleanPath(selectedPath).compare(QDir::cleanPath(backupPath)) != 0) { copyPath(selectedPath, backupPath); } emit updateMessage(QObject::tr("Scanning Files...")); QCoreApplication::processEvents(); if (prismaSmartConfigFile.exists()) // TODO AXT || !configFile.isReadable() fails { // TODO AXT extract char out[12]; int serialInDecimal; sscanf(info.serial.toLocal8Bit().data() , "%x", &serialInDecimal); snprintf(out, 12, "%010d", serialInDecimal); ScanFiles(info, selectedPath + QDir::separator() + out); } else if (prismaLineConfigFile.exists()) { // TODO AXT: this is just a quick hack to load the zipped therapy files for the // Prisma Line devices. This should be extracted into a loader class, like the // PrismaEventFile. If this extraction is done, then loading the machine info // could become much easier. QSet sessions; QHash eventFiles; QHash signalFiles; QFile prismaLineTherapyFile(selectedPath + QDir::separator() + PRISMA_LINE_THERAPY_FILE); if (!prismaLineTherapyFile.exists()) { // TODO AXT || !configFile.isReadable() fails qDebug() << "Prisma line therapy file error" << prismaLineTherapyFile.fileName(); return 0; } if (!prismaLineTherapyFile.open(QIODevice::ReadOnly)) { qDebug() << "Prisma line therapy file not readable" << prismaLineTherapyFile.fileName(); return 0; } QByteArray therapyData = prismaLineTherapyFile.readAll(); prismaLineTherapyFile.close(); mz_bool status; mz_zip_archive zip_archive; mz_zip_archive_file_stat file_stat; memset(&zip_archive, 0, sizeof(zip_archive)); status = mz_zip_reader_init_mem(&zip_archive, (const void*)therapyData.constData(), therapyData.size(), 0); if (!status) { qDebug() << "mz_zip_reader_init_file() failed!"; return 0; } int n = mz_zip_reader_get_num_files(&zip_archive); for (int i = 0; i < n; ++i) { if (!mz_zip_reader_file_stat(&zip_archive, i, &file_stat)) { qDebug() << "mz_zip_reader_file_stat() failed!"; mz_zip_reader_end(&zip_archive); return 0; } qDebug() << file_stat.m_filename; QString fileName(file_stat.m_filename); if (fileName.contains("event_") && fileName.endsWith(".xml")) { SessionID sid = fileName.mid(fileName.size()-4-6,6).toLong(); sessions += sid; eventFiles[sid] = fileName; } if (fileName.contains("signal_") && fileName.endsWith(".wmedf")) { SessionID sid = fileName.mid(fileName.size()-6-6,6).toLong(); sessions += sid; signalFiles[sid] = fileName; } } qDebug() << sessions; for(auto & sid : sessions) { size_t uncomp_size_events; void *extract_events = mz_zip_reader_extract_file_to_heap(&zip_archive, eventFiles[sid].toLocal8Bit(), &uncomp_size_events, 0); QByteArray eventData((const char*)extract_events, uncomp_size_events); free(extract_events); size_t uncomp_size_signals; void *extract_signals = mz_zip_reader_extract_file_to_heap(&zip_archive, signalFiles[sid].toLocal8Bit(), &uncomp_size_signals, 0); QByteArray signalData((const char*)extract_signals, uncomp_size_signals); free(extract_signals); queTask(new PrismaImport(this, info, sid, eventData, signalData)); } mz_zip_reader_end(&zip_archive); } else { qDebug() << "Prisma config file error" << selectedPath; return 0; } int tasks = countTasks(); qDebug() << "Task count " << tasks; emit updateMessage(QObject::tr("Importing Sessions...")); QCoreApplication::processEvents(); runTasks(AppSetting->multithreading()); m_ctx->FlushUnexpectedMessages(); return tasks; } MachineInfo PrismaLoader::PeekInfo(const QString & selectedPath) { qDebug() << "PeekInfo " << selectedPath; if (!Detect(selectedPath)) return MachineInfo(); return PeekInfoFromConfig(selectedPath + QDir::separator() + PRISMA_SMART_CONFIG_FILE); } MachineInfo PrismaLoader::PeekInfoFromConfig(const QString & selectedPath) { QFile prismaSmartConfigFile(selectedPath + QDir::separator() + PRISMA_SMART_CONFIG_FILE); QFile prismaLineConfigFile(selectedPath + QDir::separator() + PRISMA_LINE_CONFIG_FILE); // TODO AXT, extract into ConfigFile class if (prismaSmartConfigFile.exists()) { if (!prismaSmartConfigFile.open(QIODevice::ReadOnly)) { return MachineInfo(); } MachineInfo info = newInfo(); QByteArray configData = prismaSmartConfigFile.readAll(); prismaSmartConfigFile.close(); QJsonDocument configDoc(QJsonDocument::fromJson(configData)); QJsonObject configObj = configDoc.object(); QJsonObject devObj = configObj["dev"].toObject(); info.modelnumber=configObj["devid"].toString(); info.model = s_PrismaModelInfo.Name(info.modelnumber); info.serial = devObj["sn"].toString(); info.series = devObj["hwversion"].toString(); // TODO AXT load propserties here, // we should use these to set the physical limits of the device info.properties["cica"] = "mica"; return info; } else if (prismaLineConfigFile.exists()) { // TODO AXT prismaLine machine info loader not supported at all at this time // first extract the therapy data loader in PrismaLoader::Open(), it will help // to solve this issue too if (!prismaLineConfigFile.open(QIODevice::ReadOnly)) { return MachineInfo(); } MachineInfo info = newInfo(); prismaLineConfigFile.close(); info.modelnumber=42; info.model = "Unknown PrismaLine"; info.serial = "0x42424242"; // TODO AXT load props info.properties["cica"] = "mica"; return info; } return MachineInfo(); } void PrismaLoader::ImportDataDir(QDir& dataDir, QSet& sessions, QHash& eventFiles, QHash& signalFiles) { dataDir.setFilter(QDir::NoDotAndDotDot | QDir::Files | QDir::NoSymLinks); dataDir.setSorting(QDir::Name); if (dataDir.exists()) { for (auto & inputFile : dataDir.entryInfoList()) { QString fileName = inputFile.fileName().toLower(); if (fileName.startsWith("event_") && fileName.endsWith(".xml")) { SessionID sid = fileName.mid(6,fileName.size()-4-6).toLong(); sessions += sid; eventFiles[sid] = inputFile.canonicalFilePath(); } if (inputFile.fileName().toLower().startsWith("signal_") && inputFile.fileName().toLower().endsWith(".wmedf")) { SessionID sid = fileName.mid(7,fileName.size()-6-7).toLong(); sessions += sid; signalFiles[sid] = inputFile.canonicalFilePath(); } } } } // TODO AXT PrismaSmart specific, extract it into a parser class with the config files void PrismaLoader::ScanFiles(const MachineInfo& info, const QString & machinePath) { Q_ASSERT(m_ctx); qDebug() << "SCANFILES" << machinePath; QDir machineDir(machinePath); machineDir.setFilter(QDir::NoDotAndDotDot | QDir::Dirs | QDir::NoSymLinks); machineDir.setSorting(QDir::Name); QFileInfoList dayListing = machineDir.entryInfoList(); QSet sessions; QHash eventFiles; QHash signalFiles; qint64 ignoreBefore = m_ctx->IgnoreSessionsOlderThan().toMSecsSinceEpoch()/1000; bool ignoreOldSessions = m_ctx->ShouldIgnoreOldSessions(); qDebug() << "INFO " << ignoreBefore << " " << ignoreOldSessions; for (auto & dayDirInfo : dayListing) { QDir dayDir(dayDirInfo.canonicalFilePath()); dayDir.setFilter(QDir::NoDotAndDotDot | QDir::Dirs | QDir::NoSymLinks); dayDir.setSorting(QDir::Name); QFileInfoList subDirs = dayDir.entryInfoList(); if (subDirs.size() == 0) { ImportDataDir(dayDir, sessions, eventFiles, signalFiles); } else { for (auto & dataDirInfo: subDirs) { QDir dataDir(dataDirInfo.canonicalFilePath()); ImportDataDir(dataDir, sessions, eventFiles, signalFiles); } } } for(auto & sid : sessions) { QByteArray eventData; QByteArray signalData; QFile efile(eventFiles[sid]); if(efile.open(QIODevice::ReadOnly)) { eventData = efile.readAll(); efile.close(); } QFile sfile(signalFiles[sid]); if(sfile.open(QIODevice::ReadOnly)) { signalData = sfile.readAll(); sfile.close(); } queTask(new PrismaImport(this, info, sid, eventData, signalData)); } } using namespace schema; void PrismaLoader::initChannels() { Channel * chan = nullptr; channel.add(GRP_CPAP, chan = new Channel(Prisma_Mode=0xe400, SETTING, MT_CPAP, SESSION, "PrismaMode", QObject::tr("Mode"), QObject::tr("PAP Mode"), QObject::tr("Mode"), "", LOOKUP, Qt::green)); chan->addOption(PRISMA_COMBINED_MODE_UNKNOWN, QObject::tr("UNKNOWN")); chan->addOption(PRISMA_COMBINED_MODE_CPAP, QObject::tr("CPAP")); chan->addOption(PRISMA_COMBINED_MODE_APAP_STD, QObject::tr("APAP (std)")); chan->addOption(PRISMA_COMBINED_MODE_APAP_DYN, QObject::tr("APAP (dyn)")); chan->addOption(PRISMA_COMBINED_MODE_AUTO_S, QObject::tr("Auto S")); chan->addOption(PRISMA_COMBINED_MODE_AUTO_ST, QObject::tr("Auto S/T")); chan->addOption(PRISMA_COMBINED_MODE_ACSV, QObject::tr("AcSV")); channel.add(GRP_CPAP, chan = new Channel(Prisma_SoftPAP=0xe401, SETTING, MT_CPAP, SESSION, "Prisma_SoftPAP", QObject::tr("SoftPAP Mode"), QObject::tr("Pressure relief during exhalation"), QObject::tr("SoftPAP Mode"), "", LOOKUP, Qt::green)); chan->addOption(Prisma_SoftPAP_OFF, QObject::tr("Off")); chan->addOption(Prisma_SoftPAP_SLIGHT, QObject::tr("Slight")); chan->addOption(Prisma_SoftPAP_STANDARD, QObject::tr("Standard")); channel.add(GRP_CPAP, new Channel(Prisma_PSoft=0xe402, SETTING, MT_CPAP, SESSION, "Prisma_PSoft", QObject::tr("Softstart pressure"), QObject::tr("Pressure during soft start period"), QObject::tr("PSoft"), STR_UNIT_CMH2O, DEFAULT, Qt::green)); channel.add(GRP_CPAP, new Channel(Prisma_PSoft_Min=0xe403, SETTING, MT_CPAP, SESSION, "Prisma_PSoft_Min", QObject::tr("Softstart minimum pressure"), QObject::tr("Minimum pressure during soft start period"), QObject::tr("PSoftMin"), STR_UNIT_CMH2O, DEFAULT, Qt::green)); channel.add(GRP_CPAP, chan = new Channel(Prisma_AutoStart=0xe404, SETTING, MT_CPAP, SESSION, "Prisma_AutoStart", QObject::tr("Auto start"), QObject::tr("Automatically turn on the device by breathing"), QObject::tr("Auto start"), "", LOOKUP, Qt::green)); chan->addOption(0, STR_TR_Off); chan->addOption(1, STR_TR_On); channel.add(GRP_CPAP, new Channel(Prisma_Softstart_Time=0xe405, SETTING, MT_CPAP, SESSION, "Prisma_Softstart_Time", QObject::tr("Softstart time"), QObject::tr("Lenght of soft start period"), QObject::tr("Softstart time"), STR_UNIT_Minutes, LOOKUP, Qt::green)); channel.add(GRP_CPAP, new Channel(Prisma_Softstart_TimeMax=0xe406, SETTING, MT_CPAP, SESSION, "Prisma_Softstart_TimeMax", QObject::tr("Soft start maximum time"), QObject::tr("Maximum lenght of soft start period"), QObject::tr("Soft start max. time"), STR_UNIT_Minutes, LOOKUP, Qt::green)); channel.add(GRP_CPAP, new Channel(Prisma_Softstart_Pressure=0xe407, SETTING, MT_CPAP, SESSION, "Prisma_Softstart_Pressure", QObject::tr("Soft start pressure"), QObject::tr("Pressure during soft start period"), QObject::tr("Soft start pressure"), STR_UNIT_CMH2O, DEFAULT, Qt::green)); channel.add(GRP_CPAP, new Channel(Prisma_PMaxOA=0xe408, SETTING, MT_CPAP, SESSION, "Prisma_PMaxOA", QObject::tr("PMaxOA"), QObject::tr("PMaxOA"), QObject::tr("PMaxOA"), STR_UNIT_CMH2O, DEFAULT, Qt::green)); channel.add(GRP_CPAP, new Channel(CPAP_EEPAPLo=0xe409, SETTING, MT_CPAP, SESSION, "CPAP_EEPAPLo", QObject::tr("EEPAPMin"), QObject::tr("Lower End Expiratory Pressure"), QObject::tr("EEPAPMin"), STR_UNIT_CMH2O, DEFAULT, Qt::green)); channel.add(GRP_CPAP, new Channel(CPAP_EEPAPHi=0xe40a, SETTING, MT_CPAP, SESSION, "CPAP_EEPAPHi", QObject::tr("EEPAPMax"), QObject::tr("Higher End Expiratory Pressure"), QObject::tr("EEPAPMax"), STR_UNIT_CMH2O, DEFAULT, Qt::green)); channel.add(GRP_CPAP, new Channel(Prisma_HumidifierLevel=0xe40b, SETTING, MT_CPAP, SESSION, "Prisma_HumidLevel", QObject::tr("Humidifier level"), QObject::tr("Humidifier level"), QObject::tr("Humidifier level"), "", DEFAULT, Qt::green)); channel.add(GRP_CPAP, new Channel(Prisma_TubeType=0xe40c, SETTING, MT_CPAP, SESSION, "Prisma_TubeType", QObject::tr("Tube type"), QObject::tr("Tube type"), QObject::tr("Tube type"), STR_UNIT_CM, DEFAULT, Qt::green)); channel.add(GRP_CPAP, chan = new Channel(Prisma_Warning=0xe40d, SETTING, MT_CPAP, SESSION, "Prisma_Warning", QObject::tr("Warning"), QObject::tr("Warning"), QObject::tr("Warning"), "", LOOKUP, Qt::green)); chan->addOption(1, "Mode is not supported yet, please send sample data."); chan->addOption(2, "Mode partially supported, please send sample data."); channel.add(GRP_CPAP, chan = new Channel(Prisma_ObstructLevel=0xe440, WAVEFORM, MT_CPAP, SESSION, "Prisma_ObstructLevel", QObject::tr("Obstruction level"), QObject::tr("Obstruction level in percentage"), QObject::tr("Obstruction level"), STR_UNIT_Percentage, DEFAULT, QColor("light purple"))); chan->setUpperThreshold(100); chan->setLowerThreshold(0); channel.add(GRP_CPAP, chan = new Channel(Prisma_rMVFluctuation=0xe441, WAVEFORM, MT_CPAP, SESSION, "Prisma_rMVFluctuation", QObject::tr("rRMVFluctuation"), QObject::tr("Relative respiratory minute volume fluctuation"), QObject::tr("rRMVFluctuation"), STR_UNIT_Unknown, DEFAULT, QColor("light purple"))); chan->setUpperThreshold(16); chan->setLowerThreshold(0); channel.add(GRP_CPAP, new Channel(Prisma_rRMV=0xe442, WAVEFORM, MT_CPAP, SESSION, "Prisma_rRMV", QObject::tr("rRMV"), QObject::tr("Relative respiratory minute volume"), QObject::tr("rRMV"), STR_UNIT_Unknown, DEFAULT, QColor("light purple"))); channel.add(GRP_CPAP, new Channel(Prisma_PressureMeasured=0xe443, WAVEFORM, MT_CPAP, SESSION, "Prisma_PressureMeasured", QObject::tr("Measured pressure"), QObject::tr("Measured pressure"), QObject::tr("Measured pressure"), STR_UNIT_CMH2O, DEFAULT, QColor("black"))); channel.add(GRP_CPAP, new Channel(Prisma_FlowFull=0xe444, WAVEFORM, MT_CPAP, SESSION, "Prisma_FlowFull", QObject::tr("Full flow"), QObject::tr("Full flow"), QObject::tr("Full flow"), STR_UNIT_Unknown, DEFAULT, QColor("black"))); //Note: removed the channel, but keeping this code here, because of the channel id allocation, maybe we will bring it back in the future //channel.add(GRP_CPAP, new Channel(Prisma_SPRStatus=0xe445, WAVEFORM, MT_CPAP, SESSION, // "Prisma_SPRStatus", // QObject::tr("SPRStatus"), // QObject::tr("SPRStatus"), // QObject::tr("SPRStatus"), // STR_UNIT_Unknown, DEFAULT, QColor("black"))); channel.add(GRP_CPAP, new Channel(Prisma_Artifact=0xe446, SPAN, MT_CPAP, SESSION, "Prisma_Artifact", QObject::tr("Artefact"), QObject::tr("Irregularity in measured data, that doesn't represents a breathing event (e.g swallowing, coughing, or speaking)"), QObject::tr("ART"), STR_UNIT_Percentage, DEFAULT, QColor("salmon"))); channel.add(GRP_CPAP, new Channel(Prisma_CriticalLeak = 0xe447, SPAN, MT_CPAP, SESSION, "Prisma_CriticalLeak", QObject::tr("CriticalLeak"), QObject::tr("Mask leakage is above a critical treshold"), QObject::tr("CL"), STR_UNIT_EventsPerHour, DEFAULT, QColor("orchid"))); channel.add(GRP_CPAP, chan = new Channel(Prisma_eMO = 0xe448, SPAN, MT_CPAP, SESSION, "Prisma_eMO", QObject::tr("eMO"), QObject::tr("Epoch (2 mins) with Mild Obstruction"), QObject::tr("eMO"), STR_UNIT_Percentage, DEFAULT, QColor("orange"))); chan->setEnabled(false); channel.add(GRP_CPAP, chan = new Channel(Prisma_eSO = 0xe449, SPAN, MT_CPAP, SESSION, "Prisma_eSO", QObject::tr("eSO"), QObject::tr("Epoch (2 mins) with Severe Obstruction"), QObject::tr("eSO"), STR_UNIT_Percentage, DEFAULT, QColor("red"))); chan->setEnabled(false); channel.add(GRP_CPAP, chan = new Channel(Prisma_eS = 0xe44a, SPAN, MT_CPAP, SESSION, "Prisma_eS", QObject::tr("eS"), QObject::tr("Epoch (2 mins) with Snoring"), QObject::tr("eS"), STR_UNIT_Percentage, DEFAULT, QColor("light green"))); chan->setEnabled(false); channel.add(GRP_CPAP, chan = new Channel(Prisma_eF = 0xe44b, SPAN, MT_CPAP, SESSION, "Prisma_eFL", QObject::tr("eFL"), QObject::tr("Epoch (2 mins) with Flow Limitation"), QObject::tr("eFL"), STR_UNIT_Percentage, DEFAULT, QColor("yellow"))); chan->setEnabled(false); channel.add(GRP_CPAP, chan = new Channel(Prisma_DeepSleep = 0xe44c, SPAN, MT_CPAP, SESSION, "Prisma_DS", QObject::tr("Deep Sleep"), QObject::tr("Deep sleep, stable respiration"), QObject::tr("DS"), STR_UNIT_Percentage, DEFAULT, QColor("light blue"))); chan->setEnabled(false); channel.add(GRP_CPAP, chan = new Channel(Prisma_TimedBreath = 0xe44d, FLAG, MT_CPAP, SESSION, "Prisma_TB", QObject::tr("Timed breath"), QObject::tr("Machine Initiated Breath"), QObject::tr("TB"), STR_UNIT_Percentage, DEFAULT, QColor("purple"))); channel.add(GRP_CPAP, chan = new Channel(Prisma_BiSoft=0xe44e, SETTING, MT_CPAP, SESSION, "Prisma_BiSoft", QObject::tr("BiSoft Mode"), QObject::tr("BiSoft Mode"), QObject::tr("BiSoft Mode"), "", LOOKUP, Qt::green)); chan->addOption(Prisma_BiSoft_Off, QObject::tr("Off")); chan->addOption(Prisma_BiSoft_1, QObject::tr("BiSoft 1")); chan->addOption(Prisma_BiSoft_2, QObject::tr("BiSoft 2")); chan->addOption(Prisma_TriLevel, QObject::tr("TriLevel")); } bool PrismaLoader::initialized = false; void PrismaLoader::Register() { if (initialized) { return; } qDebug() << "Registering PrismaLoader"; RegisterLoader(new PrismaLoader()); initialized = true; } OSCAR-code-v1.5.1/oscar/SleepLib/loader_plugins/prisma_loader.h000066400000000000000000000231461450332542600243040ustar00rootroot00000000000000/* SleepLib Löwenstein Prisma 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 PRISMA_LOADER_H #define PRISMA_LOADER_H #include "SleepLib/machine_loader.h" #include "SleepLib/loader_plugins/edfparser.h" #include #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 prisma_data_version = 1; // //******************************************************************************************** const QString prisma_class_name = STR_MACH_Prisma; //******************************************************************************************** // NOTE: Prisma Smart and Prisma Line devices use distinct parameter id-s, but the correct solution // would be to transform these, into separate enums / parsers. enum Prisma_Parameters { PRISMA_SMART_MODE = 6, PRISMA_SMART_PRESSURE = 9, PRISMA_SMART_PRESSURE_MAX = 10, PRISMA_SMART_PSOFT_MIN = 11, PRISMA_SMART_PSOFT = 12, PRISMA_SMART_SOFTPAP = 13, PRISMA_SMART_APAP_DYNAMIC = 15, PRISMA_SMART_HUMIDLEVEL = 16, PRISMA_SMART_AUTOSTART = 17, PRISMA_SMART_SOFTSTART_TIME_MAX = 18, PRISMA_SMART_SOFTSTART_TIME = 19, PRISMA_SMART_TUBE_TYPE = 21, PRISMA_SMART_PMAXOA = 38, PRISMA_LINE_MODE = 1003, PRISMA_LINE_TI = 1011, PRISMA_LINE_TE = 1012, PRISMA_LINE_TARGET_VOLUME = 1016, PRISMA_LINE_IPAP_SPEED = 1017, PRISMA_LINE_HUMIDLEVEL = 1083, PRISMA_LINE_AUTOSTART = 1084, PRISMA_LINE_TUBE_TYPE = 1091, PRISMA_LINE_BACTERIUMFILTER = 1092, PRISMA_LINE_SOFT_PAP_LEVEL = 1123, PRISMA_LINE_SOFT_START_PRESS = 1125, PRISMA_LINE_SOFT_START_TIME = 1127, PRISMA_LINE_EEPAP_MIN = 1138, PRISMA_LINE_EEPAP_MAX = 1139, PRISMA_LINE_PDIFF_NORM = 1140, PRISMA_LINE_PDIFF_MAX = 1141, PRISMA_LINE_IPAP_MAX = 1199, PRISMA_LINE_IPAP = 1200, PRISMA_LINE_EPAP = 1201, // PRISMA_LINE_ALARM_LEAK_ACTIVE = 1202, // PRISMA_LINE_ALARM_DISCONNECTION_ACTIVE = 1203, PRISMA_LINE_APAP_DYNAMIC = 1209, PRISMA_LINE_EXTRA_OBSTRUCTION_PROTECTION = 1154, // BiSoft off = 0, BiSoft1 = 2, BiSoft2 = 3, TriLevel = 1 PRISMA_LINE_AUTO_PDIFF = 1219 }; // NOTE: Modes should be reverse engineered. Based on the samples we had, for now I didn't saw any overlap, // so I assumed, the mode settings is generic between the two device lines. If you run into an overlap, the // above mentioned parsers should be introduced. Enum values are coming from the prisma data files. enum Prisma_Mode { // Prisma Smart PRISMA_MODE_CPAP = 1, PRISMA_MODE_APAP = 2, // Prisma Line PRISMA_MODE_ACSV = 3, PRISMA_MODE_AUTO_S = 9, PRISMA_MODE_AUTO_ST = 10, }; enum Prisma_APAP_Mode { PRISMA_APAP_MODE_STANDARD = 1, PRISMA_APAP_MODE_DYNAMIC = 2, }; enum Prisma_SoftPAP_Mode { Prisma_SoftPAP_OFF = 0, Prisma_SoftPAP_SLIGHT = 1, Prisma_SoftPAP_STANDARD = 2 }; enum Prisma_BiSoft_Mode { Prisma_BiSoft_Off = 0, Prisma_BiSoft_1 = 2, Prisma_BiSoft_2 = 3, Prisma_TriLevel = 1, }; // NOTE: This enum represents a "virtual mode" which combines the main mode of the device with the APAP submode, // if it makes sense. The reason for this is, that we can see the Standard and Dynamic APAP modes on the statistics // page. Enum values are internal to the loader. We use -1 to indicate a mode that is not recognized. enum Prisma_Combined_Mode { PRISMA_COMBINED_MODE_CPAP = 1, PRISMA_COMBINED_MODE_APAP_STD = 2, PRISMA_COMBINED_MODE_APAP_DYN = 3, PRISMA_COMBINED_MODE_AUTO_S = 4, PRISMA_COMBINED_MODE_AUTO_ST = 5, PRISMA_COMBINED_MODE_ACSV = 6, PRISMA_COMBINED_MODE_UNKNOWN = -1, }; enum Prisma_Event_Type { PRISMA_EVENT_EPOCH_SEVERE_OBSTRUCTION = 1, PRISMA_EVENT_EPOCH_MILD_OBSTRUCTION = 2, PRISMA_EVENT_EPOCH_FLOW_LIMITATION = 3, PRISMA_EVENT_EPOCH_SNORE = 4, PRISMA_EVENT_EPOCH_PERIODIC_BREATHING = 5, PRISMA_EVENT_OBSTRUCTIVE_APNEA = 101, PRISMA_EVENT_CENTRAL_APNEA = 102, PRISMA_EVENT_APNEA_LEAKAGE = 103, PRISMA_EVENT_APNEA_HIGH_PRESSURE = 105, PRISMA_EVENT_APNEA_MOVEMENT = 106, PRISMA_EVENT_OBSTRUCTIVE_HYPOPNEA= 111, PRISMA_EVENT_CENTRAL_HYPOPNEA = 112, PRISMA_EVENT_HYPOPNEA_LEAKAGE = 113, PRISMA_EVENT_RERA = 121, PRISMA_EVENT_SNORE = 131, PRISMA_EVENT_ARTIFACT = 141, PRISMA_EVENT_FLOW_LIMITATION = 151, PRISMA_EVENT_CRITICAL_LEAKAGE = 161, PRISMA_EVENT_CS_RESPIRATION = 181, PRISMA_EVENT_TIMED_BREATH = 221, PRISMA_EVENT_EPOCH_DEEPSLEEP = 261, }; //******************************************************************************************** // Prisma WMEDF differs from the original EDF, by introducing 8 bit signal data channels. class WMEDFInfo : public EDFInfo { virtual bool ParseSignalData(); protected: qint8 Read8S(); quint8 Read8U(); }; //******************************************************************************************** class PrismaLoader; class PrismaEventFile; /*! \class PrismaImport * \brief Contains the functions to parse a single session... multithreaded */ class PrismaImport:public ImportTask { public: PrismaImport(PrismaLoader * l, const MachineInfo& m, SessionID s, QByteArray e, QByteArray d): loader(l), machineInfo(m), sessionid(s), eventData(e), signalData(d) {} virtual ~PrismaImport() {}; //! \brief PrismaImport thread starts execution here. virtual void run(); protected: PrismaLoader * loader; const MachineInfo & machineInfo; SessionID sessionid; QByteArray eventData; QByteArray signalData; qint64 startdate; qint64 enddate; WMEDFInfo wmedf; PrismaEventFile * eventFile; Session * session; void AddWaveform(ChannelID code, QString edfLabel); void AddEvents(ChannelID channel, Prisma_Event_Type eventType) { QList eventTypes = { eventType }; AddEvents(channel, eventTypes); } void AddEvents(ChannelID channel, QList eventTypes); }; //******************************************************************************************** /*! \class PrismaLoader \brief Löwenstein Prisma Loader Module */ class PrismaLoader : public CPAPLoader { Q_OBJECT static bool initialized; public: PrismaLoader(); virtual ~PrismaLoader(); //! \brief Detect if the given path contains a valid Folder structure virtual bool Detect(const QString & path); //! \brief Load MachineInfo structure. virtual MachineInfo PeekInfo(const QString & path); //! \brief Scans directory path for valid Prisma signature virtual int Open(const QString & path); //! \brief Returns the database version of this loader virtual int Version() { return prisma_data_version; } //! \brief Return the loaderName, in this case "Prisma" virtual const QString &loaderName() { return prisma_class_name; } //! \brief Register this Module to the list of Loaders, so it knows to search for Prisma data. static void Register(); //! \brief Generate a generic MachineInfo structure, with basic Prisma info to be expanded upon. virtual MachineInfo newInfo() { return MachineInfo(MT_CPAP, 0, prisma_class_name, QObject::tr("Löwenstein"), QObject::tr("Prisma Smart"), QString(), QString(), QObject::tr(""), QDateTime::currentDateTime(), prisma_data_version); } virtual QString PresReliefLabel(); virtual ChannelID CPAPModeChannel(); virtual ChannelID PresReliefMode(); //! \brief Called at application init, to set up any custom Prisma Channels virtual void initChannels(); QHash sesstasks; protected: MachineInfo PeekInfoFromConfig(const QString & selectedPath); void ImportDataDir(QDir& dataDir, QSet& sessions, QHash& eventFiles, QHash& signalFiles); //! \brief Scans the given directories for session data and create an import task for each logical session. void ScanFiles(const MachineInfo& info, const QString & path); }; //******************************************************************************************** class PrismaEvent { public: PrismaEvent(int endTime, int duration, int pressure, int strength) : m_endTime(endTime), m_duration(duration), m_pressure(pressure), m_strenght(strength) {} int endTime() { return m_endTime; } int duration() { return m_duration; } int strength() { return m_strenght; } protected: int m_endTime; int m_duration; int m_pressure; int m_strenght; }; class PrismaEventFile { public: PrismaEventFile(QByteArray &buffer); QHash getParameters() {return m_parameters; } QList getEvents(int eventId) {return m_events.contains(eventId) ? m_events[eventId] : QList(); } protected: QHash m_parameters; QHash> m_events; }; //******************************************************************************************** class PrismaModelInfo { protected: QHash m_modelNames; public: PrismaModelInfo(); bool IsTested(const QString & deviceId) const; const char* Name(const QString & deviceId) const; }; #endif // PRISMA_LOADER_H OSCAR-code-v1.5.1/oscar/SleepLib/loader_plugins/prs1_loader.cpp000066400000000000000000003647001450332542600242350ustar00rootroot00000000000000/* SleepLib PRS1 Loader Implementation * * Copyright (c) 2019-2023 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. */ #define TEST_MACROS_ENABLEDoff #include #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 "SleepLib/crypto.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)" }, { "552P", 0, 3, "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)" }, { "761P", 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" }, { "500G150", 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" }, { "410X150C", 0, 6, "DreamStation 2 CPAP" }, { "420X150C", 0, 6, "DreamStation 2 Advanced CPAP" }, // from FDA filing { "520X110C", 0, 6, "DreamStation 2 Auto CPAP Advanced" }, // based on bottom label, boot screen says "Advanced Auto CPAP" { "520X130C", 0, 6, "DreamStation 2 Auto CPAP Advanced" }, // from user report { "520X150C", 0, 6, "DreamStation 2 Auto CPAP Advanced" }, // from user report { "521X120C", 0, 6, "DreamStation 2 Auto CPAP Advanced with P-Flex" }, // inferred from 501X120 and presence of "P-Flex" on bottom label { "521X140C", 0, 6, "DreamStation 2 avec GSM + Humidificateur" }, // from brochure { "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 { "961TCA", 5, 2, "BiPAP autoSV Advanced 30 (System One 60 Series)" }, { "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; }; //******************************************************************************************** // 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, QHash & keycache); virtual ~PRDS2File() {}; bool isValid() const; QString guid() const; private: bool parseDS2Header(); int read16(); QByteArray readBytes(); bool initializeKey(); bool decryptData(); QByteArray m_iv; QByteArray m_salt; QByteArray m_export_key; QByteArray m_export_key_tag; 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, QHash & keycache) : RawDataFile(file) { bool valid = parseDS2Header(); if (valid) { QByteArray key = m_iv + m_salt + m_export_key + m_export_key_tag; m_payload_key = keycache[key]; if (m_payload_key.isEmpty()) { // Derive the key (slow). valid = initializeKey(); if (valid) { // Cache the result for the next file. keycache[key] = m_payload_key; } } if (valid) { valid = decryptData(); } } m_valid = valid; if (m_valid) { seek(0); // initialize internal position } } bool PRDS2File::isValid() const { return m_valid; } QString PRDS2File::guid() const { QString guid(m_guid); return guid; } 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; QByteArray ciphertext = m_device.read(m_device.size() - m_device.pos()); QByteArray plaintext; CryptoResult error = decrypt_aes256_gcm(m_payload_key, m_iv, ciphertext, m_payload_tag, plaintext); if (error) { if (error == InvalidTag) { static const QByteArray s_zero_tag(16, 0); if (m_payload_tag == s_zero_tag) { // This has been observed where the tag is zero and the data appears truncated. // Decrypt and ignore the tag. Rely on the decrypted payload's CRC for integrity. qWarning() << name() << "DS2 payload has zero tag, recovering data"; error = encrypt_aes256_gcm(m_payload_key, m_iv, ciphertext, plaintext, m_payload_tag); if (error) { qWarning() << "*** DS2 unexpected exception decrypting" << name(); } } else { qWarning() << name() << "DS2 payload doesn't match tag, skipping"; } } else { qWarning() << "*** DS2 unexpected exception decrypting" << name(); } } if (!error) { m_payload.setData(plaintext); m_payload.open(QIODevice::ReadOnly); valid = true; } return valid; } static const int KEY_SIZE = 256 / 8; // AES-256 static const uint8_t OSCAR_KEY[KEY_SIZE+1] = "Patient access to their own data"; static const uint8_t COMMON_KEY[KEY_SIZE] = { 0x75, 0xB3, 0xA2, 0x12, 0x4A, 0x65, 0xAF, 0x97, 0x54, 0xD8, 0xC1, 0xF3, 0xE5, 0x2E, 0xB6, 0xF0, 0x23, 0x20, 0x57, 0x69, 0x7E, 0x38, 0x0E, 0xC9, 0x4A, 0xDC, 0x46, 0x45, 0xB6, 0x92, 0x5A, 0x98 }; static const QByteArray s_oscar_key((const char*) OSCAR_KEY, KEY_SIZE); static const QByteArray s_common_key((const char*) COMMON_KEY, KEY_SIZE); bool PRDS2File::initializeKey() { bool valid = false; QByteArray common_key; CryptoResult error = decrypt_aes256(s_oscar_key, s_common_key, common_key); if (error) { qWarning() << "*** DS2 unexpected exception deriving common key"; return false; } QByteArray salted_key(KEY_SIZE, 0); error = pbkdf2_sha256(common_key, m_salt, 10000, salted_key); if (error) { qWarning() << "*** DS2 unexpected exception deriving salted key for" << name(); return false; } error = decrypt_aes256_gcm(salted_key, m_iv, m_export_key, m_export_key_tag, m_payload_key); if (error) { if (error == InvalidTag) { qWarning() << "DS2 validation of payload key failed for" << name(); } else { qWarning() << "*** DS2 unexpected exception deriving key for" << name(); } } else { valid = true; } return valid; } bool PRDS2File::parseDS2Header() { if (m_device.size() == 0) { qWarning() << name() << "is empty, skipping"; return false; } 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 m_salt = readBytes(); // 128-bit salt used to decrypt export key if (m_iv.size() != 12 || m_salt.size() != 16) { qWarning() << "DS2 IV,salt sizes =" << m_iv.size() << m_salt.size(); } else { //qDebug() << "DS2 IV,salt =" << m_iv.toHex() << m_salt.toHex(); } int f = read16(); int g = read16(); if (f != 0 || g != 1) { qWarning() << "DS2 unexpected middle bytes =" << f << g; } QByteArray import_key = readBytes(); // payload key encrypted with device-specific key QByteArray import_key_tag = readBytes(); // tag of import key if (import_key.size() != 32 || import_key_tag.size() != 16) { qWarning() << "DS2 import_key sizes =" << import_key.size() << import_key_tag.size(); } else { //qDebug() << "DS2 import_key,tag =" << import_key.toHex() << import_key_tag.toHex(); } m_export_key = readBytes(); // payload key encrypted with salted common key m_export_key_tag = readBytes(); // tag of export key if (m_export_key.size() != 32 || m_export_key_tag.size() != 16) { qWarning() << "DS2 export_key sizes =" << m_export_key.size() << m_export_key_tag.size(); } else { //qDebug() << "DS2 export_key,tag =" << m_export_key.toHex() << m_export_key_tag.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 device 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 device folder propertyfiles.append(mfi); } if (QDir::match("PROP.BIN", mfi.fileName())) { // Found a DreamStation 2 properties file, this is a device folder propertyfiles.append(mfi); } } } } // Sort devices 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, m_keycache); if (!ds2->isValid()) { //qWarning() << filename << "unable to decrypt"; delete ds2; return false; } src = ds2; props["GUID"] = ds2->guid(); } 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 device 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 devices were found. if (machines.isEmpty()) { qDebug() << "No PRS1 devices found at" << path; return -1; } // Import each device, from oldest to newest. // TODO: Loaders should return the set of devices 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) { m_keycache.clear(); 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 { 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 device 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) { #if QT_VERSION >= QT_VERSION_CHECK(5,14,0) QStringList pathlist = QDir::toNativeSeparators(inpath).split(QDir::separator(), Qt::SkipEmptyParts); #else QStringList pathlist = QDir::toNativeSeparators(inpath).split(QDir::separator(), QString::SkipEmptyParts); #endif 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 device 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 device 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->compliance)) { // Never seen identical compliance chunks, so keep logging this for now. qDebug() << chunkComparison(chunk, task->compliance); } else { qWarning() << chunkComparison(chunk, task->compliance); } 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 device. // We can't just use the full list of non-slice events, since on some devices // PS is generated by slice events (EPAP/IPAP average). // Duplicates need to be removed. QSet does the removal. #if QT_VERSION < QT_VERSION_CHECK(5,14,0) // convert QVvector to QList then to QSet QSet supportedNonSliceEvents = QSet::fromList( QList::fromVector( supported ) ); #else // release 5.14 supports the direct conversion. QSet supportedNonSliceEvents(supported.begin(),supported.end() ) ; #endif 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 devices, 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 devices, 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 devices' 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 devices 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 devices 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) firmware V1.0.1 clamps the values at 127 (15.875 cmH2O) // due to incorrectly treating this value as a signed integer. This bug is fixed // in firmware V1.0.6. 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, m_keycache); 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 device"), 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("Device 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 device 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 device 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 device 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 device 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.5.1/oscar/SleepLib/loader_plugins/prs1_loader.h000066400000000000000000000235351450332542600237000ustar00rootroot00000000000000/* 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 = 21; // //******************************************************************************************** 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 device mode to the importable vendor-neutral enum. CPAPMode importMode(int mode); //! \brief Parse all the chunks in a single device 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 device detected on an SD card, from oldest to newest QStringList FindMachinesOnCard(const QString & cardPath); //! \brief Opens the SD folder structure for this device, 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 device 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; //! \brief DS2 key derivation is very slow, but keys are reused in multiple files, so we cache the derived keys. QHash m_keycache; }; //******************************************************************************************** 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.5.1/oscar/SleepLib/loader_plugins/prs1_parser.cpp000066400000000000000000001517661450332542600242710ustar00rootroot00000000000000/* 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" // The qt5.15 obsolescence of hex requires this change. // this solution to QT's obsolescence is only used in debug statements #if QT_VERSION >= QT_VERSION_CHECK(5,15,0) #define QTHEX Qt::hex #define QTDEC Qt::dec #else #define QTHEX hex #define QTDEC dec #endif 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 devices 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) { // all tube temperatures seen if (tubetemp) { CHECK_VALUES(tubehumidlevel, 1, 3); } } 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 case 4: diam = 12; break; // HT12, seen on DreamStation 2 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" << QTHEX << chunk->calcCrc << "!= stored" << QTHEX << 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 << "@" << QTHEX << 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 << "@" << QTHEX << 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 device, 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; } // The PRS1 CRC32 considers every byte a 32-bit wchar_t, presumably due to // use of the STM32 CRC calculation unit, in which "CRC computation is done // on the whole 32-bit data word, and not byte per byte". 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.5.1/oscar/SleepLib/loader_plugins/prs1_parser.h000066400000000000000000000556341450332542600237330ustar00rootroot00000000000000/* 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 device 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 device 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 device 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 device 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 device 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) device bool ParseSummaryF3V03(void); //! \brief Parse a single data chunk from a .001 file containing summary data for a family 3 ventilator (family version 6) device 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 device bool ParseSummaryF5V012(void); //! \brief Parse a single data chunk from a .001 file containing summary data for a family 5 ASV family version 3 device 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) devices: 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) devices 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 devices (differs from other 60-Series devices) 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 devices 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 devices void ParseTubingTypeV3(unsigned char type); //! \brief Figures out which Event Parser to call, based on device 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 device bool ParseEventsF0V23(void); //! \brief Parse a single data chunk from a .002 file containing event data for a 60 Series family 0 CPAP/APAP 60 device bool ParseEventsF0V4(void); //! \brief Parse a single data chunk from a .002 file containing event data for a DreamStation family 0 CPAP/APAP device 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 device bool ParseEventsF3V03(void); //! \brief Parse a single data chunk from a .002 file containing event data for a family 3 ventilator family version 6 device bool ParseEventsF3V6(void); //! \brief Parse a single data chunk from a .002 file containing event data for a family 5 ASV family version 0 device bool ParseEventsF5V0(void); //! \brief Parse a single data chunk from a .002 file containing event data for a family 5 ASV family version 1 device bool ParseEventsF5V1(void); //! \brief Parse a single data chunk from a .002 file containing event data for a family 5 ASV family version 2 device bool ParseEventsF5V2(void); //! \brief Parse a single data chunk from a .002 file containing event data for a family 5 ASV family version 3 device 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.5.1/oscar/SleepLib/loader_plugins/prs1_parser_asv.cpp000066400000000000000000002157301450332542600251320ustar00rootroot00000000000000/* 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" // The qt5.15 obsolescence of hex requires this change. // this solution to QT's obsolescence is only used in debug statements #if QT_VERSION >= QT_VERSION_CHECK(5,15,0) #define QTHEX Qt::hex #define QTDEC Qt::dec #else #define QTHEX hex #define QTDEC dec #endif //******************************************************************************************** // 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, GAIN)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_EPAP_MAX, imax_epap, GAIN)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP_MIN, imin_epap + imin_ps, GAIN)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_IPAP_MAX, imax_pressure, GAIN)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MIN, imin_ps, GAIN)); this->AddEvent(new PRS1PressureSettingEvent(PRS1_SETTING_PS_MAX, imax_ps, GAIN)); //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 devices 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 0xb in F5V2 and 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 0xc in F5V2 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 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, PRS1PressurePulseEvent::TYPE, PRS1TimedBreathEvent::TYPE, PRS1ObstructiveApneaEvent::TYPE, PRS1ClearAirwayEvent::TYPE, PRS1HypopneaEvent::TYPE, PRS1FlowLimitationEvent::TYPE, PRS1VibratorySnoreEvent::TYPE, 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}, {8,3}, {9,4}, {0xa,3}, {0xb,2}, {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; } // F5V2 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); pos += 2; } switch (code) { //case 0x00: // never seen on F5V2 //case 0x01: // never seen on F5V2 case 0x02: // Pressure adjustment this->AddEvent(new PRS1EPAPSetEvent(t, data[pos++], GAIN)); 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 // 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: // never seen on F5V2 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: // Vibratory Snore, note this is 0xa in F5V1 and 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 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: // never seen on F5V2 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; 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 device-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 device, 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 device 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:" << QTHEX << 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.5.1/oscar/SleepLib/loader_plugins/prs1_parser_vent.cpp000066400000000000000000001633251450332542600253170ustar00rootroot00000000000000/* 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" // The qt5.15 obsolescence of hex requires this change. // this solution to QT's obsolescence is only used in debug statements #if QT_VERSION >= QT_VERSION_CHECK(5,15,0) #define QTHEX Qt::hex #define QTDEC Qt::dec #else #define QTHEX hex #define QTDEC dec #endif 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) devices 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 device-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 device, 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:" << QTHEX << 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.5.1/oscar/SleepLib/loader_plugins/prs1_parser_xpap.cpp000066400000000000000000003511131450332542600253050ustar00rootroot00000000000000/* 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" // The qt5.15 obsolescence of hex requires this change. // this solution to QT's obsolescence is only used in debug statements #if QT_VERSION >= QT_VERSION_CHECK(5,15,0) #define QTHEX Qt::hex #define QTDEC Qt::dec #else #define QTHEX hex #define QTDEC dec #endif //******************************************************************************************** // 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 devices 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 devices 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 devices 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 devices 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); // 16-bit CA count //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 devices 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 device, 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 < 55) UNEXPECTED_VALUE(chunk_size, ">= 55"); 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 devices? //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 devices) 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 device 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] > 4) UNEXPECTED_VALUE(data[pos], "0-4"); // 0 = 22mm, 1 = 15mm, 2 = 15HT, 3 = 12mm, 4 = HT12 // TODO: Confirm that 4 is 12HT and update ParseTubingTypeV3. this->ParseTubingTypeV3(data[pos]); break; case 0x48: // ??? Seen on DreamStation 2 non-Advanced (410) but not either Advanced (420 or 520) // Appears between 0x2C (ramp time) and 0x2E (flex mode), with a value of 0-4. CHECK_VALUE(len, 1); if (data[pos] > 4) { UNEXPECTED_VALUE(data[pos], "0-4"); } //this->AddEvent(new PRS1UnknownDataEvent(QByteArray((const char*) data, size), pos, len)); 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:" << QTHEX << 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 devices (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.5.1/oscar/SleepLib/loader_plugins/resmed_EDFinfo.cpp000066400000000000000000000053641450332542600246310ustar00rootroot00000000000000/* 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.5.1/oscar/SleepLib/loader_plugins/resmed_EDFinfo.h000066400000000000000000000135371450332542600242770ustar00rootroot00000000000000/* 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" // moved to 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; epapAuto = -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; s_RampPressure = -1; s_EasyBreathe = -1; s_RiseEnable = -1; s_RiseTime = -1; s_Cycle = -1; s_Trigger = -1; s_TiMax = -1; s_TiMin = -1; date=QDate(); } STRRecord(const STRRecord & /*copy*/) = default; ~STRRecord() {}; // required to get rid of warning // 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 epapAuto; 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 s_RampPressure; 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; int s_EasyBreathe; int s_RiseEnable; EventDataType s_RiseTime; int s_Cycle; int s_Trigger; EventDataType s_TiMax; EventDataType s_TiMin; }; 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.5.1/oscar/SleepLib/loader_plugins/resmed_loader.cpp000066400000000000000000004743551450332542600246370ustar00rootroot00000000000000/* 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. */ #define TEST_MACROS_ENABLEDoff #include #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 #if QT_VERSION >= QT_VERSION_CHECK(5,15,0) #define QTCOMBINE insert //idmap.insert(hash); #else #define QTCOMBINE unite //idmap.unite(hash); #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; ChannelID RMAS1x_EasyBreathe, RMAS1x_RiseEnable, RMAS1x_RiseTime, RMAS1x_Cycle, RMAS1x_Trigger, RMAS1x_TiMax, RMAS1x_TiMin; 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, 39421, 39423, 39483, 39485, 39517, 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("Device 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("Device 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")); chan->addOption(0, STR_TR_Off); chan->addOption(1, STR_TR_On); channel.add(GRP_CPAP, chan = new Channel(RMAS1x_RiseEnable = 0xe212, SETTING, MT_CPAP, SESSION, "RMAS1x_RiseEnable", QObject::tr("RiseEnable"), QObject::tr("RiseEnable"), QObject::tr("RiseEnable"), "", LOOKUP, Qt::black)); chan->addOption(0, STR_TR_Off); chan->addOption(1, "Enabled"); channel.add(GRP_CPAP, chan = new Channel(RMAS1x_RiseTime = 0xe213, SETTING, MT_CPAP, SESSION, "RMAS1x_RiseTime", QObject::tr("RiseTime"), QObject::tr("RiseTime"), QObject::tr("RiseTime"), STR_UNIT_milliSeconds, INTEGER, Qt::black)); channel.add(GRP_CPAP, chan = new Channel(RMAS1x_Cycle = 0xe214, SETTING, MT_CPAP, SESSION, "RMAS1x_Cycle", QObject::tr("Cycle"), QObject::tr("Cycle"), QObject::tr("Cycle"), "", LOOKUP, Qt::black)); chan->addOption(0, "Very Low"); chan->addOption(1, "Low"); chan->addOption(2, "Med"); chan->addOption(3, "High"); chan->addOption(4, "Very High"); channel.add(GRP_CPAP, chan = new Channel(RMAS1x_Trigger = 0xe215, SETTING, MT_CPAP, SESSION, "RMAS1x_Trigger", QObject::tr("Trigger"), QObject::tr("Trigger"), QObject::tr("Trigger"), "", LOOKUP, Qt::black)); chan->addOption(0, "Very Low"); chan->addOption(1, "Low"); chan->addOption(2, "Med"); chan->addOption(3, "High"); chan->addOption(4, "Very High"); channel.add(GRP_CPAP, chan = new Channel(RMAS1x_TiMax = 0xe216, SETTING, MT_CPAP, SESSION, "RMAS1x_TiMax", QObject::tr("TiMax"), QObject::tr("TiMax"), QObject::tr("TiMax"), STR_UNIT_Seconds, DOUBLE, Qt::black)); chan->addOption(0, "0"); channel.add(GRP_CPAP, chan = new Channel(RMAS1x_TiMin = 0xe217, SETTING, MT_CPAP, SESSION, "RMAS1x_TiMin", QObject::tr("TiMin"), QObject::tr("TiMin"), QObject::tr("TiMin"), STR_UNIT_Seconds, DOUBLE, Qt::black)); chan->addOption(0, "0"); // 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 device 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 device 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 device object (unless it's already registered) /////////////////////////////////////////////////////////////////////////////////// QDate firstImportDay = QDate().fromString("2010-01-01", "yyyy-MM-dd"); // Before Series 8 devices (I think) Machine *mach = p_profile->lookupMachine(info.serial, info.loadername); if ( mach ) { // we have seen this device qDebug() << "We have seen this machime"; mach->setInfo( info ); // update info QDate lastDate = mach->LastDay(); // use the last day for this device 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 device or just purged"; p_profile->forceResmedPrefs(); int modelNum = info.modelnumber.toInt(); if ( modelNum >= 39000 ) { if ( ! AS11TestedModels.contains(modelNum) ) { QMessageBox::information(QApplication::activeWindow(), QObject::tr("Device Untested"), QObject::tr("Your ResMed CPAP device (Model %1) has not been tested yet.").arg(info.modelnumber) +"\n\n"+ QObject::tr("It seems similar enough to other devices that it might work, but the developers would like a .zip copy of this device'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 device 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 device, 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 device, // 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 devices 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 devices if (!maskon) { maskon = str.lookupLabel("MaskOn"); // Series 1x devices } 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 10: mode = MODE_UNKNOWN; // it's PAC, whatever that is 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.s_RampPressure = 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.s_RampPressure = 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.s_RampPressure = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((R.mode == MODE_BILEVEL_FIXED) && (sig = str.lookupLabel("S.BL.StartPress"))) { // Bilevel Starting Pressure R.s_RampPressure = 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.s_RampPressure = 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.s_RampPressure = 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.EPAPAuto"))) { R.epapAuto = 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.epapAuto == 0) ) { 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.s_RampPressure << "Fixed EPAP" << ((R.epapAuto == 0) ? "True" : "False") << "EPAP" << R.epap << "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.s_RampPressure = 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.s_RampPressure = 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 devices 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.rms9_mode >= 2) && (R.rms9_mode <= 5)) { // S, ST, or T modes qDebug() << "BiLevel Mode found" << R.rms9_mode; if (R.rms9_mode == 3) { // S mode only if ((sig = str.lookupLabel("S.EasyBreathe"))) { R.s_EasyBreathe = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } } if ((sig = str.lookupLabel("S.RiseEnable"))) { R.s_RiseEnable = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.RiseTime"))) { R.s_RiseTime = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((R.rms9_mode ==3) || (R.rms9_mode ==4)) { // S or ST mode if ((sig = str.lookupLabel("S.Cycle"))) { R.s_Cycle = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.Trigger"))) { R.s_Trigger = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.TiMax"))) { R.s_TiMax = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } if ((sig = str.lookupLabel("S.TiMin"))) { R.s_TiMin = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; } } } if (R.rms9_mode == 6) { // vAuto mode qDebug() << "vAuto mode found" << 6; if ((sig = str.lookupLabel("S.Cycle"))) { R.s_Cycle = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; qDebug() << "Cycle" << R.s_Cycle; } if ((sig = str.lookupLabel("S.Trigger"))) { R.s_Trigger = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; qDebug() << "Trigger" << R.s_Trigger; } if ((sig = str.lookupLabel("S.TiMax"))) { R.s_TiMax = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; qDebug() << QString("TiMax %1").arg( R.s_TiMax, 0, 'f', 1); } if ((sig = str.lookupLabel("S.TiMin"))) { R.s_TiMin = EventDataType(sig->dataArray[rec]) * sig->gain + sig->offset; qDebug() << QString("TiMin %1").arg( R.s_TiMin, 0, 'f', 1); } } 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 device 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.QTCOMBINE(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->QTCOMBINE(hash1); } if (product.contains("ProductCode")) { info->modelnumber = product["ProductCode"].toString(); hash2["ProductCode"] = info->modelnumber; if (idmap) idmap->QTCOMBINE(hash2); } if (product.contains("ProductName")) { info->model = product["ProductName"].toString(); hash3["ProductName"] = info->model; if (idmap) idmap->QTCOMBINE(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; if (R.s_Cycle >= 0) sess->settings[ RMAS1x_Cycle ] = R.s_Cycle; if (R.s_Trigger >= 0) sess->settings[ RMAS1x_Trigger ] = R.s_Trigger; if (R.s_TiMax >= 0) sess->settings[ RMAS1x_TiMax ] = R.s_TiMax; if (R.s_TiMin >= 0) sess->settings[ RMAS1x_TiMin ] = R.s_TiMin; } 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.s_RampPressure >= 0) { sess->settings[CPAP_RampPressure] = R.s_RampPressure; } } } 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 device. // 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 // Always create CSR event list so that overview always finds something EventList *CSR = sess->AddEventList(CPAP_CSR, EVL_Event); // 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 devices 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 devices 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, true); } 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, true); } 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, true); } 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); // Fix ToTimeDelta to store inverse of edf data - also fix labels and tool tip // 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 device computes for us, but 20 seconds if ( (code == CPAP_MinuteVent) || (code == CPAP_RespRate) || (code == CPAP_TidalVolume) || (code == CPAP_Ti) || (code == CPAP_Te) || (code == CPAP_IE) ) { 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 devices // 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 :/ // STR signals 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" }; 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.5.1/oscar/SleepLib/loader_plugins/resmed_loader.h000066400000000000000000000153031450332542600242640ustar00rootroot00000000000000/* 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 device loaders static void Register(); //! \brief Detect if the given path contains a valid Folder structure virtual bool Detect(const QString & path); //! \brief Look up device 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 device 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.5.1/oscar/SleepLib/loader_plugins/resvent_loader.cpp000066400000000000000000000605131450332542600250310ustar00rootroot00000000000000/* SleepLib Resvent Loader Implementation * * Copyright (c) 2019-2023 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 resvent_data_version in resvent_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 "resvent_loader.h" #ifdef DEBUG_EFFICIENCY #include // only available in 4.8 #endif // Files WXX_XX contain flow rate and pressure and PXX_XX contain Pressure, IPAP, EPAP, Leak, Vt, MV, RR, Ti, IE, Spo2, PR // Both files contain a little header of size 0x24 bytes. In offset 0x12 contain the total number of different records in // the different files of the same type. And later contain the previous describe quantity of description header of size 0x20 // containing the details for every type of record (e.g. sample chunk size). ResventLoader::ResventLoader() { const QString RESVENT_ICON = ":/icons/resvent.png"; QString s = newInfo().series; m_pixmap_paths[s] = RESVENT_ICON; m_pixmaps[s] = QPixmap(RESVENT_ICON); m_type = MT_CPAP; } ResventLoader::~ResventLoader() { } const QString kResventTherapyFolder = "THERAPY"; const QString kResventConfigFolder = "CONFIG"; const QString kResventRecordFolder = "RECORD"; const QString kResventSysConfigFilename = "SYSCFG"; constexpr qint64 kDateTimeOffset = 7 * 60 * 60 * 1000; constexpr int kMainHeaderSize = 0x24; constexpr int kDescriptionHeaderSize = 0x20; constexpr int kChunkDurationInSecOffset = 0x10; constexpr int kDescriptionCountOffset = 0x12; constexpr int kDescriptionSamplesByChunk = 0x1e; constexpr double kDefaultGain = 0.01; bool ResventLoader::Detect(const QString & givenpath) { QDir dir(givenpath); if (!dir.exists()) { return false; } if (!dir.exists(kResventTherapyFolder)) { return false; } dir.cd(kResventTherapyFolder); if (!dir.exists(kResventConfigFolder)) { return false; } if (!dir.exists(kResventRecordFolder)) { return false; } return true; } MachineInfo ResventLoader::PeekInfo(const QString & path) { if (!Detect(path)) { return MachineInfo(); } MachineInfo info = newInfo(); const auto sys_config_path = path + QDir::separator() + kResventTherapyFolder + QDir::separator() + kResventConfigFolder + QDir::separator() + kResventSysConfigFilename; if (!QFile::exists(sys_config_path)) { qDebug() << "Resvent Data card has no" << kResventSysConfigFilename << "file in " << sys_config_path; return MachineInfo(); } QFile f(sys_config_path); f.open(QIODevice::ReadOnly | QIODevice::Text); f.seek(4); while (!f.atEnd()) { QString line = f.readLine().trimmed(); const auto elems = line.split("="); Q_ASSERT(elems.size() == 2); if (elems[0] == "models") { info.model = elems[1]; } else if (elems[0] == "sn") { info.serial = elems[1]; } else if (elems[0] == "num") { info.version = elems[1].toInt(); } else if (elems[0] == "num") { info.type = MachineType::MT_CPAP; } } if (info.model.contains("Point", Qt::CaseInsensitive)) { info.brand = "Hoffrichter"; } else { info.brand = "Resvent"; } return info; } QVector GetSessionsDate(const QString& dirpath) { QVector sessions_date; const auto records_path = dirpath + QDir::separator() + kResventTherapyFolder + QDir::separator() + kResventRecordFolder; QDir records_folder(records_path); const auto year_month_folders = records_folder.entryList(QStringList(), QDir::Dirs|QDir::NoDotAndDotDot, QDir::Name); std::for_each(year_month_folders.cbegin(), year_month_folders.cend(), [&](const QString& year_month_folder_name){ if (year_month_folder_name.length() != 6) { return; } const int year = std::stoi(year_month_folder_name.left(4).toStdString()); const int month = std::stoi(year_month_folder_name.right(2).toStdString()); const auto year_month_folder_path = records_path + QDir::separator() + year_month_folder_name; QDir year_month_folder(year_month_folder_path); const auto session_folders = year_month_folder.entryList(QStringList(), QDir::Dirs|QDir::NoDotAndDotDot, QDir::Name); std::for_each(session_folders.cbegin(), session_folders.cend(), [&](const QString& day_folder){ const auto day = std::stoi(day_folder.toStdString()); sessions_date.push_back(QDate(year, month, day)); }); }); return sessions_date; } enum class EventType { UsageSec= 1, UnixStart = 2, ObstructiveApnea = 17, CentralApnea = 18, Hypopnea = 19, FlowLimitation = 20, RERA = 21, PeriodicBreathing = 22, Snore = 23 }; struct EventData { EventType type; QDateTime date_time; int duration; }; struct UsageData { QString number{}; QDateTime start_time{}; QDateTime end_time{}; qint32 countAHI = 0; qint32 countOAI = 0; qint32 countCAI = 0; qint32 countAI = 0; qint32 countHI = 0; qint32 countRERA = 0; qint32 countSNI = 0; qint32 countBreath = 0; }; void UpdateEvents(EventType event_type, const QMap>& events, Session* session) { static QMap mapping {{EventType::ObstructiveApnea, CPAP_Obstructive}, {EventType::CentralApnea, CPAP_Apnea}, {EventType::Hypopnea, CPAP_Hypopnea}, {EventType::FlowLimitation, CPAP_FlowLimit}, {EventType::RERA, CPAP_RERA}, {EventType::PeriodicBreathing, CPAP_PB}, {EventType::Snore, CPAP_Snore}}; const auto it_events = events.find(event_type); const auto it_mapping = mapping.find(event_type); if (it_events == events.cend() || it_mapping == mapping.cend()) { return; } EventList* event_list = session->AddEventList(it_mapping.value(), EVL_Event); std::for_each(it_events.value().cbegin(), it_events.value().cend(), [&](const EventData& event_data){ event_list->AddEvent(event_data.date_time.toMSecsSinceEpoch() + kDateTimeOffset, event_data.duration); }); } QString GetSessionFolder(const QString& dirpath, const QDate& session_date) { const auto year_month_folder = QString::number(session_date.year()) + (session_date.month() > 10 ? "" : "0") + QString::number(session_date.month()); const auto day_folder = (session_date.day() > 10 ? "" : "0") + QString::number(session_date.day()); const auto session_folder_path = dirpath + QDir::separator() + kResventTherapyFolder + QDir::separator() + kResventRecordFolder + QDir::separator() + year_month_folder + QDir::separator() + day_folder; return session_folder_path; } void LoadEvents(const QString& session_folder_path, Session* session, const UsageData& usage) { const auto event_file_path = session_folder_path + QDir::separator() + "EV" + usage.number; QMap> events; QFile f(event_file_path); f.open(QIODevice::ReadOnly | QIODevice::Text); f.seek(4); while (!f.atEnd()) { QString line = f.readLine().trimmed(); #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) const auto elems = line.split(",", Qt::SkipEmptyParts); #else const auto elems = line.split(",", QString::SkipEmptyParts); #endif if (elems.size() != 4) { continue; } const auto event_type_elems = elems.at(0).split("="); const auto date_time_elems = elems.at(1).split("="); const auto duration_elems = elems.at(2).split("="); Q_ASSERT(event_type_elems.size() == 2); Q_ASSERT(date_time_elems.size() == 2); const auto event_type = static_cast(std::stoi(event_type_elems[1].toStdString())); const auto date_time = QDateTime::fromTime_t(std::stoi(date_time_elems[1].toStdString())); const auto duration = std::stoi(duration_elems[1].toStdString()); events[event_type].push_back(EventData{event_type, date_time, duration}); } static QVector mapping {EventType::ObstructiveApnea, EventType::CentralApnea, EventType::Hypopnea, EventType::FlowLimitation, EventType::RERA, EventType::PeriodicBreathing, EventType::Snore}; std::for_each(mapping.cbegin(), mapping.cend(), [&](EventType event_type){ UpdateEvents(event_type, events, session); }); } template T read_from_file(QFile& f) { T data{}; f.read(reinterpret_cast(&data), sizeof(T)); return data; } struct WaveFileData { unsigned int wave_event_id; QString file_base_name; unsigned int sample_rate_offset; unsigned int start_offset; }; EventList* GetEventList(const QString& name, Session* session, float sample_rate = 0.0) { if (name == "Press") { return session->AddEventList(CPAP_Pressure, EVL_Event); } else if (name == "IPAP") { return session->AddEventList(CPAP_IPAP, EVL_Event); } else if (name == "EPAP") { return session->AddEventList(CPAP_EPAP, EVL_Event); } else if (name == "Leak") { return session->AddEventList(CPAP_Leak, EVL_Event); } else if (name == "Vt") { return session->AddEventList(CPAP_TidalVolume, EVL_Event); } else if (name == "MV") { return session->AddEventList(CPAP_MinuteVent, EVL_Event); } else if (name == "RR") { return session->AddEventList(CPAP_RespRate, EVL_Event); } else if (name == "Ti") { return session->AddEventList(CPAP_Ti, EVL_Event); } else if (name == "I:E") { return session->AddEventList(CPAP_IE, EVL_Event); } else if (name == "SpO2" || name == "PR") { // Not present return nullptr; } else if (name == "Pressure") { return session->AddEventList(CPAP_MaskPressure, EVL_Waveform, kDefaultGain, 0.0, 0.0, 0.0, 1000.0 / sample_rate); } else if (name == "Flow") { return session->AddEventList(CPAP_FlowRate, EVL_Waveform, kDefaultGain, 0.0, 0.0, 0.0, 1000.0 / sample_rate); } else { // Not supported Q_ASSERT(false); return nullptr; } } struct ChunkData { EventList* event_list; uint16_t samples_by_chunk; qint64 start_time; int total_samples_by_chunk; float sample_rate; }; QString ReadDescriptionName(QFile& f) { constexpr int kNameSize = 9; QVector name(kNameSize); const auto readed = f.read(name.data(), kNameSize - 1); Q_ASSERT(readed == kNameSize - 1); return QString(name.data()); } void ReadWaveFormsHeaders(QFile& f, QVector& wave_forms, Session* session, const UsageData& usage) { f.seek(kChunkDurationInSecOffset); const auto chunk_duration_in_sec = read_from_file(f); f.seek(kDescriptionCountOffset); const auto description_count = read_from_file(f); wave_forms.resize(description_count); for (unsigned int i = 0; i < description_count; i++) { const auto description_header_offset = kMainHeaderSize + i * kDescriptionHeaderSize; f.seek(description_header_offset); const auto name = ReadDescriptionName(f); f.seek(description_header_offset + kDescriptionSamplesByChunk); const auto samples_by_chunk = read_from_file(f); wave_forms[i].sample_rate = 1.0 * samples_by_chunk / chunk_duration_in_sec; wave_forms[i].event_list = GetEventList(name, session, wave_forms[i].sample_rate); wave_forms[i].samples_by_chunk = samples_by_chunk; wave_forms[i].start_time = usage.start_time.toMSecsSinceEpoch(); } } void LoadOtherWaveForms(const QString& session_folder_path, Session* session, const UsageData& usage) { QDir session_folder(session_folder_path); const auto wave_files = session_folder.entryList(QStringList() << "P" + usage.number + "_*", QDir::Files, QDir::Name); QVector wave_forms; bool initialized = false; std::for_each(wave_files.cbegin(), wave_files.cend(), [&](const QString& wave_file){ // W01_ file QFile f(session_folder_path + QDir::separator() + wave_file); f.open(QIODevice::ReadOnly); if (!initialized) { ReadWaveFormsHeaders(f, wave_forms, session, usage); initialized = true; } f.seek(kMainHeaderSize + wave_forms.size() * kDescriptionHeaderSize); std::vector chunk(std::max_element(wave_forms.cbegin(), wave_forms.cend(), [](const ChunkData& lhs, const ChunkData& rhs){ return lhs.samples_by_chunk < rhs.samples_by_chunk; })->samples_by_chunk); while (!f.atEnd()) { for (int i = 0; i < wave_forms.size(); i++) { const auto& wave_form = wave_forms[i].event_list; const auto samples_by_chunk_actual = wave_forms[i].samples_by_chunk; auto& start_time_current = wave_forms[i].start_time; auto& total_samples_by_chunk = wave_forms[i].total_samples_by_chunk; const auto sample_rate = wave_forms[i].sample_rate; const auto readed = f.read(reinterpret_cast(chunk.data()), chunk.size() * sizeof(qint16)); if (wave_form) { const auto readed_elements = readed / sizeof(qint16); if (readed_elements != samples_by_chunk_actual) { std::fill(std::begin(chunk) + readed_elements, std::end(chunk), 0); } int offset = 0; std::for_each(chunk.cbegin(), chunk.cend(), [&](const qint16& value){ wave_form->AddEvent(start_time_current + offset + kDateTimeOffset, value * kDefaultGain); offset += 1000.0 / sample_rate; }); } start_time_current += samples_by_chunk_actual * 1000.0 / sample_rate; total_samples_by_chunk += samples_by_chunk_actual; } } }); QVector chunk; for (int i = 0; i < wave_forms.size(); i++) { const auto& wave_form = wave_forms[i]; const auto expected_samples = usage.start_time.msecsTo(usage.end_time) / 1000.0 * wave_form.sample_rate; if (wave_form.total_samples_by_chunk < expected_samples) { chunk.resize(expected_samples - wave_form.total_samples_by_chunk); if (wave_form.event_list) { int offset = 0; std::for_each(chunk.cbegin(), chunk.cend(), [&](const qint16& value){ wave_form.event_list->AddEvent(wave_form.start_time + offset + kDateTimeOffset, value * kDefaultGain); offset += 1000.0 / wave_form.sample_rate; }); } } } } void LoadWaveForms(const QString& session_folder_path, Session* session, const UsageData& usage) { QDir session_folder(session_folder_path); const auto wave_files = session_folder.entryList(QStringList() << "W" + usage.number + "_*", QDir::Files, QDir::Name); QVector wave_forms; bool initialized = false; std::for_each(wave_files.cbegin(), wave_files.cend(), [&](const QString& wave_file){ // W01_ file QFile f(session_folder_path + QDir::separator() + wave_file); f.open(QIODevice::ReadOnly); if (!initialized) { ReadWaveFormsHeaders(f, wave_forms, session, usage); initialized = true; } f.seek(kMainHeaderSize + wave_forms.size() * kDescriptionHeaderSize); QVector chunk(std::max_element(wave_forms.cbegin(), wave_forms.cend(), [](const ChunkData& lhs, const ChunkData& rhs){ return lhs.samples_by_chunk < rhs.samples_by_chunk; })->samples_by_chunk); while (!f.atEnd()) { for (int i = 0; i < wave_forms.size(); i++) { const auto& wave_form = wave_forms[i].event_list; const auto samples_by_chunk_actual = wave_forms[i].samples_by_chunk; auto& start_time_current = wave_forms[i].start_time; auto& total_samples_by_chunk = wave_forms[i].total_samples_by_chunk; const auto sample_rate = wave_forms[i].sample_rate; const auto duration = samples_by_chunk_actual * 1000.0 / sample_rate; const auto readed = f.read(reinterpret_cast(chunk.data()), chunk.size() * sizeof(qint16)); if (wave_form) { const auto readed_elements = readed / sizeof(qint16); if (readed_elements != samples_by_chunk_actual) { std::fill(std::begin(chunk) + readed_elements, std::end(chunk), 0); } wave_form->AddWaveform(start_time_current + kDateTimeOffset, chunk.data(), samples_by_chunk_actual, duration); } start_time_current += duration; total_samples_by_chunk += samples_by_chunk_actual; } } }); QVector chunk; for (int i = 0; i < wave_forms.size(); i++) { const auto& wave_form = wave_forms[i]; const auto expected_samples = usage.start_time.msecsTo(usage.end_time) / 1000.0 * wave_form.sample_rate; if (wave_form.total_samples_by_chunk < expected_samples) { chunk.resize(expected_samples - wave_form.total_samples_by_chunk); if (wave_form.event_list) { const auto duration = chunk.size() * 1000.0 / wave_form.sample_rate; wave_form.event_list->AddWaveform(wave_form.start_time + kDateTimeOffset, chunk.data(), chunk.size(), duration); } } } } void LoadStats(const UsageData& /*usage_data*/, Session* session) { // session->settings[CPAP_AHI] = usage_data.countAHI; // session->setCount(CPAP_AI, usage_data.countAI); // session->setCount(CPAP_CAI, usage_data.countCAI); // session->setCount(CPAP_HI, usage_data.countHI); // session->setCount(CPAP_Obstructive, usage_data.countOAI); // session->settings[CPAP_RERA] = usage_data.countRERA; // session->settings[CPAP_Snore] = usage_data.countSNI; session->settings[CPAP_Mode] = MODE_APAP; } UsageData ReadUsage(const QString& session_folder_path, const QString& usage_number) { UsageData usage_data; usage_data.number = usage_number; const auto session_stat_path = session_folder_path + QDir::separator() + "STAT" + usage_number; if (!QFile::exists(session_stat_path)) { qDebug() << "Resvent Data card has no " << session_stat_path; return usage_data; } QFile f(session_stat_path); f.open(QIODevice::ReadOnly | QIODevice::Text); f.seek(4); while (!f.atEnd()) { QString line = f.readLine().trimmed(); const auto elems = line.split("="); Q_ASSERT(elems.size() == 2); if (elems[0] == "secStart") { usage_data.start_time = QDateTime::fromTime_t(std::stoi(elems[1].toStdString())); } else if (elems[0] == "secUsed") { usage_data.end_time = QDateTime::fromTime_t(usage_data.start_time.toTime_t() + std::stoi(elems[1].toStdString())); } else if (elems[0] == "cntAHI") { usage_data.countAHI = std::stoi(elems[1].toStdString()); } else if (elems[0] == "cntOAI") { usage_data.countOAI = std::stoi(elems[1].toStdString()); } else if (elems[0] == "cntCAI") { usage_data.countCAI = std::stoi(elems[1].toStdString()); } else if (elems[0] == "cntAI") { usage_data.countAI = std::stoi(elems[1].toStdString()); } else if (elems[0] == "cntHI") { usage_data.countHI = std::stoi(elems[1].toStdString()); } else if (elems[0] == "cntRERA") { usage_data.countRERA = std::stoi(elems[1].toStdString()); } else if (elems[0] == "cntSNI") { usage_data.countSNI = std::stoi(elems[1].toStdString()); } else if (elems[0] == "cntBreath") { usage_data.countBreath = std::stoi(elems[1].toStdString()); } } return usage_data; } QVector GetDifferentUsage(const QString& session_folder_path) { QDir session_folder(session_folder_path); const auto stat_files = session_folder.entryList(QStringList() << "STAT*", QDir::Files, QDir::Name); QVector usage_data; std::for_each(stat_files.cbegin(), stat_files.cend(), [&](const QString& stat_file){ if (stat_file.size() != 6) { return; } auto usageData = ReadUsage(session_folder_path, stat_file.right(2)); usage_data.push_back(usageData); }); return usage_data; } int LoadSession(const QString& dirpath, const QDate& session_date, Machine* machine) { const auto session_folder_path = GetSessionFolder(dirpath, session_date); const auto different_usage = GetDifferentUsage(session_folder_path); return std::accumulate(different_usage.cbegin(), different_usage.cend(), 0, [&](int base, const UsageData& usage){ if (machine->SessionExists(usage.start_time.toMSecsSinceEpoch() + kDateTimeOffset)) { return base; } Session* session = new Session(machine, usage.start_time.toMSecsSinceEpoch() + kDateTimeOffset); session->SetChanged(true); session->really_set_first(usage.start_time.toMSecsSinceEpoch() + kDateTimeOffset); session->really_set_last(usage.end_time.toMSecsSinceEpoch() + kDateTimeOffset); LoadStats(usage, session); LoadWaveForms(session_folder_path, session, usage); LoadOtherWaveForms(session_folder_path, session, usage); LoadEvents(session_folder_path, session, usage); session->UpdateSummaries(); session->Store(machine->getDataPath()); machine->AddSession(session); return base + 1; }); } /////////////////////////////////////////////////////////////////////////////////////////// // Sorted EDF files that need processing into date records according to ResMed noon split /////////////////////////////////////////////////////////////////////////////////////////// int ResventLoader::Open(const QString & dirpath) { const auto machine_info = PeekInfo(dirpath); // Abort if no serial number if (machine_info.serial.isEmpty()) { qDebug() << "Resvent Data card has no valid serial number in " << kResventSysConfigFilename; return -1; } const auto sessions_date = GetSessionsDate(dirpath); Machine *machine = p_profile->CreateMachine(machine_info); int new_sessions = 0; std::for_each(sessions_date.cbegin(), sessions_date.cend(), [&](const QDate& session_date){ new_sessions += LoadSession(dirpath, session_date, machine); }); machine->Save(); return new_sessions; } void ResventLoader::initChannels() { } ChannelID ResventLoader::PresReliefMode() { return 0; } ChannelID ResventLoader::PresReliefLevel() { return 0; } ChannelID ResventLoader::CPAPModeChannel() { return 0; } bool resvent_initialized = false; void ResventLoader::Register() { if (resvent_initialized) { return; } qDebug() << "Registering ResventLoader"; RegisterLoader(new ResventLoader()); resvent_initialized = true; } OSCAR-code-v1.5.1/oscar/SleepLib/loader_plugins/resvent_loader.h000066400000000000000000000055431450332542600245000ustar00rootroot00000000000000/* SleepLib Resvent Loader Implementation * * Copyright (c) 2019-2023 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 RESVENT_LOADER_H #define RESVENT_LOADER_H #include #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 resvent_data_version = 1; // //******************************************************************************************** const QString resvent_class_name = "Resvent/Hoffrichter"; /*! \class ResventLoader \brief Importer for Resvent iBreezer and Hoffrichter Point 3 */ class ResventLoader : public CPAPLoader { Q_OBJECT public: ResventLoader(); virtual ~ResventLoader(); //! \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); //! \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 Resvent loader virtual int Version() { return resvent_data_version; } //! \brief Returns the Machine class name of this loader. ("Resvent") virtual const QString &loaderName() { return resvent_class_name; } //! \brief Register the ResmedLoader with the list of other machine loaders static void Register(); virtual MachineInfo newInfo() { return MachineInfo(MT_CPAP, 0, resvent_class_name, QObject::tr("Resvent/Hoffrichter"), QString(), QString(), QString(), QObject::tr("iBreeze/Point3"), QDateTime::currentDateTime(), resvent_data_version); } virtual void initChannels(); //////////////////////////////////////////////////////////////////////////////////////////////////////////// // Now for some CPAPLoader overrides //////////////////////////////////////////////////////////////////////////////////////////////////////////// virtual QString PresReliefLabel() { return QObject::tr("EPR: "); } virtual ChannelID PresReliefMode(); virtual ChannelID PresReliefLevel(); virtual ChannelID CPAPModeChannel(); //////////////////////////////////////////////////////////////////////////////////////////////////////////// }; #endif // RESVENT_LOADER_H OSCAR-code-v1.5.1/oscar/SleepLib/loader_plugins/sleepstyle_EDFinfo.cpp000066400000000000000000000065571450332542600255500ustar00rootroot00000000000000/* 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.5.1/oscar/SleepLib/loader_plugins/sleepstyle_EDFinfo.h000066400000000000000000000033701450332542600252030ustar00rootroot00000000000000/* 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 }; // moved to edfparser.h // 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.5.1/oscar/SleepLib/loader_plugins/sleepstyle_loader.cpp000066400000000000000000001052631450332542600255360ustar00rootroot00000000000000/* 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 device 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 device 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 device 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 device 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 device 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 device 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 device 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; Q_UNUSED( totalrecs ); 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 *OA = sess->AddEventList(CPAP_Obstructive, EVL_Event); EventList *CA = sess->AddEventList(CPAP_ClearAirway, 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) { OA->AddEvent(ti+60000, 0); } // Grouped by F&P as A if (a2 & bitmask) { CA->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.5.1/oscar/SleepLib/loader_plugins/sleepstyle_loader.h000066400000000000000000000105471450332542600252030ustar00rootroot00000000000000/* 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 = 2; // //******************************************************************************************** /*! \class SleepStyle \brief F&P SleepStyle customized device 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 device class name of this CPAP device, "SleepStyle" virtual const QString & loaderName() { return sleepstyle_class_name; } // ! \brief Creates a device 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.5.1/oscar/SleepLib/loader_plugins/somnopose_loader.cpp000066400000000000000000000206721450332542600253670ustar00rootroot00000000000000/* 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; } #if QT_VERSION >= QT_VERSION_CHECK(5,14,0) QDateTime epoch(QDate(2001, 1, 1).startOfDay(Qt::OffsetFromUTC)); qint64 ep = epoch.toMSecsSinceEpoch() , time=0; #else QDateTime epoch(QDate(2001, 1, 1)); qint64 ep = qint64(epoch.toTime_t()+epoch.offsetFromUtc()) * 1000, time=0; #endif 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.5.1/oscar/SleepLib/loader_plugins/somnopose_loader.h000066400000000000000000000030361450332542600250270ustar00rootroot00000000000000/* 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.5.1/oscar/SleepLib/loader_plugins/viatom_loader.cpp000066400000000000000000000417361450332542600246500ustar00rootroot00000000000000/* 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 devices, 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 device 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 device // 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; if ((records.size() % 2) != 0) { // An odd number of samples inherently can't be all duplicates. all_are_duplicated = false; } else { 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.5.1/oscar/SleepLib/loader_plugins/viatom_loader.h000066400000000000000000000050461450332542600243070ustar00rootroot00000000000000/* 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.5.1/oscar/SleepLib/loader_plugins/weinmann_loader.cpp000066400000000000000000000423361450332542600251620ustar00rootroot00000000000000/* 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" // The qt5.15 obsolescence of hex requires this change. // this solution to QT's obsolescence is only used in debug statements #if QT_VERSION >= QT_VERSION_CHECK(5,15,0) #define QTHEX Qt::hex #define QTDEC Qt::dec #else #define QTHEX hex #define QTDEC dec #endif 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") << "=" << QTHEX << 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::asprintf("%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::asprintf("%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::asprintf("%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; Q_UNUSED (tt); 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.5.1/oscar/SleepLib/loader_plugins/weinmann_loader.h000066400000000000000000000112471450332542600246240ustar00rootroot00000000000000/* 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 device 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) {} CompInfo& operator=(const CompInfo & other) = default; 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 device loader name of this class virtual const QString &loaderName() { return weinmann_class_name; } int ParseIndex(QFile & wmdata); //! \brief Creates a device 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.5.1/oscar/SleepLib/loader_plugins/zeo_loader.cpp000066400000000000000000000227111450332542600241360ustar00rootroot00000000000000/* 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.5.1/oscar/SleepLib/loader_plugins/zeo_loader.h000066400000000000000000000037561450332542600236130ustar00rootroot00000000000000/* 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.5.1/oscar/SleepLib/machine.cpp000066400000000000000000001117531450332542600204150ustar00rootroot00000000000000/* SleepLib Device 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. */ #define TEST_MACROS_ENABLEDoff #include #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/session.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(); } ////////////////////////////////////////////////////////////////////////////////////////// // Device Base-Class implmementation ////////////////////////////////////////////////////////////////////////////////////////// Machine::Machine(Profile *_profile, MachineID id) : profile(_profile) { day.clear(); highest_sessionid = 0; m_suppressUntestedWarning = false; // TODO: Have the device 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 DeviceID 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 device: " << hex << m_id; //%lx",m_id); m_type = MT_UNKNOWN; firstsession = true; } Machine::~Machine() { saveSessionInfo(); //qDebug() << "Destroy device" << 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 << (quint8)(sess->enabled(true)); } 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); quint8 b = true; b &= 0x1; if (it != sess->settings.end()) { b = it.value().toBool(); } else { } 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; quint8 b; for (int i=0; i< size; ++i) { in >> sid; in >> b; b &= 0x1; 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 device 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 devices 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 device 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(); QElapsedTimer 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 device 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) { QElapsedTimer 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(true) ? "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.5.1/oscar/SleepLib/machine.h000066400000000000000000000227451450332542600200640ustar00rootroot00000000000000/* SleepLib Device 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 device class is the Heart of SleepyLib, representing a single device and holding it's data */ class Machine { friend class SaveThread; // friend class MachineLaoder; public: /*! \fn Machine(MachineID id=0); \brief Constructs a device 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 device 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 device data in the SleepLib database bool Purge(int secret); //! \brief Unlink a session from any device 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 device 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::asprintf("%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 devices sessions QMap day; //! \brief Contains all sessions for this device, 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 device object.. */ class CPAP: public Machine { public: CPAP(Profile *, MachineID id = 0); virtual ~CPAP(); }; /*! \class Oximeter \brief An Oximeter classed device object.. */ class Oximeter: public Machine { public: Oximeter(Profile *, MachineID id = 0); virtual ~Oximeter(); protected: }; /*! \class SleepStage \brief A SleepStage classed device object.. */ class SleepStage: public Machine { public: SleepStage(Profile *, MachineID id = 0); virtual ~SleepStage(); protected: }; /*! \class PositionSensor \brief A PositionSensor classed device object.. */ class PositionSensor: public Machine { public: PositionSensor(Profile *, MachineID id = 0); virtual ~PositionSensor(); protected: }; #endif // MACHINE_H OSCAR-code-v1.5.1/oscar/SleepLib/machine_common.cpp000066400000000000000000000052671450332542600217670ustar00rootroot00000000000000/* SleepLib Common Device 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, CPAP_EEPAP, CPAP_EEPAPLo, CPAP_EEPAPHi; 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.5.1/oscar/SleepLib/machine_common.h000066400000000000000000000212211450332542600214200ustar00rootroot00000000000000/* SleepLib Common Device 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 device. MT_CPAP is any type of xPAP device, 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 devices that provide // multiple kinds of data, such as oximetry + motion/position, or sleep stage + oximetry, etc. // // Device/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 device 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 devices 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 device'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, MODE_TRILEVEL_AUTO_VARIABLE_PDIFF }; /*! \enum PRTypes \brief Pressure Relief Types, used by CPAP devices */ 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() {}; 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 device 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_EEPAP, CPAP_EEPAPLo, CPAP_EEPAPHi, 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.5.1/oscar/SleepLib/machine_loader.cpp000066400000000000000000000176761450332542600217540ustar00rootroot00000000000000/* SleepLib Device 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. */ #define TEST_MACROS_ENABLEDoff #include #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.5.1/oscar/SleepLib/machine_loader.h000066400000000000000000000122301450332542600213760ustar00rootroot00000000000000/* SleepLib DeviceLoader 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 device 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 device 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 device 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 DeviceLoader 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 DeviceInfo record virtual MachineInfo newInfo() { return MachineInfo(); } //! \brief Override to returns the class name of this DeviceLoader 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 device 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.5.1/oscar/SleepLib/preferences.cpp000066400000000000000000000252111450332542600213030ustar00rootroot00000000000000/* 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 devices 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.5.1/oscar/SleepLib/preferences.h000066400000000000000000000123771450332542600207610ustar00rootroot00000000000000/* 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.5.1/oscar/SleepLib/profiles.cpp000066400000000000000000001536201450332542600206330ustar00rootroot00000000000000/* 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. */ #define TEST_MACROS_ENABLEDoff #include #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 device 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().toUtf8()); 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 device 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 device 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 device 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("Device 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 device 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 device 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 device 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 device 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 device 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 device 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()];; } //profiles.xml is never read so it does not need to be saved. #if 0 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(); } #endif 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 #ifndef REMOVE_FITNESS << STR_AS_OverviewLinechartMode #endif ; 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 // profiles.xml is never read so it does not need to be saved. // saveProfileList(); } } // namespace Profiles // Returns a list of all days records matching device 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 device 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 device 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 device 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 // Profile/User/chanels.xml is not read so it does not need to be saved // 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()) break; 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()) break; } f.close(); resetOxiChannelPref(); } void Profile::resetOxiChannelPref() { schema::channel[OXI_Pulse].setLowerThreshold(oxi->flagPulseBelow()); schema::channel[OXI_Pulse].setUpperThreshold(oxi->flagPulseAbove()); schema::channel[OXI_SPO2].setLowerThreshold(oxi->oxiDesaturationThreshold()); }; OSCAR-code-v1.5.1/oscar/SleepLib/profiles.h000066400000000000000000001165171450332542600203040ustar00rootroot00000000000000/* 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 device 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 devices 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 device to this profiles machlist void AddMachine(Machine *m); //! \brief Remove device from this profiles machlist void DelMachine(Machine *m); //! \brief Loads all device (summary) data belonging to this profile void LoadMachineData(ProgressDialog *progress); //! \brief Unloads all device (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 device 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 device 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 devices of type t QList GetMachines(MachineType t = MT_UNKNOWN); //! \brief Returns the device of type t used on date, nullptr if none.. Machine *GetMachine(MachineType t, QDate date); //! \brief return the first device 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 device type between start and end dates QList getDays(MachineType mt, QDate start, QDate end); //! \brief Returns a count of all days (with data) of device 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 device 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 device 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 device 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 device 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 devices 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 device 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 device 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 devicetype QDate FirstDay(MachineType mt = MT_UNKNOWN); //! \brief Looks for the last date containing a day record matching devicetype QDate LastDay(MachineType mt = MT_UNKNOWN); //! \brief Looks for the first date containing a day record with enabled sessions matching devicetype QDate FirstGoodDay(MachineType mt = MT_UNKNOWN); //! \brief Looks for the last date containing a day record with enabled sessions matching devicetype 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(); void resetOxiChannelPref(); 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_SkipOxiIntroScreen = "SkipOxiIntroScreen"; 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_oxiDesaturationThreshold = "oxiDesaturationThreshold"; const QString STR_OS_flagPulseAbove = "flagPulseAbove"; const QString STR_OS_flagPulseBelow = "flagPulseBelow"; const QString STR_OS_OxiDiscardThreshold = "OxiDiscardThreshold"; // CPAPSettings Strings const QString STR_CS_ComplianceHours = "ComplianceHours"; const QString STR_CS_ClinicalMode = "ClinicalMode"; 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"; //Apperance Settings const QString STR_AS_EventFlagSessionBar = "EventFlagSessionBar"; // 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_StatReportDate = "StatReportDate"; 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) { // Intialized non-user changable item - set during import of data? initPref(STR_OS_EnableOximetry, false); initPref(STR_OS_DefaultDevice, QString()); initPref(STR_OS_SyncOximeterClock, true); initPref(STR_OS_OximeterType, 0); initPref(STR_OS_SkipOxiIntroScreen, false); // Initialize Changeable via GUI parameters with default values initPref(STR_OS_SPO2DropDuration, defaultValue_OS_SPO2DropDuration); initPref(STR_OS_SPO2DropPercentage, defaultValue_OS_SPO2DropPercentage); initPref(STR_OS_PulseChangeDuration, defaultValue_OS_PulseChangeDuration); initPref(STR_OS_PulseChangeBPM, defaultValue_OS_PulseChangeBPM); initPref(STR_OS_OxiDiscardThreshold, defaultValue_OS_OxiDiscardThreshold); initPref(STR_OS_oxiDesaturationThreshold, defaultValue_OS_oxiDesaturationThreshold); initPref(STR_OS_flagPulseAbove, defaultValue_OS_flagPulseAbove); initPref(STR_OS_flagPulseBelow, defaultValue_OS_flagPulseBelow); } const double defaultValue_OS_SPO2DropDuration = 8.0; const double defaultValue_OS_SPO2DropPercentage = 3.0; const double defaultValue_OS_PulseChangeDuration = 8.0; const double defaultValue_OS_PulseChangeBPM = 5.0; const double defaultValue_OS_OxiDiscardThreshold = 0.0; const double defaultValue_OS_oxiDesaturationThreshold = 88.0; const double defaultValue_OS_flagPulseAbove = 99.0; const double defaultValue_OS_flagPulseBelow = 40.0; 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(); } bool skipOxiIntroScreen() const { return getPref(STR_OS_SkipOxiIntroScreen).toBool(); } 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(); } double oxiDiscardThreshold() const { return getPref(STR_OS_OxiDiscardThreshold).toDouble(); } double oxiDesaturationThreshold() const { return getPref(STR_OS_oxiDesaturationThreshold).toDouble(); } double flagPulseAbove() const { return getPref(STR_OS_flagPulseAbove).toDouble(); } double flagPulseBelow() const { return getPref(STR_OS_flagPulseBelow).toDouble(); } 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); } void setOxiDesaturationThreshold(double value) { setPref(STR_OS_oxiDesaturationThreshold, value); } void setFlagPulseAbove(double value) { setPref(STR_OS_flagPulseAbove, value); } void setFlagPulseBelow(double value) { setPref(STR_OS_flagPulseBelow, value); } }; /*! \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(); m_clinicalMode = initPref(STR_CS_ClinicalMode, false).toBool(); 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 clinicalMode() const { return m_clinicalMode; } 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 setClinicalMode(bool mode) { setPref(STR_CS_ClinicalMode, m_clinicalMode=mode); } 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, m_clinicalMode; 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) { m_eventFlagSessionBar = initPref(STR_AS_EventFlagSessionBar, false).toBool(); } //Getters bool eventFlagSessionBar() const { return m_eventFlagSessionBar; } //Setters void setEventFlagSessionBar(bool b) { setPref(STR_AS_EventFlagSessionBar, m_eventFlagSessionBar = b); } bool m_eventFlagSessionBar; }; /*! \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); setPref(STR_US_EventWindowSize, 3.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_StatReportDate, QDate(1,1,2000)); 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 statReportDate() const { return getPref(STR_US_StatReportDate).toDate(); } 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 setStatReportDate(QDate i) { setPref(STR_US_StatReportDate, 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.5.1/oscar/SleepLib/progressdialog.cpp000066400000000000000000000031211450332542600220220ustar00rootroot00000000000000/* 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.5.1/oscar/SleepLib/progressdialog.h000066400000000000000000000021401450332542600214670ustar00rootroot00000000000000/* 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.5.1/oscar/SleepLib/schema.cpp000066400000000000000000001344371450332542600202550ustar00rootroot00000000000000/* 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; // SessionEnabledChannel is not used//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", "", ""); #if 0 // SessionEnabledChannel is not used SessionEnabledChannel = new Channel(1, DATA, MT_UNKNOWN, DAY, "Enabled", "Enabled", "Session Enabled", "", ""); channel.channels[1] = SessionEnabledChannel; channel.names["Enabled"] = SessionEnabledChannel; SESSION_ENABLED = 1; #endif // SessionEnabledChannel is not used 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_EEPAP = 0x11A7, WAVEFORM, MT_CPAP, SESSION, "EEPAP", STR_TR_EEPAP, QObject::tr("End Expiratory Pressure"), STR_TR_EEPAP, STR_UNIT_CMH2O, DEFAULT, QColor("purple"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_EEPAPLo = 0x11A8, WAVEFORM, MT_CPAP, SESSION, "EEPAPLo", STR_TR_EEPAPLo, QObject::tr("Lower End Expiratory Pressure"), STR_TR_EEPAPLo, STR_UNIT_CMH2O, DEFAULT, QColor("orange"))); schema::channel.add(GRP_CPAP, new Channel(CPAP_EEPAPHi = 0x11A9, WAVEFORM, MT_CPAP, SESSION, "EEPAPHi", STR_TR_EEPAPHi, QObject::tr("Higher End Expiratory Pressure"), STR_TR_EEPAPHi, STR_UNIT_CMH2O, DEFAULT, QColor("light blue"))); 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 (CSR)"), 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 (PB)"),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 (CA)"), 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 Apnea (OA)"), 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 (H)"), 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 (UA)"), 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 (A)"), QObject::tr("An apnea reported by your CPAP device."),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 (FL)"), 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 (RE)"),QObject::tr("Respiratory Effort Related Arousal: A restriction in breathing that causes either 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 (VS)"), 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 detected by a System One device"),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 (LF)"), QObject::tr("A large mask leak affecting device 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 (LL)"),QObject::tr("A large mask leak affecting device 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 (NR)"), 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 (EP)"), 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 (SA)"),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 (UF1)"), 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 (UF2)"),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 (UF3)"),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.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.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 (PC)"), 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 (SD)"), 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 Value"), QObject::tr("Ratio between Inspiratory and Expiratory time"), QObject::tr("I/E Value"), 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 (AHI)"), 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 (RDI)"), 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, "FeelingMeter", QObject::tr("Feeling"), QObject::tr("How you feel (1 = like crap, 10 = unstoppable)"), QObject::tr("Feeling"), 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(); // ahiChannels did not get cleared since day1 OSCAR when a reset was required. // when reset occured then the Overview AHI graph would should an addtional set of channels. // this fix just clears the variable that stores ahi data. // probelm #59 https://gitlab.com/pholy/OSCAR-code/-/issues/59 ahiChannels.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(); } } } #if 0 // Profile/User/chanels.xml is not read so it does not need to be saved 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; } #endif // Profile/User/chanels.xml is not read so it does not need to be saved } // 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.5.1/oscar/SleepLib/schema.h000066400000000000000000000222101450332542600177030ustar00rootroot00000000000000/* 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) {} ~ChannelCalc() {}; 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: Device setting, such as EPR, temperature, Ramp enabled. /// FLAG: Event flags reported by CPAP device. 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 device (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.5.1/oscar/SleepLib/serialoximeter.cpp000066400000000000000000000110111450332542600220270ustar00rootroot00000000000000/* SleepLib Device 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.5.1/oscar/SleepLib/serialoximeter.h000066400000000000000000000076601450332542600215130ustar00rootroot00000000000000/* SleepLib DeviceLoader 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.5.1/oscar/SleepLib/session.cpp000066400000000000000000001716741450332542600205040ustar00rootroot00000000000000/* 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. */ #define TEST_MACROS_ENABLEDoff #include #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(); } bool Session::enabled(bool realValues) const { if (p_profile->cpap->clinicalMode() && !realValues) return true; return s_enabled; } 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::asprintf("%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=QString::asprintf("%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::asprintf("%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::asprintf("%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 device 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::asprintf("%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();// Device 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();// Device 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; // Device 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; // Device 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 Device 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 device 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.5.1/oscar/SleepLib/session.h000066400000000000000000000404261450332542600201370ustar00rootroot00000000000000/* 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 device 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 device, 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. bool enabled(bool realValues=false) const; //! \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() { qint64 duration=s_last - s_first; return duration<0?0:duration; // 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 devices 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 DeviceID 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(); QString eventFile() const; //! \brief Returns DeviceType 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; quint8 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.5.1/oscar/SleepLib/thirdparty/000077500000000000000000000000001450332542600204675ustar00rootroot00000000000000OSCAR-code-v1.5.1/oscar/SleepLib/thirdparty/botan_all.cpp000066400000000000000000007575131450332542600231500ustar00rootroot00000000000000/* * 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; } } /* * Hash Functions * (C) 2015 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #if defined(BOTAN_HAS_ADLER32) #endif #if defined(BOTAN_HAS_CRC24) #endif #if defined(BOTAN_HAS_CRC32) #endif #if defined(BOTAN_HAS_GOST_34_11) #endif #if defined(BOTAN_HAS_KECCAK) #endif #if defined(BOTAN_HAS_MD4) #endif #if defined(BOTAN_HAS_MD5) #endif #if defined(BOTAN_HAS_RIPEMD_160) #endif #if defined(BOTAN_HAS_SHA1) #endif #if defined(BOTAN_HAS_SHA2_32) #endif #if defined(BOTAN_HAS_SHA2_64) #endif #if defined(BOTAN_HAS_SHA3) #endif #if defined(BOTAN_HAS_SHAKE) #endif #if defined(BOTAN_HAS_SKEIN_512) #endif #if defined(BOTAN_HAS_STREEBOG) #endif #if defined(BOTAN_HAS_SM3) #endif #if defined(BOTAN_HAS_TIGER) #endif #if defined(BOTAN_HAS_WHIRLPOOL) #endif #if defined(BOTAN_HAS_PARALLEL_HASH) #endif #if defined(BOTAN_HAS_COMB4P) #endif #if defined(BOTAN_HAS_BLAKE2B) #endif #if defined(BOTAN_HAS_OPENSSL) #endif #if defined(BOTAN_HAS_COMMONCRYPTO) #endif namespace Botan { std::unique_ptr HashFunction::create(const std::string& algo_spec, const std::string& provider) { #if defined(BOTAN_HAS_COMMONCRYPTO) if(provider.empty() || provider == "commoncrypto") { if(auto hash = make_commoncrypto_hash(algo_spec)) return hash; if(!provider.empty()) return nullptr; } #endif #if defined(BOTAN_HAS_OPENSSL) if(provider.empty() || provider == "openssl") { if(auto hash = make_openssl_hash(algo_spec)) return hash; if(!provider.empty()) return nullptr; } #endif if(provider.empty() == false && provider != "base") return nullptr; // unknown provider #if defined(BOTAN_HAS_SHA1) if(algo_spec == "SHA-160" || algo_spec == "SHA-1" || algo_spec == "SHA1") { return std::unique_ptr(new SHA_160); } #endif #if defined(BOTAN_HAS_SHA2_32) if(algo_spec == "SHA-224") { return std::unique_ptr(new SHA_224); } if(algo_spec == "SHA-256") { return std::unique_ptr(new SHA_256); } #endif #if defined(BOTAN_HAS_SHA2_64) if(algo_spec == "SHA-384") { return std::unique_ptr(new SHA_384); } if(algo_spec == "SHA-512") { return std::unique_ptr(new SHA_512); } if(algo_spec == "SHA-512-256") { return std::unique_ptr(new SHA_512_256); } #endif #if defined(BOTAN_HAS_RIPEMD_160) if(algo_spec == "RIPEMD-160") { return std::unique_ptr(new RIPEMD_160); } #endif #if defined(BOTAN_HAS_WHIRLPOOL) if(algo_spec == "Whirlpool") { return std::unique_ptr(new Whirlpool); } #endif #if defined(BOTAN_HAS_MD5) if(algo_spec == "MD5") { return std::unique_ptr(new MD5); } #endif #if defined(BOTAN_HAS_MD4) if(algo_spec == "MD4") { return std::unique_ptr(new MD4); } #endif #if defined(BOTAN_HAS_GOST_34_11) if(algo_spec == "GOST-R-34.11-94" || algo_spec == "GOST-34.11") { return std::unique_ptr(new GOST_34_11); } #endif #if defined(BOTAN_HAS_ADLER32) if(algo_spec == "Adler32") { return std::unique_ptr(new Adler32); } #endif #if defined(BOTAN_HAS_CRC24) if(algo_spec == "CRC24") { return std::unique_ptr(new CRC24); } #endif #if defined(BOTAN_HAS_CRC32) if(algo_spec == "CRC32") { return std::unique_ptr(new CRC32); } #endif const SCAN_Name req(algo_spec); #if defined(BOTAN_HAS_TIGER) if(req.algo_name() == "Tiger") { return std::unique_ptr( new Tiger(req.arg_as_integer(0, 24), req.arg_as_integer(1, 3))); } #endif #if defined(BOTAN_HAS_SKEIN_512) if(req.algo_name() == "Skein-512") { return std::unique_ptr( new Skein_512(req.arg_as_integer(0, 512), req.arg(1, ""))); } #endif #if defined(BOTAN_HAS_BLAKE2B) if(req.algo_name() == "Blake2b" || req.algo_name() == "BLAKE2b") { return std::unique_ptr( new Blake2b(req.arg_as_integer(0, 512))); } #endif #if defined(BOTAN_HAS_KECCAK) if(req.algo_name() == "Keccak-1600") { return std::unique_ptr( new Keccak_1600(req.arg_as_integer(0, 512))); } #endif #if defined(BOTAN_HAS_SHA3) if(req.algo_name() == "SHA-3") { return std::unique_ptr( new SHA_3(req.arg_as_integer(0, 512))); } #endif #if defined(BOTAN_HAS_SHAKE) if(req.algo_name() == "SHAKE-128") { return std::unique_ptr(new SHAKE_128(req.arg_as_integer(0, 128))); } if(req.algo_name() == "SHAKE-256") { return std::unique_ptr(new SHAKE_256(req.arg_as_integer(0, 256))); } #endif #if defined(BOTAN_HAS_STREEBOG) if(algo_spec == "Streebog-256") { return std::unique_ptr(new Streebog_256); } if(algo_spec == "Streebog-512") { return std::unique_ptr(new Streebog_512); } #endif #if defined(BOTAN_HAS_SM3) if(algo_spec == "SM3") { return std::unique_ptr(new SM3); } #endif #if defined(BOTAN_HAS_WHIRLPOOL) if(req.algo_name() == "Whirlpool") { return std::unique_ptr(new Whirlpool); } #endif #if defined(BOTAN_HAS_PARALLEL_HASH) if(req.algo_name() == "Parallel") { std::vector> hashes; for(size_t i = 0; i != req.arg_count(); ++i) { auto h = HashFunction::create(req.arg(i)); if(!h) { return nullptr; } hashes.push_back(std::move(h)); } return std::unique_ptr(new Parallel(hashes)); } #endif #if defined(BOTAN_HAS_COMB4P) if(req.algo_name() == "Comb4P" && req.arg_count() == 2) { std::unique_ptr h1(HashFunction::create(req.arg(0))); std::unique_ptr h2(HashFunction::create(req.arg(1))); if(h1 && h2) return std::unique_ptr(new Comb4P(h1.release(), h2.release())); } #endif return nullptr; } //static std::unique_ptr HashFunction::create_or_throw(const std::string& algo, const std::string& provider) { if(auto hash = HashFunction::create(algo, provider)) { return hash; } throw Lookup_Error("Hash", algo, provider); } std::vector HashFunction::providers(const std::string& algo_spec) { return probe_providers_of(algo_spec, {"base", "openssl", "commoncrypto"}); } } /* * 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); } } /* * HMAC * (C) 1999-2007,2014,2020 Jack Lloyd * 2007 Yves Jerschow * * Botan is released under the Simplified BSD License (see license.txt) */ namespace Botan { /* * Update a HMAC Calculation */ void HMAC::add_data(const uint8_t input[], size_t length) { verify_key_set(m_ikey.empty() == false); m_hash->update(input, length); } /* * Finalize a HMAC Calculation */ void HMAC::final_result(uint8_t mac[]) { verify_key_set(m_okey.empty() == false); m_hash->final(mac); m_hash->update(m_okey); m_hash->update(mac, m_hash_output_length); m_hash->final(mac); m_hash->update(m_ikey); } Key_Length_Specification HMAC::key_spec() const { // Support very long lengths for things like PBKDF2 and the TLS PRF return Key_Length_Specification(0, 4096); } size_t HMAC::output_length() const { return m_hash_output_length; } /* * HMAC Key Schedule */ void HMAC::key_schedule(const uint8_t key[], size_t length) { const uint8_t ipad = 0x36; const uint8_t opad = 0x5C; m_hash->clear(); m_ikey.resize(m_hash_block_size); m_okey.resize(m_hash_block_size); clear_mem(m_ikey.data(), m_ikey.size()); clear_mem(m_okey.data(), m_okey.size()); /* * Sometimes the HMAC key length itself is sensitive, as with PBKDF2 where it * reveals the length of the passphrase. Make some attempt to hide this to * side channels. Clearly if the secret is longer than the block size then the * branch to hash first reveals that. In addition, counting the number of * compression functions executed reveals the size at the granularity of the * hash function's block size. * * The greater concern is for smaller keys; being able to detect when a * passphrase is say 4 bytes may assist choosing weaker targets. Even though * the loop bounds are constant, we can only actually read key[0..length] so * it doesn't seem possible to make this computation truly constant time. * * We don't mind leaking if the length is exactly zero since that's * trivial to simply check. */ if(length > m_hash_block_size) { m_hash->update(key, length); m_hash->final(m_ikey.data()); } else if(length > 0) { for(size_t i = 0, i_mod_length = 0; i != m_hash_block_size; ++i) { /* access key[i % length] but avoiding division due to variable time computation on some processors. */ auto needs_reduction = CT::Mask::is_lte(length, i_mod_length); i_mod_length = needs_reduction.select(0, i_mod_length); const uint8_t kb = key[i_mod_length]; auto in_range = CT::Mask::is_lt(i, length); m_ikey[i] = static_cast(in_range.if_set_return(kb)); i_mod_length += 1; } } for(size_t i = 0; i != m_hash_block_size; ++i) { m_ikey[i] ^= ipad; m_okey[i] = m_ikey[i] ^ ipad ^ opad; } m_hash->update(m_ikey); } /* * Clear memory of sensitive data */ void HMAC::clear() { m_hash->clear(); zap(m_ikey); zap(m_okey); } /* * Return the name of this type */ std::string HMAC::name() const { return "HMAC(" + m_hash->name() + ")"; } /* * Return a clone of this object */ MessageAuthenticationCode* HMAC::clone() const { return new HMAC(m_hash->clone()); } /* * HMAC Constructor */ HMAC::HMAC(HashFunction* hash) : m_hash(hash), m_hash_output_length(m_hash->output_length()), m_hash_block_size(m_hash->hash_block_size()) { BOTAN_ARG_CHECK(m_hash_block_size >= m_hash_output_length, "HMAC is not compatible with this hash function"); } } /* * Message Authentication Code base class * (C) 1999-2008 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #if defined(BOTAN_HAS_CBC_MAC) #endif #if defined(BOTAN_HAS_CMAC) #endif #if defined(BOTAN_HAS_GMAC) #endif #if defined(BOTAN_HAS_HMAC) #endif #if defined(BOTAN_HAS_POLY1305) #endif #if defined(BOTAN_HAS_SIPHASH) #endif #if defined(BOTAN_HAS_ANSI_X919_MAC) #endif namespace Botan { std::unique_ptr MessageAuthenticationCode::create(const std::string& algo_spec, const std::string& provider) { const SCAN_Name req(algo_spec); #if defined(BOTAN_HAS_GMAC) if(req.algo_name() == "GMAC" && req.arg_count() == 1) { if(provider.empty() || provider == "base") { if(auto bc = BlockCipher::create(req.arg(0))) return std::unique_ptr(new GMAC(bc.release())); } } #endif #if defined(BOTAN_HAS_HMAC) if(req.algo_name() == "HMAC" && req.arg_count() == 1) { // TODO OpenSSL if(provider.empty() || provider == "base") { if(auto h = HashFunction::create(req.arg(0))) return std::unique_ptr(new HMAC(h.release())); } } #endif #if defined(BOTAN_HAS_POLY1305) if(req.algo_name() == "Poly1305" && req.arg_count() == 0) { if(provider.empty() || provider == "base") return std::unique_ptr(new Poly1305); } #endif #if defined(BOTAN_HAS_SIPHASH) if(req.algo_name() == "SipHash") { if(provider.empty() || provider == "base") { return std::unique_ptr( new SipHash(req.arg_as_integer(0, 2), req.arg_as_integer(1, 4))); } } #endif #if defined(BOTAN_HAS_CMAC) if((req.algo_name() == "CMAC" || req.algo_name() == "OMAC") && req.arg_count() == 1) { // TODO: OpenSSL CMAC if(provider.empty() || provider == "base") { if(auto bc = BlockCipher::create(req.arg(0))) return std::unique_ptr(new CMAC(bc.release())); } } #endif #if defined(BOTAN_HAS_CBC_MAC) if(req.algo_name() == "CBC-MAC" && req.arg_count() == 1) { if(provider.empty() || provider == "base") { if(auto bc = BlockCipher::create(req.arg(0))) return std::unique_ptr(new CBC_MAC(bc.release())); } } #endif #if defined(BOTAN_HAS_ANSI_X919_MAC) if(req.algo_name() == "X9.19-MAC") { if(provider.empty() || provider == "base") { return std::unique_ptr(new ANSI_X919_MAC); } } #endif BOTAN_UNUSED(req); BOTAN_UNUSED(provider); return nullptr; } std::vector MessageAuthenticationCode::providers(const std::string& algo_spec) { return probe_providers_of(algo_spec, {"base", "openssl"}); } //static std::unique_ptr MessageAuthenticationCode::create_or_throw(const std::string& algo, const std::string& provider) { if(auto mac = MessageAuthenticationCode::create(algo, provider)) { return mac; } throw Lookup_Error("MAC", algo, provider); } void MessageAuthenticationCode::start_msg(const uint8_t nonce[], size_t nonce_len) { BOTAN_UNUSED(nonce); if(nonce_len > 0) throw Invalid_IV_Length(name(), nonce_len); } /* * Default (deterministic) MAC verification operation */ bool MessageAuthenticationCode::verify_mac(const uint8_t mac[], size_t length) { secure_vector our_mac = final(); if(our_mac.size() != length) return false; return constant_time_compare(our_mac.data(), mac, length); } } /* * Merkle-Damgard Hash Function * (C) 1999-2008,2018 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ namespace Botan { /* * MDx_HashFunction Constructor */ MDx_HashFunction::MDx_HashFunction(size_t block_len, bool byte_big_endian, bool bit_big_endian, uint8_t cnt_size) : m_pad_char(bit_big_endian == true ? 0x80 : 0x01), m_counter_size(cnt_size), m_block_bits(ceil_log2(block_len)), m_count_big_endian(byte_big_endian), m_count(0), m_buffer(block_len), m_position(0) { if(!is_power_of_2(block_len)) throw Invalid_Argument("MDx_HashFunction block length must be a power of 2"); if(m_block_bits < 3 || m_block_bits > 16) throw Invalid_Argument("MDx_HashFunction block size too large or too small"); if(m_counter_size < 8 || m_counter_size > block_len) throw Invalid_State("MDx_HashFunction invalid counter length"); } /* * Clear memory of sensitive data */ void MDx_HashFunction::clear() { zeroise(m_buffer); m_count = m_position = 0; } /* * Update the hash */ void MDx_HashFunction::add_data(const uint8_t input[], size_t length) { const size_t block_len = static_cast(1) << m_block_bits; m_count += length; if(m_position) { buffer_insert(m_buffer, m_position, input, length); if(m_position + length >= block_len) { compress_n(m_buffer.data(), 1); input += (block_len - m_position); length -= (block_len - m_position); m_position = 0; } } // Just in case the compiler can't figure out block_len is a power of 2 const size_t full_blocks = length >> m_block_bits; const size_t remaining = length & (block_len - 1); if(full_blocks > 0) { compress_n(input, full_blocks); } buffer_insert(m_buffer, m_position, input + full_blocks * block_len, remaining); m_position += remaining; } /* * Finalize a hash */ void MDx_HashFunction::final_result(uint8_t output[]) { const size_t block_len = static_cast(1) << m_block_bits; clear_mem(&m_buffer[m_position], block_len - m_position); m_buffer[m_position] = m_pad_char; if(m_position >= block_len - m_counter_size) { compress_n(m_buffer.data(), 1); zeroise(m_buffer); } write_count(&m_buffer[block_len - m_counter_size]); compress_n(m_buffer.data(), 1); copy_out(output); clear(); } /* * Write the count bits to the buffer */ void MDx_HashFunction::write_count(uint8_t out[]) { BOTAN_ASSERT_NOMSG(m_counter_size <= output_length()); BOTAN_ASSERT_NOMSG(m_counter_size >= 8); const uint64_t bit_count = m_count * 8; if(m_count_big_endian) store_be(bit_count, out + m_counter_size - 8); else store_le(bit_count, out + m_counter_size - 8); } } /* * 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; } } /* * PBKDF * (C) 2012 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #if defined(BOTAN_HAS_PBKDF1) #endif #if defined(BOTAN_HAS_PBKDF2) #endif #if defined(BOTAN_HAS_PGP_S2K) #endif namespace Botan { std::unique_ptr PBKDF::create(const std::string& algo_spec, const std::string& provider) { const SCAN_Name req(algo_spec); #if defined(BOTAN_HAS_PBKDF2) if(req.algo_name() == "PBKDF2") { // TODO OpenSSL if(provider.empty() || provider == "base") { if(auto mac = MessageAuthenticationCode::create(req.arg(0))) return std::unique_ptr(new PKCS5_PBKDF2(mac.release())); if(auto mac = MessageAuthenticationCode::create("HMAC(" + req.arg(0) + ")")) return std::unique_ptr(new PKCS5_PBKDF2(mac.release())); } return nullptr; } #endif #if defined(BOTAN_HAS_PBKDF1) if(req.algo_name() == "PBKDF1" && req.arg_count() == 1) { if(auto hash = HashFunction::create(req.arg(0))) return std::unique_ptr(new PKCS5_PBKDF1(hash.release())); } #endif #if defined(BOTAN_HAS_PGP_S2K) if(req.algo_name() == "OpenPGP-S2K" && req.arg_count() == 1) { if(auto hash = HashFunction::create(req.arg(0))) return std::unique_ptr(new OpenPGP_S2K(hash.release())); } #endif BOTAN_UNUSED(req); BOTAN_UNUSED(provider); return nullptr; } //static std::unique_ptr PBKDF::create_or_throw(const std::string& algo, const std::string& provider) { if(auto pbkdf = PBKDF::create(algo, provider)) { return pbkdf; } throw Lookup_Error("PBKDF", algo, provider); } std::vector PBKDF::providers(const std::string& algo_spec) { return probe_providers_of(algo_spec, { "base", "openssl" }); } void PBKDF::pbkdf_timed(uint8_t out[], size_t out_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, std::chrono::milliseconds msec, size_t& iterations) const { iterations = pbkdf(out, out_len, passphrase, salt, salt_len, 0, msec); } void PBKDF::pbkdf_iterations(uint8_t out[], size_t out_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, size_t iterations) const { if(iterations == 0) throw Invalid_Argument(name() + ": Invalid iteration count"); const size_t iterations_run = pbkdf(out, out_len, passphrase, salt, salt_len, iterations, std::chrono::milliseconds(0)); BOTAN_ASSERT_EQUAL(iterations, iterations_run, "Expected PBKDF iterations"); } secure_vector PBKDF::pbkdf_iterations(size_t out_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, size_t iterations) const { secure_vector out(out_len); pbkdf_iterations(out.data(), out_len, passphrase, salt, salt_len, iterations); return out; } secure_vector PBKDF::pbkdf_timed(size_t out_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, std::chrono::milliseconds msec, size_t& iterations) const { secure_vector out(out_len); pbkdf_timed(out.data(), out_len, passphrase, salt, salt_len, msec, iterations); return out; } } /* * (C) 2018 Ribose Inc * * Botan is released under the Simplified BSD License (see license.txt) */ #if defined(BOTAN_HAS_PBKDF2) #endif #if defined(BOTAN_HAS_PGP_S2K) #endif #if defined(BOTAN_HAS_SCRYPT) #endif #if defined(BOTAN_HAS_ARGON2) #endif #if defined(BOTAN_HAS_PBKDF_BCRYPT) #endif namespace Botan { std::unique_ptr PasswordHashFamily::create(const std::string& algo_spec, const std::string& provider) { const SCAN_Name req(algo_spec); #if defined(BOTAN_HAS_PBKDF2) if(req.algo_name() == "PBKDF2") { // TODO OpenSSL if(provider.empty() || provider == "base") { if(auto mac = MessageAuthenticationCode::create(req.arg(0))) return std::unique_ptr(new PBKDF2_Family(mac.release())); if(auto mac = MessageAuthenticationCode::create("HMAC(" + req.arg(0) + ")")) return std::unique_ptr(new PBKDF2_Family(mac.release())); } return nullptr; } #endif #if defined(BOTAN_HAS_SCRYPT) if(req.algo_name() == "Scrypt") { return std::unique_ptr(new Scrypt_Family); } #endif #if defined(BOTAN_HAS_ARGON2) if(req.algo_name() == "Argon2d") { return std::unique_ptr(new Argon2_Family(0)); } else if(req.algo_name() == "Argon2i") { return std::unique_ptr(new Argon2_Family(1)); } else if(req.algo_name() == "Argon2id") { return std::unique_ptr(new Argon2_Family(2)); } #endif #if defined(BOTAN_HAS_PBKDF_BCRYPT) if(req.algo_name() == "Bcrypt-PBKDF") { return std::unique_ptr(new Bcrypt_PBKDF_Family); } #endif #if defined(BOTAN_HAS_PGP_S2K) if(req.algo_name() == "OpenPGP-S2K" && req.arg_count() == 1) { if(auto hash = HashFunction::create(req.arg(0))) { return std::unique_ptr(new RFC4880_S2K_Family(hash.release())); } } #endif BOTAN_UNUSED(req); BOTAN_UNUSED(provider); return nullptr; } //static std::unique_ptr PasswordHashFamily::create_or_throw(const std::string& algo, const std::string& provider) { if(auto pbkdf = PasswordHashFamily::create(algo, provider)) { return pbkdf; } throw Lookup_Error("PasswordHashFamily", algo, provider); } std::vector PasswordHashFamily::providers(const std::string& algo_spec) { return probe_providers_of(algo_spec, { "base", "openssl" }); } } /* * PBKDF2 * (C) 1999-2007 Jack Lloyd * (C) 2018 Ribose Inc * * Botan is released under the Simplified BSD License (see license.txt) */ namespace Botan { namespace { void pbkdf2_set_key(MessageAuthenticationCode& prf, const char* password, size_t password_len) { try { prf.set_key(cast_char_ptr_to_uint8(password), password_len); } catch(Invalid_Key_Length&) { throw Invalid_Argument("PBKDF2 cannot accept passphrase of the given size"); } } } size_t pbkdf2(MessageAuthenticationCode& prf, uint8_t out[], size_t out_len, const std::string& password, const uint8_t salt[], size_t salt_len, size_t iterations, std::chrono::milliseconds msec) { if(iterations == 0) { iterations = PBKDF2(prf, out_len, msec).iterations(); } PBKDF2 pbkdf2(prf, iterations); pbkdf2.derive_key(out, out_len, password.c_str(), password.size(), salt, salt_len); return iterations; } namespace { size_t tune_pbkdf2(MessageAuthenticationCode& prf, size_t output_length, uint32_t msec) { if(output_length == 0) output_length = 1; const size_t prf_sz = prf.output_length(); BOTAN_ASSERT_NOMSG(prf_sz > 0); secure_vector U(prf_sz); const size_t trial_iterations = 2000; // Short output ensures we only need a single PBKDF2 block Timer timer("PBKDF2"); const auto tune_time = BOTAN_PBKDF_TUNING_TIME; prf.set_key(nullptr, 0); timer.run_until_elapsed(tune_time, [&]() { uint8_t out[12] = { 0 }; uint8_t salt[12] = { 0 }; pbkdf2(prf, out, sizeof(out), salt, sizeof(salt), trial_iterations); }); if(timer.events() == 0) return trial_iterations; const uint64_t duration_nsec = timer.value() / timer.events(); const uint64_t desired_nsec = static_cast(msec) * 1000000; if(duration_nsec > desired_nsec) return trial_iterations; const size_t blocks_needed = (output_length + prf_sz - 1) / prf_sz; const size_t multiplier = static_cast(desired_nsec / duration_nsec / blocks_needed); if(multiplier == 0) return trial_iterations; else return trial_iterations * multiplier; } } void pbkdf2(MessageAuthenticationCode& prf, uint8_t out[], size_t out_len, const uint8_t salt[], size_t salt_len, size_t iterations) { if(iterations == 0) throw Invalid_Argument("PBKDF2: Invalid iteration count"); clear_mem(out, out_len); if(out_len == 0) return; const size_t prf_sz = prf.output_length(); BOTAN_ASSERT_NOMSG(prf_sz > 0); secure_vector U(prf_sz); uint32_t counter = 1; while(out_len) { const size_t prf_output = std::min(prf_sz, out_len); prf.update(salt, salt_len); prf.update_be(counter++); prf.final(U.data()); xor_buf(out, U.data(), prf_output); for(size_t i = 1; i != iterations; ++i) { prf.update(U); prf.final(U.data()); xor_buf(out, U.data(), prf_output); } out_len -= prf_output; out += prf_output; } } // PBKDF interface size_t PKCS5_PBKDF2::pbkdf(uint8_t key[], size_t key_len, const std::string& password, const uint8_t salt[], size_t salt_len, size_t iterations, std::chrono::milliseconds msec) const { if(iterations == 0) { iterations = PBKDF2(*m_mac, key_len, msec).iterations(); } PBKDF2 pbkdf2(*m_mac, iterations); pbkdf2.derive_key(key, key_len, password.c_str(), password.size(), salt, salt_len); return iterations; } std::string PKCS5_PBKDF2::name() const { return "PBKDF2(" + m_mac->name() + ")"; } PBKDF* PKCS5_PBKDF2::clone() const { return new PKCS5_PBKDF2(m_mac->clone()); } // PasswordHash interface PBKDF2::PBKDF2(const MessageAuthenticationCode& prf, size_t olen, std::chrono::milliseconds msec) : m_prf(prf.clone()), m_iterations(tune_pbkdf2(*m_prf, olen, static_cast(msec.count()))) {} std::string PBKDF2::to_string() const { return "PBKDF2(" + m_prf->name() + "," + std::to_string(m_iterations) + ")"; } void PBKDF2::derive_key(uint8_t out[], size_t out_len, const char* password, const size_t password_len, const uint8_t salt[], size_t salt_len) const { pbkdf2_set_key(*m_prf, password, password_len); pbkdf2(*m_prf, out, out_len, salt, salt_len, m_iterations); } std::string PBKDF2_Family::name() const { return "PBKDF2(" + m_prf->name() + ")"; } std::unique_ptr PBKDF2_Family::tune(size_t output_len, std::chrono::milliseconds msec, size_t) const { return std::unique_ptr(new PBKDF2(*m_prf, output_len, msec)); } std::unique_ptr PBKDF2_Family::default_params() const { return std::unique_ptr(new PBKDF2(*m_prf, 150000)); } std::unique_ptr PBKDF2_Family::from_params(size_t iter, size_t, size_t) const { return std::unique_ptr(new PBKDF2(*m_prf, iter)); } std::unique_ptr PBKDF2_Family::from_iterations(size_t iter) const { return std::unique_ptr(new PBKDF2(*m_prf, iter)); } } /* * (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 } /* * SHA-{224,256} * (C) 1999-2010,2017 Jack Lloyd * 2007 FlexSecure GmbH * * Botan is released under the Simplified BSD License (see license.txt) */ namespace Botan { namespace { std::string sha256_provider() { #if defined(BOTAN_HAS_SHA2_32_X86) if(CPUID::has_intel_sha()) { return "shani"; } #endif #if defined(BOTAN_HAS_SHA2_32_X86_BMI2) if(CPUID::has_bmi2()) { return "bmi2"; } #endif #if defined(BOTAN_HAS_SHA2_32_ARMV8) if(CPUID::has_arm_sha2()) { return "armv8"; } #endif return "base"; } } std::unique_ptr SHA_224::copy_state() const { return std::unique_ptr(new SHA_224(*this)); } std::unique_ptr SHA_256::copy_state() const { return std::unique_ptr(new SHA_256(*this)); } /* * SHA-256 F1 Function * * Use a macro as many compilers won't inline a function this big, * even though it is much faster if inlined. */ #define SHA2_32_F(A, B, C, D, E, F, G, H, M1, M2, M3, M4, magic) do { \ uint32_t A_rho = rotr<2>(A) ^ rotr<13>(A) ^ rotr<22>(A); \ uint32_t E_rho = rotr<6>(E) ^ rotr<11>(E) ^ rotr<25>(E); \ uint32_t M2_sigma = rotr<17>(M2) ^ rotr<19>(M2) ^ (M2 >> 10); \ uint32_t M4_sigma = rotr<7>(M4) ^ rotr<18>(M4) ^ (M4 >> 3); \ H += magic + E_rho + ((E & F) ^ (~E & G)) + M1; \ D += H; \ H += A_rho + ((A & B) | ((A | B) & C)); \ M1 += M2_sigma + M3 + M4_sigma; \ } while(0); /* * SHA-224 / SHA-256 compression function */ void SHA_256::compress_digest(secure_vector& digest, const uint8_t input[], size_t blocks) { #if defined(BOTAN_HAS_SHA2_32_X86) if(CPUID::has_intel_sha()) { return SHA_256::compress_digest_x86(digest, input, blocks); } #endif #if defined(BOTAN_HAS_SHA2_32_X86_BMI2) if(CPUID::has_bmi2()) { return SHA_256::compress_digest_x86_bmi2(digest, input, blocks); } #endif #if defined(BOTAN_HAS_SHA2_32_ARMV8) if(CPUID::has_arm_sha2()) { return SHA_256::compress_digest_armv8(digest, input, blocks); } #endif uint32_t A = digest[0], B = digest[1], C = digest[2], D = digest[3], E = digest[4], F = digest[5], G = digest[6], H = digest[7]; for(size_t i = 0; i != blocks; ++i) { uint32_t W00 = load_be(input, 0); uint32_t W01 = load_be(input, 1); uint32_t W02 = load_be(input, 2); uint32_t W03 = load_be(input, 3); uint32_t W04 = load_be(input, 4); uint32_t W05 = load_be(input, 5); uint32_t W06 = load_be(input, 6); uint32_t W07 = load_be(input, 7); uint32_t W08 = load_be(input, 8); uint32_t W09 = load_be(input, 9); uint32_t W10 = load_be(input, 10); uint32_t W11 = load_be(input, 11); uint32_t W12 = load_be(input, 12); uint32_t W13 = load_be(input, 13); uint32_t W14 = load_be(input, 14); uint32_t W15 = load_be(input, 15); SHA2_32_F(A, B, C, D, E, F, G, H, W00, W14, W09, W01, 0x428A2F98); SHA2_32_F(H, A, B, C, D, E, F, G, W01, W15, W10, W02, 0x71374491); SHA2_32_F(G, H, A, B, C, D, E, F, W02, W00, W11, W03, 0xB5C0FBCF); SHA2_32_F(F, G, H, A, B, C, D, E, W03, W01, W12, W04, 0xE9B5DBA5); SHA2_32_F(E, F, G, H, A, B, C, D, W04, W02, W13, W05, 0x3956C25B); SHA2_32_F(D, E, F, G, H, A, B, C, W05, W03, W14, W06, 0x59F111F1); SHA2_32_F(C, D, E, F, G, H, A, B, W06, W04, W15, W07, 0x923F82A4); SHA2_32_F(B, C, D, E, F, G, H, A, W07, W05, W00, W08, 0xAB1C5ED5); SHA2_32_F(A, B, C, D, E, F, G, H, W08, W06, W01, W09, 0xD807AA98); SHA2_32_F(H, A, B, C, D, E, F, G, W09, W07, W02, W10, 0x12835B01); SHA2_32_F(G, H, A, B, C, D, E, F, W10, W08, W03, W11, 0x243185BE); SHA2_32_F(F, G, H, A, B, C, D, E, W11, W09, W04, W12, 0x550C7DC3); SHA2_32_F(E, F, G, H, A, B, C, D, W12, W10, W05, W13, 0x72BE5D74); SHA2_32_F(D, E, F, G, H, A, B, C, W13, W11, W06, W14, 0x80DEB1FE); SHA2_32_F(C, D, E, F, G, H, A, B, W14, W12, W07, W15, 0x9BDC06A7); SHA2_32_F(B, C, D, E, F, G, H, A, W15, W13, W08, W00, 0xC19BF174); SHA2_32_F(A, B, C, D, E, F, G, H, W00, W14, W09, W01, 0xE49B69C1); SHA2_32_F(H, A, B, C, D, E, F, G, W01, W15, W10, W02, 0xEFBE4786); SHA2_32_F(G, H, A, B, C, D, E, F, W02, W00, W11, W03, 0x0FC19DC6); SHA2_32_F(F, G, H, A, B, C, D, E, W03, W01, W12, W04, 0x240CA1CC); SHA2_32_F(E, F, G, H, A, B, C, D, W04, W02, W13, W05, 0x2DE92C6F); SHA2_32_F(D, E, F, G, H, A, B, C, W05, W03, W14, W06, 0x4A7484AA); SHA2_32_F(C, D, E, F, G, H, A, B, W06, W04, W15, W07, 0x5CB0A9DC); SHA2_32_F(B, C, D, E, F, G, H, A, W07, W05, W00, W08, 0x76F988DA); SHA2_32_F(A, B, C, D, E, F, G, H, W08, W06, W01, W09, 0x983E5152); SHA2_32_F(H, A, B, C, D, E, F, G, W09, W07, W02, W10, 0xA831C66D); SHA2_32_F(G, H, A, B, C, D, E, F, W10, W08, W03, W11, 0xB00327C8); SHA2_32_F(F, G, H, A, B, C, D, E, W11, W09, W04, W12, 0xBF597FC7); SHA2_32_F(E, F, G, H, A, B, C, D, W12, W10, W05, W13, 0xC6E00BF3); SHA2_32_F(D, E, F, G, H, A, B, C, W13, W11, W06, W14, 0xD5A79147); SHA2_32_F(C, D, E, F, G, H, A, B, W14, W12, W07, W15, 0x06CA6351); SHA2_32_F(B, C, D, E, F, G, H, A, W15, W13, W08, W00, 0x14292967); SHA2_32_F(A, B, C, D, E, F, G, H, W00, W14, W09, W01, 0x27B70A85); SHA2_32_F(H, A, B, C, D, E, F, G, W01, W15, W10, W02, 0x2E1B2138); SHA2_32_F(G, H, A, B, C, D, E, F, W02, W00, W11, W03, 0x4D2C6DFC); SHA2_32_F(F, G, H, A, B, C, D, E, W03, W01, W12, W04, 0x53380D13); SHA2_32_F(E, F, G, H, A, B, C, D, W04, W02, W13, W05, 0x650A7354); SHA2_32_F(D, E, F, G, H, A, B, C, W05, W03, W14, W06, 0x766A0ABB); SHA2_32_F(C, D, E, F, G, H, A, B, W06, W04, W15, W07, 0x81C2C92E); SHA2_32_F(B, C, D, E, F, G, H, A, W07, W05, W00, W08, 0x92722C85); SHA2_32_F(A, B, C, D, E, F, G, H, W08, W06, W01, W09, 0xA2BFE8A1); SHA2_32_F(H, A, B, C, D, E, F, G, W09, W07, W02, W10, 0xA81A664B); SHA2_32_F(G, H, A, B, C, D, E, F, W10, W08, W03, W11, 0xC24B8B70); SHA2_32_F(F, G, H, A, B, C, D, E, W11, W09, W04, W12, 0xC76C51A3); SHA2_32_F(E, F, G, H, A, B, C, D, W12, W10, W05, W13, 0xD192E819); SHA2_32_F(D, E, F, G, H, A, B, C, W13, W11, W06, W14, 0xD6990624); SHA2_32_F(C, D, E, F, G, H, A, B, W14, W12, W07, W15, 0xF40E3585); SHA2_32_F(B, C, D, E, F, G, H, A, W15, W13, W08, W00, 0x106AA070); SHA2_32_F(A, B, C, D, E, F, G, H, W00, W14, W09, W01, 0x19A4C116); SHA2_32_F(H, A, B, C, D, E, F, G, W01, W15, W10, W02, 0x1E376C08); SHA2_32_F(G, H, A, B, C, D, E, F, W02, W00, W11, W03, 0x2748774C); SHA2_32_F(F, G, H, A, B, C, D, E, W03, W01, W12, W04, 0x34B0BCB5); SHA2_32_F(E, F, G, H, A, B, C, D, W04, W02, W13, W05, 0x391C0CB3); SHA2_32_F(D, E, F, G, H, A, B, C, W05, W03, W14, W06, 0x4ED8AA4A); SHA2_32_F(C, D, E, F, G, H, A, B, W06, W04, W15, W07, 0x5B9CCA4F); SHA2_32_F(B, C, D, E, F, G, H, A, W07, W05, W00, W08, 0x682E6FF3); SHA2_32_F(A, B, C, D, E, F, G, H, W08, W06, W01, W09, 0x748F82EE); SHA2_32_F(H, A, B, C, D, E, F, G, W09, W07, W02, W10, 0x78A5636F); SHA2_32_F(G, H, A, B, C, D, E, F, W10, W08, W03, W11, 0x84C87814); SHA2_32_F(F, G, H, A, B, C, D, E, W11, W09, W04, W12, 0x8CC70208); SHA2_32_F(E, F, G, H, A, B, C, D, W12, W10, W05, W13, 0x90BEFFFA); SHA2_32_F(D, E, F, G, H, A, B, C, W13, W11, W06, W14, 0xA4506CEB); SHA2_32_F(C, D, E, F, G, H, A, B, W14, W12, W07, W15, 0xBEF9A3F7); SHA2_32_F(B, C, D, E, F, G, H, A, W15, W13, W08, W00, 0xC67178F2); A = (digest[0] += A); B = (digest[1] += B); C = (digest[2] += C); D = (digest[3] += D); E = (digest[4] += E); F = (digest[5] += F); G = (digest[6] += G); H = (digest[7] += H); input += 64; } } std::string SHA_224::provider() const { return sha256_provider(); } std::string SHA_256::provider() const { return sha256_provider(); } /* * SHA-224 compression function */ void SHA_224::compress_n(const uint8_t input[], size_t blocks) { SHA_256::compress_digest(m_digest, input, blocks); } /* * Copy out the digest */ void SHA_224::copy_out(uint8_t output[]) { copy_out_vec_be(output, output_length(), m_digest); } /* * Clear memory of sensitive data */ void SHA_224::clear() { MDx_HashFunction::clear(); m_digest[0] = 0xC1059ED8; m_digest[1] = 0x367CD507; m_digest[2] = 0x3070DD17; m_digest[3] = 0xF70E5939; m_digest[4] = 0xFFC00B31; m_digest[5] = 0x68581511; m_digest[6] = 0x64F98FA7; m_digest[7] = 0xBEFA4FA4; } /* * SHA-256 compression function */ void SHA_256::compress_n(const uint8_t input[], size_t blocks) { SHA_256::compress_digest(m_digest, input, blocks); } /* * Copy out the digest */ void SHA_256::copy_out(uint8_t output[]) { copy_out_vec_be(output, output_length(), m_digest); } /* * Clear memory of sensitive data */ void SHA_256::clear() { MDx_HashFunction::clear(); m_digest[0] = 0x6A09E667; m_digest[1] = 0xBB67AE85; m_digest[2] = 0x3C6EF372; m_digest[3] = 0xA54FF53A; m_digest[4] = 0x510E527F; m_digest[5] = 0x9B05688C; m_digest[6] = 0x1F83D9AB; m_digest[7] = 0x5BE0CD19; } } /* * 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.5.1/oscar/SleepLib/thirdparty/botan_all.h000066400000000000000000000010721450332542600225730ustar00rootroot00000000000000/* 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.5.1/oscar/SleepLib/thirdparty/botan_linux.h000066400000000000000000006206541450332542600231770ustar00rootroot00000000000000/* * 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 --cpu=generic --disable-shared --minimized-build --enable-modules=aes,gcm,sha2_32,pbkdf2' * * 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_HASH 20180112 #define BOTAN_HAS_HEX_CODEC 20131128 #define BOTAN_HAS_HMAC 20131128 #define BOTAN_HAS_MAC 20150626 #define BOTAN_HAS_MDX_HASH_FUNCTION 20131128 #define BOTAN_HAS_MODES 20150626 #define BOTAN_HAS_PBKDF 20180902 #define BOTAN_HAS_PBKDF2 20180902 #define BOTAN_HAS_SHA2_32 20131128 #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 { /** * This class represents hash function (message digest) objects */ class BOTAN_PUBLIC_API(2,0) HashFunction : public Buffered_Computation { public: /** * Create an instance based on a name, or return null if the * algo/provider combination cannot be found. If provider is * empty then best available is chosen. */ 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 Lookup_Error if not 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 * @param algo_spec algorithm name */ static std::vector providers(const std::string& algo_spec); /** * @return new object representing the same algorithm as *this */ virtual HashFunction* clone() const = 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"; } virtual ~HashFunction() = default; /** * Reset the state. */ virtual void clear() = 0; /** * @return the hash function name */ virtual std::string name() const = 0; /** * @return hash block size as defined for this algorithm */ virtual size_t hash_block_size() const { return 0; } /** * Return a new hash object with the same state as *this. This * allows computing the hash of several messages with a common * prefix more efficiently than would otherwise be possible. * * This function should be called `clone` but that was already * used for the case of returning an uninitialized object. * @return new hash object */ virtual std::unique_ptr copy_state() const = 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); } namespace Botan { /** * This class represents Message Authentication Code (MAC) objects. */ class BOTAN_PUBLIC_API(2,0) MessageAuthenticationCode : public Buffered_Computation, 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 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 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); virtual ~MessageAuthenticationCode() = default; /** * Prepare for processing a message under the specified nonce * * Most MACs neither require nor support a nonce; for these algorithms * calling `start_msg` is optional and calling it with anything other than * an empty string is an error. One MAC which *requires* a per-message * nonce be specified is GMAC. * * @param nonce the message nonce bytes * @param nonce_len the size of len in bytes * Default implementation simply rejects all non-empty nonces * since most hash/MAC algorithms do not support randomization */ virtual void start_msg(const uint8_t nonce[], size_t nonce_len); /** * Begin processing a message with a nonce * * @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); } /** * Verify a MAC. * @param in the MAC to verify as a byte array * @param length the length of param in * @return true if the MAC is valid, false otherwise */ virtual bool verify_mac(const uint8_t in[], size_t length); /** * Verify a MAC. * @param in the MAC to verify as a byte array * @return true if the MAC is valid, false otherwise */ virtual bool verify_mac(const std::vector& in) { return verify_mac(in.data(), in.size()); } /** * Verify a MAC. * @param in the MAC to verify as a byte array * @return true if the MAC is valid, false otherwise */ virtual bool verify_mac(const secure_vector& in) { return verify_mac(in.data(), in.size()); } /** * Get a new object representing the same algorithm as *this */ virtual MessageAuthenticationCode* clone() const = 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"; } }; typedef MessageAuthenticationCode MAC; } BOTAN_FUTURE_INTERNAL_HEADER(hmac.h) namespace Botan { /** * HMAC */ class BOTAN_PUBLIC_API(2,0) HMAC final : public MessageAuthenticationCode { public: void clear() override; std::string name() const override; MessageAuthenticationCode* clone() const override; size_t output_length() const override; Key_Length_Specification key_spec() const override; /** * @param hash the hash to use for HMACing */ explicit HMAC(HashFunction* hash); HMAC(const HMAC&) = delete; HMAC& operator=(const HMAC&) = delete; private: void add_data(const uint8_t[], size_t) override; void final_result(uint8_t[]) override; void key_schedule(const uint8_t[], size_t) override; std::unique_ptr m_hash; secure_vector m_ikey, m_okey; size_t m_hash_output_length; size_t m_hash_block_size; }; } 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(mdx_hash.h) namespace Botan { /** * MDx Hash Function Base Class */ class BOTAN_PUBLIC_API(2,0) MDx_HashFunction : public HashFunction { public: /** * @param block_length is the number of bytes per block, which must * be a power of 2 and at least 8. * @param big_byte_endian specifies if the hash uses big-endian bytes * @param big_bit_endian specifies if the hash uses big-endian bits * @param counter_size specifies the size of the counter var in bytes */ MDx_HashFunction(size_t block_length, bool big_byte_endian, bool big_bit_endian, uint8_t counter_size = 8); size_t hash_block_size() const override final { return m_buffer.size(); } protected: void add_data(const uint8_t input[], size_t length) override final; void final_result(uint8_t output[]) override final; /** * Run the hash's compression function over a set of blocks * @param blocks the input * @param block_n the number of blocks */ virtual void compress_n(const uint8_t blocks[], size_t block_n) = 0; void clear() override; /** * Copy the output to the buffer * @param buffer to put the output into */ virtual void copy_out(uint8_t buffer[]) = 0; /** * Write the count, if used, to this spot * @param out where to write the counter to */ virtual void write_count(uint8_t out[]); private: const uint8_t m_pad_char; const uint8_t m_counter_size; const uint8_t m_block_bits; const bool m_count_big_endian; uint64_t m_count; secure_vector m_buffer; size_t m_position; }; } 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); } namespace Botan { /** * Base class for PBKDF (password based key derivation function) * implementations. Converts a password into a key using a salt * and iterated hashing to make brute force attacks harder. * * Starting in 2.8 this functionality is also offered by PasswordHash. * The PBKDF interface may be removed in a future release. */ class BOTAN_PUBLIC_API(2,0) PBKDF { 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 */ static std::vector providers(const std::string& algo_spec); /** * @return new instance of this same algorithm */ virtual PBKDF* clone() const = 0; /** * @return name of this PBKDF */ virtual std::string name() const = 0; virtual ~PBKDF() = default; /** * Derive a key from a passphrase for a number of iterations * specified by either iterations or if iterations == 0 then * running until msec time has elapsed. * * @param out buffer to store the derived key, must be of out_len bytes * @param out_len the desired length of the key to produce * @param passphrase the password to derive the key from * @param salt a randomly chosen salt * @param salt_len length of salt in bytes * @param iterations the number of iterations to use (use 10K or more) * @param msec if iterations is zero, then instead the PBKDF is * run until msec milliseconds has passed. * @return the number of iterations performed */ virtual size_t pbkdf(uint8_t out[], size_t out_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, size_t iterations, std::chrono::milliseconds msec) const = 0; /** * Derive a key from a passphrase for a number of iterations. * * @param out buffer to store the derived key, must be of out_len bytes * @param out_len the desired length of the key to produce * @param passphrase the password to derive the key from * @param salt a randomly chosen salt * @param salt_len length of salt in bytes * @param iterations the number of iterations to use (use 10K or more) */ void pbkdf_iterations(uint8_t out[], size_t out_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, size_t iterations) const; /** * Derive a key from a passphrase, running until msec time has elapsed. * * @param out buffer to store the derived key, must be of out_len bytes * @param out_len the desired length of the key to produce * @param passphrase the password to derive the key from * @param salt a randomly chosen salt * @param salt_len length of salt in bytes * @param msec if iterations is zero, then instead the PBKDF is * run until msec milliseconds has passed. * @param iterations set to the number iterations executed */ void pbkdf_timed(uint8_t out[], size_t out_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, std::chrono::milliseconds msec, size_t& iterations) const; /** * Derive a key from a passphrase for a number of iterations. * * @param out_len the desired length of the key to produce * @param passphrase the password to derive the key from * @param salt a randomly chosen salt * @param salt_len length of salt in bytes * @param iterations the number of iterations to use (use 10K or more) * @return the derived key */ secure_vector pbkdf_iterations(size_t out_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, size_t iterations) const; /** * Derive a key from a passphrase, running until msec time has elapsed. * * @param out_len the desired length of the key to produce * @param passphrase the password to derive the key from * @param salt a randomly chosen salt * @param salt_len length of salt in bytes * @param msec if iterations is zero, then instead the PBKDF is * run until msec milliseconds has passed. * @param iterations set to the number iterations executed * @return the derived key */ secure_vector pbkdf_timed(size_t out_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, std::chrono::milliseconds msec, size_t& iterations) const; // Following kept for compat with 1.10: /** * Derive a key from a passphrase * @param out_len the desired length of the key to produce * @param passphrase the password to derive the key from * @param salt a randomly chosen salt * @param salt_len length of salt in bytes * @param iterations the number of iterations to use (use 10K or more) */ OctetString derive_key(size_t out_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, size_t iterations) const { return pbkdf_iterations(out_len, passphrase, salt, salt_len, iterations); } /** * Derive a key from a passphrase * @param out_len the desired length of the key to produce * @param passphrase the password to derive the key from * @param salt a randomly chosen salt * @param iterations the number of iterations to use (use 10K or more) */ template OctetString derive_key(size_t out_len, const std::string& passphrase, const std::vector& salt, size_t iterations) const { return pbkdf_iterations(out_len, passphrase, salt.data(), salt.size(), iterations); } /** * Derive a key from a passphrase * @param out_len the desired length of the key to produce * @param passphrase the password to derive the key from * @param salt a randomly chosen salt * @param salt_len length of salt in bytes * @param msec is how long to run the PBKDF * @param iterations is set to the number of iterations used */ OctetString derive_key(size_t out_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, std::chrono::milliseconds msec, size_t& iterations) const { return pbkdf_timed(out_len, passphrase, salt, salt_len, msec, iterations); } /** * Derive a key from a passphrase using a certain amount of time * @param out_len the desired length of the key to produce * @param passphrase the password to derive the key from * @param salt a randomly chosen salt * @param msec is how long to run the PBKDF * @param iterations is set to the number of iterations used */ template OctetString derive_key(size_t out_len, const std::string& passphrase, const std::vector& salt, std::chrono::milliseconds msec, size_t& iterations) const { return pbkdf_timed(out_len, passphrase, salt.data(), salt.size(), msec, iterations); } }; /* * Compatibility typedef */ typedef PBKDF S2K; /** * Password based key derivation function factory method * @param algo_spec the name of the desired PBKDF algorithm * @param provider the provider to use * @return pointer to newly allocated object of that type */ inline PBKDF* get_pbkdf(const std::string& algo_spec, const std::string& provider = "") { return PBKDF::create_or_throw(algo_spec, provider).release(); } inline PBKDF* get_s2k(const std::string& algo_spec) { return get_pbkdf(algo_spec); } } namespace Botan { /** * Base class for password based key derivation functions. * * Converts a password into a key using a salt and iterated hashing to * make brute force attacks harder. */ class BOTAN_PUBLIC_API(2,8) PasswordHash { public: virtual ~PasswordHash() = default; virtual std::string to_string() const = 0; /** * Most password hashes have some notion of iterations. */ virtual size_t iterations() const = 0; /** * Some password hashing algorithms have a parameter which controls how * much memory is used. If not supported by some algorithm, returns 0. */ virtual size_t memory_param() const { return 0; } /** * Some password hashing algorithms have a parallelism parameter. * If the algorithm does not support this notion, then the * function returns zero. This allows distinguishing between a * password hash which just does not support parallel operation, * vs one that does support parallel operation but which has been * configured to use a single lane. */ virtual size_t parallelism() const { return 0; } /** * Returns an estimate of the total memory usage required to perform this * key derivation. * * If this algorithm uses a small and constant amount of memory, with no * effort made towards being memory hard, this function returns 0. */ virtual size_t total_memory_usage() const { return 0; } /** * Derive a key from a password * * @param out buffer to store the derived key, must be of out_len bytes * @param out_len the desired length of the key to produce * @param password the password to derive the key from * @param password_len the length of password in bytes * @param salt a randomly chosen salt * @param salt_len length of salt in bytes * * This function is const, but is not thread safe. Different threads should * either use unique objects, or serialize all access. */ virtual void derive_key(uint8_t out[], size_t out_len, const char* password, size_t password_len, const uint8_t salt[], size_t salt_len) const = 0; }; class BOTAN_PUBLIC_API(2,8) PasswordHashFamily { 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 */ static std::vector providers(const std::string& algo_spec); virtual ~PasswordHashFamily() = default; /** * @return name of this PasswordHash */ virtual std::string name() const = 0; /** * Return a new parameter set tuned for this machine * @param output_length how long the output length will be * @param msec the desired execution time in milliseconds * * @param max_memory_usage_mb some password hash functions can use a tunable * amount of memory, in this case max_memory_usage limits the amount of RAM * the returned parameters will require, in mebibytes (2**20 bytes). It may * require some small amount above the request. Set to zero to place no * limit at all. */ virtual std::unique_ptr tune(size_t output_length, std::chrono::milliseconds msec, size_t max_memory_usage_mb = 0) const = 0; /** * Return some default parameter set for this PBKDF that should be good * enough for most users. The value returned may change over time as * processing power and attacks improve. */ virtual std::unique_ptr default_params() const = 0; /** * Return a parameter chosen based on a rough approximation with the * specified iteration count. The exact value this returns for a particular * algorithm may change from over time. Think of it as an alternative to * tune, where time is expressed in terms of PBKDF2 iterations rather than * milliseconds. */ virtual std::unique_ptr from_iterations(size_t iterations) const = 0; /** * Create a password hash using some scheme specific format. * Eg PBKDF2 and PGP-S2K set iterations in i1 * Scrypt uses N,r,p in i{1-3} * Bcrypt-PBKDF just has iterations * Argon2{i,d,id} would use iterations, memory, parallelism for i{1-3}, * and Argon2 type is part of the family. * * Values not needed should be set to 0 */ virtual std::unique_ptr from_params( size_t i1, size_t i2 = 0, size_t i3 = 0) const = 0; }; } BOTAN_FUTURE_INTERNAL_HEADER(pbkdf2.h) namespace Botan { BOTAN_PUBLIC_API(2,0) size_t pbkdf2(MessageAuthenticationCode& prf, uint8_t out[], size_t out_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, size_t iterations, std::chrono::milliseconds msec); /** * Perform PBKDF2. The prf is assumed to be keyed already. */ BOTAN_PUBLIC_API(2,8) void pbkdf2(MessageAuthenticationCode& prf, uint8_t out[], size_t out_len, const uint8_t salt[], size_t salt_len, size_t iterations); /** * PBKDF2 */ class BOTAN_PUBLIC_API(2,8) PBKDF2 final : public PasswordHash { public: PBKDF2(const MessageAuthenticationCode& prf, size_t iter) : m_prf(prf.clone()), m_iterations(iter) {} PBKDF2(const MessageAuthenticationCode& prf, size_t olen, std::chrono::milliseconds msec); size_t iterations() const override { return m_iterations; } std::string to_string() const override; void derive_key(uint8_t out[], size_t out_len, const char* password, size_t password_len, const uint8_t salt[], size_t salt_len) const override; private: std::unique_ptr m_prf; size_t m_iterations; }; /** * Family of PKCS #5 PBKDF2 operations */ class BOTAN_PUBLIC_API(2,8) PBKDF2_Family final : public PasswordHashFamily { public: PBKDF2_Family(MessageAuthenticationCode* prf) : m_prf(prf) {} std::string name() const override; std::unique_ptr tune(size_t output_len, std::chrono::milliseconds msec, size_t max_memory) const override; /** * Return some default parameter set for this PBKDF that should be good * enough for most users. The value returned may change over time as * processing power and attacks improve. */ std::unique_ptr default_params() const override; std::unique_ptr from_iterations(size_t iter) const override; std::unique_ptr from_params( size_t iter, size_t, size_t) const override; private: std::unique_ptr m_prf; }; /** * PKCS #5 PBKDF2 (old interface) */ class BOTAN_PUBLIC_API(2,0) PKCS5_PBKDF2 final : public PBKDF { public: std::string name() const override; PBKDF* clone() const override; size_t pbkdf(uint8_t output_buf[], size_t output_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, size_t iterations, std::chrono::milliseconds msec) const override; /** * Create a PKCS #5 instance using the specified message auth code * @param mac_fn the MAC object to use as PRF */ explicit PKCS5_PBKDF2(MessageAuthenticationCode* mac_fn) : m_mac(mac_fn) {} private: std::unique_ptr m_mac; }; } 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; } } BOTAN_FUTURE_INTERNAL_HEADER(sha2_32.h) namespace Botan { /** * SHA-224 */ class BOTAN_PUBLIC_API(2,0) SHA_224 final : public MDx_HashFunction { public: std::string name() const override { return "SHA-224"; } size_t output_length() const override { return 28; } HashFunction* clone() const override { return new SHA_224; } std::unique_ptr copy_state() const override; void clear() override; std::string provider() const override; SHA_224() : MDx_HashFunction(64, true, true), m_digest(8) { clear(); } private: void compress_n(const uint8_t[], size_t blocks) override; void copy_out(uint8_t[]) override; secure_vector m_digest; }; /** * SHA-256 */ class BOTAN_PUBLIC_API(2,0) SHA_256 final : public MDx_HashFunction { public: std::string name() const override { return "SHA-256"; } size_t output_length() const override { return 32; } HashFunction* clone() const override { return new SHA_256; } std::unique_ptr copy_state() const override; void clear() override; std::string provider() const override; SHA_256() : MDx_HashFunction(64, true, true), m_digest(8) { clear(); } /* * Perform a SHA-256 compression. For internal use */ static void compress_digest(secure_vector& digest, const uint8_t input[], size_t blocks); private: #if defined(BOTAN_HAS_SHA2_32_ARMV8) static void compress_digest_armv8(secure_vector& digest, const uint8_t input[], size_t blocks); #endif #if defined(BOTAN_HAS_SHA2_32_X86_BMI2) static void compress_digest_x86_bmi2(secure_vector& digest, const uint8_t input[], size_t blocks); #endif #if defined(BOTAN_HAS_SHA2_32_X86) static void compress_digest_x86(secure_vector& digest, const uint8_t input[], size_t blocks); #endif void compress_n(const uint8_t[], size_t blocks) override; void copy_out(uint8_t[]) override; secure_vector m_digest; }; } #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.5.1/oscar/SleepLib/thirdparty/botan_macos.h000066400000000000000000006210501450332542600231310ustar00rootroot00000000000000/* * 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,sha2_32,pbkdf2' * * 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_HASH 20180112 #define BOTAN_HAS_HEX_CODEC 20131128 #define BOTAN_HAS_HMAC 20131128 #define BOTAN_HAS_MAC 20150626 #define BOTAN_HAS_MDX_HASH_FUNCTION 20131128 #define BOTAN_HAS_MODES 20150626 #define BOTAN_HAS_PBKDF 20180902 #define BOTAN_HAS_PBKDF2 20180902 #define BOTAN_HAS_SHA2_32 20131128 #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 { /** * This class represents hash function (message digest) objects */ class BOTAN_PUBLIC_API(2,0) HashFunction : public Buffered_Computation { public: /** * Create an instance based on a name, or return null if the * algo/provider combination cannot be found. If provider is * empty then best available is chosen. */ 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 Lookup_Error if not 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 * @param algo_spec algorithm name */ static std::vector providers(const std::string& algo_spec); /** * @return new object representing the same algorithm as *this */ virtual HashFunction* clone() const = 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"; } virtual ~HashFunction() = default; /** * Reset the state. */ virtual void clear() = 0; /** * @return the hash function name */ virtual std::string name() const = 0; /** * @return hash block size as defined for this algorithm */ virtual size_t hash_block_size() const { return 0; } /** * Return a new hash object with the same state as *this. This * allows computing the hash of several messages with a common * prefix more efficiently than would otherwise be possible. * * This function should be called `clone` but that was already * used for the case of returning an uninitialized object. * @return new hash object */ virtual std::unique_ptr copy_state() const = 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); } namespace Botan { /** * This class represents Message Authentication Code (MAC) objects. */ class BOTAN_PUBLIC_API(2,0) MessageAuthenticationCode : public Buffered_Computation, 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 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 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); virtual ~MessageAuthenticationCode() = default; /** * Prepare for processing a message under the specified nonce * * Most MACs neither require nor support a nonce; for these algorithms * calling `start_msg` is optional and calling it with anything other than * an empty string is an error. One MAC which *requires* a per-message * nonce be specified is GMAC. * * @param nonce the message nonce bytes * @param nonce_len the size of len in bytes * Default implementation simply rejects all non-empty nonces * since most hash/MAC algorithms do not support randomization */ virtual void start_msg(const uint8_t nonce[], size_t nonce_len); /** * Begin processing a message with a nonce * * @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); } /** * Verify a MAC. * @param in the MAC to verify as a byte array * @param length the length of param in * @return true if the MAC is valid, false otherwise */ virtual bool verify_mac(const uint8_t in[], size_t length); /** * Verify a MAC. * @param in the MAC to verify as a byte array * @return true if the MAC is valid, false otherwise */ virtual bool verify_mac(const std::vector& in) { return verify_mac(in.data(), in.size()); } /** * Verify a MAC. * @param in the MAC to verify as a byte array * @return true if the MAC is valid, false otherwise */ virtual bool verify_mac(const secure_vector& in) { return verify_mac(in.data(), in.size()); } /** * Get a new object representing the same algorithm as *this */ virtual MessageAuthenticationCode* clone() const = 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"; } }; typedef MessageAuthenticationCode MAC; } BOTAN_FUTURE_INTERNAL_HEADER(hmac.h) namespace Botan { /** * HMAC */ class BOTAN_PUBLIC_API(2,0) HMAC final : public MessageAuthenticationCode { public: void clear() override; std::string name() const override; MessageAuthenticationCode* clone() const override; size_t output_length() const override; Key_Length_Specification key_spec() const override; /** * @param hash the hash to use for HMACing */ explicit HMAC(HashFunction* hash); HMAC(const HMAC&) = delete; HMAC& operator=(const HMAC&) = delete; private: void add_data(const uint8_t[], size_t) override; void final_result(uint8_t[]) override; void key_schedule(const uint8_t[], size_t) override; std::unique_ptr m_hash; secure_vector m_ikey, m_okey; size_t m_hash_output_length; size_t m_hash_block_size; }; } 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(mdx_hash.h) namespace Botan { /** * MDx Hash Function Base Class */ class BOTAN_PUBLIC_API(2,0) MDx_HashFunction : public HashFunction { public: /** * @param block_length is the number of bytes per block, which must * be a power of 2 and at least 8. * @param big_byte_endian specifies if the hash uses big-endian bytes * @param big_bit_endian specifies if the hash uses big-endian bits * @param counter_size specifies the size of the counter var in bytes */ MDx_HashFunction(size_t block_length, bool big_byte_endian, bool big_bit_endian, uint8_t counter_size = 8); size_t hash_block_size() const override final { return m_buffer.size(); } protected: void add_data(const uint8_t input[], size_t length) override final; void final_result(uint8_t output[]) override final; /** * Run the hash's compression function over a set of blocks * @param blocks the input * @param block_n the number of blocks */ virtual void compress_n(const uint8_t blocks[], size_t block_n) = 0; void clear() override; /** * Copy the output to the buffer * @param buffer to put the output into */ virtual void copy_out(uint8_t buffer[]) = 0; /** * Write the count, if used, to this spot * @param out where to write the counter to */ virtual void write_count(uint8_t out[]); private: const uint8_t m_pad_char; const uint8_t m_counter_size; const uint8_t m_block_bits; const bool m_count_big_endian; uint64_t m_count; secure_vector m_buffer; size_t m_position; }; } 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); } namespace Botan { /** * Base class for PBKDF (password based key derivation function) * implementations. Converts a password into a key using a salt * and iterated hashing to make brute force attacks harder. * * Starting in 2.8 this functionality is also offered by PasswordHash. * The PBKDF interface may be removed in a future release. */ class BOTAN_PUBLIC_API(2,0) PBKDF { 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 */ static std::vector providers(const std::string& algo_spec); /** * @return new instance of this same algorithm */ virtual PBKDF* clone() const = 0; /** * @return name of this PBKDF */ virtual std::string name() const = 0; virtual ~PBKDF() = default; /** * Derive a key from a passphrase for a number of iterations * specified by either iterations or if iterations == 0 then * running until msec time has elapsed. * * @param out buffer to store the derived key, must be of out_len bytes * @param out_len the desired length of the key to produce * @param passphrase the password to derive the key from * @param salt a randomly chosen salt * @param salt_len length of salt in bytes * @param iterations the number of iterations to use (use 10K or more) * @param msec if iterations is zero, then instead the PBKDF is * run until msec milliseconds has passed. * @return the number of iterations performed */ virtual size_t pbkdf(uint8_t out[], size_t out_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, size_t iterations, std::chrono::milliseconds msec) const = 0; /** * Derive a key from a passphrase for a number of iterations. * * @param out buffer to store the derived key, must be of out_len bytes * @param out_len the desired length of the key to produce * @param passphrase the password to derive the key from * @param salt a randomly chosen salt * @param salt_len length of salt in bytes * @param iterations the number of iterations to use (use 10K or more) */ void pbkdf_iterations(uint8_t out[], size_t out_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, size_t iterations) const; /** * Derive a key from a passphrase, running until msec time has elapsed. * * @param out buffer to store the derived key, must be of out_len bytes * @param out_len the desired length of the key to produce * @param passphrase the password to derive the key from * @param salt a randomly chosen salt * @param salt_len length of salt in bytes * @param msec if iterations is zero, then instead the PBKDF is * run until msec milliseconds has passed. * @param iterations set to the number iterations executed */ void pbkdf_timed(uint8_t out[], size_t out_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, std::chrono::milliseconds msec, size_t& iterations) const; /** * Derive a key from a passphrase for a number of iterations. * * @param out_len the desired length of the key to produce * @param passphrase the password to derive the key from * @param salt a randomly chosen salt * @param salt_len length of salt in bytes * @param iterations the number of iterations to use (use 10K or more) * @return the derived key */ secure_vector pbkdf_iterations(size_t out_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, size_t iterations) const; /** * Derive a key from a passphrase, running until msec time has elapsed. * * @param out_len the desired length of the key to produce * @param passphrase the password to derive the key from * @param salt a randomly chosen salt * @param salt_len length of salt in bytes * @param msec if iterations is zero, then instead the PBKDF is * run until msec milliseconds has passed. * @param iterations set to the number iterations executed * @return the derived key */ secure_vector pbkdf_timed(size_t out_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, std::chrono::milliseconds msec, size_t& iterations) const; // Following kept for compat with 1.10: /** * Derive a key from a passphrase * @param out_len the desired length of the key to produce * @param passphrase the password to derive the key from * @param salt a randomly chosen salt * @param salt_len length of salt in bytes * @param iterations the number of iterations to use (use 10K or more) */ OctetString derive_key(size_t out_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, size_t iterations) const { return pbkdf_iterations(out_len, passphrase, salt, salt_len, iterations); } /** * Derive a key from a passphrase * @param out_len the desired length of the key to produce * @param passphrase the password to derive the key from * @param salt a randomly chosen salt * @param iterations the number of iterations to use (use 10K or more) */ template OctetString derive_key(size_t out_len, const std::string& passphrase, const std::vector& salt, size_t iterations) const { return pbkdf_iterations(out_len, passphrase, salt.data(), salt.size(), iterations); } /** * Derive a key from a passphrase * @param out_len the desired length of the key to produce * @param passphrase the password to derive the key from * @param salt a randomly chosen salt * @param salt_len length of salt in bytes * @param msec is how long to run the PBKDF * @param iterations is set to the number of iterations used */ OctetString derive_key(size_t out_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, std::chrono::milliseconds msec, size_t& iterations) const { return pbkdf_timed(out_len, passphrase, salt, salt_len, msec, iterations); } /** * Derive a key from a passphrase using a certain amount of time * @param out_len the desired length of the key to produce * @param passphrase the password to derive the key from * @param salt a randomly chosen salt * @param msec is how long to run the PBKDF * @param iterations is set to the number of iterations used */ template OctetString derive_key(size_t out_len, const std::string& passphrase, const std::vector& salt, std::chrono::milliseconds msec, size_t& iterations) const { return pbkdf_timed(out_len, passphrase, salt.data(), salt.size(), msec, iterations); } }; /* * Compatibility typedef */ typedef PBKDF S2K; /** * Password based key derivation function factory method * @param algo_spec the name of the desired PBKDF algorithm * @param provider the provider to use * @return pointer to newly allocated object of that type */ inline PBKDF* get_pbkdf(const std::string& algo_spec, const std::string& provider = "") { return PBKDF::create_or_throw(algo_spec, provider).release(); } inline PBKDF* get_s2k(const std::string& algo_spec) { return get_pbkdf(algo_spec); } } namespace Botan { /** * Base class for password based key derivation functions. * * Converts a password into a key using a salt and iterated hashing to * make brute force attacks harder. */ class BOTAN_PUBLIC_API(2,8) PasswordHash { public: virtual ~PasswordHash() = default; virtual std::string to_string() const = 0; /** * Most password hashes have some notion of iterations. */ virtual size_t iterations() const = 0; /** * Some password hashing algorithms have a parameter which controls how * much memory is used. If not supported by some algorithm, returns 0. */ virtual size_t memory_param() const { return 0; } /** * Some password hashing algorithms have a parallelism parameter. * If the algorithm does not support this notion, then the * function returns zero. This allows distinguishing between a * password hash which just does not support parallel operation, * vs one that does support parallel operation but which has been * configured to use a single lane. */ virtual size_t parallelism() const { return 0; } /** * Returns an estimate of the total memory usage required to perform this * key derivation. * * If this algorithm uses a small and constant amount of memory, with no * effort made towards being memory hard, this function returns 0. */ virtual size_t total_memory_usage() const { return 0; } /** * Derive a key from a password * * @param out buffer to store the derived key, must be of out_len bytes * @param out_len the desired length of the key to produce * @param password the password to derive the key from * @param password_len the length of password in bytes * @param salt a randomly chosen salt * @param salt_len length of salt in bytes * * This function is const, but is not thread safe. Different threads should * either use unique objects, or serialize all access. */ virtual void derive_key(uint8_t out[], size_t out_len, const char* password, size_t password_len, const uint8_t salt[], size_t salt_len) const = 0; }; class BOTAN_PUBLIC_API(2,8) PasswordHashFamily { 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 */ static std::vector providers(const std::string& algo_spec); virtual ~PasswordHashFamily() = default; /** * @return name of this PasswordHash */ virtual std::string name() const = 0; /** * Return a new parameter set tuned for this machine * @param output_length how long the output length will be * @param msec the desired execution time in milliseconds * * @param max_memory_usage_mb some password hash functions can use a tunable * amount of memory, in this case max_memory_usage limits the amount of RAM * the returned parameters will require, in mebibytes (2**20 bytes). It may * require some small amount above the request. Set to zero to place no * limit at all. */ virtual std::unique_ptr tune(size_t output_length, std::chrono::milliseconds msec, size_t max_memory_usage_mb = 0) const = 0; /** * Return some default parameter set for this PBKDF that should be good * enough for most users. The value returned may change over time as * processing power and attacks improve. */ virtual std::unique_ptr default_params() const = 0; /** * Return a parameter chosen based on a rough approximation with the * specified iteration count. The exact value this returns for a particular * algorithm may change from over time. Think of it as an alternative to * tune, where time is expressed in terms of PBKDF2 iterations rather than * milliseconds. */ virtual std::unique_ptr from_iterations(size_t iterations) const = 0; /** * Create a password hash using some scheme specific format. * Eg PBKDF2 and PGP-S2K set iterations in i1 * Scrypt uses N,r,p in i{1-3} * Bcrypt-PBKDF just has iterations * Argon2{i,d,id} would use iterations, memory, parallelism for i{1-3}, * and Argon2 type is part of the family. * * Values not needed should be set to 0 */ virtual std::unique_ptr from_params( size_t i1, size_t i2 = 0, size_t i3 = 0) const = 0; }; } BOTAN_FUTURE_INTERNAL_HEADER(pbkdf2.h) namespace Botan { BOTAN_PUBLIC_API(2,0) size_t pbkdf2(MessageAuthenticationCode& prf, uint8_t out[], size_t out_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, size_t iterations, std::chrono::milliseconds msec); /** * Perform PBKDF2. The prf is assumed to be keyed already. */ BOTAN_PUBLIC_API(2,8) void pbkdf2(MessageAuthenticationCode& prf, uint8_t out[], size_t out_len, const uint8_t salt[], size_t salt_len, size_t iterations); /** * PBKDF2 */ class BOTAN_PUBLIC_API(2,8) PBKDF2 final : public PasswordHash { public: PBKDF2(const MessageAuthenticationCode& prf, size_t iter) : m_prf(prf.clone()), m_iterations(iter) {} PBKDF2(const MessageAuthenticationCode& prf, size_t olen, std::chrono::milliseconds msec); size_t iterations() const override { return m_iterations; } std::string to_string() const override; void derive_key(uint8_t out[], size_t out_len, const char* password, size_t password_len, const uint8_t salt[], size_t salt_len) const override; private: std::unique_ptr m_prf; size_t m_iterations; }; /** * Family of PKCS #5 PBKDF2 operations */ class BOTAN_PUBLIC_API(2,8) PBKDF2_Family final : public PasswordHashFamily { public: PBKDF2_Family(MessageAuthenticationCode* prf) : m_prf(prf) {} std::string name() const override; std::unique_ptr tune(size_t output_len, std::chrono::milliseconds msec, size_t max_memory) const override; /** * Return some default parameter set for this PBKDF that should be good * enough for most users. The value returned may change over time as * processing power and attacks improve. */ std::unique_ptr default_params() const override; std::unique_ptr from_iterations(size_t iter) const override; std::unique_ptr from_params( size_t iter, size_t, size_t) const override; private: std::unique_ptr m_prf; }; /** * PKCS #5 PBKDF2 (old interface) */ class BOTAN_PUBLIC_API(2,0) PKCS5_PBKDF2 final : public PBKDF { public: std::string name() const override; PBKDF* clone() const override; size_t pbkdf(uint8_t output_buf[], size_t output_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, size_t iterations, std::chrono::milliseconds msec) const override; /** * Create a PKCS #5 instance using the specified message auth code * @param mac_fn the MAC object to use as PRF */ explicit PKCS5_PBKDF2(MessageAuthenticationCode* mac_fn) : m_mac(mac_fn) {} private: std::unique_ptr m_mac; }; } 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; } } BOTAN_FUTURE_INTERNAL_HEADER(sha2_32.h) namespace Botan { /** * SHA-224 */ class BOTAN_PUBLIC_API(2,0) SHA_224 final : public MDx_HashFunction { public: std::string name() const override { return "SHA-224"; } size_t output_length() const override { return 28; } HashFunction* clone() const override { return new SHA_224; } std::unique_ptr copy_state() const override; void clear() override; std::string provider() const override; SHA_224() : MDx_HashFunction(64, true, true), m_digest(8) { clear(); } private: void compress_n(const uint8_t[], size_t blocks) override; void copy_out(uint8_t[]) override; secure_vector m_digest; }; /** * SHA-256 */ class BOTAN_PUBLIC_API(2,0) SHA_256 final : public MDx_HashFunction { public: std::string name() const override { return "SHA-256"; } size_t output_length() const override { return 32; } HashFunction* clone() const override { return new SHA_256; } std::unique_ptr copy_state() const override; void clear() override; std::string provider() const override; SHA_256() : MDx_HashFunction(64, true, true), m_digest(8) { clear(); } /* * Perform a SHA-256 compression. For internal use */ static void compress_digest(secure_vector& digest, const uint8_t input[], size_t blocks); private: #if defined(BOTAN_HAS_SHA2_32_ARMV8) static void compress_digest_armv8(secure_vector& digest, const uint8_t input[], size_t blocks); #endif #if defined(BOTAN_HAS_SHA2_32_X86_BMI2) static void compress_digest_x86_bmi2(secure_vector& digest, const uint8_t input[], size_t blocks); #endif #if defined(BOTAN_HAS_SHA2_32_X86) static void compress_digest_x86(secure_vector& digest, const uint8_t input[], size_t blocks); #endif void compress_n(const uint8_t[], size_t blocks) override; void copy_out(uint8_t[]) override; secure_vector m_digest; }; } #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.5.1/oscar/SleepLib/thirdparty/botan_windows.h000066400000000000000000006204301450332542600235220ustar00rootroot00000000000000/* * 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,sha2_32,pbkdf2' * * 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_HASH 20180112 #define BOTAN_HAS_HEX_CODEC 20131128 #define BOTAN_HAS_HMAC 20131128 #define BOTAN_HAS_MAC 20150626 #define BOTAN_HAS_MDX_HASH_FUNCTION 20131128 #define BOTAN_HAS_MODES 20150626 #define BOTAN_HAS_PBKDF 20180902 #define BOTAN_HAS_PBKDF2 20180902 #define BOTAN_HAS_SHA2_32 20131128 #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 { /** * This class represents hash function (message digest) objects */ class BOTAN_PUBLIC_API(2,0) HashFunction : public Buffered_Computation { public: /** * Create an instance based on a name, or return null if the * algo/provider combination cannot be found. If provider is * empty then best available is chosen. */ 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 Lookup_Error if not 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 * @param algo_spec algorithm name */ static std::vector providers(const std::string& algo_spec); /** * @return new object representing the same algorithm as *this */ virtual HashFunction* clone() const = 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"; } virtual ~HashFunction() = default; /** * Reset the state. */ virtual void clear() = 0; /** * @return the hash function name */ virtual std::string name() const = 0; /** * @return hash block size as defined for this algorithm */ virtual size_t hash_block_size() const { return 0; } /** * Return a new hash object with the same state as *this. This * allows computing the hash of several messages with a common * prefix more efficiently than would otherwise be possible. * * This function should be called `clone` but that was already * used for the case of returning an uninitialized object. * @return new hash object */ virtual std::unique_ptr copy_state() const = 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); } namespace Botan { /** * This class represents Message Authentication Code (MAC) objects. */ class BOTAN_PUBLIC_API(2,0) MessageAuthenticationCode : public Buffered_Computation, 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 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 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); virtual ~MessageAuthenticationCode() = default; /** * Prepare for processing a message under the specified nonce * * Most MACs neither require nor support a nonce; for these algorithms * calling `start_msg` is optional and calling it with anything other than * an empty string is an error. One MAC which *requires* a per-message * nonce be specified is GMAC. * * @param nonce the message nonce bytes * @param nonce_len the size of len in bytes * Default implementation simply rejects all non-empty nonces * since most hash/MAC algorithms do not support randomization */ virtual void start_msg(const uint8_t nonce[], size_t nonce_len); /** * Begin processing a message with a nonce * * @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); } /** * Verify a MAC. * @param in the MAC to verify as a byte array * @param length the length of param in * @return true if the MAC is valid, false otherwise */ virtual bool verify_mac(const uint8_t in[], size_t length); /** * Verify a MAC. * @param in the MAC to verify as a byte array * @return true if the MAC is valid, false otherwise */ virtual bool verify_mac(const std::vector& in) { return verify_mac(in.data(), in.size()); } /** * Verify a MAC. * @param in the MAC to verify as a byte array * @return true if the MAC is valid, false otherwise */ virtual bool verify_mac(const secure_vector& in) { return verify_mac(in.data(), in.size()); } /** * Get a new object representing the same algorithm as *this */ virtual MessageAuthenticationCode* clone() const = 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"; } }; typedef MessageAuthenticationCode MAC; } BOTAN_FUTURE_INTERNAL_HEADER(hmac.h) namespace Botan { /** * HMAC */ class BOTAN_PUBLIC_API(2,0) HMAC final : public MessageAuthenticationCode { public: void clear() override; std::string name() const override; MessageAuthenticationCode* clone() const override; size_t output_length() const override; Key_Length_Specification key_spec() const override; /** * @param hash the hash to use for HMACing */ explicit HMAC(HashFunction* hash); HMAC(const HMAC&) = delete; HMAC& operator=(const HMAC&) = delete; private: void add_data(const uint8_t[], size_t) override; void final_result(uint8_t[]) override; void key_schedule(const uint8_t[], size_t) override; std::unique_ptr m_hash; secure_vector m_ikey, m_okey; size_t m_hash_output_length; size_t m_hash_block_size; }; } 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(mdx_hash.h) namespace Botan { /** * MDx Hash Function Base Class */ class BOTAN_PUBLIC_API(2,0) MDx_HashFunction : public HashFunction { public: /** * @param block_length is the number of bytes per block, which must * be a power of 2 and at least 8. * @param big_byte_endian specifies if the hash uses big-endian bytes * @param big_bit_endian specifies if the hash uses big-endian bits * @param counter_size specifies the size of the counter var in bytes */ MDx_HashFunction(size_t block_length, bool big_byte_endian, bool big_bit_endian, uint8_t counter_size = 8); size_t hash_block_size() const override final { return m_buffer.size(); } protected: void add_data(const uint8_t input[], size_t length) override final; void final_result(uint8_t output[]) override final; /** * Run the hash's compression function over a set of blocks * @param blocks the input * @param block_n the number of blocks */ virtual void compress_n(const uint8_t blocks[], size_t block_n) = 0; void clear() override; /** * Copy the output to the buffer * @param buffer to put the output into */ virtual void copy_out(uint8_t buffer[]) = 0; /** * Write the count, if used, to this spot * @param out where to write the counter to */ virtual void write_count(uint8_t out[]); private: const uint8_t m_pad_char; const uint8_t m_counter_size; const uint8_t m_block_bits; const bool m_count_big_endian; uint64_t m_count; secure_vector m_buffer; size_t m_position; }; } 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); } namespace Botan { /** * Base class for PBKDF (password based key derivation function) * implementations. Converts a password into a key using a salt * and iterated hashing to make brute force attacks harder. * * Starting in 2.8 this functionality is also offered by PasswordHash. * The PBKDF interface may be removed in a future release. */ class BOTAN_PUBLIC_API(2,0) PBKDF { 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 */ static std::vector providers(const std::string& algo_spec); /** * @return new instance of this same algorithm */ virtual PBKDF* clone() const = 0; /** * @return name of this PBKDF */ virtual std::string name() const = 0; virtual ~PBKDF() = default; /** * Derive a key from a passphrase for a number of iterations * specified by either iterations or if iterations == 0 then * running until msec time has elapsed. * * @param out buffer to store the derived key, must be of out_len bytes * @param out_len the desired length of the key to produce * @param passphrase the password to derive the key from * @param salt a randomly chosen salt * @param salt_len length of salt in bytes * @param iterations the number of iterations to use (use 10K or more) * @param msec if iterations is zero, then instead the PBKDF is * run until msec milliseconds has passed. * @return the number of iterations performed */ virtual size_t pbkdf(uint8_t out[], size_t out_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, size_t iterations, std::chrono::milliseconds msec) const = 0; /** * Derive a key from a passphrase for a number of iterations. * * @param out buffer to store the derived key, must be of out_len bytes * @param out_len the desired length of the key to produce * @param passphrase the password to derive the key from * @param salt a randomly chosen salt * @param salt_len length of salt in bytes * @param iterations the number of iterations to use (use 10K or more) */ void pbkdf_iterations(uint8_t out[], size_t out_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, size_t iterations) const; /** * Derive a key from a passphrase, running until msec time has elapsed. * * @param out buffer to store the derived key, must be of out_len bytes * @param out_len the desired length of the key to produce * @param passphrase the password to derive the key from * @param salt a randomly chosen salt * @param salt_len length of salt in bytes * @param msec if iterations is zero, then instead the PBKDF is * run until msec milliseconds has passed. * @param iterations set to the number iterations executed */ void pbkdf_timed(uint8_t out[], size_t out_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, std::chrono::milliseconds msec, size_t& iterations) const; /** * Derive a key from a passphrase for a number of iterations. * * @param out_len the desired length of the key to produce * @param passphrase the password to derive the key from * @param salt a randomly chosen salt * @param salt_len length of salt in bytes * @param iterations the number of iterations to use (use 10K or more) * @return the derived key */ secure_vector pbkdf_iterations(size_t out_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, size_t iterations) const; /** * Derive a key from a passphrase, running until msec time has elapsed. * * @param out_len the desired length of the key to produce * @param passphrase the password to derive the key from * @param salt a randomly chosen salt * @param salt_len length of salt in bytes * @param msec if iterations is zero, then instead the PBKDF is * run until msec milliseconds has passed. * @param iterations set to the number iterations executed * @return the derived key */ secure_vector pbkdf_timed(size_t out_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, std::chrono::milliseconds msec, size_t& iterations) const; // Following kept for compat with 1.10: /** * Derive a key from a passphrase * @param out_len the desired length of the key to produce * @param passphrase the password to derive the key from * @param salt a randomly chosen salt * @param salt_len length of salt in bytes * @param iterations the number of iterations to use (use 10K or more) */ OctetString derive_key(size_t out_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, size_t iterations) const { return pbkdf_iterations(out_len, passphrase, salt, salt_len, iterations); } /** * Derive a key from a passphrase * @param out_len the desired length of the key to produce * @param passphrase the password to derive the key from * @param salt a randomly chosen salt * @param iterations the number of iterations to use (use 10K or more) */ template OctetString derive_key(size_t out_len, const std::string& passphrase, const std::vector& salt, size_t iterations) const { return pbkdf_iterations(out_len, passphrase, salt.data(), salt.size(), iterations); } /** * Derive a key from a passphrase * @param out_len the desired length of the key to produce * @param passphrase the password to derive the key from * @param salt a randomly chosen salt * @param salt_len length of salt in bytes * @param msec is how long to run the PBKDF * @param iterations is set to the number of iterations used */ OctetString derive_key(size_t out_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, std::chrono::milliseconds msec, size_t& iterations) const { return pbkdf_timed(out_len, passphrase, salt, salt_len, msec, iterations); } /** * Derive a key from a passphrase using a certain amount of time * @param out_len the desired length of the key to produce * @param passphrase the password to derive the key from * @param salt a randomly chosen salt * @param msec is how long to run the PBKDF * @param iterations is set to the number of iterations used */ template OctetString derive_key(size_t out_len, const std::string& passphrase, const std::vector& salt, std::chrono::milliseconds msec, size_t& iterations) const { return pbkdf_timed(out_len, passphrase, salt.data(), salt.size(), msec, iterations); } }; /* * Compatibility typedef */ typedef PBKDF S2K; /** * Password based key derivation function factory method * @param algo_spec the name of the desired PBKDF algorithm * @param provider the provider to use * @return pointer to newly allocated object of that type */ inline PBKDF* get_pbkdf(const std::string& algo_spec, const std::string& provider = "") { return PBKDF::create_or_throw(algo_spec, provider).release(); } inline PBKDF* get_s2k(const std::string& algo_spec) { return get_pbkdf(algo_spec); } } namespace Botan { /** * Base class for password based key derivation functions. * * Converts a password into a key using a salt and iterated hashing to * make brute force attacks harder. */ class BOTAN_PUBLIC_API(2,8) PasswordHash { public: virtual ~PasswordHash() = default; virtual std::string to_string() const = 0; /** * Most password hashes have some notion of iterations. */ virtual size_t iterations() const = 0; /** * Some password hashing algorithms have a parameter which controls how * much memory is used. If not supported by some algorithm, returns 0. */ virtual size_t memory_param() const { return 0; } /** * Some password hashing algorithms have a parallelism parameter. * If the algorithm does not support this notion, then the * function returns zero. This allows distinguishing between a * password hash which just does not support parallel operation, * vs one that does support parallel operation but which has been * configured to use a single lane. */ virtual size_t parallelism() const { return 0; } /** * Returns an estimate of the total memory usage required to perform this * key derivation. * * If this algorithm uses a small and constant amount of memory, with no * effort made towards being memory hard, this function returns 0. */ virtual size_t total_memory_usage() const { return 0; } /** * Derive a key from a password * * @param out buffer to store the derived key, must be of out_len bytes * @param out_len the desired length of the key to produce * @param password the password to derive the key from * @param password_len the length of password in bytes * @param salt a randomly chosen salt * @param salt_len length of salt in bytes * * This function is const, but is not thread safe. Different threads should * either use unique objects, or serialize all access. */ virtual void derive_key(uint8_t out[], size_t out_len, const char* password, size_t password_len, const uint8_t salt[], size_t salt_len) const = 0; }; class BOTAN_PUBLIC_API(2,8) PasswordHashFamily { 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 */ static std::vector providers(const std::string& algo_spec); virtual ~PasswordHashFamily() = default; /** * @return name of this PasswordHash */ virtual std::string name() const = 0; /** * Return a new parameter set tuned for this machine * @param output_length how long the output length will be * @param msec the desired execution time in milliseconds * * @param max_memory_usage_mb some password hash functions can use a tunable * amount of memory, in this case max_memory_usage limits the amount of RAM * the returned parameters will require, in mebibytes (2**20 bytes). It may * require some small amount above the request. Set to zero to place no * limit at all. */ virtual std::unique_ptr tune(size_t output_length, std::chrono::milliseconds msec, size_t max_memory_usage_mb = 0) const = 0; /** * Return some default parameter set for this PBKDF that should be good * enough for most users. The value returned may change over time as * processing power and attacks improve. */ virtual std::unique_ptr default_params() const = 0; /** * Return a parameter chosen based on a rough approximation with the * specified iteration count. The exact value this returns for a particular * algorithm may change from over time. Think of it as an alternative to * tune, where time is expressed in terms of PBKDF2 iterations rather than * milliseconds. */ virtual std::unique_ptr from_iterations(size_t iterations) const = 0; /** * Create a password hash using some scheme specific format. * Eg PBKDF2 and PGP-S2K set iterations in i1 * Scrypt uses N,r,p in i{1-3} * Bcrypt-PBKDF just has iterations * Argon2{i,d,id} would use iterations, memory, parallelism for i{1-3}, * and Argon2 type is part of the family. * * Values not needed should be set to 0 */ virtual std::unique_ptr from_params( size_t i1, size_t i2 = 0, size_t i3 = 0) const = 0; }; } BOTAN_FUTURE_INTERNAL_HEADER(pbkdf2.h) namespace Botan { BOTAN_PUBLIC_API(2,0) size_t pbkdf2(MessageAuthenticationCode& prf, uint8_t out[], size_t out_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, size_t iterations, std::chrono::milliseconds msec); /** * Perform PBKDF2. The prf is assumed to be keyed already. */ BOTAN_PUBLIC_API(2,8) void pbkdf2(MessageAuthenticationCode& prf, uint8_t out[], size_t out_len, const uint8_t salt[], size_t salt_len, size_t iterations); /** * PBKDF2 */ class BOTAN_PUBLIC_API(2,8) PBKDF2 final : public PasswordHash { public: PBKDF2(const MessageAuthenticationCode& prf, size_t iter) : m_prf(prf.clone()), m_iterations(iter) {} PBKDF2(const MessageAuthenticationCode& prf, size_t olen, std::chrono::milliseconds msec); size_t iterations() const override { return m_iterations; } std::string to_string() const override; void derive_key(uint8_t out[], size_t out_len, const char* password, size_t password_len, const uint8_t salt[], size_t salt_len) const override; private: std::unique_ptr m_prf; size_t m_iterations; }; /** * Family of PKCS #5 PBKDF2 operations */ class BOTAN_PUBLIC_API(2,8) PBKDF2_Family final : public PasswordHashFamily { public: PBKDF2_Family(MessageAuthenticationCode* prf) : m_prf(prf) {} std::string name() const override; std::unique_ptr tune(size_t output_len, std::chrono::milliseconds msec, size_t max_memory) const override; /** * Return some default parameter set for this PBKDF that should be good * enough for most users. The value returned may change over time as * processing power and attacks improve. */ std::unique_ptr default_params() const override; std::unique_ptr from_iterations(size_t iter) const override; std::unique_ptr from_params( size_t iter, size_t, size_t) const override; private: std::unique_ptr m_prf; }; /** * PKCS #5 PBKDF2 (old interface) */ class BOTAN_PUBLIC_API(2,0) PKCS5_PBKDF2 final : public PBKDF { public: std::string name() const override; PBKDF* clone() const override; size_t pbkdf(uint8_t output_buf[], size_t output_len, const std::string& passphrase, const uint8_t salt[], size_t salt_len, size_t iterations, std::chrono::milliseconds msec) const override; /** * Create a PKCS #5 instance using the specified message auth code * @param mac_fn the MAC object to use as PRF */ explicit PKCS5_PBKDF2(MessageAuthenticationCode* mac_fn) : m_mac(mac_fn) {} private: std::unique_ptr m_mac; }; } 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; } } BOTAN_FUTURE_INTERNAL_HEADER(sha2_32.h) namespace Botan { /** * SHA-224 */ class BOTAN_PUBLIC_API(2,0) SHA_224 final : public MDx_HashFunction { public: std::string name() const override { return "SHA-224"; } size_t output_length() const override { return 28; } HashFunction* clone() const override { return new SHA_224; } std::unique_ptr copy_state() const override; void clear() override; std::string provider() const override; SHA_224() : MDx_HashFunction(64, true, true), m_digest(8) { clear(); } private: void compress_n(const uint8_t[], size_t blocks) override; void copy_out(uint8_t[]) override; secure_vector m_digest; }; /** * SHA-256 */ class BOTAN_PUBLIC_API(2,0) SHA_256 final : public MDx_HashFunction { public: std::string name() const override { return "SHA-256"; } size_t output_length() const override { return 32; } HashFunction* clone() const override { return new SHA_256; } std::unique_ptr copy_state() const override; void clear() override; std::string provider() const override; SHA_256() : MDx_HashFunction(64, true, true), m_digest(8) { clear(); } /* * Perform a SHA-256 compression. For internal use */ static void compress_digest(secure_vector& digest, const uint8_t input[], size_t blocks); private: #if defined(BOTAN_HAS_SHA2_32_ARMV8) static void compress_digest_armv8(secure_vector& digest, const uint8_t input[], size_t blocks); #endif #if defined(BOTAN_HAS_SHA2_32_X86_BMI2) static void compress_digest_x86_bmi2(secure_vector& digest, const uint8_t input[], size_t blocks); #endif #if defined(BOTAN_HAS_SHA2_32_X86) static void compress_digest_x86(secure_vector& digest, const uint8_t input[], size_t blocks); #endif void compress_n(const uint8_t[], size_t blocks) override; void copy_out(uint8_t[]) override; secure_vector m_digest; }; } #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.5.1/oscar/SleepLib/thirdparty/miniz.c000066400000000000000000011516571450332542600220010ustar00rootroot00000000000000/************************************************************************** * * 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.5.1/oscar/SleepLib/thirdparty/miniz.h000066400000000000000000002040011450332542600217630ustar00rootroot00000000000000/* 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.5.1/oscar/SleepLib/thirdparty/update_botan.sh000077500000000000000000000032121450332542600234710ustar00rootroot00000000000000#!/bin/bash # # To update the version of Botan included in OSCAR, simply run this script. # To change which modules are included, modify the MODULES variable below. MODULES="aes,gcm,sha2_32,pbkdf2" BOTAN_DIR=$1 if [ -z "$BOTAN_DIR" ]; then echo "Usage: $0 PATH" echo " PATH Directory of the Botan distribution to use" exit 1 fi # Convert to absolute path BOTAN_DIR="$(cd "$(dirname "$BOTAN_DIR")"; pwd -P)/$(basename "$BOTAN_DIR")" SCRIPT_DIR="$(cd -P "$(dirname "${BASH_SOURCE[0]}")" && pwd)" TMP_DIR=$(mktemp -d 2>/dev/null || mktemp -d -t 'tmp') echo "Building in ${TMP_DIR}" pushd "${TMP_DIR}" > /dev/null declare -a PLATFORMS generate() { label="$1"; shift cpu="$1"; shift os="$label" if [[ $# -gt 0 ]]; then os="$1" fi PLATFORMS+=($label) mkdir "${label}" pushd "${label}" > /dev/null echo "Generating ${label}:" "$BOTAN_DIR/configure.py" --amalgamation --os="$os" --cpu="$cpu" --disable-shared --minimized-build --enable-modules="$MODULES" popd > /dev/null } generate linux generic generate macos generic generate windows generic mingw # Make sure all the cpp files are identical as expected. for platform in "${PLATFORMS[@]}"; do cmp "${PLATFORMS[0]}/botan_all.cpp" "${platform}/botan_all.cpp" || exit 1 done echo "Copying files..." cp "${PLATFORMS[0]}/botan_all.cpp" "${SCRIPT_DIR}/botan_all.cpp" || exit 1 # Copy the platform-specific h files. for platform in "${PLATFORMS[@]}"; do cp "${platform}/botan_all.h" "${SCRIPT_DIR}/botan_${platform}.h" || exit 1 done for platform in "${PLATFORMS[@]}"; do rm -r "${platform}" done popd > /dev/null rmdir ${TMP_DIR} echo "Done." OSCAR-code-v1.5.1/oscar/SleepLib/xmlreplay.cpp000066400000000000000000000371431450332542600210260ustar00rootroot00000000000000/* 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.5.1/oscar/SleepLib/xmlreplay.h000066400000000000000000000345141450332542600204720ustar00rootroot00000000000000/* 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.5.1/oscar/UpdaterWindow.ui000066400000000000000000000316351450332542600177410ustar00rootroot00000000000000 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.5.1/oscar/VERSION000066400000000000000000000002421450332542600156440ustar00rootroot00000000000000// 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.5.1" OSCAR-code-v1.5.1/oscar/aboutdialog.cpp000066400000000000000000000127351450332542600176040ustar00rootroot00000000000000/* 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")+"
" ""+QString("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.5.1/oscar/aboutdialog.h000066400000000000000000000015701450332542600172440ustar00rootroot00000000000000/* 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.5.1/oscar/aboutdialog.ui000066400000000000000000000151121450332542600174270ustar00rootroot00000000000000 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.5.1/oscar/checkupdates.cpp000066400000000000000000000262711450332542600177550ustar00rootroot00000000000000/* CheckUpdates * * Copyright (c) 2011-2018 Mark Watkins * Copyright (c) 2020-2023 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://apneaboard.net/OSCAR/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.5.1/oscar/checkupdates.h000066400000000000000000000033121450332542600174110ustar00rootroot00000000000000/* 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 #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; QElapsedTimer 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.5.1/oscar/common_gui.cpp000066400000000000000000000063051450332542600174420ustar00rootroot00000000000000/* 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.5.1/oscar/common_gui.h000066400000000000000000000076021450332542600171100ustar00rootroot00000000000000/* 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_IE = "I/E"; 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 = "Feeling"; 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"; const QString STR_GRAPH_ObstructLevel = "ObstructLevel"; const QString STR_GRAPH_PressureMeasured = "PressureMeasured"; const QString STR_GRAPH_rRMV = "rRMV"; const QString STR_GRAPH_rMVFluctuation = "rMVFluctuation"; const QString STR_GRAPH_FlowFull = "FlowFull"; //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.5.1/oscar/cprogressbar.cpp000066400000000000000000000052531450332542600200030ustar00rootroot00000000000000/* 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.5.1/oscar/cprogressbar.h000066400000000000000000000030701450332542600174430ustar00rootroot00000000000000/* 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.5.1/oscar/csv.cpp000066400000000000000000000033711450332542600161010ustar00rootroot00000000000000/* 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.5.1/oscar/csv.h000066400000000000000000000014201450332542600155370ustar00rootroot00000000000000/* 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.5.1/oscar/daily.cpp000066400000000000000000003210571450332542600164140ustar00rootroot00000000000000/* 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. */ #define TEST_MACROS_ENABLEDoff #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "daily.h" #include "ui_daily.h" #include "dailySearchTab.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; extern ChannelID Prisma_ObstructLevel, Prisma_rMVFluctuation, Prisma_rRMV, Prisma_PressureMeasured, Prisma_FlowFull; // 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_IE, 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, STR_GRAPH_ObstructLevel, STR_GRAPH_PressureMeasured, STR_GRAPH_rRMV, STR_GRAPH_rMVFluctuation, STR_GRAPH_FlowFull }; // 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_IE, 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, STR_GRAPH_ObstructLevel, STR_GRAPH_PressureMeasured, STR_GRAPH_rRMV, STR_GRAPH_rMVFluctuation, STR_GRAPH_FlowFull }; // 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); 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); sleepFlags = SF; 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, Prisma_ObstructLevel, Prisma_rRMV, Prisma_rMVFluctuation, Prisma_PressureMeasured, Prisma_FlowFull }; // 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(); sleepFlagsGroup = fg; SF->AddLayer(fg); SF->setBlockZoom(true); SF->AddLayer(new gShadowArea()); SF->AddLayer(new gLabelArea(fg),LayerLeft,gYAxis::Margin); 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_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()]; FRW->AddLayer(l); l -> setMinimumHeight(80); // set the layer height to 80. or about 130 graph height. // 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_EEPAP, 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[Prisma_ObstructLevel].code()]->AddLayer(new gLineChart(Prisma_ObstructLevel, square)); graphlist[schema::channel[Prisma_PressureMeasured].code()]->AddLayer(new gLineChart(Prisma_PressureMeasured, square)); graphlist[schema::channel[Prisma_rRMV].code()]->AddLayer(new gLineChart(Prisma_rRMV, square)); graphlist[schema::channel[Prisma_rMVFluctuation].code()]->AddLayer(new gLineChart(Prisma_rMVFluctuation, square)); graphlist[schema::channel[Prisma_FlowFull].code()]->AddLayer(new gLineChart(Prisma_FlowFull, 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); #ifndef REMOVE_FITNESS ZombieMeterMoved=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); } #else // REMOVE_FITNESS // hide the parent widget to make the gridlayout invisible QWidget *myparent ; myparent = ui->gridLayout->parentWidget(); if(myparent) myparent->hide(); #endif 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); saveGraphLayoutSettings=nullptr; dailySearchTab = new DailySearchTab(this,ui->searchTab,ui->tabWidget); } 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; if (saveGraphLayoutSettings!=nullptr) delete saveGraphLayoutSettings; delete dailySearchTab; } void Daily::showEvent(QShowEvent *) { // qDebug() << "Start showEvent in Daily object"; // sleep(3); RedrawGraphs(); // qDebug() << "Finished showEvent Daily object"; // sleep(3); } bool Daily::rejectToggleSessionEnable( Session*sess) { if (!sess) return true; if (p_profile->cpap->clinicalMode()) { QMessageBox mbox(QMessageBox::Warning, tr("Clinical Mode"), tr(" Disabling Sessions requires Permissive Mode be set in OSCAR Preferences in the Clinical tab."), QMessageBox::Ok , this); mbox.exec(); return true; } sess->setEnabled(!sess->enabled()); return false; } void Daily::doToggleSession(Session * sess) { if (rejectToggleSessionEnable( sess) ) return; LoadDate(previous_date); mainwin->getOverview()->graphView()->dataChanged(); mainwin->GenerateStatistics(); } 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); // int i=webView->page()->mainFrame()->scrollBarMaximum(Qt::Vertical)-webView->page()->mainFrame()->scrollBarValue(Qt::Vertical); if (rejectToggleSessionEnable( sess) ) return; // Reload day LoadDate(previous_date); mainwin->getOverview()->graphView()->dataChanged(); // 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); // int i=webView->page()->mainFrame()->scrollBarMaximum(Qt::Vertical)-webView->page()->mainFrame()->scrollBarValue(Qt::Vertical); if (rejectToggleSessionEnable( sess) ) return; // Reload day LoadDate(previous_date); mainwin->getOverview()->graphView()->dataChanged(); // 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 (rejectToggleSessionEnable( sess) ) return; LoadDate(previous_date); mainwin->getOverview()->graphView()->dataChanged(); } 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 (rejectToggleSessionEnable( sess) ) return; LoadDate(previous_date); mainwin->getOverview()->graphView()->dataChanged(); } 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; 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.. 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")),eventTypeStart); QTreeWidgetItem * end = new QTreeWidgetItem(QStringList(tr("Session End Times")),eventTypeEnd); tree->insertTopLevelItem(cnt++ , start); tree->insertTopLevelItem(cnt++ , end); for (QList::iterator s=day->begin(); s!=day->end(); ++s) { Session* sess = *s; if ( (sess->type() != MT_CPAP) && (sess->type() != MT_OXIMETER) && (sess->type() != MT_POSITION)) continue; QDateTime st = QDateTime::fromMSecsSinceEpoch(sess->first()); // Localtime QDateTime et = QDateTime::fromMSecsSinceEpoch(sess->last()); // Localtime QTreeWidgetItem * item = new QTreeWidgetItem(QStringList(st.toString("HH:mm:ss"))); item->setData(0,Qt::UserRole, sess->first()); start->addChild(item); item = new QTreeWidgetItem(QStringList(et.toString("HH:mm:ss"))); item->setData(0,Qt::UserRole, sess->last()); end->addChild(item); } } tree->sortByColumn(0,Qt::AscendingOrder); } 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(); QElapsedTimer 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(); #ifndef REMOVE_FITNESS ZombieMeterMoved=false; #endif 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); #ifndef REMOVE_FITNESS ZombieMeterMoved=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); } #endif 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) showAllGraphs(true); showAllEvents(true); // 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("Device 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("Device 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:QString("99.5%")); ChannelID chans[]={ CPAP_Pressure,CPAP_PressureSet,CPAP_EPAP,CPAP_EPAPSet,CPAP_IPAP,CPAP_IPAPSet,CPAP_PS,CPAP_PTB, PRS1_PeakFlow, Prisma_ObstructLevel, Prisma_PressureMeasured, Prisma_rRMV, Prisma_rMVFluctuation, 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::asprintf("%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::asprintf("%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(); updateEventsCombo(day); 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 device 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 device 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 { // Device 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("no data :(")+"

"+tr("Sorry, this device 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); #ifndef REMOVE_FITNESS ui->ZombieMeter->blockSignals(true); ui->weightSpinBox->blockSignals(true); ui->ouncesSpinBox->blockSignals(true); ui->weightSpinBox->setValue(0); ui->ouncesSpinBox->setValue(0); ui->ZombieMeter->setValue(0); set_ZombieMeterLabel(); ui->ouncesSpinBox->blockSignals(false); ui->weightSpinBox->blockSignals(false); ui->ZombieMeter->blockSignals(false); ui->BMI->display(0); ui->BMI->setVisible(false); ui->BMIlabel->setVisible(false); #endif BookmarksChanged=false; Session *journal=GetJournalSession(date); if (journal) { if (journal->settings.contains(Journal_Notes)) ui->JournalNotes->setHtml(journal->settings[Journal_Notes].toString()); #ifndef REMOVE_FITNESS bool ok; 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)); set_ZombieMeterLabel(); ui->ZombieMeter->blockSignals(false); } #endif 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() { #ifndef REMOVE_FITNESS 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); } #endif } 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 ) { if (!item) return; QTreeWidgetItem* parent = item->parent(); if (!parent) return; gGraph *g=GraphView->findGraph(STR_GRAPH_SleepFlags); if (!g) return; QDateTime d; if (!item->data(0,Qt::UserRole).isNull()) { int eventType = parent->type(); qint64 time = item->data(0,Qt::UserRole).toLongLong(); // for events display 3 minutes before and 20 seconds after // for start time skip abit before graph // for end time skip abut after graph qint64 period = qint64(p_profile->general->eventWindowSize())*60000L; // eventwindowsize units minutes qint64 small = period/10; qint64 start,end; if (eventType == eventTypeStart ) { start = time - small; end = time + period; } else { start = time - period; end = time + small; } GraphView->SetXBounds(start,end); } } 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(); } #ifndef REMOVE_FITNESS void Daily::set_ZombieMeterLabel() { if (ui->ZombieMeter->value()==0 ) { ui->ZombieValue->setText(tr("No Value Selected")); } else { ui->ZombieValue->setText(QString("%1:%2").arg(tr("Value")).arg(ui->ZombieMeter->value())); } } void Daily::on_ZombieMeter_valueChanged(int action) { Q_UNUSED(action); set_ZombieMeterLabel(); 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(); } #endif void Daily::on_bookmarkTable_itemChanged(QTableWidgetItem *item) { Q_UNUSED(item); update_Bookmarks(); } #ifndef REMOVE_FITNESS 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(); } #endif 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; int lastIndex = ui->graphCombo->count()-1; // account for hideshow button if (numOff == 0) { // all graphs are shown graphText = QObject::tr("%1 Graphs").arg(numTotal); ui->graphCombo->setItemText(lastIndex,STR_HIDE_ALL_GRAPHS); } else { // some graphs are hidden graphText = QObject::tr("%1 of %2 Graphs").arg(numTotal-numOff).arg(numTotal); if (numOff == numTotal) { // all graphs are hidden ui->graphCombo->setItemText(lastIndex,STR_SHOW_ALL_GRAPHS); } } ui->graphCombo->setItemText(0, graphText); } void Daily::setFlagText () { int numOff = 0; int numTotal = 0; int lastIndex = ui->eventsCombo->count()-1; // account for hideshow button for (int i=1; i < lastIndex; ++i) { numTotal++; ChannelID code = ui->eventsCombo->itemData(i, Qt::UserRole).toUInt(); schema::Channel * chan = &schema::channel[code]; if (!chan->enabled()) numOff++; } int numOn=numTotal; ui->eventsCombo->setItemIcon(0, numOff ? *icon_warning : *icon_up_down); QString flagsText; if (numOff == 0) { flagsText = (QObject::tr("%1 Event Types")+" ").arg(numTotal); ui->eventsCombo->setItemText(lastIndex,STR_HIDE_ALL_EVENTS); } else { numOn-=numOff; flagsText = QObject::tr("%1 of %2 Event Types").arg(numOn).arg(numTotal); if (numOn==0) ui->eventsCombo->setItemText(lastIndex,STR_SHOW_ALL_EVENTS); } ui->eventsCombo->setItemText(0, flagsText); sleepFlagsGroup->refreshConfiguration(sleepFlags); // need to know display changes before painting. } void Daily::showAllGraphs(bool show) { //Skip over first button - label for comboBox int lastIndex = ui->graphCombo->count()-1; for (int i=1;iupdateScale(); GraphView->redraw(); } void Daily::showGraph(int index,bool show, bool updateGraph) { ui->graphCombo->setItemData(index,show,Qt::UserRole); ui->graphCombo->setItemIcon(index, show ? *icon_on : *icon_off); if (!updateGraph) return; QString graphName = ui->graphCombo->itemText(index); gGraph* graph=GraphView->findGraphTitle(graphName); if (graph) graph->setVisible(show); } void Daily::on_graphCombo_activated(int index) { if (index<0) return; int lastIndex = ui->graphCombo->count()-1; if (index > 0) { bool nextOn =!ui->graphCombo->itemData(index,Qt::UserRole).toBool(); if ( index == lastIndex ) { // user just pressed hide show button - toggle sates of the button and apply the new state showAllGraphs(nextOn); showGraph(index,nextOn,false); } else { showGraph(index,nextOn,true); } ui->graphCombo->showPopup(); } ui->graphCombo->setCurrentIndex(0); setGraphText(); updateCube(); GraphView->updateScale(); GraphView->redraw(); } void Daily::updateCube() { //brick.. if ((GraphView->visibleGraphs()==0)) { 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); } } } } void Daily::updateGraphCombo() { ui->graphCombo->clear(); gGraph *g; ui->graphCombo->addItem(*icon_up_down, "", true); // text updated in setGRaphText 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->addItem(*icon_on,STR_HIDE_ALL_GRAPHS,true); ui->graphCombo->setCurrentIndex(0); setGraphText(); updateCube(); } void Daily::updateEventsCombo(Day* day) { 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->clear(); ui->eventsCombo->addItem(*icon_up_down, "", 0); // text updated in setflagText for (int i=0; i < available.size(); ++i) { ChannelID code = available.at(i); int comboxBoxIndex = i+1; schema::Channel & chan = schema::channel[code]; ui->eventsCombo->addItem(chan.enabled() ? *icon_on : * icon_off, chan.label(), code); ui->eventsCombo->setItemData(comboxBoxIndex, chan.fullname(), Qt::ToolTipRole); } ui->eventsCombo->addItem(*icon_on,"" , Qt::ToolTipRole); ui->eventsCombo->setCurrentIndex(0); setFlagText(); } void Daily::showAllEvents(bool show) { // Mark all events as active int lastIndex = ui->eventsCombo->count()-1; // account for hideshow button for (int i=1;ieventsCombo->itemData(i, Qt::UserRole).toUInt(); schema::Channel * chan = &schema::channel[code]; if (chan->enabled()!=show) { Daily::on_eventsCombo_activated(i); } } ui->eventsCombo->setItemData(lastIndex,show,Qt::UserRole); ui->eventsCombo->setCurrentIndex(0); setFlagText(); } void Daily::on_eventsCombo_activated(int index) { if (index<0) return; int lastIndex = ui->eventsCombo->count()-1; if (index > 0) { if ( index == lastIndex ) { bool nextOn =!ui->eventsCombo->itemData(index,Qt::UserRole).toBool(); showAllEvents(nextOn); } else { 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->showPopup(); } ui->eventsCombo->setCurrentIndex(0); setFlagText(); GraphView->redraw(); } 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); } void Daily::on_layout_clicked() { if (!saveGraphLayoutSettings) { saveGraphLayoutSettings= new SaveGraphLayoutSettings("daily",this); } if (saveGraphLayoutSettings) { saveGraphLayoutSettings->menu(GraphView); } } OSCAR-code-v1.5.1/oscar/daily.h000066400000000000000000000266441450332542600160650ustar00rootroot00000000000000/* 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/gGraphView.h" #include "Graphs/gLineChart.h" #include "sessionbar.h" #include "mytextbrowser.h" #include "saveGraphLayoutSettings.h" namespace Ui { class Daily; } class MainWindow; class DailySearchTab; class gFlagsGroup; /*! \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(); void showAllGraphs(bool show); void showGraph(int index,bool show, bool updateGraph=true); void showAllEvents(bool show); void showEvent(int index,bool show, bool updateEvent=true); void updateEventsCombo(Day*); QString STR_HIDE_ALL_EVENTS =QString(tr("Hide All Events")); QString STR_SHOW_ALL_EVENTS =QString(tr("Show All Events")); QString STR_HIDE_ALL_GRAPHS =QString(tr("Hide All Graphs")); QString STR_SHOW_ALL_GRAPHS =QString(tr("Show All Graphs")); 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); void on_graphCombo_activated(int index); #ifndef REMOVE_FITNESS /*! \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 set_ZombieMeterLabel(); /*! \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); #endif bool rejectToggleSessionEnable(Session * sess); void doToggleSession(Session *); void on_eventsCombo_activated(int index); void updateGraphCombo(); void on_splitter_2_splitterMoved(int pos, int index); void on_layout_clicked(); 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 device 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(); DailySearchTab* dailySearchTab = nullptr; //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; gGraph* sleepFlags; gFlagsGroup* sleepFlagsGroup; 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; const int eventTypeStart = QTreeWidgetItem::UserType+1; const int eventTypeEnd = QTreeWidgetItem::UserType+2; #ifndef REMOVE_FITNESS bool ZombieMeterMoved; #endif bool BookmarksChanged; SaveGraphLayoutSettings* saveGraphLayoutSettings=nullptr; }; #endif // DAILY_H OSCAR-code-v1.5.1/oscar/daily.ui000066400000000000000000001344021450332542600162430ustar00rootroot00000000000000 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 Feelings Feelings have a range 1 - 10. 1 is the worst feeling and 10 is the Best. 0 0 true Poor false I'm feeling ... Qt::AlignCenter 0 0 true Awesome Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Weight If height is greater than zero in Preferences Dialog, setting weight here will show Body Mass Index (BMI) value 1499.000000000000000 Feelings have a range 1 - 10. 1 is the worst feeling and 10 is the Best. 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 1 5 Qt::Horizontal QSlider::TicksAbove 1 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 Search 0 1 0 0 0 0 0 0 QFrame::StyledPanel QFrame::Raised QFrame::StyledPanel QFrame::Sunken 2 2 0 2 0 12 QLayout::SetMaximumSize Layout :/icons/cog.png:/icons/cog.png Save and Restore Graph Layout Settings 0 0 Show/hide available graphs. QComboBox::AdjustToContents 20 20 false OSCAR-code-v1.5.1/oscar/dailySearchTab.cpp000066400000000000000000001440501450332542600201650ustar00rootroot00000000000000/* user graph settings Implementation * * 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. */ #define TEST_MACROS_ENABLEDoff #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dailySearchTab.h" #include "SleepLib/day.h" #include "SleepLib/profiles.h" #include "daily.h" enum DS_COL { DS_COL_LEFT=0, DS_COL_RIGHT, DS_COL_MAX }; enum DS_ROW{ DS_ROW_HEADER, DS_ROW_DATA }; #define DS_ROW_MAX (passDisplayLimit+DS_ROW_DATA) /* layout of searchTAB +========================+ | HELP | +========================+ | HELPText | +========================+ | Match | Clear | start | |------------------------| | control:cmd op value | +========================+ | Progress | +========================+ | Summary | +========================+ | RESULTS | +========================+ */ DailySearchTab::DailySearchTab(Daily* daily , QWidget* searchTabWidget , QTabWidget* dailyTabWidget) : daily(daily) , parent(daily) , searchTabWidget(searchTabWidget) ,dailyTabWidget(dailyTabWidget) { m_icon_selected = new QIcon(":/icons/checkmark.png"); m_icon_notSelected = new QIcon(":/icons/empty_box.png"); m_icon_configure = new QIcon(":/icons/cog.png"); createUi(); connectUi(true); } DailySearchTab::~DailySearchTab() { connectUi(false); delete m_icon_selected; delete m_icon_notSelected; delete m_icon_configure ; }; void DailySearchTab::createUi() { searchTabLayout = new QVBoxLayout(searchTabWidget); resultTable = new QTableWidget(DS_ROW_MAX,DS_COL_MAX,searchTabWidget); helpButton = new QPushButton(searchTabWidget); helpText = new QTextEdit(searchTabWidget); startWidget = new QWidget(searchTabWidget); startLayout = new QHBoxLayout; matchButton = new QPushButton( startWidget); clearButton = new QPushButton( startWidget); startButton = new QPushButton( startWidget); commandWidget = new QWidget(searchTabWidget); commandLayout = new QHBoxLayout(); commandButton = new QPushButton(commandWidget); operationCombo = new QComboBox(commandWidget); operationButton = new QPushButton(commandWidget); selectDouble = new QDoubleSpinBox(commandWidget); selectInteger = new QSpinBox(commandWidget); selectString = new QLineEdit(commandWidget); selectUnits = new QLabel(commandWidget); commandList = new QListWidget(resultTable); summaryWidget = new QWidget(searchTabWidget); summaryLayout = new QHBoxLayout(); summaryProgress = new QPushButton(summaryWidget); summaryFound = new QPushButton(summaryWidget); summaryMinMax = new QPushButton(summaryWidget); progressBar = new QProgressBar(searchTabWidget); populateControl(); searchTabLayout->setContentsMargins(0, 0, 0, 0); searchTabLayout->addSpacing(2); searchTabLayout->setMargin(2); startLayout->addWidget(matchButton); startLayout->addWidget(clearButton); startLayout->addWidget(startButton); startLayout->addStretch(0); startLayout->addSpacing(2); startLayout->setMargin(2); startWidget->setLayout(startLayout); commandLayout->addWidget(commandButton); commandLayout->addWidget(operationCombo); commandLayout->addWidget(operationButton); commandLayout->addWidget(selectInteger); commandLayout->addWidget(selectString); commandLayout->addWidget(selectDouble); commandLayout->addWidget(selectUnits); commandLayout->setMargin(2); commandLayout->setSpacing(2); commandLayout->addStretch(0); commandWidget->setLayout(commandLayout); summaryLayout->addWidget(summaryProgress); summaryLayout->addWidget(summaryFound); summaryLayout->addWidget(summaryMinMax); summaryLayout->setMargin(2); summaryLayout->setSpacing(2); summaryWidget->setLayout(summaryLayout); QString styleSheetWidget = QString("border: 1px solid black; padding:none;"); startWidget->setStyleSheet(styleSheetWidget); searchTabLayout ->addWidget(helpButton); searchTabLayout ->addWidget(helpText); searchTabLayout ->addWidget(startWidget); searchTabLayout ->addWidget(commandWidget); searchTabLayout ->addWidget(commandList); searchTabLayout ->addWidget(progressBar); searchTabLayout ->addWidget(summaryWidget); searchTabLayout ->addWidget(resultTable); // End of UI creatation //Setup each BUtton / control item QString styleButton=QString( "QPushButton { color: black; border: 1px solid black; padding: 5px ;background-color:white; }" "QPushButton:disabled { background-color: #EEEEFF;}" //"QPushButton:disabled { color: #333333; border: 1px solid #333333; background-color: #dddddd;}" ); QSizePolicy sizePolicyEP = QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Preferred); QSizePolicy sizePolicyEE = QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Expanding); QSizePolicy sizePolicyEM = QSizePolicy(QSizePolicy::Expanding,QSizePolicy::Minimum); QSizePolicy sizePolicyMM = QSizePolicy(QSizePolicy::Minimum,QSizePolicy::Minimum); QSizePolicy sizePolicyMxMx = QSizePolicy(QSizePolicy::Maximum,QSizePolicy::Maximum); Q_UNUSED (sizePolicyEP); Q_UNUSED (sizePolicyMM); Q_UNUSED (sizePolicyMxMx); helpText->setReadOnly(true); helpText->setLineWrapMode(QTextEdit::NoWrap); QSize size = QFontMetrics(this->font()).size(0, helpString); size.rheight() += 35 ; // scrollbar size.rwidth() += 35 ; // scrollbar helpText->setText(helpString); helpText->setMinimumSize(textsize(this->font(),helpString)); helpText->setSizePolicy( sizePolicyEE ); helpButton->setStyleSheet( styleButton ); // helpButton->setText(tr("Help")); helpMode = true; on_helpButton_clicked(); matchButton->setIcon(*m_icon_configure); matchButton->setStyleSheet( styleButton ); clearButton->setStyleSheet( styleButton ); startButton->setStyleSheet( styleButton ); //matchButton->setSizePolicy( sizePolicyEE); //clearButton->setSizePolicy( sizePolicyEE); //startButton->setSizePolicy( sizePolicyEE); //startWidget->setSizePolicy( sizePolicyEM); setText(matchButton,tr("Match")); setText(clearButton,tr("Clear")); commandButton->setStyleSheet("border:none;"); //float height = float(1+commandList->count())*commandListItemHeight ; float height = float(commandList->count())*commandListItemHeight ; commandList->setMinimumHeight(height); commandList->setMinimumWidth(commandListItemMaxWidth); setCommandPopupEnabled(false); setText(operationButton,""); operationButton->setStyleSheet("border:none;"); operationButton->hide(); operationCombo->setSizePolicy(QSizePolicy::Minimum,QSizePolicy::Minimum); setOperationPopupEnabled(false); selectDouble->setSizePolicy(QSizePolicy::Minimum,QSizePolicy::Minimum); selectInteger->setSizePolicy(QSizePolicy::Minimum,QSizePolicy::Minimum); selectString->setSizePolicy(QSizePolicy::Minimum,QSizePolicy::Minimum); selectUnits->setSizePolicy(QSizePolicy::Minimum,QSizePolicy::Minimum); setText(selectUnits,""); progressBar->setStyleSheet( "QProgressBar{border: 1px solid black; text-align: center;}" "QProgressBar::chunk { border: none; background-color: #ccddFF; } "); //QString styleLabel=QString( "QLabel { color: black; border: 1px solid black; padding: 5px ;background-color:white; }"); summaryProgress->setStyleSheet( styleButton ); summaryFound->setStyleSheet( styleButton ); summaryMinMax->setStyleSheet( styleButton ); summaryProgress->setSizePolicy( sizePolicyEM ) ; summaryFound->setSizePolicy( sizePolicyEM ) ; summaryMinMax->setSizePolicy( sizePolicyEM ) ; resultTable->horizontalHeader()->hide(); // hides numbers above each column resultTable->horizontalHeader()->setStretchLastSection(true); // get rid of selection coloring resultTable->setStyleSheet("QTableView{background-color: white; selection-background-color: white; }"); float width = 14/*styleSheet:padding+border*/ + QFontMetrics(this->font()).size(Qt::TextSingleLine ,"WWW MMM 99 2222").width(); resultTable->setColumnWidth(DS_COL_LEFT, width); width = 30/*iconWidthPlus*/+QFontMetrics(this->font()).size(Qt::TextSingleLine ,clearButton->text()).width(); //width = clearButton->width(); resultTable->setShowGrid(false); setResult(DS_ROW_HEADER,0,QDate(),tr("DATE\nJumps to Date")); on_clearButton_clicked(); } void DailySearchTab::connectUi(bool doConnect) { if (doConnect) { daily->connect(startButton, SIGNAL(clicked()), this, SLOT(on_startButton_clicked()) ); daily->connect(clearButton, SIGNAL(clicked()), this, SLOT(on_clearButton_clicked()) ); daily->connect(matchButton, SIGNAL(clicked()), this, SLOT(on_matchButton_clicked()) ); daily->connect(helpButton , SIGNAL(clicked()), this, SLOT(on_helpButton_clicked()) ); daily->connect(commandButton, SIGNAL(clicked()), this, SLOT(on_commandButton_clicked()) ); daily->connect(operationButton, SIGNAL(clicked()), this, SLOT(on_operationButton_clicked()) ); daily->connect(commandList, SIGNAL(itemActivated(QListWidgetItem*)), this, SLOT(on_commandList_activated(QListWidgetItem*) )); daily->connect(commandList, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(on_commandList_activated(QListWidgetItem*) )); daily->connect(operationCombo, SIGNAL(activated(int)), this, SLOT(on_operationCombo_activated(int) )); daily->connect(selectInteger, SIGNAL(valueChanged(int)), this, SLOT(on_intValueChanged(int)) ); daily->connect(selectDouble, SIGNAL(valueChanged(double)), this, SLOT(on_doubleValueChanged(double)) ); daily->connect(selectString, SIGNAL(textEdited(QString)), this, SLOT(on_textEdited(QString)) ); } else { daily->disconnect(startButton, SIGNAL(clicked()), this, SLOT(on_startButton_clicked()) ); daily->disconnect(clearButton, SIGNAL(clicked()), this, SLOT(on_clearButton_clicked()) ); daily->disconnect(matchButton, SIGNAL(clicked()), this, SLOT(on_matchButton_clicked()) ); daily->disconnect(helpButton , SIGNAL(clicked()), this, SLOT(on_helpButton_clicked()) ); daily->disconnect(commandButton, SIGNAL(clicked()), this, SLOT(on_commandButton_clicked()) ); daily->disconnect(operationButton, SIGNAL(clicked()), this, SLOT(on_operationButton_clicked()) ); daily->disconnect(commandList, SIGNAL(itemActivated(QListWidgetItem*)), this, SLOT(on_commandList_activated(QListWidgetItem*) )); daily->disconnect(commandList, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(on_commandList_activated(QListWidgetItem*) )); daily->disconnect(operationCombo, SIGNAL(activated(int)), this, SLOT(on_operationCombo_activated(int) )); daily->disconnect(selectInteger, SIGNAL(valueChanged(int)), this, SLOT(on_intValueChanged(int)) ); daily->disconnect(selectDouble, SIGNAL(valueChanged(double)), this, SLOT(on_doubleValueChanged(double)) ); daily->disconnect(selectString, SIGNAL(textEdited(QString)), this, SLOT(on_textEdited(QString)) ); } } QListWidgetItem* DailySearchTab::calculateMaxSize(QString str,int topic) { //calculate max size of strings float scaleX= (float)(QWidget::logicalDpiX()*100.0)/(float)(QWidget::physicalDpiX()); float percentX = scaleX/100.0; float width = QFontMetricsF(this->font()).size(Qt::TextSingleLine , str).width(); width += 30 ; // account for scrollbar width; commandListItemMaxWidth = max (commandListItemMaxWidth, (width*percentX)); commandListItemHeight = QFontMetricsF(this->font()).size(Qt::TextSingleLine , str).height(); QListWidgetItem* item = new QListWidgetItem(str); item->setData(Qt::UserRole,topic); return item; } void DailySearchTab::populateControl() { commandList->clear(); commandList->addItem(calculateMaxSize(tr("Notes"),ST_NOTES)); commandList->addItem(calculateMaxSize(tr("Notes containing"),ST_NOTES_STRING)); commandList->addItem(calculateMaxSize(tr("Bookmarks"),ST_BOOKMARKS)); commandList->addItem(calculateMaxSize(tr("Bookmarks containing"),ST_BOOKMARKS_STRING)); commandList->addItem(calculateMaxSize(tr("AHI "),ST_AHI)); commandList->addItem(calculateMaxSize(tr("Daily Duration"),ST_DAILY_USAGE)); commandList->addItem(calculateMaxSize(tr("Session Duration" ),ST_SESSION_LENGTH)); commandList->addItem(calculateMaxSize(tr("Days Skipped"),ST_DAYS_SKIPPED)); if ( !p_profile->cpap->clinicalMode() ) { commandList->addItem(calculateMaxSize(tr("Disabled Sessions"),ST_DISABLED_SESSIONS)); } commandList->addItem(calculateMaxSize(tr("Number of Sessions"),ST_SESSIONS_QTY)); //commandList->insertSeparator(commandList->count()); // separate from events // Now add events QDate date = p_profile->LastDay(MT_CPAP); if ( !date.isValid()) return; Day* day = p_profile->GetDay(date); if (!day) return; // the following is copied from daily. qint32 chans = schema::SPAN | schema::FLAG | schema::MINOR_FLAG; if (p_profile->general->showUnknownFlags()) chans |= schema::UNKNOWN; QList available; available.append(day->getSortedMachineChannels(chans)); for (int i=0; i < available.size(); ++i) { ChannelID id = available.at(i); schema::Channel chan = schema::channel[ id ]; // new stuff now QString displayName= chan.fullname(); commandList->addItem(calculateMaxSize(displayName,id)); } opCodeMap.clear(); opCodeMap.insert( opCodeStr(OP_LT),OP_LT); opCodeMap.insert( opCodeStr(OP_GT),OP_GT); opCodeMap.insert( opCodeStr(OP_NE),OP_NE); opCodeMap.insert( opCodeStr(OP_LE),OP_LE); opCodeMap.insert( opCodeStr(OP_GE),OP_GE); opCodeMap.insert( opCodeStr(OP_EQ),OP_EQ); opCodeMap.insert( opCodeStr(OP_NE),OP_NE); opCodeMap.insert( opCodeStr(OP_CONTAINS),OP_CONTAINS); opCodeMap.insert( opCodeStr(OP_WILDCARD),OP_WILDCARD); // The order here is the order in the popup box operationCombo->clear(); operationCombo->addItem(opCodeStr(OP_LT)); operationCombo->addItem(opCodeStr(OP_GT)); operationCombo->addItem(opCodeStr(OP_LE)); operationCombo->addItem(opCodeStr(OP_GE)); operationCombo->addItem(opCodeStr(OP_EQ)); operationCombo->addItem(opCodeStr(OP_NE)); } void DailySearchTab::on_helpButton_clicked() { helpMode = !helpMode; if (helpMode) { resultTable->setMinimumSize(QSize(50,200)+textsize(helpText->font(),helpString)); resultTable->setSizePolicy(QSizePolicy::MinimumExpanding,QSizePolicy::MinimumExpanding); //setText(helpButton,tr("Click HERE to close Help")); helpButton->setText(tr("Click HERE to close Help")); helpText ->show(); } else { resultTable->setMinimumWidth(250); helpText ->hide(); //setText(helpButton,tr("Help")); helpButton->setText(tr("Help")); } } QRegExp DailySearchTab::searchPatterToRegex (QString searchPattern) { const static QChar bSlash('\\'); const static QChar asterisk('*'); const static QChar qMark('?'); const static QChar emptyChar('\0'); const QString emptyStr(""); const QString anyStr(".*"); const QString singleStr("."); searchPattern = searchPattern.simplified(); //QString wilDebug = searchPattern; // // wildcard searches uses '*' , '?' and '\' // '*' will match zero or more characters. '?' will match one character. the escape character '\' always matches the next character. // '\\' will match '\'. '\*' will match '*'. '\?' mach for '?'. otherwise the '\' is ignored // The user request will be mapped into a valid QRegularExpression. All RegExp meta characters in the request must be handled. // Most of the meta characters will be escapped. backslash, asterisk, question mark will be treated. // '\*' -> '\*' // '\?' -> '\?' // '*' -> '.*' // really asterisk followed by asterisk or questionmark -> as a single asterisk. // '?' -> '.' // '.' -> '\.' // default for all other meta characters. // '\\' -> '[\\]' // QT documentation states regex reserved characetrs are $ () * + . ? [ ] ^ {} | // seems to be missing / \ - // Regular expression reserved characters / \ [ ] () {} | + ^ . $ ? * - static const QString metaClass = QString( "[ / \\\\ \\[ \\] ( ) { } | + ^ . $ ? * - ]").replace(" ",""); // slash,bSlash,[,],(,),{,},+,^,.,$,?,*,-,| static const QRegExp metaCharRegex(metaClass); #if 0 // Verify search pattern if (!metaCharRegex.isValid()) { DEBUGFW Q(metaCharRegex.errorString()) Q(metaCharRegex) O("============================================"); return QRegExp(); } #endif // regex meta characetrs. all regex meta character must be acounts to us regular expression to make wildcard work. // they will be escaped. except for * and ? which will be treated separately searchPattern = searchPattern.simplified(); // remove witespace at ends. and multiple white space to a single space. // now handle each meta character requested. int pos=0; int len=1; QString replace; QChar metaChar; QChar nextChar; while (pos < (len = searchPattern.length()) ) { pos = searchPattern.indexOf(metaCharRegex,pos); if (pos<0) break; metaChar = searchPattern.at(pos); if (pos+1=len) break; nextChar = searchPattern.at(next); } replace = anyStr; // if asterisk then write dot asterisk } else if (metaChar == qMark ) { replace = singleStr; } else { if ((metaChar == bSlash ) ) { if ( ((nextChar == bSlash ) || (nextChar == asterisk ) || (nextChar == qMark ) ) ) { pos+=2; continue; } replace = emptyStr; //match next character. same as deleteing the backslash } else { // Now have a regex reserved character that needs escaping. // insert an escape '\' before meta characters. replace = QString("\\%1").arg(metaChar); } } searchPattern.replace(pos,replaceCount,replace); pos+=replace.length(); // skip over characters added. } // searchPattern = QString("^.*%1.*$").arg(searchPattern); // add asterisk to end end points. QRegExp convertedRegex =QRegExp(searchPattern,Qt::CaseInsensitive, QRegExp::RegExp); // verify convertedRegex to use if (!convertedRegex.isValid()) { qWarning() << QFileInfo( __FILE__).baseName() <<"[" << __LINE__ << "] " << convertedRegex.errorString() << convertedRegex ; return QRegExp(); } return convertedRegex; } bool DailySearchTab::compare(QString find , QString target) { OpCode opCode = operationOpCode; bool ret=false; if (opCode==OP_CONTAINS) { ret = target.contains(find,Qt::CaseInsensitive); } else if (opCode==OP_WILDCARD) { QRegExp regex = searchPatterToRegex(find); ret = target.contains(regex); } return ret; } bool DailySearchTab::compare(int aa , int bb) { OpCode opCode = operationOpCode; if (opCode>=OP_END_NUMERIC) return false; int mode=0; if (aa bb ) { mode |= OP_GT; } else { mode |= OP_EQ; } return ( (mode & (int)opCode)!=0); } QString DailySearchTab::valueToString(int value, QString defaultValue) { switch (valueMode) { case hundredths : return QString("%1").arg( (double(value)/100.0),0,'f',2); break; case hoursToMs: case minutesToMs: return formatTime(value); break; case displayWhole: case opWhole: return QString().setNum(value); break; case displayString: case opString: return foundString; break; case invalidValueMode: case notUsed: break; } return defaultValue; } void DailySearchTab::on_operationCombo_activated(int index) { QString text = operationCombo->itemText(index); OpCode opCode = opCodeMap[text]; if (opCode>OP_INVALID && opCode < OP_END_NUMERIC) { operationOpCode = opCode; setText(operationButton,opCodeStr(operationOpCode)); } else if (opCode == OP_CONTAINS || opCode == OP_WILDCARD) { operationOpCode = opCode; setText(operationButton,opCodeStr(operationOpCode)); } else { // null case; } setOperationPopupEnabled(false); criteriaChanged(); }; QSize DailySearchTab::setText(QLabel* label ,QString text) { QSize size = textsize(label->font(),text); int width = size.width(); width += 20 ; //margings label->setMinimumWidth(width); label->setSizePolicy(QSizePolicy::Fixed,QSizePolicy::Fixed); //label->setSizePolicy(QSizePolicy::Minimum,QSizePolicy::Fixed); label->setText(text); return size; } QSize DailySearchTab::setText(QPushButton* but ,QString text) { QSize size = textsize(but->font(),text); int width = size.width(); width += but->iconSize().width(); width += 4 ; //margings but->setMinimumWidth(width); but->setSizePolicy(QSizePolicy::Fixed,QSizePolicy::Fixed); //but->setSizePolicy(QSizePolicy::Minimum,QSizePolicy::Fixed); but->setText(text); return size; } void DailySearchTab::on_commandList_activated(QListWidgetItem* item) { // here to select new search criteria // must reset all variables and label, button, etc on_clearButton_clicked() ; valueMode = notUsed; selectValue = 0; // workaround for combo box alignmnet and sizing. // copy selections to a pushbutton. hide combobox and show pushButton. Pushbutton activation can show popup. // always hide first before show. allows for best fit setText(commandButton, item->text()); setCommandPopupEnabled(false); operationOpCode = OP_INVALID; // get item selected int itemTopic = item->data(Qt::UserRole).toInt(); if (itemTopic>=ST_EVENT) { channelId = itemTopic; searchTopic = ST_EVENT; } else { searchTopic = (SearchTopic)itemTopic; } switch (searchTopic) { case ST_NONE : // should never get here. setResult(DS_ROW_HEADER,1,QDate(),""); nextTab = TW_NONE ; setoperation( OP_INVALID ,notUsed); break; case ST_DAYS_SKIPPED : setResult(DS_ROW_HEADER,1,QDate(),tr("No Data\nJumps to Date's Details ")); nextTab = TW_DETAILED ; setoperation(OP_NO_PARMS,notUsed); break; case ST_DISABLED_SESSIONS : setResult(DS_ROW_HEADER,1,QDate(),tr("Number Disabled Session\nJumps to Date's Details ")); nextTab = TW_DETAILED ; selectInteger->setValue(0); setoperation(OP_NO_PARMS,displayWhole); break; case ST_NOTES : setResult(DS_ROW_HEADER,1,QDate(),tr("Note\nJumps to Date's Notes")); nextTab = TW_NOTES ; setoperation( OP_NO_PARMS ,displayString); break; case ST_BOOKMARKS : setResult(DS_ROW_HEADER,1,QDate(),tr("Bookmark\nJumps to Date's Bookmark")); nextTab = TW_BOOKMARK ; setoperation( OP_NO_PARMS ,displayString); break; case ST_BOOKMARKS_STRING : setResult(DS_ROW_HEADER,1,QDate(),tr("Bookmark\nJumps to Date's Bookmark")); nextTab = TW_BOOKMARK ; //setoperation(OP_CONTAINS,opString); setoperation(OP_WILDCARD,opString); selectString->clear(); break; case ST_NOTES_STRING : setResult(DS_ROW_HEADER,1,QDate(),tr("Note\nJumps to Date's Notes")); nextTab = TW_NOTES ; //setoperation(OP_CONTAINS,opString); setoperation(OP_WILDCARD,opString); selectString->clear(); break; case ST_AHI : setResult(DS_ROW_HEADER,1,QDate(),tr("AHI\nJumps to Date's Details")); nextTab = TW_DETAILED ; setoperation(OP_GT,hundredths); setText(selectUnits,tr(" EventsPerHour")); selectDouble->setValue(5.0); selectDouble->setSingleStep(0.1); break; case ST_SESSION_LENGTH : setResult(DS_ROW_HEADER,1,QDate(),tr("Session Duration\nJumps to Date's Details")); nextTab = TW_DETAILED ; setoperation(OP_LT,minutesToMs); selectDouble->setValue(5.0); setText(selectUnits,tr(" Minutes")); selectDouble->setSingleStep(0.1); selectInteger->setValue((int)selectDouble->value()*60000.0); //convert to ms break; case ST_SESSIONS_QTY : setResult(DS_ROW_HEADER,1,QDate(),tr("Number of Sessions\nJumps to Date's Details")); nextTab = TW_DETAILED ; setoperation(OP_GT,opWhole); selectInteger->setRange(0,999); selectInteger->setValue(2); setText(selectUnits,tr(" Sessions")); break; case ST_DAILY_USAGE : setResult(DS_ROW_HEADER,1,QDate(),tr("Daily Duration\nJumps to Date's Details")); nextTab = TW_DETAILED ; setoperation(OP_LT,hoursToMs); selectDouble->setValue(p_profile->cpap->complianceHours()); selectDouble->setSingleStep(0.1); selectInteger->setValue((int)selectDouble->value()*3600000.0); //convert to ms setText(selectUnits,tr(" Hours")); break; case ST_EVENT: // Have an Event setResult(DS_ROW_HEADER,1,QDate(),tr("Number of events\nJumps to Date's Events")); nextTab = TW_EVENTS ; setoperation(OP_GT,opWhole); selectInteger->setValue(0); setText(selectUnits,tr(" Events")); break; } criteriaChanged(); if (operationOpCode == OP_NO_PARMS ) { // auto start searching setText(startButton,tr("Automatic start")); startButtonMode=true; on_startButton_clicked(); return; } return; } void DailySearchTab::setResult(int row,int column,QDate date,QString text) { if(column<0 || column>1) { DEBUGTFW O("Column out of range ERROR") Q(row) Q(column) Q(date) Q(text); return; } else if ( row < DS_ROW_HEADER || row >= DS_ROW_MAX) { DEBUGTFW O("Row out of range ERROR") Q(row) Q(column) Q(date) Q(text); return; } QWidget* header = resultTable->cellWidget(row,column); GPushButton* item; if (header == nullptr) { item = new GPushButton(row,column,date,this); //item->setStyleSheet("QPushButton {text-align: left;vertical-align:top;}"); item->setStyleSheet( "QPushButton { text-align: left;color: black; border: 1px solid black; padding: 5px ;background-color:white; }" ); resultTable->setCellWidget(row,column,item); } else { item = dynamic_cast(header); if (item == nullptr) { DEBUGFW Q(header) Q(item) Q(row) Q(column) Q(text) QQ("error","======================="); return; } item->setDate(date); } if (row == DS_ROW_HEADER) { QSize size=setText(item,text); resultTable->setRowHeight(DS_ROW_HEADER,8/*margins*/+size.height()); } else { item->setIcon(*m_icon_notSelected); if (column == 0) { setText(item,date.toString()); } else { setText(item,text); } } if ( row == DS_ROW_DATA ) { resultTable->setRowHidden(DS_ROW_HEADER,false); } resultTable->setRowHidden(row,false); } void DailySearchTab::updateValues(qint32 value) { foundValue = value; if (!minMaxValid ) { minMaxValid = true; minInteger = value; maxInteger = value; } else if ( value < minInteger ) { minInteger = value; } else if ( value > maxInteger ) { maxInteger = value; } } void DailySearchTab::find(QDate& date) { bool found=false; Qt::Alignment alignment=Qt::AlignCenter; Day* day = p_profile->GetDay(date); if ( (!day) && (searchTopic != ST_DAYS_SKIPPED)) { daysSkipped++; return;}; switch (searchTopic) { case ST_DAYS_SKIPPED : found=!day; break; case ST_DISABLED_SESSIONS : { qint32 numDisabled=0; QList sessions = day->getSessions(MT_CPAP,true); for (auto & sess : sessions) { if (!sess->enabled()) { numDisabled ++; found=true; } } updateValues(numDisabled); } break; case ST_NOTES : { Session* journal=daily->GetJournalSession(date); if (journal && journal->settings.contains(Journal_Notes)) { QString jcontents = convertRichText2Plain(journal->settings[Journal_Notes].toString()); foundString = jcontents.simplified().left(stringDisplayLen).simplified(); found=true; alignment=Qt::AlignLeft; } } break; case ST_BOOKMARKS : { Session* journal=daily->GetJournalSession(date); if (journal && journal->settings.contains(Bookmark_Notes)) { found=true; QStringList notes = journal->settings[Bookmark_Notes].toStringList(); for ( const auto & note : notes) { foundString = note.simplified().left(stringDisplayLen).simplified(); alignment=Qt::AlignLeft; break; } } } break; case ST_BOOKMARKS_STRING : { Session* journal=daily->GetJournalSession(date); if (journal && journal->settings.contains(Bookmark_Notes)) { QStringList notes = journal->settings[Bookmark_Notes].toStringList(); QString findStr = selectString->text(); for ( const auto & note : notes) { if (compare(findStr , note)) { found=true; foundString = note.simplified().left(stringDisplayLen).simplified(); alignment=Qt::AlignLeft; break; } } } } break; case ST_NOTES_STRING : { Session* journal=daily->GetJournalSession(date); if (journal && journal->settings.contains(Journal_Notes)) { QString jcontents = convertRichText2Plain(journal->settings[Journal_Notes].toString()); QString findStr = selectString->text(); if (jcontents.contains(findStr,Qt::CaseInsensitive) ) { found=true; foundString = jcontents.simplified().left(stringDisplayLen).simplified(); alignment=Qt::AlignLeft; } } } break; case ST_AHI : { EventDataType dahi =calculateAhi(day); dahi += 0.005; dahi *= 100.0; int ahi = (int)dahi; updateValues(ahi); found = compare (ahi , selectValue); } break; case ST_SESSION_LENGTH : { bool valid=false; qint64 value=0; QList sessions = day->getSessions(MT_CPAP); for (auto & sess : sessions) { qint64 ms = sess->length(); updateValues(ms); if (compare (ms , selectValue) ) { found =true; } if (!valid) { valid=true; value=ms; } else if (compare (ms , value) ) { value=ms; } } if (valid) updateValues(value); } break; case ST_SESSIONS_QTY : { QList sessions = day->getSessions(MT_CPAP); qint32 size = sessions.size(); updateValues(size); found=compare (size , selectValue); } break; case ST_DAILY_USAGE : { QList sessions = day->getSessions(MT_CPAP); qint64 sum = 0 ; for (auto & sess : sessions) { sum += sess->length(); } updateValues(sum); found=compare (sum , selectValue); } break; case ST_EVENT : { qint32 count = day->count(channelId); updateValues(count); found=compare (count , selectValue); } break; case ST_NONE : break; } if (found) { addItem(date , valueToString(foundValue,"------"),alignment ); passFound++; daysFound++; } return ; }; void DailySearchTab::search(QDate date) { if (!date.isValid()) { qWarning() << "DailySearchTab::find invalid date." << date; return; } hideResults(false); foundString.clear(); passFound=0; while (date >= earliestDate) { nextDate = date; if (passFound >= passDisplayLimit) break; find(date); progressBar->setValue(++daysProcessed); date=date.addDays(-1); } endOfPass(); return ; }; void DailySearchTab::addItem(QDate date, QString value,Qt::Alignment alignment) { int row = passFound; Q_UNUSED(alignment); setResult(DS_ROW_DATA+row,0,date,value); setResult(DS_ROW_DATA+row,1,date,value); } void DailySearchTab::endOfPass() { startButtonMode=false; // display Continue; QString display; if ((passFound >= passDisplayLimit) && (daysProcessedsetEnabled(true); setText(startButton,(tr("Continue Search"))); } else if (daysFound>0) { startButton->setEnabled(false); setText(startButton,tr("End of Search")); } else { startButton->setEnabled(false); setText(startButton,tr("No Matches")); } displayStatistics(); } void DailySearchTab::hideCommand(bool showButton) { //operationCombo->hide(); //operationCombo->clear(); operationButton->hide(); selectDouble->hide(); selectInteger->hide(); selectString->hide(); selectUnits->hide(); commandWidget->setVisible(showButton); commandButton->setVisible(showButton); }; void DailySearchTab::setCommandPopupEnabled(bool on) { if (on) { commandPopupEnabled=true; hideCommand(); commandList->show(); hideResults(true); } else { commandPopupEnabled=false; commandList->hide(); hideCommand(true/*show button*/); } } void DailySearchTab::on_operationButton_clicked() { if (operationOpCode == OP_CONTAINS ) { operationOpCode = OP_WILDCARD; } else if (operationOpCode == OP_WILDCARD) { operationOpCode = OP_CONTAINS ; } else { setOperationPopupEnabled(true); return; } QString text=opCodeStr(operationOpCode); setText(operationButton,text); criteriaChanged(); }; void DailySearchTab::on_matchButton_clicked() { setCommandPopupEnabled(!commandPopupEnabled); } void DailySearchTab::on_commandButton_clicked() { setCommandPopupEnabled(true); } void DailySearchTab::setOperationPopupEnabled(bool on) { //if (operationOpCode= OP_END_NUMERIC) return; if (on) { operationButton->hide(); operationCombo->show(); //operationCombo->setEnabled(true); operationCombo->showPopup(); } else { operationCombo->hidePopup(); //operationCombo->setEnabled(false); operationCombo->hide(); operationButton->show(); } } void DailySearchTab::setoperation(OpCode opCode,ValueMode mode) { valueMode = mode; operationOpCode = opCode; setText(operationButton,opCodeStr(operationOpCode)); setOperationPopupEnabled(false); if (opCode > OP_INVALID && opCode setDecimals(2); selectDouble->setRange(0,999); selectDouble->setDecimals(2); selectInteger->setRange(0,999); selectDouble->setSingleStep(0.1); } switch (valueMode) { case hundredths : selectUnits->show(); selectDouble->show(); break; case hoursToMs: setText(selectUnits,tr(" Hours")); selectUnits->show(); selectDouble->show(); break; case minutesToMs: setText(selectUnits,tr(" Minutes")); selectUnits->show(); selectDouble->setRange(0,9999); selectDouble->show(); break; case opWhole: selectUnits->show(); selectInteger->show(); break; case displayWhole: selectInteger->hide(); break; case opString: operationButton->show(); selectString ->show(); break; case displayString: selectString ->hide(); break; case invalidValueMode: case notUsed: break; } } void DailySearchTab::hideResults(bool hide) { if (hide) { for (int index = DS_ROW_HEADER; indexsetRowHidden(index,true); } } progressBar->setMinimumHeight(matchButton->height()); progressBar->setVisible(!hide); summaryWidget->setVisible(!hide); } QSize DailySearchTab::textsize(QFont font ,QString text) { return QFontMetrics(font).size(0 , text); } void DailySearchTab::on_clearButton_clicked() { searchTopic = ST_NONE; // make these button text back to start. startButton->setText(tr("Start Search")); startButtonMode=true; startButton->setEnabled( false); // hide widgets //Reset Select area commandList->hide(); setCommandPopupEnabled(false); setText(commandButton,""); commandButton->show(); operationCombo->hide(); setOperationPopupEnabled(false); operationButton->hide(); selectDouble->hide(); selectInteger->hide(); selectString->hide(); selectUnits->hide(); hideResults(true); // show these widgets; //controlWidget->show(); } void DailySearchTab::on_startButton_clicked() { hideResults(false); if (startButtonMode) { search (latestDate ); startButtonMode=false; } else { search (nextDate ); } } void DailySearchTab::on_intValueChanged(int ) { selectInteger->findChild()->deselect(); criteriaChanged(); } void DailySearchTab::on_doubleValueChanged(double ) { selectDouble->findChild()->deselect(); criteriaChanged(); } void DailySearchTab::on_textEdited(QString ) { criteriaChanged(); } void DailySearchTab::displayStatistics() { QString extra; summaryProgress->show(); // display days searched QString skip= daysSkipped==0?"":QString(tr(" Skip:%1")).arg(daysSkipped); setText(summaryProgress,centerLine(QString(tr("%1/%2%3 days")).arg(daysProcessed).arg(daysTotal).arg(skip) )); // display days found setText(summaryFound,centerLine(QString(tr("Found %1 ")).arg(daysFound) )); // display associated value extra =""; if (minMaxValid) { extra = QString("%1 / %2").arg(valueToString(minInteger)).arg(valueToString(maxInteger)); } if (extra.size()>0) { setText(summaryMinMax,extra); summaryMinMax->show(); } else { summaryMinMax->hide(); } summaryProgress->show(); summaryFound->show(); } void DailySearchTab::criteriaChanged() { // setup before start button if (valueMode != notUsed ) { setText(operationButton,opCodeStr(operationOpCode)); operationButton->show(); } switch (valueMode) { case hundredths : selectValue = (int)(selectDouble->value()*100.0); //convert to hundreths of AHI. break; case minutesToMs: selectValue = (int)(selectDouble->value()*60000.0); //convert to ms break; case hoursToMs: selectValue = (int)(selectDouble->value()*3600000.0); //convert to ms break; case displayWhole: case opWhole: selectValue = selectInteger->value();; break; case opString: case displayString: case invalidValueMode: case notUsed: break; } commandList->hide(); commandButton->show(); setText(startButton,tr("Start Search")); startButtonMode=true; startButton->setEnabled( true); hideResults(true); minMaxValid = false; minInteger = 0; maxInteger = 0; minDouble = 0.0; maxDouble = 0.0; earliestDate = p_profile->FirstDay(MT_CPAP); latestDate = p_profile->LastDay(MT_CPAP); daysTotal= 1+earliestDate.daysTo(latestDate); daysFound=0; daysSkipped=0; daysProcessed=0; startButtonMode=true; //initialize progress bar. progressBar->setMinimum(0); progressBar->setMaximum(daysTotal); progressBar->setTextVisible(true); progressBar->setMinimumHeight(commandListItemHeight); progressBar->setMaximumHeight(commandListItemHeight); progressBar->reset(); } // inputs character string. // outputs cwa centered html string. // converts \n to
QString DailySearchTab::centerLine(QString line) { return line; return QString( "
%1
").arg(line).replace("\n","
"); } QString DailySearchTab::helpStr() { QStringList str; str < , >= , < , <= , == , != " <<"\n" < "; case OP_GE : return ">="; case OP_LT : return "< "; case OP_LE : return "<="; case OP_EQ : return "=="; case OP_NE : return "!="; case OP_CONTAINS : return "=="; // or use 0x220B case OP_WILDCARD : return "*?"; case OP_INVALID: case OP_END_NUMERIC: case OP_NO_PARMS: break; } return QString(); }; EventDataType DailySearchTab::calculateAhi(Day* day) { if (!day) return 0.0; // copied from daily.cpp double hours=day->hours(MT_CPAP); if (hours<=0) return 0; EventDataType ahi=day->count(AllAhiChannels); if (p_profile->general->calculateRDI()) ahi+=day->count(CPAP_RERA); ahi/=hours; return ahi; } void DailySearchTab::on_activated(GPushButton* item ) { int row=item->row(); int col=item->column(); if (row=DS_ROW_MAX) return; row-=DS_ROW_DATA; item->setIcon (*m_icon_selected); daily->LoadDate( item->date() ); if ((col!=0) && nextTab>=0 && nextTab < dailyTabWidget->count()) { dailyTabWidget->setCurrentIndex(nextTab); // 0 = details ; 1=events =2 notes ; 3=bookarks; } } GPushButton::GPushButton (int row,int column,QDate date,DailySearchTab* parent) : QPushButton(parent), _parent(parent), _row(row), _column(column), _date(date) { connect(this, SIGNAL(clicked()), this, SLOT(on_clicked())); connect(this, SIGNAL(activated(GPushButton*)), _parent, SLOT(on_activated(GPushButton*))); }; GPushButton::~GPushButton() { //the following disconnects trigger a crash during exit or profile change. - anytime daily is destroyed. //disconnect(this, SIGNAL(clicked()), this, SLOT(on_clicked())); //disconnect(this, SIGNAL(activated(GPushButton*)), _parent, SLOT(on_activated(GPushButton*))); }; void GPushButton::on_clicked() { emit activated(this); }; OSCAR-code-v1.5.1/oscar/dailySearchTab.h000066400000000000000000000152131450332542600176300ustar00rootroot00000000000000/* search GUI Headers * * 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 SEARCHDAILY_H #define SEARCHDAILY_H #include #include #include #include #include #include #include #include #include #include "SleepLib/common.h" class GPushButton; class QWidget ; class QDialog ; class QComboBox ; class QListWidget ; class QProgressBar ; class QHBoxLayout ; class QVBoxLayout ; class QLabel ; class QDoubleSpinBox ; class QSpinBox ; class QLineEdit ; class QTableWidget ; class QTableWidgetItem ; class QSizeF ; class Day; //forward declaration. class Daily; //forward declaration. class DailySearchTab : public QWidget { Q_OBJECT public: DailySearchTab ( Daily* daily , QWidget* , QTabWidget* ) ; ~DailySearchTab(); private: // these values are hard coded. because dynamic translation might not do the proper assignment. // Dynamic code is commented out using c preprocess #if #endif const int TW_NONE = -1; const int TW_DETAILED = 0; const int TW_EVENTS = 1; const int TW_NOTES = 2; const int TW_BOOKMARK = 3; const int TW_SEARCH = 4; const int dateRole = Qt::UserRole; const int valueRole = 1+Qt::UserRole; const int passDisplayLimit = 30; const int stringDisplayLen = 80; enum ValueMode { invalidValueMode , notUsed , minutesToMs , hoursToMs , hundredths , opWhole , displayWhole , opString , displayString}; enum SearchTopic { ST_NONE = 0 , ST_DAYS_SKIPPED = 1 , ST_DISABLED_SESSIONS = 2 , ST_NOTES = 3 , ST_NOTES_STRING , ST_BOOKMARKS , ST_BOOKMARKS_STRING , ST_AHI , ST_SESSION_LENGTH , ST_SESSIONS_QTY , ST_DAILY_USAGE, ST_EVENT }; enum OpCode { //DO NOT CHANGE NUMERIC OP CODES because THESE VALUES impact compare operations. // start of fixed codes OP_INVALID , OP_LT , OP_GT , OP_NE , OP_EQ , OP_LE , OP_GE , OP_END_NUMERIC , // end of fixed codes OP_CONTAINS , OP_WILDCARD , OP_NO_PARMS }; Daily* daily; QWidget* parent; QWidget* searchTabWidget; QTabWidget* dailyTabWidget; QVBoxLayout* searchTabLayout; QTableWidget* resultTable; // start Widget QWidget* startWidget; QHBoxLayout* startLayout; QPushButton* startButton; QPushButton* matchButton; QPushButton* clearButton; // Command command Widget QWidget* commandWidget; QHBoxLayout* commandLayout; QPushButton* helpButton; QTextEdit* helpText; QProgressBar* progressBar; // control Widget QWidget* summaryWidget; QHBoxLayout* summaryLayout; // Command Widget QListWidget* commandList; // command Widget QPushButton* commandButton; QComboBox* operationCombo; QPushButton* operationButton; QLabel* selectUnits; QDoubleSpinBox* selectDouble; QSpinBox* selectInteger; QLineEdit* selectString; QPushButton* summaryProgress; QPushButton* summaryFound; QPushButton* summaryMinMax; QIcon* m_icon_selected; QIcon* m_icon_notSelected; QIcon* m_icon_configure; QMap opCodeMap; QString opCodeStr(OpCode); OpCode operationOpCode = OP_INVALID; bool helpMode=false; QString helpString = helpStr(); void createUi(); void populateControl(); QSize setText(QPushButton*,QString); QSize setText(QLabel*,QString); QSize textsize(QFont font ,QString text); void search(QDate date); void find(QDate&); void criteriaChanged(); void endOfPass(); void displayStatistics(); void setResult(int row,int column,QDate date,QString value); void addItem(QDate date, QString value, Qt::Alignment alignment); void setCommandPopupEnabled(bool ); void setOperationPopupEnabled(bool ); void setOperation( ); void hideResults(bool); void hideCommand(bool showcommand=false); void connectUi(bool); QString helpStr(); QString centerLine(QString line); QString formatTime (qint32) ; QString convertRichText2Plain (QString rich); QRegExp searchPatterToRegex (QString wildcard); QListWidgetItem* calculateMaxSize(QString str,int topic); float commandListItemMaxWidth = 0; float commandListItemHeight = 0; EventDataType calculateAhi(Day* day); bool compare(int,int ); bool compare(QString aa , QString bb); bool createUiFinished=false; bool startButtonMode=true; bool commandPopupEnabled=false; SearchTopic searchTopic; int nextTab; int channelId; QDate earliestDate ; QDate latestDate ; QDate nextDate; // int daysTotal; int daysSkipped; int daysProcessed; int daysFound; int passFound; void setoperation(OpCode opCode,ValueMode mode) ; ValueMode valueMode; qint32 selectValue=0; bool minMaxValid; qint32 minInteger; qint32 maxInteger; void updateValues(qint32); QString valueToString(int value, QString empty = ""); qint32 foundValue; QString foundString; double maxDouble; double minDouble; QTextDocument richText; public slots: private slots: void on_startButton_clicked(); void on_clearButton_clicked(); void on_matchButton_clicked(); void on_helpButton_clicked(); void on_commandButton_clicked(); void on_operationButton_clicked(); void on_commandList_activated(QListWidgetItem* item); void on_operationCombo_activated(int index); void on_intValueChanged(int); void on_doubleValueChanged(double); void on_textEdited(QString); void on_activated(GPushButton*); }; class GPushButton : public QPushButton { Q_OBJECT public: GPushButton (int,int,QDate,DailySearchTab* parent); virtual ~GPushButton(); int row() { return _row;}; int column() { return _column;}; QDate date() { return _date;}; void setDate(QDate date) {_date=date;}; private: const DailySearchTab* _parent; const int _row; const int _column; QDate _date; signals: void activated(GPushButton*); public slots: void on_clicked(); }; #endif // SEARCHDAILY_H OSCAR-code-v1.5.1/oscar/docs/000077500000000000000000000000001450332542600155265ustar00rootroot00000000000000OSCAR-code-v1.5.1/oscar/docs/0.0.gif000066400000000000000000000267641450332542600165310ustar00rootroot00000000000000GIF89a, "        !!! "#*!-*$#**+& , .,)/,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.5.1/oscar/docs/GPLv3-en_US000066400000000000000000001045131450332542600173570ustar00rootroot00000000000000 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.5.1/oscar/docs/PRS1 Data Format.odt000066400000000000000000000343561450332542600210410ustar00rootroot00000000000000PKB>^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.5.1/oscar/docs/about.html000066400000000000000000000062141450332542600175310ustar00rootroot00000000000000 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.5.1/oscar/docs/about.xxxx000066400000000000000000000062141450332542600176040ustar00rootroot00000000000000 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.5.1/oscar/docs/channels.xml000066400000000000000000000043701450332542600200470ustar00rootroot00000000000000 OSCAR-code-v1.5.1/oscar/docs/countries.txt000066400000000000000000000034471450332542600203120ustar00rootroot00000000000000Afghanistan 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.5.1/oscar/docs/credits.html000066400000000000000000000070161450332542600200550ustar00rootroot00000000000000 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.5.1/oscar/docs/credits.xxxx000066400000000000000000000070161450332542600201300ustar00rootroot00000000000000 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.5.1/oscar/docs/graphs.xml000066400000000000000000000034171450332542600175410ustar00rootroot00000000000000 OSCAR-code-v1.5.1/oscar/docs/release_notes.html000066400000000000000000000362721450332542600212560ustar00rootroot00000000000000 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.5.1/oscar/docs/release_notes.xxxx000066400000000000000000000321721450332542600213240ustar00rootroot00000000000000 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.5.1/oscar/docs/schema.xml000066400000000000000000000036151450332542600175150ustar00rootroot00000000000000 OSCAR-code-v1.5.1/oscar/docs/script.js000066400000000000000000000042201450332542600173660ustar00rootroot00000000000000/* 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.5.1/oscar/docs/startup_tips.txt000066400000000000000000000002241450332542600210260ustar00rootroot00000000000000You 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.5.1/oscar/docs/tooltips.css000066400000000000000000000031501450332542600201140ustar00rootroot00000000000000 OSCAR-code-v1.5.1/oscar/docs/tz.txt000066400000000000000000000076611450332542600167360ustar00rootroot00000000000000Pacific/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.5.1/oscar/exportcsv.cpp000066400000000000000000000405631450332542600173470ustar00rootroot00000000000000/* 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::asprintf("%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); sess->OpenEvents(); 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::asprintf("%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.5.1/oscar/exportcsv.h000066400000000000000000000027371450332542600170150ustar00rootroot00000000000000/* 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.5.1/oscar/exportcsv.ui000066400000000000000000000170771450332542600172060ustar00rootroot00000000000000 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.5.1/oscar/fonts/000077500000000000000000000000001450332542600157275ustar00rootroot00000000000000OSCAR-code-v1.5.1/oscar/fonts/FreeSans.ttf000066400000000000000000016031401450332542600201610ustar00rootroot00000000000000 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.5.1/oscar/help.cpp000066400000000000000000000237701450332542600162430ustar00rootroot00000000000000/* 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.5.1/oscar/help.h000066400000000000000000000031061450332542600156770ustar00rootroot00000000000000/* 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.5.1/oscar/help.ui000066400000000000000000000136111450332542600160670ustar00rootroot00000000000000 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.5.1/oscar/help/000077500000000000000000000000001450332542600155265ustar00rootroot00000000000000OSCAR-code-v1.5.1/oscar/help/help_en/000077500000000000000000000000001450332542600171405ustar00rootroot00000000000000OSCAR-code-v1.5.1/oscar/help/help_en/OSCAR_Guide_en.qhp000066400000000000000000000105361450332542600223250ustar00rootroot00000000000000 nightowlsoftware.ca.OSCAR_Guide doc
default.css *.html OSCAR-code-v1.5.1/oscar/help/help_en/daily.html000066400000000000000000000004511450332542600211300ustar00rootroot00000000000000 daily

Exploring the Daily View

OSCAR-code-v1.5.1/oscar/help/help_en/default.css000066400000000000000000000000001450332542600212640ustar00rootroot00000000000000OSCAR-code-v1.5.1/oscar/help/help_en/faq.html000066400000000000000000000051361450332542600206020ustar00rootroot00000000000000 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.5.1/oscar/help/help_en/gettingstarted.html000066400000000000000000000004511450332542600230560ustar00rootroot00000000000000 gettingstarted

    Getting Started

    OSCAR-code-v1.5.1/oscar/help/help_en/glossary.html000066400000000000000000002066231450332542600217020ustar00rootroot00000000000000 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.5.1/oscar/help/help_en/import.html000066400000000000000000000004401450332542600213360ustar00rootroot00000000000000 import

    Importing Data

    OSCAR-code-v1.5.1/oscar/help/help_en/index.html000066400000000000000000000026011450332542600211340ustar00rootroot00000000000000 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.5.1/oscar/help/help_en/overview.html000066400000000000000000000004521450332542600216750ustar00rootroot00000000000000 overview

    Exploring the Overview

    OSCAR-code-v1.5.1/oscar/help/help_en/oximetry.html000066400000000000000000000005761450332542600217160ustar00rootroot00000000000000 oximetry

    Using an Oximeter



    OSCAR-code-v1.5.1/oscar/help/help_en/reportingbugs.html000066400000000000000000000005701450332542600227220ustar00rootroot00000000000000 reportingbugs

    Reporting Bugs

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

    OSCAR-code-v1.5.1/oscar/help/help_en/statistics.html000066400000000000000000000004551450332542600222240ustar00rootroot00000000000000 statistics

    Statistics & Trends

    OSCAR-code-v1.5.1/oscar/help/help_en/supported.html000066400000000000000000000056011450332542600220550ustar00rootroot00000000000000 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.5.1/oscar/help/help_en/tipsntricks.html000066400000000000000000000016301450332542600224030ustar00rootroot00000000000000 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.5.1/oscar/help/help_nl/000077500000000000000000000000001450332542600171475ustar00rootroot00000000000000OSCAR-code-v1.5.1/oscar/help/help_nl/OSCAR_Guide_nl.qhp000066400000000000000000000105431450332542600223410ustar00rootroot00000000000000 nightowlsoftware.ca.OSCAR_Guide doc
    default.css *.html OSCAR-code-v1.5.1/oscar/help/help_nl/daily.html000066400000000000000000000004761450332542600211460ustar00rootroot00000000000000 daily

    Het verkennen van de dagelijkse weergave

    OSCAR-code-v1.5.1/oscar/help/help_nl/default.css000066400000000000000000000000001450332542600212730ustar00rootroot00000000000000OSCAR-code-v1.5.1/oscar/help/help_nl/faq.html000066400000000000000000000043771450332542600206170ustar00rootroot00000000000000 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.5.1/oscar/help/help_nl/gettingstarted.html000066400000000000000000000004551450332542600230710ustar00rootroot00000000000000 gettingstarted

    Ermee beginnen

    OSCAR-code-v1.5.1/oscar/help/help_nl/glossary.html000066400000000000000000000404721450332542600217070ustar00rootroot00000000000000 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.5.1/oscar/help/help_nl/import.html000066400000000000000000000004521450332542600213500ustar00rootroot00000000000000 import

    Gegevens importeren

    OSCAR-code-v1.5.1/oscar/help/help_nl/index.html000066400000000000000000000026311450332542600211460ustar00rootroot00000000000000 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.5.1/oscar/help/help_nl/overview.html000066400000000000000000000004601450332542600217030ustar00rootroot00000000000000 overview

    Het overzicht verkennen

    OSCAR-code-v1.5.1/oscar/help/help_nl/oximetry.html000066400000000000000000000004571450332542600217230ustar00rootroot00000000000000 oximetry

    Een oximeter gebruiken

    OSCAR-code-v1.5.1/oscar/help/help_nl/reportingbugs.html000066400000000000000000000005701450332542600227310ustar00rootroot00000000000000 reportingbugs

    Bugs melden


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

    Statistieken en trends

    OSCAR-code-v1.5.1/oscar/help/help_nl/supported.html000066400000000000000000000055721450332542600220730ustar00rootroot00000000000000 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.5.1/oscar/help/help_nl/tipsntricks.html000066400000000000000000000021171450332542600224130ustar00rootroot00000000000000 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.5.1/oscar/help/images/000077500000000000000000000000001450332542600167735ustar00rootroot00000000000000OSCAR-code-v1.5.1/oscar/help/images/aircurve.png000066400000000000000000001352031450332542600213250ustar00rootroot00000000000000PNG  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.5.1/oscar/help/images/airsense10.png000066400000000000000000002207641450332542600214660ustar00rootroot00000000000000PNG  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.5.1/oscar/help/images/cms50f.png000066400000000000000000002132011450332542600205750ustar00rootroot00000000000000PNG  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.5.1/oscar/help/images/dreamstation.png000066400000000000000000001533001450332542600221750ustar00rootroot00000000000000PNG  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.5.1/oscar/help/images/dv64.png000066400000000000000000001710331450332542600202710ustar00rootroot00000000000000PNG  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.5.1/oscar/help/images/fp_icon.png000066400000000000000000002211631450332542600211230ustar00rootroot00000000000000PNG  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.5.1/oscar/help/images/intellipap.png000066400000000000000000002323501450332542600216470ustar00rootroot00000000000000PNG  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.5.1/oscar/help/images/oximeter.png000066400000000000000000001235411450332542600213430ustar00rootroot00000000000000PNG  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.5.1/oscar/help/images/prs1_60s.png000066400000000000000000002044751450332542600210720ustar00rootroot00000000000000PNG  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.5.1/oscar/help/images/prs1_960.png000066400000000000000000001325111450332542600207670ustar00rootroot00000000000000PNG  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.5.1/oscar/help/index.qhcp000066400000000000000000000010621450332542600175110ustar00rootroot00000000000000 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.5.1/oscar/highresolution.cpp000066400000000000000000000100721450332542600203450ustar00rootroot00000000000000/* Hi Resolution Implementation * * Copyright (c) 2023 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. */ #define TEST_MACROS_ENABLEDoff #include #include #include #include #include #include "SleepLib/profiles.h" #include "highresolution.h" namespace HighResolution { const QString HI_RES_FILENAME("hiResolutionMode.txt"); HiResolutionMode hiResolutionNextSessionMode = HRM_INVALID; HiResolutionMode hiResolutionOperaingMode = HRM_INVALID; int readMode( QString filename) { QFile file ( filename ); int mode=0; if (file.open(QFile::ReadOnly)) { QTextStream in(&file); in >> mode; file.close(); } return mode; } void writeMode( QString filename, int data , QString description) { QFile file ( filename ); if (file.open(QFile::WriteOnly|QFile::Text)) { QTextStream out(&file); out << data << " " << description << "\n" ; file.close(); } } HiResolutionMode setHiResolutionMode(HiResolutionMode value) { QString filename = GetAppData() + HI_RES_FILENAME; if (value == HRM_ENABLED ) { writeMode( filename , HRM_ENABLED ,"HiResolutionMode Enabled"); return HRM_ENABLED; } else { writeMode( filename , HRM_DISABLED , "HiResolutionMode Disabled"); } return HRM_DISABLED; } HiResolutionMode getHiResolutionMode() { QString filename = GetAppData() + HI_RES_FILENAME; int hiResMode= readMode( filename ); return (hiResMode == HRM_ENABLED ) ? HRM_ENABLED : HRM_DISABLED ; } // this function is used to control the text name of the high resolution preference checkbox. void checkBox(bool set,QCheckBox* button) { if (set) { hiResolutionNextSessionMode = button->isChecked()? HRM_ENABLED : HRM_DISABLED ; setHiResolutionMode(hiResolutionNextSessionMode); if ( hiResolutionOperaingMode != hiResolutionNextSessionMode ) { QMessageBox::information(nullptr, STR_MessageBox_Information, QObject::tr("High Resolution Mode change will take effect when OSCAR is restarted.")); } return; } QString label; if (hiResolutionNextSessionMode == HRM_ENABLED ) { if ( hiResolutionOperaingMode == hiResolutionNextSessionMode ) { label = QObject::tr("High Resolution Mode is Enabled"); } else { label = QObject::tr("The High Resolution Mode will be Enabled after Oscar is restarted."); } button->setChecked(true); } else { if ( hiResolutionOperaingMode == hiResolutionNextSessionMode ) { label = QObject::tr("High Resolution Mode is Disabled"); } else { label = QObject::tr("High Resolution Mode will be Disabled after Oscar is restarted."); } button->setChecked(false); } button->setText(label); } // These functions are for main.cpp void init() { hiResolutionOperaingMode = getHiResolutionMode(); }; void init(HiResolutionMode mode) { hiResolutionOperaingMode = mode; }; bool isEnabled() { hiResolutionNextSessionMode = hiResolutionOperaingMode ; return hiResolutionOperaingMode >= HRM_ENABLED; }; void display(bool actuallyEnabled) { bool shouldBeEnabled= (hiResolutionOperaingMode >= HRM_ENABLED) ; if (shouldBeEnabled != actuallyEnabled) { DEBUGFC O("RESULT MISMATCH") Q(hiResolutionOperaingMode) Q(shouldBeEnabled) Q(actuallyEnabled); } if (actuallyEnabled) { qDebug() << "High Resolution Mode is Enabled"; } else { qDebug() << "High Resolution Mode is Disabled"; } } } OSCAR-code-v1.5.1/oscar/highresolution.h000066400000000000000000000012001450332542600200030ustar00rootroot00000000000000/* Overview GUI Headers * * Copyright (c) 2023-2023 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 HIGHRESOLUTION_H #define HIGHRESOLUTION_H namespace HighResolution { // used by main.cpp enum HiResolutionMode {HRM_INVALID = 0 , HRM_DISABLED = 1, HRM_ENABLED = 2} ; void init(); void init(HiResolutionMode); bool isEnabled(); void display(bool); // used by preferences void checkBox(bool set,QCheckBox* button); } #endif // HIGHRESOLUTION_H OSCAR-code-v1.5.1/oscar/icons/000077500000000000000000000000001450332542600157115ustar00rootroot00000000000000OSCAR-code-v1.5.1/oscar/icons/OSCAR.icns000066400000000000000000010351611450332542600174450ustar00rootroot00000000000000icns: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.5.1/oscar/icons/README.txt000066400000000000000000000003051450332542600174050ustar00rootroot00000000000000These 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.5.1/oscar/icons/aircurve.png000066400000000000000000001352031450332542600202430ustar00rootroot00000000000000PNG  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.5.1/oscar/icons/airsense10.png000066400000000000000000002207641450332542600204040ustar00rootroot00000000000000PNG  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.5.1/oscar/icons/arrow-end.png000066400000000000000000000060361450332542600203220ustar00rootroot00000000000000PNG  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.5.1/oscar/icons/back.png000066400000000000000000000057061450332542600173270ustar00rootroot00000000000000PNG  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.5.1/oscar/icons/checkmark.png000066400000000000000000000065651450332542600203630ustar00rootroot00000000000000PNG  IHDRddpTPiTXtXML:com.adobe.xmp frown ]SiCCPsRGB IEC61966-2.1(u+DQ?baa1 Ml,fOl<ͨzo$*)JlZUJ)ٲ&69oFd{s]t&gEGþ؜ 5_lsrzDR[nQ5L+'<&<3]nђߒ ߸z.'鲥D#i%~qkI+-,/#Z~㾤^LKlo&(a|3L= "@/ݲL~?EVr5MVX"A~Q.]b߾F_oz} ՛w:8ϔ0&zuA:^6m@뽩ZjACC vس}@Y] UWgފ3 pHYs  >IDATx{TE?N_aIjfIT썅)F#(JVrPS!$ʲ[d$RSfٕ}ٹ{Μ3s6@7"i X@{|<\^$=67㻩H n&3B[sk9($0*4*!f{ǪՉtJ# ($0B# ($0BH=XM@ܟv @RF!aq~bq $ʏ]V,n$e9 Xhqs,j!w%0h*-c-n>ʕgݠY(+5+y!w*p ,{闕GckQȸX2 (Tz2P 36(dWHي bq=;  )eeS~ܡ1Kx^! bq=JqE) ȠQU!2%F*Ž&ߔ)!t=#Ȍ ܮ7C0[!3B茨A̪#bq۔^̟JYG !֡.E4Y)n`+ervSB,?0\)%U<RV4 U{A0;:a:ӕJҐB,2`RR*IC AuU '@oCVV*bqM@RLi(!Ut~z% fRV4+RVE4Rɂ٫UPނ?uiW*GuU1}@xx`Mƌ<fRVŨwYw Fayzs 7⪪rUBrL7\zu7Y\$+C5tőI;jESYi]JtDSZCW"BrR_?%hC>RLI{U-Br# ?+gY ou*IW03Ț$ʨA8D0KaIUCmP0Y"?ku,$s@0죀*;>u"'@Hi &?M\` f6=Uu@LzI,{_0%N݅L/ܩ3qG0;ӂo1]ja`V^tNHl8i*e8: #y{ &ͮ/qG0? >}\s)ޕfmJS.M]7b>\QeZt9!yrT ] QH`D!FQH`D!FF?F8'K XH.+0n8ЦQY]"ɳm+hy#G[p p24a߮1IENDB`OSCAR-code-v1.5.1/oscar/icons/cms50f.png000066400000000000000000002132011450332542600175130ustar00rootroot00000000000000PNG  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.5.1/oscar/icons/cog.png000066400000000000000000000075551450332542600172030ustar00rootroot00000000000000PNG  IHDRddpTOiTXtXML:com.adobe.xmp frown ([iCCPsRGB IEC61966-2.1(uKA$j$FA_I6əq VD_ ւ(jh3BD,;ogYF2JVA6WB\teBVq]Ei-v5kՎךFE S+-v%[>hrASWT?M" X[]_JZ qg3E>KlX.'IF ⧏a~+;jgK"^2)Z Y'dd(W=9_ @av}q`C=y߇77{Zŷl:՘l2$CsڮP9GwY]/4|gK_b] pHYs  7IDATxilUE>ZBM0j.Hq]K"* \Ab?c5 "kJ)T >>{;skxsΙyof3B2eʔ)Sf@0$mꀡ@ЕE@7P(1t@`lT&`a-߀#Sw@OUٿ FQn dB5 mԀoǁ*'8~~BÐ ;=l@y#nϊd}OuDW%6 ]JVF芲cy!uG:v@~qԟiCryr;NA:rr+UY?q[ԛYF xN) ዸk/ƑC>EYM9QgV{I=2=PiovԷd(א-t\whGwP9̤EZs%[G ~w <ԛ5EsPBe 0 q~R.aJdS-WzttXcjzoT"Itlm=`g =.QU>'NӐ6.ulEċ@$դQ'.~T Yr quXJzK+ɣ*]+ S;tj2:A,Bj#Ŵ?q@+p;;ѐn̂ިvaf|z45 `܉N?j3߃ю{:'U;4 Df;';ea4#;™$w:ozI3דbVف|aBت>@F$cy@DC r3NF} |3cY|V`ޥbQ?Aע3]D𴅁.BXH<{?i`B?n23;8W Ӕ^< f6.';c Ӕn`ς$Tn%J.dacCJ?ɳ Z"=k9ϓݞuŊrf))$$.Ꝥidg))}y ӖI^< fKa0E)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.5.1/oscar/icons/daily.png000066400000000000000000002267121450332542600175330ustar00rootroot00000000000000PNG  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.5.1/oscar/icons/dreamstation.png000066400000000000000000001533001450332542600211130ustar00rootroot00000000000000PNG  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.5.1/oscar/icons/dv64.png000066400000000000000000001710331450332542600172070ustar00rootroot00000000000000PNG  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.5.1/oscar/icons/edit-find.png000066400000000000000000002075031450332542600202710ustar00rootroot00000000000000PNG  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.5.1/oscar/icons/empty_box.png000066400000000000000000000043421450332542600204300ustar00rootroot00000000000000PNG  IHDRddpTPiTXtXML:com.adobe.xmp frown rbiCCPsRGB IEC61966-2.1(u+DQ?3hdFɂ4bWa1kjx7&[e(k_VY+Ed˚ؠ"l+ ~n/h&!c \=}<O,IENDB`OSCAR-code-v1.5.1/oscar/icons/exit.png000066400000000000000000000100221450332542600173630ustar00rootroot00000000000000PNG  IHDRddpTOiTXtXML:com.adobe.xmp frown xiCCPsRGB IEC61966-2.1(uKA$j$FA_I6əq VD_ ւ(jh3BD,;ogYF2JVA6WB\teBVq]Ei-v5kՎךFE S+-v%[>hrASWT?M" X[]_JZ qg3E>KlX.'IF ⧏a~+;jgK"^2)Z Y'dd(W=9_ @av}q`C=y߇77{Zŷl:՘l2$CsڮP9GwY]/4|gK_b] pHYs  IDATx[]Us\8(Z!:)5x!jcR zD'IՄi)fL_vjc1L(FT *N9Ι9x.k^0$ֿ>kU2 `)p5P0m),V.gBWd:{pp pp3 Xbyqo77 0 UO ȣQJpN:D/N+A%S_V`C%= v27 O.:;_|VIph+Ce& e—?$`_[}R2#<}Xu_f~u"s{2 6>\W\] np-&/o ]jx##|E Hk ,Hxp7&!rHHNPNo山$q~k,N!5TPaT53#=`k=Ɨ7GFU(Q*OS؏Yf\/x=Q36l T}Ԯɔb7l⑑FU!h&AٺaVUftR Ӊ` p[iaTe dulVyM>֍J+K%u-;=+/Z7G:wmYVڼ٦ci`$}#J:q}#]/۶aFpcR #/9⾱NZc],U҇Z/_J/bQ*[ 2g~j9L!}}Tuq\. vMg0eF3t~qx+V?b vxhV@V@_$fc̸sdT}SAZ3 5WлR>0$#_AEF-A-j9^ )vʻյw]9MNŠpd*ivش)DuR !j1Ivxpuغ5Ȩ}u $Cb _h/8PA7<AV~YUuGЇ f*6iMN™3~].K/v:/<WUŴ|h#< _Л){S<J[W޽p}\u߆,bE^ccp]g _k( Ty ;vRaJaaT f t[Kp"mV}kEnHQE5o{ݿNb,hP>jsA|ߵ+UvA+ ۷2bZuo{Z)ykdY_N,\\H.+ՑЫxK Ugطol e'Ű =u XG Ti+uAQ+ (OD#h%V&@ uSaTO[?}ڵ6uHrCiFJҫodiHq=}Җ-6oNωJ)aT eˤdz7hH,)3Q}2Iȁ{s֠\|T&= )V [S2HL{$Sڍ\6a7MIeg0jNP(ֶAoU2G>q#;} JbQ'PnT>Υn.^> F [cc Ơp=w5P)G2L_'OvcM2Ȝ3z ݧN$scBwnO ~N EJsRh Ay3 ޯO!$s ܠI9؝|]}M)Dk01>?9< F  (4J朦{;1b(a_)p_ 0%V+1V0"_N gE+*SIENDB`OSCAR-code-v1.5.1/oscar/icons/eye.png000066400000000000000000001153001450332542600172010ustar00rootroot00000000000000PNG  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.5.1/oscar/icons/forward.png000066400000000000000000000057741450332542600201000ustar00rootroot00000000000000PNG  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.5.1/oscar/icons/fp_icon.png000066400000000000000000002363051450332542600200450ustar00rootroot00000000000000PNG  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.5.1/oscar/icons/go-home.png000066400000000000000000000051051450332542600177530ustar00rootroot00000000000000PNG  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.5.1/oscar/icons/help.png000066400000000000000000002456431450332542600173650ustar00rootroot00000000000000PNG  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.5.1/oscar/icons/intellipap.png000066400000000000000000002323501450332542600205650ustar00rootroot00000000000000PNG  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.5.1/oscar/icons/logo-lg.png000066400000000000000000000331071450332542600177630ustar00rootroot00000000000000PNG  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.5.1/oscar/icons/logo-lm.png000066400000000000000000000116771450332542600200010ustar00rootroot00000000000000PNG  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.5.1/oscar/icons/logo-md.png000066400000000000000000000106561450332542600177650ustar00rootroot00000000000000PNG  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.5.1/oscar/icons/logo-sm.png000066400000000000000000000016221450332542600177750ustar00rootroot00000000000000PNG  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.5.1/oscar/icons/mask.png000066400000000000000000001167651450332542600173720ustar00rootroot00000000000000PNG  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.5.1/oscar/icons/overview.png000066400000000000000000001457531450332542600203040ustar00rootroot00000000000000PNG  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֟B frown BiCCPsRGB IEC61966-2.1(uKA$j$FA_I6əq VD_ ւ(jh3BD,;ogYF2JVA6WB\teBVq]Ei-v5kՎךFE S+-v%[>hrASWT?M" X[]_JZ qg3E>KlX.'IF ⧏a~+;jgK"^2)Z Y'dd(W=9_ @av}q`C=y߇77{Zŷl:՘l2$CsڮP9GwY]/4|gK_b] pHYs  IDATx?nQ ! DAM:9g'ABt&)D+?cg~eiϞ]oz=_ >`:yb%w/WoO'mأ4ov>ȶ)LAd "SD 2)LAd "SD 2)LAd "SD 2)LAd "SD 2)9;Ҏ<=2=[e9d8^19Qt y_usϙ~D 2)LAd "SD 2)LAdTgPb|΀ÙgS`ZAk2=`.u Bzd=D 2)LAd "SD 2)LAd "SD 2)LAd "SD 2)LAd "SD 2;d6|d5 R,Y o5Xil$v?c2A^IENDB`OSCAR-code-v1.5.1/oscar/icons/prds2.png000066400000000000000000001211211450332542600174470ustar00rootroot00000000000000PNG  IHDR\rf cHRMms{n3'IIDATxgeIv~'Bg.e+-hI ;]93qw(f .gI0Q`.H P@5FWUJ)"C>y~}*"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.5.1/oscar/icons/preferences.png000066400000000000000000000073711450332542600207300ustar00rootroot00000000000000PNG  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.5.1/oscar/icons/prs1.png000066400000000000000000001710151450332542600173110ustar00rootroot00000000000000PNG  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.5.1/oscar/icons/prs1_60s.png000066400000000000000000002044751450332542600200100ustar00rootroot00000000000000PNG  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.5.1/oscar/icons/prs1_960.png000066400000000000000000001325111450332542600177050ustar00rootroot00000000000000PNG  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.5.1/oscar/icons/question_mark.png000066400000000000000000000061141450332542600213020ustar00rootroot00000000000000PNG  IHDRddpTOiTXtXML:com.adobe.xmp frown ,8iCCPsRGB IEC61966-2.1(uKA$j$FA_I6əq VD_ ւ(jh3BD,;ogYF2JVA6WB\teBVq]Ei-v5kՎךFE S+-v%[>hrASWT?M" X[]_JZ qg3E>KlX.'IF ⧏a~+;jgK"^2)Z Y'dd(W=9_ @av}q`C=y߇77{Zŷl:՘l2$CsڮP9GwY]/4|gK_b] pHYs  IDATx[]cLP6ҩb % ixH!N]c` [I ۑⅰz`yaDd'A q^NK=-ƀ /T8в)C|;}\ePL 0]8^%~BZq' 3IUe (ma9M lCS? +<Aˀq4\4ױ < W"es!(:7a!mtI󐽐r|8K1+ c^mfj0%q7N6{J6ԟ^IG8P}i ]4D{قƍ=6k䰏 f]c/W ]ht6q=z ;g MI7ۼ6qy@C |"!އ}L_h6+*E_"ɂTB3\+ hqn_n7qFxPHn|[5qqHsQϴg-d'U[$1\pAI $1\pAI $1\pAI $1\pAI $1.nIENDB`OSCAR-code-v1.5.1/oscar/icons/refresh.png000066400000000000000000000016501450332542600200570ustar00rootroot00000000000000PNG  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.5.1/oscar/icons/rename.png000066400000000000000000000070771450332542600177010ustar00rootroot00000000000000PNG  IHDRddpTOiTXtXML:com.adobe.xmp frown IdiCCPsRGB IEC61966-2.1(uKA$j$FA_I6əq VD_ ւ(jh3BD,;ogYF2JVA6WB\teBVq]Ei-v5kՎךFE S+-v%[>hrASWT?M" X[]_JZ qg3E>KlX.'IF ⧏a~+;jgK"^2)Z Y'dd(W=9_ @av}q`C=y߇77{Zŷl:՘l2$CsڮP9GwY]/4|gK_b] pHYs   IDATxiE,ۀ'`Ԩ#~0h0#1&j;3T<>hE\-zA`7$hqiNxhb@?D$fp6%hIl9 w9C}b{a~A~O,RV}{$Ԕ}$'cӊzKR=܂o.W hM ݊ק~84XVzg;x^YsHY*+鸟-E;Xqܫcge;*Ǎ؊Oԟ #}1/Dm9~ؠ~{;ɐ+3-|wv3$~sGnPN}Hbz:u"@?^^7s?{"AdE2Ț,B{(pvLf+KDCqLt:O %Dd0ڕp gR8 ǭY6>ۥ<[p/Hk<~܋qt=t3y%ٮpv <3NWi:}& 5\}!-}B6嘯XT[R1 sTI AW>xBtWfX(|Tf̝PY8h N1 N3uBXhlU IM/щtenTxFSϨGLǴ4K= ƚ9kA\Sj^VDYS_L&$B#p/mH2ϨVHco4 =td#.="A4Qj1N`.p~ &jJ4(^.$P<)R~re? ߜ Ys^$u(bE3SFrj2F_8+ $rOYF(2u8_\RDJ#nF4ĽClȩSx>{ڽxTc02G!˲C }nJAS#ǝҁ6TZR(PVR!sd3#|& <^C|~t67Z}S K"ɧk RYӐZ/MA4wvd "'tkAZO 3V#KiV)mFR6;A!=$rr%HOB/FL@.'5wE9\gُlM2+Px W2+H> / d/n3 2X؏dVF%A(qمǞIENDB`OSCAR-code-v1.5.1/oscar/icons/restore.png000066400000000000000000000057471450332542600201170ustar00rootroot00000000000000PNG  IHDRddpTOiTXtXML:com.adobe.xmp frown 9iCCPsRGB IEC61966-2.1(uKA$j$FA_I6əq VD_ ւ(jh3BD,;ogYF2JVA6WB\teBVq]Ei-v5kՎךFE S+-v%[>hrASWT?M" X[]_JZ qg3E>KlX.'IF ⧏a~+;jgK"^2)Z Y'dd(W=9_ @av}q`C=y߇77{Zŷl:՘l2$CsڮP9GwY]/4|gK_b] pHYs  IDATxOT/5ζRQRHiiEP*I zY⩠H/?"^*ZA-(zP))(nK;tL&d/ ۗ/,@Qr-;eπx@&k9,ϖ3 .>+/c8JH4.(c'22Jy)cg>2b/1Z'XI=2Hi/v=i32H9\hId\.%5gđ@U|A1R^)MSJik^hH U411pĔ>c *BdHyĶ-RX)xnA8N;{lXS&"eP>n>׀⻤<|bFF? VHDrD}:$Z.v?b h ˉl;VT1s"$>m,ݿnkIpxMH)`7!X7) " ‰8̤8!Q~c)G2tlʜ66t<"O$ " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "ݾ$U z>Ԕ9AQGQEQe|v Ř11^gi:=$ݻ>qwh-.ܐ%L9yӭl{,UZ߿Z2OF!E\*].wSY,e<ܙ5x(J}*uuHtݮIENDB`OSCAR-code-v1.5.1/oscar/icons/resvent.png000066400000000000000000003536711450332542600201240ustar00rootroot00000000000000PNG  IHDRX(ozTXtRaw profile type exifxڵi9r:vL79{ƌ#_7뱖Ld/rOf.f{qϟ>ߟb>~y7c1}^y ?~cV@/__}V@{?/|<~?);eߍyR';O. ~S!8:YR߾问no)[ǿ}ޅ ?9Oh|V[fbJ~VM)tjs,C1PO~V!Rra{\a*ƸbzOq@/ 7NF?k{NZ;m˽gށ?TKZ>Qfmt$oQ+UY#)b&Hщ7? m@8ua1!R 5c B ,='"cN:5i5ӎ!3:jAr.eCK)bQS͵Z[)ZvZ۰dيUӬ'H[spocƙf:۴XgUV]mkӆ?vm{pɧzڱϸ@&w-v;~vֿA·kuJol?Ƴ8DEojꙷsT3ߡT",'rÏ޹:ksNo׵-Zc)TQ}bxψ_ݿzO:Ros& ^OEjKj>N"fEB1{SFjJkbȻt(mtL(e8O.R?xtB;&M炡go0#Y=a]p wQ[h8Zc)=\:Ɋfͷé4?KK/ǯ} N4VT,j鄋s"ܰX;en&s Rs bm,Cohf8Lm_%< Y5ú;mmǽCc-*,jPp:y Vf{oOpe1Yl?Ǝ$PZs,Kf։w,.+qPZrc h!K:] |JlJ[ 43W{yvU c~OO6NC I+].uK:vi D~K].nOLH;$pwIמ>у<"(t@axRf08)}zc|jcYS Jcu%j>q \ڙJ<+keLcbkb;̓40sv⼆@0Y2\u]C~w#l*|A.Ѽo7yMj>I;Tm~ڴ9KMPP]kEhӘP6^y[yJe7Y+7Bf0.paZLhIQ*x BsM/G։2u#g,-߆. V$c*XB] "NsP UEwПp\@ycQXz +@ FAv '_A$6p]J@s~"Ne75OaOdŁtgtcsz#M|4croZZd7FDÈUeW)1 >GkhQ2fеI. 2mqwb?3 WMv {ܞP*~`ڂ t#W~0K 蚗Z[|[H N?*t&W//F&Dpi[kĈT&y@B4 \+z(|:ϤfyˡxoVWdmN=@,ZsrEy:T{eqFrTn+ 1_jqQ+&rEkR`8{/E@?pdbuJ(-`v,S0X4&ALbh F)OQWڙZdL7<ܑF2^op6T6o1RyR9&Gŕ4 i]F|44 \#;~2B;N!%>b1k'(9 !V܁8ջ# 2!` ,/3Imo v8:&(0G MEDti,pJڮJʴ]9=l ֚ e;\'4)- x .IkP""ŭRE΢tK;J)$ ]x:/C 3+̿12u֎L7ZPCe! :mˎ1cA:0 Y88i4O;y0p蟃fas `:0/8+[ zRV !4Kʆ[DȦ󪀗#]IQ#YrktWqϯ ++Ϣd A(Kq = s_R2;f/LPPRY=E}KR܌FyL;T8tgc dP{FNf`wWB1`3k hEBB$+˚~(%Uс@rV\Z7%O7}XxDʝx ![Y[6|1 Q[Ym #;fhbĖF4T3/O^HAO*0:T\m{8]l*Dm J©-Q+02Tg c0%E\Y#,Uȭ1GLbXBi |WIG""KضpVmXșClC7tb cmVS4%G 69] };Vww4bsF) uxJO M5r?Hw LY} ]:dOܯxXU5B6~t A.aA("΁Xca0dǦ!#ؠ"QA `|'5 ,3|wÈ1Da;25ùd$HĘṕ6&aUjȤC` 6 .Y@g&NaTތt/e(U{ǯ"PB/?+DyaX*m_Twkpz0g܅΋[@\`,,Ct4`H53l?amlD#N}`%F>{-Ÿj˩)%ϾmL*\۬7ˡׇkJ{ wי?a+1- (UH\"bOG[#I$uI%x)_?J- l+wTQ%VEif0mBV"ݟam " ]e@\=^~Po9vE />GyɁUU= ocds rӆdxanvYDNcPjq 3bhc1L_]?j`1nYA-.6O~147.g"pS Q|FAp1̅0kZ%DR[cp L2 *E -Sxb.Le * r${Eb-K'eDK'qp1 mc:1_Hp֬ nDbglkw P0D 6f S_ [cȲnDx*"VL+ wld6q1nnE&#贏l -S[IZcօT2<0^F $Z (ݲzPC5~[LI.SS#$1j¤-MCs4[ОPi.;(dGBLmAP?%d9 pgiEN0󐊵 ^8ui{_l^R93w!슇vw'2Qr=7|yR$c]Dϑ=Էu xT˪i/J@I/Ni\4{ T[d!L-avxv ?ͦ!o:i<;XR _<:yEwuA>f؎<(!"hAm;G3R=0}DG#žЪNh1dp. ܤ$êޣ z'd9(D   -gYdC'ZyX滟7ݞ/ |;cK")2C Ku痉"A&lRbA*DK#WDU)Ԧm^Lp2yw!+=#_^> " ĺ(&ͦJ?H"Uu*A @TI6Rv-2=+12 YZ+S@H}\礣[{sx$䡍l&6z((Eah?+9)U6l8,(:} J;r(hf*sFVVÁX_Qˎp7b)qE(7N! ڷ/٣[dX24o3C6/9U)_m*c2!0}}OUL:)yX;.Zq\x޷u"0Oy&4".#΢e_s]DavWZ?u`$~{b n}QʁDinb 5^>rS_qlVg4b=B*\zIN DK~^.SE8kjDז<[~~U_ DߞO߭o6//5iCCPICC profilex};H@ƿT ꐡbA|U(BP+`}A$Qp-8X:8*W'E)IE^V4+4hmq!]¯a<0")1'I{zcYjR@<MAXeX+W= #9}e4" B*J(FV )ڏ]H.\%(X@$^R$t85>v |Rf>Iг \\4y ɔ\)H3,{ ty5q pp ({ݝ{rT]p iTXtXML:com.adobe.xmp ȻbKGD pHYs  tIME8 IDATx\y=w޹4$%KdjɱC"+_@`טA10xg< $M +pȒ坒-*}9íԩ۷{|f7nݺ9=yaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaKC0 0gهݳBwl@H !(FFab$,E\Cd2dY80 PP*P>, 6_ X`1 0̵o_RJd#C% kfsQlڴ7Ņ"p:YsyDeCJ@I eYBP$"(h T*E+s(TPi"ˏYc0 0W( X P*C2o+(F6’!@J!e@fG.GE I(X,J DQ!,X;]PV B!X BHDPX1=ځ0 s-%BS8-AHr];=CdLbY'QB 0PJiG+e-_ V@GGJ ҋASA vvk,, ZBC;qzq?} .`A!}v Ty]8cn/<`0 +ӆВMRinRA2_$qpD$ml溓gC+iRf@[(2;Q@d,l5FffZR9,--ĉSXZZY͖e! aV`| @Gh6h4jh4<f z] Ff knUrR4#45AS Q+wyI¶m8 n2u bl8}Nus 6 ,˂8p~+/~gb}_igP$&S$uZQx$SLQm]ċ*˥-tU&nR8}#"a)%|7"wVG{|xׄhVbXHy^wK`W0Ga E-B>li! C}T+(ޜo'Ώm۝4Rma[Bl61lݲwu;nƍeBP7pfm|5 ,aw8MQ2;)hu݂%Ed\R,CRJB*͡29 ꮵy:nK kV$% @Я:|GDZPI^? `XF*4I[A+傀hXS VN1XA`HA v+Jv8m.=nMRѲcoK- d\VbرcCp)_(Xdqk8xE ,aT(T# ڣT;"8}I2;:)eH$D$lގ@)wa(`۶^8SKAJg%;RE$>z1x~kFB 7fwV?ZxfXeTR+a@ ACܚOJ !cUtHRΫ+׹ Qm|J?mx~g 2Llڂ;~!yǭܴC Re\㝓{q>ڂ0kMRJtj'BIvQԞoNw7;+MJ0ߕdHd۶6S$ B(nQZ`EQ  x ]YfPJlbnnkyʅnYnR;thxS%;dF)1'7:%sIu )8NWQt,FNmeűHzZ.sXFP0v<˭߫VjUτk6fS $r⸣` #LXn"J"3mJdO]RQ} N'J4AmR`V+ BJ/*hWx7l"7PFlP/\}#u-X`1 sĒjihD͎Zv,4Bb&2-ɡ-2SRQG̒)7Cҹ%\켤JE4 > {j5H г|GXgvv6ޮQ.E*fJrc"Ţ~#qhƳZ (M1Tt@d, Z(TPţlH-ҔK`u[v,˂B9\l~=E^҅[Cmպ#Ž̹3xZuLal 볨W붭e0נ@ҁyl6XJ %܄:84ѕ\.sN$sy)ТVE#v0D^A"E3: RIvrJ)^'(H%@U]Ec$ٍǒܷ0Tˆ,T+v}@=^gEor.>O݃åT1k˯ t,k~'} u!j%!t9I+Id*E;JX Vuh̩5|DazVXWlƶuh~1; ,Bbj !ofdGCFf-. 4CI};-5䌻mFOXN G4KLj5M)>JX05  =X,Ɖ'[)H8%! 0Hsy涓;e۶yM$a<'3ߑAFgBVu:;u(UUr/mAvkc(Q,6{6Q{])P>ܗ,̼Dž~Guz~T,ˆ,a\Dzd0;6 !ek`uX72us \h")Y1Iѝ4,c&)OD2s9Mm}kdPO&:;sx--̀QoQ:J!t6r#giiIku=feETUCfQ,4['t?rr:0tn`/]kCr Kea; r9N?o܁7m.>߯+$}_ 0N<)$OŠ Q0QQL(:FNb9JDi\kS(!ՍHLZ`k]ǻViGbu[Xˆ-G8#3%w݊+0AK\ +\:D T* #H^177Gc$0\ 6݈b/<7a7J)_0cuK*\9_)6u#);%L'tmS%[vp@ha!ufCZjaDIfSl3 ޒ`߾Ysfnr(R4f7yҍ10-][/m84KeZa|` weKeEKPG$d@Vkـ [02:2ER9I]RseKq(J8s yfT׍"\_ξDQryexa.+7M*ysP? e..̏ !>^{5ed24M=IYZpqR}k^ĕ.E K)ԪU΢T*Q=@wXUf`66u;Dg"\.ljhӧO.^y۶{EX`1 n$&`_25h,RJd2dnLJ^ ?jyZٶ} ZE֥Vׇ쁁;if{ZjaŠUqVkb͂K}4 =OJAd2=<ڳ℣~F"NY0|%`) RjK ]f0G%%g$KPzЭ21k.Мf;l,^|E[P4 RLW7㯸SZPpu{}z.PJAH l caaBA{\d A>/WhReYB 4ai6`1}y23 !`[fOB!5BZҭכ(k Q+[h4??_1N:*NÇ%,_p5-c?v/\l+}^X^IF@X6R,%CXP*Wir`1= `<D߱VVj0 8+&ġColjn0ZwE}Z H֜]ktV T)Y/0Z ߈A)٬^g;#cǎiK2wˀa+jr٥INmstgh|oTIHjQ8Y]!mh|*J4J͐AQٳx뭷{E°b0,\utR݆? @XԁA(uNr0>CY1i'@BPyh6|>nS:u @Υn0Y]B2ݏ%l3НjɖJ5?((v*XB@)g-# rJ fu262,Du9NS3(S}\mb2LSP((Κ:UiōOɎG>gݗ\m(Uلd.R(,8}t@ac\bjxp|)f7&8nmU,(l;v>5㢮xپwDP@XD^eV`k Bq' ,!B28P^ eWvT<}SZuu"DRA^1W<o(0̅N\b!ɜlCj``@Y d, t r`YqUqضb[GA6śo#G`ǎx'a6mۆ͛7^#Gpma۶mF.ӡ@;Zbnnk2Н5Bc^6Gg(z VVFu:,X‰'o6S ,j&zlxZ,jc#Na{@gq l7Sɐ:M&n;wĎ;p9=z;wC=cǎa߾}صk~i8x >O'@6+g}۷o`~~FCCC뮻p?~z+~mT*:fp!et_\\y,+ 客~˴BG!RoFaOƺuO|7n[o!",BlplMSm[Iv]'1ʹף3.e;%{)f޺人9]qH3%ߕrV:HtbblXXX={cff_3<|>~ccc  2sssd2ذa&''{!bSO=|>m۶4+4d8R X,bzzzsi]+)3{3{5i|ZdݑB?}jp'hh0 CA\,h MK8WeZbrfS_i^/۬JoPiZ6z5<PBQa۶1;;G(J# LLLhq?~KKKg?þ}9ׯ88|0<o>wCT* >Ylfm[r璴+=$ Ξ=F4,iE R"E^Sa%,G{/E*ě$4, eBDBzfN,=:D4Kd%*R%<'Lr]-6j_'[|""={jBbjj jf尴رcx |رcfgg/xqĉxwqwlɓxubQyTUX Z[lOJ.ƍu҉'p|W4M8pAٳZ ɓ'QTkX,BJÇ+jAqXkqeUS@R I)aAt}^)TCo 4EOQnJ)J)*aYN, CCC:uݎ@wfm9M҉]ɩŹiCjR! f6g%*M'W!Qb 1EɆ%dVy 6l@6E&8( z2N<۷@.wߍG, =XGά{" Ce>|Qȑ#D^ǿ# C_'N~3wrzӧO. 뺨T*xch4f/{EGn^RӧQ,Y`1JJdL@NJ@t >#I{RLtx=剖̆K>m dA P.QVuȢ20S->(89###j(;{]/r^ݰa2 rRT*;* ^z%l޼ׯ4^z%>}G<2fffHT+1GZ.@輄4۶QoWOb22k&#uuF)`)պ׍3u,bzzAvر HpȽ-."|-n⿺7؀BǕ %}6mtc:7f,k2'cLa}حn+LI8MK'c:rvN?v&$Մ?&4书FC eG3zO!QLin+l6Ӷcs]صknlܸڑj6T*:¹swqIe6KI痾3<΢T*! k3  EQ+͂ߞl7l;0B!7l] @o-:+*-F>=;I!S3'g v" 088|>sTUJ%R"cѹD.egW 喒Jsq@=Б y>6tnNBIt>҄t^Jm ]4.s5O|pDɤfrVՈ IDAT'gr:ٶ;v`ppP K Rc.J\ fO?4>永n Z /K{dfS~,;ZQlO64u<,ԢXe $It?7;ދ)_zb02y͡(Z1-b֭w^GTf Dkn rG$gUQCm'3y|H̜Ҿ|ɿqdKʨ16gsµmBiA?flckd1gFbV+H%}6?88 rXi b500 ^jz),6nܨxW 788Qj5q~ؼy3y,--a||| ϩ888Zd;k@K%̒b|=Z%gVvJP_t?#Z/,Zf (ۜm7ƂĮ]p۰n: Ԏ _jsss8z(?y]'cbbv݂[oLNn=܇_|{c^k%CZB^*^ G+E됪#vyiڲrP]_ԫNJ:~vF^gQNdgZ 8q R`ii c#mSSSعs'ۧgDruᓠ)ySl 9tgَt-9݆x;Ea;VʬXў(?g2q0n& p~8p>ԩSXZZ|\.#oƓO>_6l pA~( h6}j1)l6 ݮfVN-=ϠŖ-[p-`ǎؾ&_SSSD67QVQ,WQapp ف8~&(sկ~x7)v%q?Ƕm''xꩧuV|͛iFɸ4*~EZ1j s BVa~~^;.AuJB_DICvL#ԘC3 @25=]V"q`ϙ3ũuPnAiŻ݆hm3f!e:VCVH9bn41P}_*ꫯ'?Eعs'.9rַ{?8: 000144c|Md2TU3u?99?(lق dY=VkHI>/4 0H~ u}g/ؑqHW(s?(¾}p!?׿u|C_22 SOv*USOS ytJ<$bWo~ʺU7;nsηN#PX,TU1.0񎡉bZQ!0997w;vJQA}|>k \.Tl%I\8ELzrF׵!e<Z&inyҮGs@'APzHg{mx'd}cxtº]f; [[oŞ={3}݇F?S?G\SO=o}[M{8;u`\ 7܀g19yj_)PV;tP,y|=۶uVbǕJEl6|/2/ qp 7`rrѣ}ό6<]?fLMM&UPp2񃗌"Lm߉<۾%j }[)hqhuJ#Cغy3FFFZ@6y77O~'ސy!laJӛ+l6xqa'?/p2Y qYgu5}ZO8|0lقꇂÇ#T((οY09c{52ZTAV\ܾupbǎj87; 'H?Q٦(3W7l؈]n(lopmD;#;O'aKDLɑ ӽ]&;/} BYϳP*s8!{Z9הF0Z9{ d#q%ٵri+ . Q7TzRƎLGFte[v!c\6nܨkٶ0J:[nv/K|K_!o~e0<< )%ưeavvVBs=@S֭[u{gϞ?4;+1PVfN"rhM>{gbΫѵjgh˒#*٦xı:@;}LA AqT3 XKdY)Z(nkΰ}8guTK":u {QJntp=}_]]v$IGRA_.}~d1Aە ipΥ.x?l?.qdŋq|kנix}I|"d//NpK.6DsssXXX]w݅U={~X .]hY^z%BpXD"!'i{Nt3OKRPB\ds2jee3>iJ+C㮡H28t`&(%u ƀ5hv:#9?3\Νߣዿxg/HA9wѠJrOI CpPaTIXKRFswHV0P0Fb81nb1J L P0&EmPK%.c,4sJ(8ed2\v kaںԮ躎RT:zÌk=9G`m@ń!ĥ/_FVC*7O??<4 ?q|#SO=z}XZZ@Zo}k8qя~T<(J3ϠR( si:sq}>$j`/"wJ,yϮ"g>idK/ĉ8w ѣGdk!ѣ//} O<~#X,o|ccUW"c߾}r><85?#pa\zfOիCbh4OS;&??.Niŝ~Rfb(?tWWX[[q&)b`Sy=uBBw0t"`[anӧO4XE:񹮋˗/T*!LJ^z ӧO'e>}J(Nu\xַ駟WU,qybc*~ª+_ ~ab1,,,^ X ]nj颟UܖaC&dʂPԎ!PQ0(!(˞`t(ˠ[p-ɓH&h5ڛVO7S!Dj__x[oqEK׾}Cq9T*pGlj'PՐpwooPp%|Ȳt:c C8ow܁G|2ww ΝåK~,..Jq=zN‹/{g=܃9\rO=4%DU,+l ѣx'pi|i`_n0d!>?;_җSO8d}e?T*zrW1DC*{1? !'jP'P&U38y$m1%n9QL .xPB"HH+?Lp,PRڨ">ibaaZ-\p.]BE:FTB6ǺnZjd//fPL&:qbo+kW,۶O{sv@%9uA 3B@+jp"ӮnF3Ls+a"`8l {zcotJx/x7j:+u6a hcp݉N,<#?1Ν;}^c}}|_~sss#q4Mi-T*5T:ず ~4 .T\8*a%equTѺ{~ݷoQ*rmNs8D2834*SQ |>/t9uyTU$݈5Qt @8Ο;?_,napk4cW:%<<$NQ귔~.ȁ=~N@ 1Z|}4kFƔo̽}\7KHZ&5 r9:u } .]3g;3O0oIU+ڵkd2|qVh4d&biZC mۖ_^OB&0tP*$x4 CW}TݍF12ި22dߗ*cێjjYZZzz*|I>}mCYRqƋ!mM#xmXIa K=M IDAT*̌\V'3g6͡72>1Zq-N<~_,,9%jW,Z`i)l"*dƗIL>:&e&$*M(%[Xo` "DC&B.[26664 i_0nU%B?, /2^?뺘ŁP,A)E^ѣGQVqiu9o_nOK^mh6T*X]MnDbPsv5M,3kkkzǵkj!n'|?Op1|sÉ'p^V ?ӧO㳟LPu$OOa&N>I#2B,&jj:ZglZ+*t:XYYeD~Ha)<(Zb~~^TM`YTI5 Ksk6[N*fTv|R M@U*BX,fffP.Nz#< N:][`(Jx! a5_es lYS wh,I_pA2‚%i ,@y,fJoyjmaHjEp#7328mnSN4|zfle%4ڮAdt{gyyWz u 3_ѳ,{]y !nӆal:Rpm?>u@Xc jMpI\p?! c\~|O?/_1 :1="BbU0#&|1b1$IYjˁ9KGi^1g{O\3^ONZ x>Ӥ`$|MmyNGY4'puW zĥ>,T{GR( a*]2$D"@S >ݮN‘\}8qQ,133#;,:oI94+٬҈ t:=aJ:`\Nj,x'08D"WC?? ڵk0書rƿrXI@0eBC2؎F"=i"x \z333;p!d2acc 8x ߏB_|//Y+ w ՜BY6Ξ=;gR't?j~ ;hl?.a yUnf8O5's4UH- HCRKjqQ8W Bɶ^/2lIs2um?"*Μ9:\A* cTtk#϶m/JI,x5'?k׆Indqi<˱,nxo|=̠nT*EJ#ʁmPxG 2C~X.O<2C7q4CaJ>=u 'ƕ)eA&FW Llav~g3vP*[A%:뉀KR&wnQV@R H3`;;fkT݊JKnJCP X5Y,u1Qjy-Ѫ %Xn۷OJEi#oj7b;IG5W\t͊`P;$l;@YXE[EL"un!jx ]RK08#;^p2t`-!2 b1/Y07|]K4 YOU n&~O|_5ReMXAt8Փ= Ont`fd2k0곅DJL@0 T*T*%/м漕cޒ<TæwBeq4j9vàcҒ\X􌊧ğ*ۭN tUQ`YD79&>wS*pE@CZEדm".]"jBŀUkT w' .hTd:9dV lN`'@*0d+>nu-HUN)(L&H$^"Q6!cff( ^$666QEE݆m{Hn^KD"!3:wݔ{P i+Re*ih8{,*PWHx+"sW^y?rzi[ܱ.JQw&dD܀NayX 48Var;ذ&0QtU&9rx<.*666N<}I}X00 nFhP>}3ɎB%UpR N^BȬ`ƕvzU4 F3LӔZ.ѭ*@Uk9쮚r]ݖ:,̦9jWxD"\.E ͂1h4س8'jӗ}!:k}K=Qל'Inmm\rNg(o%8/7M<8tbquSf&G Ħ/dufƭ *hQ݋l/ ,Ku2$>1ic^GR2OlH$|{Av3؁\;8V;BU6مi鏷g´NB' %L-8bsQpsv'Dht}~ҹG1-ɠX,JzQ l[hҋv_8lw$U]f/0g i+`8K("y RcdAxnWzW:̔lQvfaZ666xgYLPH$H&tT-g* x✃qg:\ U@DxQs-J!r^| q A>]mxZc{gMb; CaݧؿKך( [^?1F(='c` ՛m4j 8j:{M¢>wdTDP )˄ hdWWʬR"Cu:t333X__G*BT^{L,&~ݎw BHL p\.^7X`h8xF_۷z/"Ν;';ה .>QN< (bPPVq`T&}J+<9 )B!b.t2ԠH5u]ە̛`]a$7 FuP,`2!XL~!m}… (pd舉ydȘQ旓۶;Op;IxEuVNzb1PJaY/#05(H$P.epeYX]]EZE>@077L&x<.[C {Σw$DNGzҩ \.c߾}x7fqYu]X__Ǚ3g͛vx<.3 nzxgPT@)ťKh4侄Ct(W}\׎xC* Ž/D`]yH6x"ΏeYқˍtKَʙlGdJ$R/fZūGZ¶,۶O Iq\9+r%nIŨ$f[ Y.Wb::@7‘4ZenWH**AgޗL&duB ݴԻ\.9vG^GVC>Ǚ3gd \.c~~.]B\QX~;u]mr2`P(^#!Jaccc(^t -hs4b`IH 6ìMדEapa6c.v-MP]JQ蚆摈$oqR,(dV3gΠjI(D4R7WEп}MQ`-yQ\WV(vvN{Ոvrp3z dR>VINqA9h 堡ccwgV8h6C`Dh`6>²,9rDvj5躎GP(ȑ#Q(p]wAum\v bu/ŋ T!Z9dGnWWGV_„1'w}` h4tdsjpS׊ x\yd2Y7hqy{}(qDϳVN>jۣ>Ítj*k|D nRN9ڀ^aK-ؙLtZz`8֥%R)ɤ =cqz&M`9rbaaaA.ZP(~0;;+[.666pyXgϢV!͢R… d2x$%_in/188;6C~7n >ܷVp}/0Z-[w+u]ZNF!ȝH&fC98vvmm ׯ_Kw:gU-4vp)E~ Lc)\7)JAVf9w}7y˿ fggq8pÃ>ZGst:r\L&L&bvv(J  *% d@ &YoK 'Nw&Acbp&@}Յe{BvnaccC@A߶L4Rqk/$;rhqJrLj\@hZ|2]umD0!|$bghF\?nan̰:. O[%4j'ape3]:F.C"@׃8k~~Rd2e]i *`' r,9,BE6Z$B|>(.^G}gŋьd077B:c}} u?*~ P ,5Fx s&[p|C; ݿ(뺀/Գh4Hq!{C#4t?>؊p!nc`hҥKvSt7|A8 |mHLbð2(vAI%j'8*c4Bw!w:FXur9)v{l ` Gw#HQfRp8նb|aIw“O>oXWbaaL/Ν;\.SNRҥKX]]:* ^*'n+off CRt:d2F1R2aՖklyN !8[NnVmz=R)/%<՚ 4A94q|}*c&yկZ IDAT)a"28zd˒a̴jt:؜x<.]lTYxX,v=dL:f ([#K"T_#D؍Q٤LBp7"HY*0pF䨙m CՑBfC C1DtLFvkaAQ£m+q˲v%BJC㮋iŨ .3DG23N.,n(HȎt:;N. X _IqF!ېH{A\iSrX=6Z:Xׅ`$ XF \BûGf6Z岼7mƵkװGix7l  bL z%j Gw՘X5g,.#Kco20Lh1q077|>+.[`;$+:DP 00\F VN6! P#ao|&f+ʴci\}y4ô v4 3 ،ڿQ*Šu95j]L&+7u]hB}auu\Nj;krގCQ8 Nh&\!K &P1z FlNNSyaZ`o<J؉%:?ԠG?'FDοI) 塍OaŀT,`*>r9#iHW_䱄3}SƏqi+žG,YK(vNGIJ%+ԃ.o4nQɣJtR 6⦘\|>/=VX,Rl6+ ,@oR8 v,::C .8eIPNN=PD(8qkmi\v.;e!9o(04.w[1Pܫ& 6}+q}t]RqP9j1Mc{1g*:(k1iw0[D k'}u .wXM^M1D}ȹ66cͰ j Di¬T"C[]Rs%E\~}ȧ0,Rn+1?2+"QĠ,REz, Re9 g?n|3Q>_7bo솚A%tXAe/W𳨯u]a !LʎZ&\.\.ܹsH2^ip@ kx2 RqA64{#x~W ն=evj(B3+"[d `ltѸBto~6Xj  ͛V93} -0ߊW}Oj5iY{s`\Aq<]m̓kɮ:Rxf8ʑ610F= bL;4#Ha3L2 F/'?d3mdlw~,AcXW,pB`s@lIG?i!6HF{8xLh`r%6~gX iq (DE6oiP8J:pe΁+,8u +N(zcLjKt9E|!DB[H)%$nrŲ$bfW.v~wzD%s薪RJ7!(0p̤@7Hnڵk!r6J%4ju'>H#~3͖aqDiQurkN|nvO"4M6+bKn@tP v(t8mNTK6!Mh2 lzjv\P*dn(= 5D*(>GM0ze'}0 o]eEf_хkq8u;XPõkp66H[-.ڼbn! QHz.c (ܓ2̓Wp*8pgQJv b11%K̗Qitt4D<ϐHH$u28) 3FYJr$ tVC< -OFAHɠrq.i1U -)b?%ZMBӬ$/۶e v*BA o;ի+~]l#t/ P rU%$4Mvl9ah[ vDr/NŤP=X %r FEIP-7;,M N4 V4i!7(Ddg % dY_#:ͦ|}T 84뵩ovK 9PΛ:X~vdht=Džpaׇv m\Mt. ,ۆJAhfjFn.T*B)'bJ(.Frk6*`}>h ğ9jSq]jCU+SCf dbHg(T*LD*Ca\@"a`n Ri%8`Gxu np-\ҷ7X89,qwgyz j7Ij6RB+ql&(xnY 8Zfgg/ 꽢PcE(K,Fdc/cr,u]HN\mDߕٚRf΂8Huʊ0 z=!^$_DfFQT1)Xr͍z2ӰL ~J"Jt GevmfgDX,Ngj0 :mTkuA'ff~(ᕮ4ss zvB3( 8P˂`s4{WXg=\ʖq?::']׽!j3%ŃgUs!\k6(J, VR þR!-j ЎWM~Xp%,zy6%n[i8Wez  r6( 5wǰwa>Hh,t]d7:Rɤн"JC)JǨ$t܏ Zn6o* πb ۓ/Ϸ:0hVFe i#}h=$注] 9l)xW-+JZzVN `ŒpXБ)&1Et.:u5hԅ]pw'+5B98я\}2:ҙ4c6ޅTWV샂랼8qamPJ@ Qzʏ.z u0ĠQeAӼE]72 M8(ɔnsup• ۙ..>8MH  MT(X8Fn+R)VKF---o4 `Qjdqc` 0" Imn.RC'=mIe=~h;f {ImRϝVl/X:Q؊yhD"N#o Й`id2frZ__EA:077'ޓ$zސx[ {聑"^y׷9"+[YFl=_CbQh3J&u.AM6PtQIze.1G5P*pzI0PH F׵aYmdRq i¦4#7c8ƫ߼.30 ۀ8` B<(y F:8,X1Pr/|W]p4I.q:=s-F k1 !q8@n=lXN <9kRc;;^U*k4 Y]S)U16Mt:ϣb~~x4M/~,Snd LU}!T  <Ţaw uQ:9A u{aQekㄺըmM2 L#B~% 6qO!>W\d\`6ժ/\033b\.'o̰|=5vIGb x'cC9'jU@G堲D}di"@9c\_GL`ii T kmS|,"BPf/~T:R۶QY׵.lh|1C}V0aHghm>>}AѼmi!<^t2Z4ul8 ]zq TdQJ\ m10<Rе =0 wRH1#`̅ 0@G@/qx 3)(mu윤>YA0 _gZ,xh4dGTÇ$BQȊRm¶#08'^Yمl?,n6$Idw4,/0*sw 4X= 9 1] 471@EeٗQFtVQB{i##stPPQ* d2^Fׯ_nigf@|-Xgw` zv`J@ =,7 xA{@}&8}X tm$SqfsMN&p,l"C:M?¶b)xDRF h0iC"CE,fU2DO=;?}+ UeN׵w\+W,I8sBu d8mX&h@lF [股UcXx9 'cm5*"ffi!)؊1.=Dݿν,G`v`$X' |vJRs7 _n+8r|ay#$(pLP-J8x bxg֤h>#ALBS]!1 hڊ)V(n>8jJݞpߗzVkDejn@\xXǽ'䈉xceK%$E$@Zյ53EsTzH@ԩd> F"0\:/zJry5_8Z~ G{6B¹됝{9wTN)! <_8@##6}!%HZ_j j{?DŽ!,vڤ˅͗6Xy\P&RxHl\*4}$T*  O #֚QtJ_5ERL¹ǓdV:2*PqmVMM0lm йf6'̀ҧ0? ؠj1=GG#yLQo]5,Cp'* }s\*Ժ.TO`Bg=!BH<&OC>xw:ޝ{x?9{sC0b:?y_A ED/ Xhc, VX'm+E@ V}2{[v8r&Ii{!nW_ҥKirRIpvk 6+0EnŋdYV.Mӧfeu8Ηk뭻 h*xAPw%dHV+~R(>i~U&mFg1úp>L<9RW%(4^)2k"uu9糹1 |lɓpx3>YFoHхdx'$/X|O( A+rsO1GӸj!묭m4qpp@1cc0L `lO1s-޲% wa@a\ZVԡH34e}JK)xUnB3M؋63>T +RkfsͩT}gUQyYvAd<W X__V?`=P_^ϪFl篯WsˏaqIcV}Ne pT==~e[\YlJyuT9~k7:]A'yl/@6)%2ဂ'󎿎IkI$r[+(F24M{.|Ĺt˼YXBX:0Y%pui'&SkpNpRJZVݼpYݧv*# "cjZ .vi۵yg%h~B=ր_Zky#Y3'y9(鷴5Zj:CJC -`2i[m%4fif"k]i9Yl3[NN ( Y"#.:1XCcPX'&Zk{'cݟh8EB 4͌v_"Cԥ=D󏓷T8M*hBtᾱ<ϙf ֬9B~輾?t۷}6i/|K_ҥKuמ9Z [ߜ.Wꁳ́[^).k69 o${4'lRQ` ^HG },F1$!#t^y=/1Ŋ's/&O"Pac6zd|p4L IlE@){|w|–&L [9Wu~CD^l[o^`%M,*yH"È/~.uGJH\ lD#߹@> ]x|q·Cnzdŭͧf knz+JJp] jGVgY)ӣl4%󈢈4M7[h8M W~X.4MFUښ<*V3WÕ P i*J *uQ]8rְ"HS!0!\Ξ+ kodyN;!{6 UDe}c)Q#c#ZdBi ұRy(`|zkz?'$c>V*Q!*e aqRSPwX$Aսz9+٬NP'}Fy20lmuꫯbtOJW6FihZ\nrɱ|W9oOޓ6}0o9$ Yڬ.ܿf8[I?K(YYEar=M|OW(9{ @ystt25~kkk!ygSJWHD<FNXnw3YkD3 GyNn QO (5~.;{̆1EbMnDz $f2ъzlmy{#Fsy:=~Фi,#o@زܿD±:#"-8M/lOAkM'ŒbmotY: 2zC hQ E!ɑݴ)tBRxin/$s\ikUxrHUf?Kg鮚dA0 &+lFe1Tձ:U'uWUXS5y^@}|Mկ~+Wz}8\tw-2yٟyspp??oA=WO (Dz: 4Bf)b,PrZi*QIfI? 6YX"r? Iǡ n>\ {fcX՜+?F(IӦy)!I1}v*kk=kz>宧*;b<, $ZD'2IEŜ,I6 J R<e'Q <%'(I_L~=eB&b;B) c Cd`GԒX3Ѕ@BBcGILg0^ݎ|Z.>Tum"U *<`m+0<@iJ9+3g1"A>^6u7bXLХCc Q^Xs.z{eLyx*erW]u,[+߫P*NZlY֞ƌ)a,Z8+dRmeiܳyy!yր@6 Yw[o/I"!:_Wַ#ַu^$}kxcC\O*J8{1tQ 7^ߠ퟾o厛6$)C7K<*18PVRlIwzx^`$Jw:18u{5Y-x$J@N [ɤ[nUFDx5q˧Dd=vHNAJ.\$u랲h|+;9GHP~^mP4 e9s& EQP [P$)g Qihu[Ҽ`0.cm,ɔld&Qm(DNN-^<[,F* u#?P7S! ޾GY.<3UXbv1 ZgB|/cT"q9骐ց$eNP~F=dk}|,G,[D[DcvL@Rs|ttt:uY{R=x]=" %c Ip]~m~_՝vҗwOڵkt8_jZc|RksQJ֖̱ɱ8-~o{>oO>{qttTe??Blclgt__4!}$-UwZ0y>k]u#Z-8.o fgHVQ DQTOx/0 ب={ӝuoLz!/"ȶH̕"/^Z <'|Of)#I@DQI0;\/} {WgqgtJ)%qe$0v9шvq {/ϵ+UsЇU2bt5r|&ҫ Ofwt _x\_Č}2K3.K+" 3dmk`mL#l,X,(uQ 3ŕL` 3HټC< I"'?FX-#?1( <01T%l{i.$~lq Bc:NͶ[mZKSB@Z@_ojV.ܾ}-_#X.]ׯ__?bww1Ķ*+Z^zo| ziє2/?/pcguiXdA:$Dj2[̌{Rge7Ie&F\p`8jjϙYVRJznf4e8q n޼#(5Z Gn }:eFL9B"1$E(̦s"Չ"$EKJ߫<$46A}@"c32VbJ wu&FG[FNp4]VeUimZȳ#6^*c>>>c8<k)lO[0J'9; w){N宜N* ht:e0p5}Nٍ$KڃzIj06G(Aɬ@UG2B _kT_;v;b:l0@JXG+P2@IClhBӣW(KA1M0p OnbϏ&Ὗ]('-,Z+i 2*SV]"v@i.Hd0=MY"ȧ=ÃÃÃӉf+Cb4OVpoo)%6=k=iL,.Qa&I>Zg7DaHQ`!=.\`07^e41>w|^/`6.]ŋ\v7nԂ4-o}{? I(* cƕ5-;lŁ2IrI\*YCsʹtXa$I]\z![[[\pa!Xq{cpi8~d,(w~0_)V0kNn/JvCJ_I*PYNJL6} )wbqBp#hg 4'6Fa0ZA,c‡0ϓ8AaL 틊/s=:3W3[[slw~l6yb>&MYNH9Øɑa<ݜqT4kU < }et)JxCʀVEլESc i KkUϲ6zIq_)tQ0}ߙ#ʅiBd+8bi'#S&W^KE(5:< x4u]%t:uNU$HY!=G*1p:'}f8/\R,b(jj/*hDeDQDg{{0 kUrR^5 |k=(jFLc A$di`EjH eEQwy%mu !|3MxGrp0g&TH_Qqٜf)YZ79RNOkGN,|'G]ɮt%>zR םC[ghcu m|666t:AJ{u}_,y{Za.eqٜ ҇<҅QC. &^"f|NǴ:~'2H~LN+Rh +SB_!%GnWkā`qoc!A!S(tRw.bI6779dz4&<:'/t#[LHi£Z&QVh` g%R@rVȒMؔ.s4a6u"cHBM'/P^֮5BJA`M Vht]y2n߾h4޽ܺu_|q=43EVoXaտYqmoǿs:,,Rx!(s-C =))NB]dVycwá=,,;FBaR'SJe2^udzVjhO)WVb|߯WJcmץ+peـ|UӤÒJa 7ay\x!K\R9M n`RL)L 4ɊZRf{Sa`u#$cZ>FhЧP Z^' @zH!MZ>VI(lFؓl?8E+XBRb!Osw5!ڭ"4&nT`7qyqa֋ejUZ-.v(jgb>NLfrTgٜJ:eu_Yq}'svdC 0 ѰJapaͫӢ(,KGܻ snܸ+WzQՁ=Sofss͍[[[ce82LmC Y5*?=~X$ l6c[ C.FqjJ`-҉ZF Z1;B x>AAAE$Sn3` ;Vr0T׻ 9<6Xm jIUh! - fY:Lwǿx,eЩ@R 0 J_-%kϦ^-43 H5A@e<s˪k \; `dS9F*DXX]1 d6sHƯnB&L[7 H#OSuBhq.>:r4i^-t:NG#Fܿ8v6 A6JOq~خŪ3Z3KuK/|>wu_/>GdQ=IWtjd-co3ݎJ_(5F锘jaP n'5=Xkcx='C"D[0dp%ɲNk)l_'Ecq35Fj^UB5+Fk$f>3LNdYV6c/KG̊,]X¢QNbiE~c&7MΫ: :xFZ5?LI'G|Uo P-`gH.4m74n/oJse0!qeL&~zmpWXaנ1b@;"ڤE #hT |=_Ɍ,]itTt- Y`g3dDg=򃂝wFl\Yc+kxj|>7G18ݎtZ6#F!Bcm2%l;Sr "#~z3 Xm1Fz}T,KJ O¢TP^89w)%QEm_f|PJm* C&B qUniږH)] Z NdVUga=X YgcйqAv-f[(G-jhƵ6+I7V7`+KXl|jkr!8-})c:H'4z_`|jl0ZrI1(V_-#GW<=H7Q%t_՝6$cgr7888<.]T*; 82XB.>xA@ENQh phӄ$IPJ#'LšaaME9Y(!=;z*b:$pM =gif7 qNR8ZJJ/-@y~-;>c+*XC5ybl*,ꨄR9"b)l%[ޜ3r\<Ϊk*;lV[5NM Ab*F)$N3UX<8wIKvNõ}H>8ըR-tl6Cnѱlt2 (*㣖uiO'3N{|j8H䨗lLYhsFSf*6jyqڍsZH-O:' 49_gEXa\`9Ut:6WKP>yS& [lm] [X; !m\[rիeYDgXBZ3kDad;gHBE Tmrn(RjҐHg 0HaC"=ԤC>7CMri8b;LcC:mˋID-vlZ k]t)BXA(N.(6.>1x6 'iͫ,[݄~YnŭXkJ`;)Xc hZ ׋ a2kU-e?Ch_Z#=w痿zý}^~dˎa=rN Miґ2xT=J 4$Y+ \WF vyHtvᥕ{V˗/Z8ũcdh)T `8%dfjH,!R8KsuH(s VEBI]ވۻ$%X"#= o]!L#M 30DmOHq2;̣i!Eye.mrSEk_ojDJ'L/|\vbe8sZwnll> guTMΞGiU)8~0k6W_eggw.t WtM]YՒW9,ӲD7笯iZ ۗ űoFrc$il6b̰ L},טx|y{{C\l>t^|OoMC9Rϛb)p9=I6sUy//%S%G7j%8?Xnښ3Ӛ|am _g0w& `~L) &/L. ǠHJݻuKܸ"锏?Y]x~OQoʕ+|_׿5L&V٬j-k@oVbO*6S56. dfM#f ->~vPR, .);׎47m%Zsbl0UyZ;;; DWݬ +&0 kos N/>lLyH]CoRU,V0g?סNb%4wfLG)ō7X__/K'>c#WA3IXtc]):!m:ܽ˛_d!N\WSZMaJ?Q!&7F#SR;4~Wkɟ >CTUme7h49?q||>LJT$m^Oƴ-Y/ʰ6*q_dFh{?p1ϥ9` ÀiEVfz7_kZ2L7f>. c =B.4M0W@_JOy H `i6eui]fݡY;MO'D+_X˂]Ș2MsI$~¶ty$ͅaMvwޅmۙ>:س`U?,Q"sc- 4A-Pςi0QKi:׃iP)CbB#d z ] ;{h%p0 %w9&OC0@^C`l,p۱DE?ihh~090M > Zsϙ#_J^Vz9qwlǙ.YAFр,DE]tfO?u}G{9^|-|0Mf$a(ȢzpJTsw."ub/T1>_+;fucБ ^w`@"{|ntny8YJf8.7}8gO dI#z | nj:.`Iѥ0c'L{d-gS^}W㓔dӃ#m vcC4!<\.6#@^Gqo2j2l ÚL&A s?8Q#sU !B$|g FBeig0}m@ |VCaE&2MX; t* = q8@4P`3`p Hdp $x FBڵ.bc4c4j`Y1JY]Wgnu篬͎*eVkmM8sFFQBȔC둾ݷ?|X}|>GR.?~ u8=z Z-z=hx`4quQwRヒNlhh*S9tYi?,YsOB@xh,k'[ 0kg,Y^T֢gfE;b,,k8<<=op60 U z-n2ժTdC~Ijt+bNq*%^R4MK|>Bw1ܺu Z-!n=m)MTg@:B3@HpSSЏ@|VaN(t˄ HA2f  !)B;DKIHa!p8eZ퍦,y(׭̑B |žQtC#4i,zAycCծakMuzky&(AƀJFNg >nܸOb_?F-1N?iTVeČYZ-hx%1>cI P⌵bڳyβQiIMZ(NU.|TYc;$~83Ů<61]Hu ݦLfx+4j.z,,F /mc4R&gO˓ܬμ6USby8@}F `؁QjdlNty PuVBrZ&(3(`ð7Ʊ qŪ\+G%uGq/r2 i>O`t hLjGfC- 03h thTUӁ B93`:6EH @9e{! )4B8r[tЬT1 VNV>l6aNpC\~}Y-B<D;i\Z2F=hV=Lǹn߅,Y#f"r_O@jGAY0QiEUl Gބ.!gغ=U)n(ңk׮0 i^( Uxn+wy\y#>CzA7dmbFKa"behvf4Fu+V|EJ(g>˯PSAkpfFjSo>PCMMXCtܝEeB0@AAIj^IVS mFT̏26b,Zh4O~|x+!_W|*|˗/9>S B008@Zjw",s*JWTXyP!^J,X9_c OQSA5/JbEP>t-*ӕRݘqPfvŐGṚ: G̏[ET |6Z7jp@δ3%ѢlI, `UFid_& 'tb,F# ~mh mLbV[W<@߇f VYj]uAl7. .XXU"@I볗5W֬z7aNSA˗/qpp 6^q VE8N Piyi:)*:KviդubVe7\Nq}%"{&+5}=c)Eш`<8dP>@x4Z r}O4QPt3FQ9HCP ޺ Qc0 NAD4)> @4Z` L!{,[!GLc Z0*LKmk ;4 Uh:A QY+ t1cuÄWbV͡S%cѭ 'FC/_ē'O$GVZL9 @ Tp%>`VT`Ůeaer\ˮaݼJg~L8뗚*wzH DB(9/HonhQ5d|4*:YZ xRga &4୷BǙ/W EזEIW׸0(&U"43}~sDjU!`Sb"d T94ʹ18J+q4i>O/OU(Ngp8`6Bw9t &|]20#wzF51Vlz zz.2N.=f;l}駸~&JKO+~QeC s0F677{(pކ#r7OYD(_ 0 ֟(+v '*ܜ5pM^<!#/*J}ŵQjZN)\Cx<ϳmf3ae&}NLhW*~7pa!`C>)-TZ!\oFR[ѢL(p2# hp ɮ=y炁Pz |fs7 f1v3 nr # f 66¬<"~'2sXXz;SYuC{Cϋx ʺ":rzz<5Rh B,A0ceYxw`  xFHF@d8x.}_Ъ{.BC{ȖA{(ЇO<L{pp4rƒu:w}oT0un lАw^(B9<ω"iN`66r/BA¥ZX۶qMlmmEd mSNF钝`D`szFZШv_B/>V2{󄉨l/áܸ{(uefd;mTs*7\( C<{ ^/a*NBVxfb9@rqU>҃8+8*[2H߿Xp,s ;WSNM,Gɏd~LB !` vqèA44'jLeSLý!FhMGFC 4N&+bn l9ôtNQޞhXC3HEH2P+Ye~Jec/ϋ0=vCp8% 7]V_l꺞SQ! c Rj,@4Z v-7Bg[1Z}R8kԨ3BYCR '5\[-v/۲D~\ȽL{lN|^Y. _;wA\H&I%BƢ4RX8`m4uhOPa]vCpMA|՝J B .Ư nÐ@LtooHb6Ohi.10gmFL=be(3A P0bISf944"4 MN(1&**[6 aȁp\D…~U0VPA'|R`+ H<|>_*3紑 DO0`wAuB[jhxƴlVi뺜cUQAM.LV<O>^]/HJj0 yrRLOyӋlI+dt|tBmYuXuLFU ^(j666`X&\?{á3lm֭[%͟ Y"1 0x ABFai@ 9LSQGhA c 7j&7,0F'6H]j b p]i=xA@BPCCլ¶mT-X&BÇVV -{Ny<+O]wU}JX8q'rUp'ƤEuLR둾4 Cf3TՄmu ;V snn!_($$JV+MTW}{7Udy &Hh,a@:`/thp =\K> U8܁:u08bp'v .&`F]ӠsFow}sTjCOT@RZ U J|YcPkM38E!F"5Bu)7"Y8߿_Cj1ŋ5LPz/5M2w˄KaMl6B͢]YO1β@\̂z>^E (@t]c*fBjnQo-u4"q*0b^;y0O_)X9u,CE%4 ЧO|_ZHb#SKW.eZ1DeJB8Geonqk-X^ p ҂qA32l^ZvRB9ҟGl\!اJ" ݿ9JjUz57^']DFaooZ f?GWӓk&>JpuVU2]BH"J I^j-aTzG~(*c{{[¾F~G 08>xC>0)HCjx?p?z(BQ"jͯqLHBkz4@8qC|!?!^>CgCL$`ˬcʲ!(RJFZ`.@$Y%Ubl J}VדdZ1h&KAHfkϛhs7K+s2ykk ܅E, zu*Xx+[tI"/;(V1)9? U3aTYIÉN#ù̕Ri(-yòl6aYii41,xw'`/'7{'~;u^/e{.=hW33p`z<{y ۯ=p@E4a6+,\)4!c`Vm4NWj!. `z  9diV L7;,1Dfâtf҂|SbVΠ9ϚCKVT`lP;WY <4#u 9EWT.L<75]rD;VM?J_N,EL %Flɝ^>t+u[EБst8gʺ>PK 7Jnl61a Gp]jf~{NgP. ZVchTDNV IDATƄ)Kʃk>)6)kiKKOt8(AaL`̍\YGL@@(ǢeYT*U 6{BV9q;u INBd ^7\`r=A)/aj8x/_h6k'ᅯ]-ɉoPHqgiw}RY)bVV^풡=P)HTɿGt}9T6퇕veP[L®AH뺎NFN_JFLpjIUt打.mtG !f.* q@Aǿ~ّR2a)G%Z-loon`f!|2Teq#D@BQH-@">H[WE?EO2MmV JEO( uԤ qcCRhq8sjEGi+W uv]s?`xciD'QP߬I)MkZTyve]fN~>?*qwtZ)LRQRa܋ǶZ-4 9 ө|6776uX f W#_d @!ȻyV&LVmlmmI"l (u!>P gG=.~HQ7NPX D`I0[Bخ*Q'LE!B+uE98xz_ gtR,ёPY",UfZW۵|+vPu|2OĄjb3{F|.ܹ{Nh;4EI0WϺP u|6A>13L;s|rruϊ[?jYzR”KBC # N2+A`U{ZڛNL&h63l:bi:#d1( BfY,)4z]ZeU56WګIOY"vMچa<ɺ]Wx61p||Wbs),˲x<1١!4 f)-MNǬlYphR?-U,&LA;ab,b4 Lŗvz1c8<<뺲魷BZC}wgO,)_{Rq@4p} /^ݛ79a@cqZLtikIX= Q$%6҈3R]3J䜃,CXShTumbGl۶<nLȔ4W VQWۋb @9ϏnE! Vs4.}:'O[0,̴{ydq$0 H98Q=戬Xy-bN|qmL&?(t:O>~a:֭[V/n.>CZ-ionCAtǏZhXv5O.+ro&c ٳgk{Pps_@F]bZ^2N@mr/[M!DvQ|hԐe%BJ'N<<В'vO~nt@E>t^]IgmѨ<4 9a뷊#$nlu xp >| LN$,,\E/k.[ ̫DR*GH.RHQ,S xx1[q}e@aCH\e= /-q_ď,8Bp0¢sEIf*gN\yW3i},PQ*}LT*jsc-S⭷l6qulll˗hZ4 Ǹ}vY;mۆapGoz.͹$Dʐ<z`wnX9?_ð`C3 CXC } `0`:&vjTYx~) ;}dnyY-^5K?Jɢg6lۖu{{ w}%是_&Weh.Hz QB&Փ-VYZvMlllV%Uֹa ^_4懶Fw @28?Gلrq}T*_~Z=fj6ڂeYDtssJNgMUhFNbsu;Z_{p0˗/ uօ3X_@f0 a y\KjAHC݅8pGLDJ0,Y쬋COhbyU !lVQ]ףX@הgrIv t:!g]8T* ڼ|;_s}X 5TK,}j&F"&H=YǺ%5Wk4C*ڳ,,آm^ŹH՗j<cooN{{{S"iRx!/'O෿-nݺf)Escܺu ׯ_æi&w{aՐ'Wl,'h/^qN˄4- Z4Aq]1N1 AR=!¡c(.¶mBd=R Y.QUQtV]v0YO;H2L'X‘usĶL*庳DhI|h4P#V{]f0 ؇PZ}!JYt`qYsPBr$%Jզq~<[bH72&KE 0ޜ,',b[ $l%ttt|;wBu ÄyaѣG/0emڂx.ݻ{ƍjj4%l5 yh4L[ەe(ELc TӠ o>!& 666t2t$r5ѣGRx-t:)x%B˘w'e{gs~U)nytҊ):gBW ˲.|G`06(unܸR®&Ѳ9|'(]njGTѺj:5 P˃%"&+T3{#IGUV,*bҏw²,v, ?'X?3ǃl6sju]|~~oFkyl2:.z@GV*T\J1q9áݽ XTطb4?_~%1vvvjd'XRI&#Хp[8 \jGgźdMƎ~\$WτAL#aUUYFw&]Ɲ;w@AףvQ7k|E}\MP1_rLaaibZ,+nyڤ[&& Q|Q6H,$NNzZ1]|ej @#eY{& oa2?O8> nO>˗/n10,L) Fx<ϋ* )Sqn⬸O+1(EqppnWC,J\v ׯ_GǘNp]^{{{888ܹ Bpas#2낙 (yL.Ts:FOu[M;4z.;aB0Lv] ۶qضhS0vIЭf‰*2\,"MjжmX tIBxyeX䁬*Y."bD/r|"@ xl|>G29vt0CKhݿݓ f| |Z raYY{:.fZ$(Di4bEc8s?KҶ/OD@qMq| f4l57&B7]eZ]u^Gq?͸G|;۶A)pf`U*x$Nh'DшڟopnCܻwa͛gcc[[[ 7iT :(՝j8RŪ~)'$/ M@WYuɕ9!(% 8ONs]>G~VJ:9[ T+5Pm9^ v1l aPƒzb°=@e 8XJHϺԺWt ,«KUYN! yU*&DGZ r1F*P(le閄e-=σeY [\GU+ & U`W*U@qG h*R`c0 /`4`ssM8+A/h܄I1Cl6jpm0aYV(eUWBq FuSh*Z2R*ۈfXuبuFiL!xw_\e`iwq6k-[,X]5T`]t&vYPT T, qrWj} Ĝp|| 󠙖0MGO.5✃J4[:I<85TC4t:lmmh4>}ln}S֖%m>娴tqV e[W LϪ& 8 !*$v "}F"vi>c?ĝC|?y{@E|֬cLd Ɲ?%@bAHUayc,I0US%TKoifiVK3eALu]#pՍYFʪrN{0 C zu7gźKe,%L; txpyxwyFC8^/^@uj5lnnjI(-w*,:-M0YKQ_.y,f٩ʋ )eKFZXd0Ldt6Sbz5\g%tZ<|0 jxuI"kS֭}Uuҏ-tYybs#2LjpxU-7Se< 19Y({u\V<l~)ee~iڍ;?+Iáyp]WDH`ZLZq,yژa;:.:fjbܔqv-˷łIUo~Fq]`Ă @=xnn_}le(+DˬgUFu3acTGr4M qA[[ܼy3z|d/2hjr=`|JPe2XeHUZ* IJ>pGUqΥ.J<J]CԀq(l̹6ȢJt5  /ǑUd28"u .Bh{Ů#d1$gB,CD;!_.`Q>}#<'a6j]ǭ[7@v!<~O}4MdШeYV OU3Gk&d_˔+&] D8!@*mD8Fb#AHܭ-mQCDX0p-k5N540..r:lhDQT'zaYcfV':R[U,pZlIĵkפhR,eaƫ&漙2X(wc6H'fB4]e=TvV*u Dp<KFݻvn&9k7 YD<5m*[$Z*cIUR_YIn2 xV"3Px ,MډlBe^od2h4R~yV -Y& gId1̨ d_-)?nFxwy)?cJ)q5t:z.cZTdzezz u82uDe&sMӔ֬F2YѶmlnnP G]Boaww_|Ų5D7EĞcRߢEO}Ęe@દye{;I_ʔ~<.Y뼮T{ײ7G\GUPu!HW{1Cg,.= JP dPr,Fh*A `f|5 À82.AN课IhCƍҫK-{eu(w*"@9Bg$.YS$kB~nw}`ssS*]pu$41e,Zֶmˎ4#ݥTْ޺zeiv.,5/yUi0ASdjҠKd}p[su\?:fl7.=*aC?@h2E;w{Qw!R l @0E㌠ilF7!1zv_"t:|;˲ZQw5 y0*EY` qjt:#DZV%2M8<vww&"wdP5]һ! G8 {.Z&I&qϪ<{^zOPPUUT*D)8+Ȝm*~j]+ﳧAìyN4Jweϳ{.M`W:o.{,..l5eK L$.W[qj;4JL* YYc"v̈IOZ:{=  ØsPT2AeYd MӢ`G^5Dz%^`K<j9{gm<\.0je_TLNSEf!TT*T$"[|]oտ˂"e=:Xh[{_Y*,=Y^ugʋ.:@Kw6l?#Z_%t¹dHRˑ`9;z1`U߬0xI)`K5k40 ^L ]>\Z~:_Pqk ,i6jNVmmV q`IV֭[{ݖ!,>kmDpUpȆ%Dp,"+J^q2=вҴEC^hQ^W9—Yބ;36$毬@4/jE`Ҟ#Uo&>V8XvaLa-ruXbL9::c,#v؅;r}ga&98 : EIFM yZLTLM#HB rn7^YXdU[Đkю9}EQQy-`1.T^gJHxNav"ã1Dzw{{n݂a ^lV^RVI(vW}[Va6lۆ%c0`>E`0Kxe²Ʊgj2ZtBc5%?" !qEsY:kN~:찈pQA9νϩ[4v:@bVU #7%gy;"1qlu%j`a6<ܸ~ V N0I>u" #' )T2[^o߆arbiFe1Yi! Ty~r)cTdZautu|U RTtPVK؛('JG O#l>|>|Ύ\MSF#TUlooKC˅@Ti#Y+N@pPmQ}Q*Q,ѳXODa2&EtG^F` f LDѐa<H%B<_7eꭂ]Vj⪋ڽCZ9Y-냕y,jv_b~t2)ͳ>xUꊽ:^vmi,T|ҵYi6u],K 'f.'|!83lCptG888iFfS #+*wbAHx1Šo:a2FG^_)e,lтZsV:oVv92E֝m lc?|d&WE^Vjj2꽖/-7c;mnyz3 +{S:TJܯܔ1^i7`AFBuK7)˲$#!?L0~:b>w1L`6(2 ۨ ':?~/^x)|}9ܹM_ +LG@iJ+d],UWwcϣ:^7eYnܸ]9w,ףDhq$?Acz+[[ @ v˶#f90s-h8r&Ec4UELx]A#f {x1_t{Q dr| d6aғd*pc޻hQY:HB4ޢLdLn##`ooO.vݻQۙiVUN7-3@C/t\9۶Q)i)w깈@Hɸ:S*/<@7Ci?.t,:o?яQ0KvH[-*C=)õ꽊ydQ2rRs2ѼzXz]ex٦!@D8tSfOUg̓SL>v|cGd橡2S'O1ַ֥Zoj>(2}l+ڧ:uVJ?KU[CʖdVR<`0@^׬t:վjjSVN,d75;ؘ !e!@R y3Am&/&:!?V&C f&W%XX}Q@\'ku +KѼznyox}@&MIvC袁^u3t1c@J4St2%NOOZptt~8>>%+ݥo7ķ-GCܿA6nx%%Z֫mTY&a<7ur D Tmp.:s`Zj!IkhX.]1f0sٿ|'Mt H{[O ~.،6ˀ ]UMvm Om?$Z~{ֈBǟ«e"Qם0 B0XZ+> EDw(`S<bs(^PbȨ^< C-~&ct;w⥗n)\j [nxh4Zzb!V鉝OJw5;IRpo}`\E`0￯5NSp8_qzzuW];Y,\0;ߤ=TlLeSo+ۀm_q1&]66rS~oznc _|Ocϓbig8E$AǐA9A0 !@֫8~M?sQiq>c6)kAz+flؖz]i9qv[w稚m>Rk}jY]fgޣhh ! \\\`:pttn &ٴM2XwZWAοk&PJ5LeZd[6r(,3J +cVOSg~C!]ƛw~PױZrT`]d=Wh`XXhGq )8āw$u$C&1$ڰ&2,vh6 quujb8q8:`00MX,Ъ74E& 0_ 9A:z]8õM0m3U;Zk\1s+w7ypiQjrh4{.o6g,&*:t["h2M:IM([1Wshڃ4 B`<_2^y]l}akfӱM<0Po8ʋ*Cr_>3"-ZE9L݊'< *nU%mR9ZU`3V]/}Id=wx KhaInptt#ݻIsB`)Ꞿ;zrʉdik=0PhwH`nd'zZǭ[8~w{a:bAFpzzr@2oVyX.X׹蠪۳^}ni2;Zn^L5JcS%4֓jj悥///14sK/a0g+6l rR{5 #8J$$kKEA#)f`TLg>ܾ};HC~mG?B@tZa2&T*WjBlQ%߶OjBGO?Nע#~EJ JkB\|mZ8<<~< lax?ض4kYyUBm2k𤻎s~z>xPU=Z HlVśv}Yo}$@~o/:i,3!ooAZ0OSȁX)_B<%ǝJ\ѷmm4 |h?s͢"# CN|wVUm "\\\k5I̅ۓ -qg`"%Zͦb&DQFZ:Zt:!.)޽ˍfS!Fr]Wk}`\EcZr C|_8F\Q;F'Τ\j w2'a ZJnx'j@\;Z:8Q9(tؾOv_je)RTnݺzklձ":I\yFqf.)X)l'*&h[kUN~]? {G;* FQ;wK_~W~?3?(%6q}?XfÙL&ru(!\8<8|UOxz f)=ܽ{ˊ./jtW5됮:@C5} @~*D$15z vG֣ l2q3"6WK>cdI{щ+uQB7m[ 9l5 I +Š EQo IYHHjNB@Jt0`B,K&NXVzk5Bטjݻk> HNZ 1O@j'#Wl7ٯ2ZNRbƛmݞʘI|oT*՝zpQ CmpzzbzGƧ?iR[IoXVA+'̍G15mve~n븿Kͮm͓xzQ~xmBxMYMR9/:v6p kPxڪ(Jso޼/| ye|sCĊY1>*{Xh4^Te fn$D1qqq sx~v  Ftz&)>C # jl|!j5n7NN$ 8BD܎ԝfdI1H \!Z";uh2HY sBI<-TlCQ:I0 iɉ>zx1֩H4I,4XL2@V|ͤ( h.]۞ɺ~hpl6[q_ x`2n6Z4f !=1q}F#W^{RPNqXv_G9w3XjkZ驜fH5[!Ӥ5 hh0NX,j4H&NDӂyqprr\ő4[X/a>x=X2l:'al۲X{q~1U_J ,v{Kr+yUx1ݻcNz8>>FVM^ B~BmwEkf3mv kZ+ lbX( H"NB(ߠ8zƇr #^~eIlb8ÇOC=tC\]]ja0ѣG8;;9nݺd>?j0kQ$X+\^N8nO)$a.6ujr`Q$!ebCi6Qfp8n޼7nݻz\z7+Sq2$^#5 L(Az7h/skH I:BdEAOO´^ab>WSh4).//1N1l6qpp`?>ĝ;wpm,KRӟ4h4xwS}_??Gh4:s!s麢dzT {vnmn„فfϭ}.'|2IIBYSdm1\VnXLJ.yGzS,ͫD4h4tvN͛ :d*ʸO h~na(q_%yXxXV J\RN+gu6f͡v)hyR^ۂ}Ny6[:ǘp[@;gM,-caȴdC{dRh4Pov[!qppF# C}M2dGy%Du[ԥ(5ρ97Rq1Iju>:\EQ twD$R8V)}t¤,[v|'pՄ#БRl֮O@GY]Y 2}O#H1Q^媴j=]p8DG%ݓV+umt]\]]8_8ͳmǢmy. +f[ݿhг/"0u71J6niX/e\[JL }D5ӐPW_!ݾScn!sTtLTK&\GeOFEm򒠹_IHr Q%XYgggdy"fLxT8-ROZlzNcmQZ7P~uuREi5M~t4x<ёfSs͇D Y HGGh%IU=vZ^$!r-_-b5g(lŹXFYVqKcdM]-j٘!^RGE+Ys8U\UOꅥADC|MV ~5(.J_+ Cz(FCS #p8`0P8%7ywZ-޽Zài0]@,فU Vr#Yf%Qu|LdM@D=~>}S6O¥G^z%m{pxx#Z'w|e6tźƊ04Re0W&|7SqhL?I[-$z?řIxtk^hvaܹN:%NsUdLUDIO^wW^y% I! pq]ܼySw&0iuk$&z BZG1\:A}1׊i&خd`@t+HiebtO2/) t$R(@7k`.rH.6xFa̶S,1'M~۲915H259Y9OZ6 Z-ziށHhpy8:IPI&EPE.^SD98^,2=O21ؓ5,-iLEƕ%E9`Y60 Lkؚ;@lZ hᕟ+x7.py1KKx(ʱ1E Z9&W3MTSڦuU,-ǚmUlVUcj5CZt%)1/qKn߾~_; C͌Fb;+{)q[m:77#Ƀ Fyh'5xjei.7Ӏ\8͙P3ܦʼx LU9{8<<`0[ |Nf}nuKAI@ :NfXs68i]9eCsJ+4q+]Q^::Brۇ~l`qp}?0 q h-ٕX-evy> <-`+xѷmF*XZmcʓޒ88oPΗkzO/ I蟀VH $uiQsT1"Nrh@WWWz \2b@.eDɄՌ uBDI[H,BF|"J0.ez~<7oinE}zz4q>7gnDmڶ|<aJ[tnciPvt\#E10*c/s8׉)##u||az;78ʡ{t%"qYhqcLa$-I.qž(WX_[4%x'z=lN117oqjlr6Te^B_aBֶ F&I25I#kRu/K\ iZyuzyЙx5Nlu⛥Dy`rxbFэG{elr~-'t~*q O׊$I/n pqqV/<˼OMPbYIv Vy`B>/qcjP8.:Xt#ܼy)]^o+}lgܲ;Hd#1ű |LļDW k~I]yr Hټh 3e.=~MC:;wƍv v{(E*B)k\,`#j3Dp!ܴb`RuJͱ/T^jBʅ! ]ABd, vr;ϺiHT#B @Et:un߾`_'S'LY0Ebn5&  Ecjߟ $8l^kpX,0^jF[wj^ 掗⤨_͍[69U36fW=g*gr_̘!2ɉݶZzyq=f Ȕ.`+Nƪ$WLͱrwaVcXم)5(fumRZ,[1mTn% kݥ8ө򗿌?'sd7'[۠j&cS\}B=vԄ2gyI:.ݮ6>Sz=XQ<BԽ(TԢ3"@e@BMV<#bNEQX,Ե,PPb"mq@M:OmRqҿ*+sPmUN.Mz*OeMw]cͰXOi#<&  Jˆ dXVn4z8?? hT٤\0RXm՛.m g=)]oZAhbrW3,yqT(VeoV^f /ܘp]zcxH1*D/"n7MܻwOpO)!'qw}@Z,oe8L2n=Y>!f\̓/{,z_sz.ڝjrV1@JؕHHh{l^eNL]e|nFe%MUVC"1߸]ԗ__T-\3.).ϱZf,Np @A"f+z]*f9=l[Ƣl]ʶJ_ S;"*ZLv}_~Z.&IRJ_2o`2lXOmTwɁmJM ԁF/~y{:FOnlv1Q5qppVÊ.FFnv*gbnfy\IX*GE rOtjfAnyuu!T6}fqq. w2e:21DI">RrJboN, )k!&Zej)kļ&In;Flpuʣ]^+Q89^sҋDz8;;|>̊)$K6W%ЩͰGݤSql6 tT1~0V2[Kn4kGu\ 9 9H&y0pppx^|P?G;A!pƁxv ~SRRI A2fڄ3",WTQ4Fiq D94D1X$FCd!I L*{9x_˜ȟ:Z~Эh(ԙRr s?x.0Ky}Vq4>k{yvrr"Oϴ.oF3оk] zd$IҔV^7-̔n m(ڶêVWVSH%*YI@aXɧ$QݷoRJf3o6A: |jb/of3L5ǧقiΥ n:^5TL7sd,|&=[c9|RǺ7 4Cns")L@QF9/N3}m%N/ȃ0 1ϱX,^1s{R3ȴhߐF&ql,[s;Lf$fi5W{He8H˥.}u]zPr:iJ?&rjՍ3Qx6%Kg>F05` y1cb AȱvCF H)`; bNU`lI6[o[RAK~6tIp"0@Y<B^WWo5~tM}`dIffMOcW̕xKx'G w3>b]^FG)#5DAF]9tR{5874a^`{ܠ5^1BX$Z#uD"E4R0N_11)n+&׈`<'H;'I?ϱZ*: r]ifVg)o+s^BgU\9ڦeAG:!ϳ8nvl4'XC S\!DQER:)PYVzՐ&W3#3gY7(lS+k55Ly#xKe(e6r8s_jXmC5~?zb f3 kJW&K#ĞF~[-) 怨xy>MdEL/]E 8>>x$#QD_;sX|uu \?y3 1.HvT`(Ӷۦs֑ɝZSF-1'(0*>u8㒎-fSZ-s|r]V@E '[OUlDal*m+ Y=1X1Oek*OU9iP_,N[o)H:1*\tیm)1|0c+%z(0pU2$z=11^~e:Z#GrPٗ + ̤6I& b $G|slb<P OHk He"`{GsuP`(:ts\B*혙)|ܲ2?[l,rvKr,r}/e#qXh}.nORkZVg تS0^ICecv٥Q='3ëX+'K E#In\v!h DQɴz@Lqtm^e4V\P/dm󌚹%~R.0I @___\ò~mYcum<׷#I/f>\\*IE8<cZa>炴Ihr9F284A*aƙ-~y5Q(1ۈ9H)[s h` w6eٖ<0Ivf( rYήVYJ8|>O ~{uOu>ͬ5>lgY2v:oh6z2X#͡RY'pp;M_: %).//U1`|ߵRFlPn[ sǜ PuU 2ym+ynEQ|Et]s_QsJ:h@kj村MQ&j oWc?k G6=iyltz9y٬@;&m܂u.ՅL<զZp9D~nrL7n>43h &@wP<ܝZ Vq!M`^nSw ~KY û tAt~T`5:kX G?L?)ҺP`0y{^z]&SL!Ee"=> \YXńYA-g̲]~%?_3Ԁu$'''zsG`$b+th.m3`V)*8NƖt o` -m?.ime+n;њMZW4*F9Xcu/Snj f`>KAenB"mSS !{ΓU"xWw|/!I'=.&6ש$Cb~RiO⮾ز -0rf qERڣM Iu2P K7)拙e" 2n~.lyI)!`?4(rpW6>٨! @R"T `mn_&2z7T !BJxLdn8 PlԾD1Vj%iq/,!б-j[8P)+CN&V%KhVi?L7JI, IDATz2VacF>H7Z"[!X-R!'e?by}+_Gկ!...4H@ucU2o4k[<ߩl9{]EрIhaH3vsSQi{eH!`o1 0<$#ְfQ1ܩ;IDA3ἺR$5s qv`3FLj.U I4Y7L <;ab{?O)q<:j57W2vbNJ(Le7KeU.B`}u\LjGNR$I0L*:"qk,WEy^T)Wıj+3 êw̕oq.d:n΁u]T;%(\OVd`81 pSBr gi&l~f2^Ѡ L?M:=wjQ"+Gko8T)a 693SGoI.3ۍw9Bs:yY&(Sp 5"JGƬ*`@<;p-`(NAS[J{Y$ke޳E]kEGE ֬(yF#I =0Jxftϔy]!$LQ8\XetYYG]4 juDIAhA ys8Vvۅ m,6FavYh}ќ긱i 6u$ھCeaޟ$D L"^$-ݎNBrPM^! Cx ^!~7~B_>+Uy3XUs(|Ԍd2%LDԤzn1:qƑ$rat]AD7_+B:P;ta!`ERQi愀` %veEr&-ҳycYcL>PPsRy(7%fSw5ڹb*11+2ߡ,۪{e>'Ԗم%Iʚodr|{pd +m1f&Sy,ÇjUV^N7:N5:a@H;"+h©Rtr$e E^mhT&MK;aEJ+)Kd&%)+j+?կ~wu3{x~U^JMᬍ9qsNj5t:4 C4 }n=XGGG9&LD9:<(/rEi Iߏ.@(+l?(Pb_8'GcSvᗁ-ɮ#6@oõ至i,]5$ MҞ'TQ݃_!!!O|>i,y ' ϯi6][Vڐ Z{⏥ d{t0qxxgk0: ͰnqdvptGYމYz< 9=NY>Tϩ>_\3sZ{0r?r47%F&\#ד&9O;-l꼫i-~N=7)ɕ$[4,!{/ `[V@iԩ7cx!.//qzz?@@j5RnV[', -ISj_&rσ-nVƀgUl%(sH 2miOrUdbY,9Q sATõHj^ H߷-xw5Sb~(٧sma΃XS[yBkP軒 z 8<<ĭ[04zx9s?DQޤ0*Y)! , `5 #D@% V`g:6|X<+[cZh8;8 3s!@"#kig+qg+q0E/iwe .r6湆D|"Ļc3\k_;]#Xgaxc2 Xںm@jۂ fxhl%F)؊#Z1Lpvvd+=A+`EfeVBj!r3l+ܯ2դXҌ"zXT9tDVzSCLe˄) L꺞W\Aa5ӝj> ~L&+"R,Q+]]r&)1#E?G.r eN@$`^k E5yI ,"/`' @ &Tzr]N3DŽpx^ʴr $ՊFc[PXfKi4LY;I@JJj+ ͜<({Juapj^9Wl+ QIM2J[U7hgddЊIH$FD,sS8&f#l?|?o|}q/%UrAR11 |s8>QC-&}FV;N4B8`۝PÅn2*,\"jQ(koͦ^Hҥ0gjbO<8,j9xn\r:)HPq+DzI)q):FD{w2eQ1~CrUhn,!]{UPTPm@)T8A`,KFXr+Τ`3Az9a '''<~J+[A:3gu Xfѭ)D˜~ZUl*8MDc4Gm(1|}DnDf ncuojv,@hZڿV$?==śo?*yrBoJnkx2lc0`<c0qt>M||RsJ|ROT@*A1z3%jll8&;nõNI&Sՠꚰ5nwBwQL8 mEuucTRv"W稒,_/8r]Q1Rt2ύ BVW^2ŋ٩=o K[ۑ˄𜱲,ng;B-_\\`:bZh`v~~}hS@K8ޕ-$Ȭ*CR49~؝^5 }Ȋ"gw҄D@H{a|fr1 ̻V@XJAu.bg4BU0͍ -K`͜emO?& d7pE-@۶?w+GY]Ղalv >|gJ6dj6MT#ekC'%dܓ]qK^@~ӈ{x TBMV6ּyٷ5%|Aؐ0뢚߫WG7oOAD fC>),*Ds.| p&1Ǻ|a9ScO \\ͽnp9jD;r޶xqkР/yq`16A(s|1 ͑``D0 ~IG^oPmX$P<~_, B!,rnH#&?p>As [ ۬ºniޚ oz2'T"S6gJd_/~'[e}5r.n6^k1=2@%m9X/F Q QHjr/(b@ɍXV$pcRAD*AaġaLlgsPy:tsY??OR~sGfܑ F_GwH%~S-#90-v"_2)6>VP[.4I? /T]82|M 0y~﷣NpxnBG:22aT$%Jk[w%K}{˙[6);aI~mL(ܹa]RV+vJU|}U Tp!M"vi6cFylDMAx+T빍3N'8Ϥ\^c;QTr=菗&fr^27M}+D/OŷsoHn\W^ H;bnaّJ#Dj6Պbm|/Fxge\pe\J{ %XD3y2&]F`׌s{m. /h~Oӓvblń c۰Dy~òԗUTur~ ˅]sgNuϣ(5,uGuA]ql?XcɘH+".( gMw3.-6Inu7|}sK byq4#vKP"z~Ff'M|UL/Rs/(r/c @q)Mbi-l5'WTUWU*z-ϊJ,Avwc7FU/È&->?*bO|buzr(G _ OOpm6+G$q-Tɏ)6kuCR,dP>沷_mg}rl! n#@f9Q{b)l<^(7L#H&x?HE@>qij*Փ+gOd!"2,Qm1[w!R)N$čf2"z.(1\:'J80Ѩ7C+ D*&RxW GH=Pqgd.Ka1GJnSOB wHWK1PNʦʋxii3'n72QOG: [<<bZ3Lg|4) /$r.O%㏏E鶛"_R)0nw K1OFO8{&d.TXU+3\qSrC8 %X_DJ<&~yFL\x޶-@۶5<==Ǐ?MDˏ ./_jfS0Bq0ͿJenAlI<y|{`tʙgAe:8[|g+ֆT\Ϊi]FHܗHTr?Dx/cn/?7I46huʘt)?z_Ru =UF.R̔= H(697 ^g_qcg,>m> &;`h>+pM#\%x#\xz| OOOl`]SQ]~M=GU̔c x10W/* F;nz&0w`w/)A+ԝ'EUF5To]?N.hNIv 9@U82oKϹ\85Dݖ!jfV}/gyIDATUq};/ظ22Tm#UE/s:ؙqt)`)DϭWj4qq KУt:QJZ}1|`g<4_V`Q80%ʞ2B=*" m֏~>օ qgNHtJ SgYGLlIOi8 TRDM/)BBϗx8e]s܏6T#TUU`S SXR?FlBg3%8߫FIeQWvYVzF(JU*GTYsI$BS^ҶG 9=XUY ,&?$oL"GE29Ǟ*TfA5,` agp310v[z4F>%,\LJe$PJ_ \HWwm2zeU.">|ko!`264C*Uθ/:\#P~gJ2oso.ɔsn)JW`" rA9+2ʮe_bu7n}O}÷T=v j֤m-6g6N`cL 0*{2TTZ%F\yr +T%$>`Ey3`NL7>ru|b&[]פJnݞ`Ε\Ԅ*cey6\6@)Jwud/KH6n]+g8_m[*PgT4RLmVmĤòA4L=n0ŞaV{r5Nv#Ôw02%r%?'\ɟ/0D)yRӇKIʇEeHjvݎ9NT$*5*O`YR\^3_?Wb%ֽ(JD…ZED{ƈ@ ?1x\m nl~ks[aߓWjx_VpT688I#z6O@M^Ꜫq+yAI$J{!%o/kmD5l; \RXnCMʚiod'ybrJ3R'=$J BKPֲ5GƓå , <'+6[֜-h0(`UqK*VKo9U+'y7REŗF|>HkmԷh4ޛG^}/o+>$+Yr5ږ^JNdP(R۪ZT Vi qb4˥gG )D///pxcEO^?)$*:@]VJ!#Ҩ9yXi#/+VCv'Sw*T:C*9n܆&#9S60 [!)UU3LJH T)cL7͝>/+J /y|(+~HP0|t97ˬi=Ͼ#| ڶB˪O>ϟ~ГEfBB A/RTx i y %y#Irn+0RU'`-S$%ն-[W q$dk#BABq I\ʹ$5(L$,N꘯Myn}@ uP(R(D-5I^Yn bk GnmaǏ)h*0} vIwF!2allUWu,0[< n4~-m wCEQ;"P/X 4#|TIJKʌ,:%M"ei@G| ,"Z<H$X9T¢-y4b. N'8s+1p?~vX_Z7_cb>*XÀ&}WH$y(c8ߥOK*C#V=ʇD U )DCzΓ)7sBK!a s1|DU"`THlmq UӁj|^^|o4MD>>4MaZEHPeL`y"2%cr[T3o}nST#vIOaHm7DHaDB IB;MN/#XfF$.zH) %X?&x)L_R#'lg+~B P:Np=D5O" `Ivb ]w)ʉ%UBx/NAq [7^]T }Dk`NTOd(T )nsQ B ;W̛˄lM9Gfbyjd<ZʐS:Ƨ:l|w~#8dMnt4A][S@b l{MmK1<6߼#dsVUP*H23J H%]DJ#>kbH`iBPw#[_&Q ԰8 ,6nnG"s^+rKD @%8qi+Pp9)1sUjݍs*6NZPUcc sFsc 97/'4*^CBPPխ\ & BfK1<Mm{($A֑)T˸znb5VV+xyD$;aޣ@a78_LhG=)BK-NH%%,2e:H:׫WP\HMevxJqT59I1m<^\U5| *LimLB_"X΍(oKVU?.P) %X 5_r5.o*O9_49 NEe+i8ZCtmU\ ϑJJ$/3߶cX+* Xj\߇XJ , \ 9o,Ntǥ@S>RA~Č__n=B/K'>R +Y#>&@%XT cR3qBPPΉ**L2!Q"hm\o*U)+J}_/{LJ ,Ji"+WtkXdO*k҃T"X9% /*"960U r83_JP(R(wT8yUou1sY5yY"7elG(%ʕ)BKP$d/C+g9R/)0yYZ]ybD WBP(`)w-y-E ]"X frown EiCCPsRGB IEC61966-2.1(uKCAD%⁂"VQ< hcj#x5ɚCx`Pm AQZF9̲vf؝w8fe3 hsZQ>9=}IMS|ܿV3nXcpJFO}\Pѣy~q8/p(Fa-QVI#-,/ǛN}23Ӳl$(4&^颏nQ&7*:k, O5.zLF5jPdoقmT(KmiVFZۊćC6;s~pfwgΙ~7L ƁOr5ǁ#!{ pfx4qhsl/۷XܓddgۢX lnd|l?pU _H~\|%$_ׁ{$uAnx >`+pMZ&{VOz :x{a`)Rɛ X z&L]pWn *Bf] G3E>%d7r,>Y8wJA#9OT*A`;5c2ϘEN FR"tJéBT(I*Ź mgm6N+Mஈ۽ (K}pxQYU8GY c;Vԑiѵ u ;rptqx ?A O>]mf:|^'J<쪭#]I _MA5ۊN<zɦ Ǒ)P h6 l$TA&]f;i~ {JsC BޛiF߆3koqEz5b#+#FL y8l?rpt䨂BFp!2&(][Ɓ&.zo*n>a$Y [AqfG9w# b7a'ф ogQ&SmtoJ~ƚmb$-y#)7؀Ȃ~%&pv߾}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.5.1/oscar/icons/sadface.png000066400000000000000000001273721450332542600200210ustar00rootroot00000000000000PNG  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.5.1/oscar/icons/save.png000066400000000000000000000074231450332542600173630ustar00rootroot00000000000000PNG  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.5.1/oscar/icons/sdcard-lock.png000066400000000000000000002374541450332542600206240ustar00rootroot00000000000000PNG  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.5.1/oscar/icons/sdcard.png000066400000000000000000002110141450332542600176560ustar00rootroot00000000000000PNG  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.5.1/oscar/icons/svg/000077500000000000000000000000001450332542600165105ustar00rootroot00000000000000OSCAR-code-v1.5.1/oscar/icons/svg/applications-viewers.svg000066400000000000000000000253331450332542600234070ustar00rootroot00000000000000 OSCAR-code-v1.5.1/oscar/icons/svg/back.svg000066400000000000000000000674231450332542600201450ustar00rootroot00000000000000 image/svg+xml Jakub Steiner http://jimmac.musichall.cz Go Previous go previous left arrow pointer < OSCAR-code-v1.5.1/oscar/icons/svg/calendar.svg000066400000000000000000000432301450332542600210040ustar00rootroot00000000000000 image/svg+xml Calendar Jakub Steiner http://jimmac.musichall.cz calendar date time cal OSCAR-code-v1.5.1/oscar/icons/svg/close-window.svg000066400000000000000000000226771450332542600216610ustar00rootroot00000000000000 image/svg+xml OSCAR-code-v1.5.1/oscar/icons/svg/edit-find.svg000066400000000000000000000125671450332542600211070ustar00rootroot00000000000000 OSCAR-code-v1.5.1/oscar/icons/svg/emblem-marketing.svg000066400000000000000000000243411450332542600224550ustar00rootroot00000000000000 OSCAR-code-v1.5.1/oscar/icons/svg/forward.svg000066400000000000000000000173401450332542600207020ustar00rootroot00000000000000 image/svg+xml Jakub Steiner http://jimmac.musichall.cz Go Next go next right arrow pointer > OSCAR-code-v1.5.1/oscar/icons/svg/gnome-dev-media-sdmmc.svg000066400000000000000000000251331450332542600232740ustar00rootroot00000000000000 image/svg+xml OSCAR-code-v1.5.1/oscar/icons/svg/media.svg000066400000000000000000000654031450332542600203200ustar00rootroot00000000000000 image/svg+xml Jakub Steiner http://jimmac.musichall.cz Generic Flash Media flash memory removable photo Novell, Inc., Jakub Steiner OSCAR-code-v1.5.1/oscar/icons/svg/moon.svg000066400000000000000000000170711450332542600202070ustar00rootroot00000000000000 image/svg+xml Weather Clear (Night, 220) Frank Solensky weather clear night moon 220 OSCAR-code-v1.5.1/oscar/icons/svg/preferences.svg000066400000000000000000000464211450332542600215410ustar00rootroot00000000000000 image/svg+xml OSCAR-code-v1.5.1/oscar/icons/trash_can.png000066400000000000000000000062461450332542600203710ustar00rootroot00000000000000PNG  IHDRddpTOiTXtXML:com.adobe.xmp frown ]iCCPsRGB IEC61966-2.1(uKA$j$FA_I6əq VD_ ւ(jh3BD,;ogYF2JVA6WB\teBVq]Ei-v5kՎךFE S+-v%[>hrASWT?M" X[]_JZ qg3E>KlX.'IF ⧏a~+;jgK"^2)Z Y'dd(W=9_ @av}q`C=y߇77{Zŷl:՘l2$CsڮP9GwY]/4|gK_b] pHYs  pIDATxK\E@$=cD"I(" jP.DE]ʀq**:\Ņ H?QMD]L qzzE]ݷ^U.CVwVzS`-p &,GSذ1\A\'t:p50CB);cWtX|Fzg,K\;UjQk,d$9@MԁC/`Ӏ Y j-(\l6> XCWk F Q>|Cín# mȆ 5 Ac9 JcOƎ4m!U`pŖ0;B3WI{O˗ہ0Qv^nj|V'3Gi O%ꐴu&KJ|hd搜IOH!nY؎K@NmH{} 3WFHEQ< kV6aӚսy}sSqg9o%`*]1 I}8'iQx@{`2i&la7q$52 E^ { |=܅\< 1VƁcv/ ;$[8]1w;ꝡ=i.oaqe e/3=tDVM{tayW-^EW᡻ ߛҰw眍AO-/%ma2亪j]v>]l/#,ɾ˪ ˹tY9# 4 y79%qvYh%zY"%._ԅ4;9DFH?(:QO;`t$b;D#}R,PdN 3C'"#.,)  (L4C2!EB#$3bGHYnvN#$3b;,Râ]VA3C#$3!a>>(aofh%YYi^!+ee&d?) !e80| ,7K7>b8AX6٣"{6kga 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.5.1/oscar/icons/up-down.png000066400000000000000000000002711450332542600200100ustar00rootroot00000000000000PNG  IHDR OGtEXtSoftwareAdobe ImageReadyqe< PLTEsxctRNS A7IDATxt ?Q1&M 0+T3Ij]?DFIENDB`OSCAR-code-v1.5.1/oscar/icons/update.png000066400000000000000000000060701450332542600177040ustar00rootroot00000000000000PNG  IHDRddpTOiTXtXML:com.adobe.xmp frown qpiCCPsRGB IEC61966-2.1(uKA$j$FA_I6əq VD_ ւ(jh3BD,;ogYF2JVA6WB\teBVq]Ei-v5kՎךFE S+-v%[>hrASWT?M" X[]_JZ qg3E>KlX.'IF ⧏a~+;jgK"^2)Z Y'dd(W=9_ @av}q`C=y߇77{Zŷl:՘l2$CsڮP9GwY]/4|gK_b] pHYs  IDATx[U3iUݺR)ZvmZ}T(|.xA˃`냈 /wVEDenm23>d$?a2Of (0U#S@m|by\RACM hOH(?|:X1DZAX^ƠeߔQ!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!P!(KJ`MIm 4e>v0rVۀC) >cvZ⵶NþpCHǁW跐ˀףX8.qY:[O'w-`K\;V/ L+_p#1/1);/n>)'X6LTbJ@lq9x >vH+^ic `{Ċ-\' iwhH_fl)U :V)MrHY$ [eT%L^&òvt&!c|CC\D8 \[/Q=`~#mr}j'8=JnIi#C0%IH| v7g"c I>Ƶ.׊c22D S~zN) |Oi:|xNQݮ =JI12' `߁u3Vpp9%!o*:k;Jn̻q#dr|r=_z ai>a BKAHήG녬MR;cj` 0xD=;%[l8qm PYul"Ufi,.2!_gA.?"G=DRZxexW FIENDB`OSCAR-code-v1.5.1/oscar/icons/warning.png000066400000000000000000000003121450332542600200600ustar00rootroot00000000000000PNG  IHDR ^tEXtSoftwareAdobe ImageReadyqe< PLTEf>tRNS AHIDATxڄA C?hB-#كz .E^Y@7JPN (Nxt*u*IENDB`OSCAR-code-v1.5.1/oscar/logger.cpp000066400000000000000000000202131450332542600165570ustar00rootroot00000000000000/* 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, contextstr; #ifdef VERBOSE_LOGGING contextstr = QString(context.file) + " " + context.function + ":" + QString::number(context.line) + " "; #endif switch (type) { case QtWarningMsg: typestr = QString("Warning: ") + contextstr; break; case QtFatalMsg: typestr = QString("Fatal: ") + contextstr; break; case QtCriticalMsg: typestr = QString("Critical: ") + contextstr; break; default: typestr = QString("Debug: ") + contextstr; 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) { #if QT_VERSION >= QT_VERSION_CHECK(5,14,0) *m_logStream << msg << Qt::endl; #else *m_logStream << msg << endl; #endif } 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.5.1/oscar/logger.h000066400000000000000000000023451450332542600162320ustar00rootroot00000000000000#ifndef LOGGER_H #define LOGGER_H #include #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; QElapsedTimer 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.5.1/oscar/main.cpp000066400000000000000000000776661450332542600162540ustar00rootroot00000000000000/* 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. */ #define TEST_MACROS_ENABLEDoff #include #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 "highresolution.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" #include "SleepLib/loader_plugins/prisma_loader.h" #include "SleepLib/loader_plugins/resvent_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 void optionExit(int exitCode, QString error) { if (exitCode) { error.prepend("Command option error: "); } qCritical() << error << ( R"( Help Menu Option Description -p Pauses execution for 1 second --profile Name of profile. if name does not exist then uses last used profile. --l or --language Force language prompt --datadir Use folderName as Oscar Data folder. For relatve paths: /. If folder does not exist then prompts user. --help Displays this menu and exits. --hires Enables high Resolution --hiresoff Disables high Resolution )" ); exit (exitCode); } int main(int argc, char *argv[]) { QString homeDocs = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)+"/"; QCoreApplication::setApplicationName(getAppName()); QCoreApplication::setOrganizationName(getDeveloperName()); QCoreApplication::setOrganizationDomain(getDeveloperDomain()); HighResolution::init(); bool hiResEnabled=false; for (int i = 1; i < argc; i++) { if (0 == strcmp(argv[i] ,"--hires")) { HighResolution::init(HighResolution::HRM_ENABLED); } else if (0 == strcmp(argv[i] ,"--hiresoff")) { HighResolution::init(HighResolution::HRM_DISABLED); } else if (0 == strcmp(argv[i] ,"--datadir")) { i++; } else if (0 == strcmp(argv[i] ,"-profile")) { i++; } } if (HighResolution::isEnabled()) { hiResEnabled=true; QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); } 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 mainapp(argc, argv); QStringList args = mainapp.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. bool force_data_dir = false; QString load_profile = ""; for (int i = 1; i < args.size(); i++) { if ((args[i] == "--language") || (args[i] == "--l") ) { settings.setValue(LangSetting,""); } else if (args[i] == "-p") { QThread::msleep(1000); } else if (args[i] == "--profile") { if ((i+1) < args.size()) load_profile = args[++i]; else { optionExit(1,"Missing argument to --profile"); } } 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; qDebug() << "--datadir was:" << datadirwas << "; --datadir is:" << datadir; } settings.setValue("Settings/AppData", datadir); // force_data_dir = true; } else { optionExit(2,"Missing argument to --datadir\n"); } } else if (0 == strcmp(argv[i] ,"--hires")) { // already handle in 1st scan } else if (0 == strcmp(argv[i] ,"--hiresoff")) { // already handle in 1st scan } else if (QString(args[i]).contains("help",Qt::CaseInsensitive)) { optionExit(0,QString("")); } else { optionExit(3,QString("Invalid Argument. %1").arg(args[i])); } } // 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(); HighResolution::display(hiResEnabled); #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(); PrismaLoader::Register(); ResventLoader::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(); #ifndef NO_CHECKUPDATES if (check_updates) { mainwin->CheckForUpdates(false); } #endif mainwin->SetupGUI(); mainwin->show(); int result = mainapp.exec(); DeviceConnectionManager::getInstance().record(nullptr); return result; } #endif // !UNITTEST_MODE OSCAR-code-v1.5.1/oscar/mainwindow.cpp000066400000000000000000002705741450332542600174750ustar00rootroot00000000000000/* 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. */ #define TEST_MACROS_ENABLEDoff #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 #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) #include #endif #include "common_gui.h" #include "version.h" #include "SleepLib/appsettings.h" // defines for REMSTAR_M_SUPPORT // 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 init_reportModeUi() ; #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 // toolbox is the right sidebar that contain the Navigation, bookmark , and Records tabs. // Navigation has offset 0 // Bookmarks has offset 1 // Records has offset 2 ui->toolBox->setCurrentIndex(2); bool b = AppSetting->rightSidebarVisible(); ui->action_Sidebar_Toggle->setChecked(b); ui->toolBox->setVisible(b); bool oldState = ui->actionShowPersonalData->blockSignals(true); ui->actionShowPersonalData->setChecked(AppSetting->showPersonalData()); ui->actionShowPersonalData->blockSignals(oldState); oldState = ui->actionPie_Chart->blockSignals(true); ui->actionPie_Chart->setChecked(AppSetting->showPieChart()); ui->actionPie_Chart->blockSignals(oldState); oldState = ui->actionDaily_Calendar->blockSignals(true); ui->actionDaily_Calendar->setChecked(AppSetting->calendarVisible()); ui->actionDaily_Calendar->blockSignals(oldState); on_tabWidget_currentChanged(0); #ifndef REMSTAR_M_SUPPORT ui->actionImport_RemStar_MSeries_Data->setVisible(false); #endif #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) QRandomGenerator(QDateTime::currentDateTime().toTime_t()); #else qsrand(QDateTime::currentDateTime().toTime_t()); #endif 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 reset_reportModeUi() ; if (!AppSetting->showDebug()) { ui->logText->hide(); } #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 device"), QObject::tr("OSCAR Reminder")); QThread::msleep(1000); QApplication::processEvents(); } // Profile/User/chanels.xml is not read so it does not need to be saved//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 device (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 devices 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 device 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); if (!p_profile->LastDay(MT_CPAP).isValid() ) { // quick test if new profile or not. // Override default value of clinicalMode if new profile. // Allows permissiveMode (not clinicalMode) to be the default value for existing profiles. p_profile->cpap->setClinicalMode(true); } m_clinicalMode = p_profile->cpap->clinicalMode(); 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(); // 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()); p_profile->general->setStatReportMode(STAT_MODE_STANDARD); 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); 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 Device 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 devices 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); QElapsedTimer 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 device 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); QElapsedTimer 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; } bool found=false; for (int i = 0; i < w.selectedFiles().size(); i++) { Q_FOREACH(MachineLoader * loader, loaders) { if (loader->Detect(w.selectedFiles().at(i))) { found=true; datacards.append(ImportPath(w.selectedFiles().at(i), loader)); break; } } } if (!found) { QMessageBox msgBox ( QMessageBox::Information , tr("OSCAR Information") , tr("No supported data was found") , QMessageBox::Ok ) ; msgBox.setInformativeText(w.selectedFiles().at(0)); msgBox.exec(); } } 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 (m_clinicalMode != p_profile->cpap->clinicalMode() ) { m_clinicalMode = p_profile->cpap->clinicalMode(); ; reloadProfile(); }; if (daily) { daily->RedrawGraphs(); } if (overview) { overview->ResetFont(); overview->RebuildGraphs(true); } if (welcome) welcome->refreshPage(); if (profileSelector) profileSelector->updateProfileList(); GenerateStatistics(); // 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 device 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 device:\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 device:")+ "

    " + "

    "+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 device 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 device 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 device!") + "

    " + "

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

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

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

    ") + "

    " + 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 device" << 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 caused 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() { reset_reportModeUi() ; Statistics stats; QString htmlStats = stats.GenerateHTML(); QString htmlRecords = stats.UpdateRecordsBox(); 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::init_reportModeUi() { QTextCharFormat format = ui->statStartDate->calendarWidget()->weekdayTextFormat(Qt::Saturday); format.setForeground(QBrush(Qt::black, Qt::SolidPattern)); Qt::DayOfWeek dow=firstDayOfWeekFromLocale(); ui->statStartDate->blockSignals(true); ui->statEndDate->blockSignals(true); 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->statStartDate->blockSignals(false); ui->statEndDate->blockSignals(false); } void MainWindow::reset_reportModeUi() { Statistics::updateReportDate(); int mode = STAT_MODE_STANDARD; ui->statStartDate->blockSignals(true); ui->statEndDate->blockSignals(true); if (p_profile) { mode = p_profile->general->statReportMode(); 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); } switch (mode) { default: case STAT_MODE_STANDARD: { ui->reportModeStandard->setChecked(true); ui->statEndDate->setVisible(true); ui->statStartDate->setVisible(false); ui->statEnableEndDisplay->setVisible(true); if (p_profile) { ui->statEndDate->setDate(p_profile->general->statReportDate()); } } break; case STAT_MODE_MONTHLY: ui->statStartDate->setVisible(false); ui->statEndDate->setVisible(true); ui->reportModeMonthly->setChecked(true); ui->statEnableEndDisplay->setVisible(true); if (p_profile) { ui->statEndDate->setDate(p_profile->general->statReportDate()); } break; case STAT_MODE_RANGE: ui->reportModeRange->setChecked(true); ui->statStartDate->setVisible(true); ui->statEndDate->setVisible(true); ui->statEnableEndDisplay->setVisible(false); if (p_profile) { ui->statStartDate->setDate( p_profile->general->statReportRangeStart()); ui->statEndDate->setDate( p_profile->general->statReportRangeEnd()); } break; } ui->statStartDate->blockSignals(false); ui->statEndDate->blockSignals(false); return; }; void MainWindow::on_reportModeMonthly_clicked() { if (p_profile->general->statReportMode() != STAT_MODE_MONTHLY) { p_profile->general->setStatReportMode(STAT_MODE_MONTHLY); GenerateStatistics(); } } void MainWindow::on_reportModeStandard_clicked() { if (p_profile->general->statReportMode() != STAT_MODE_STANDARD) { p_profile->general->setStatReportMode(STAT_MODE_STANDARD); GenerateStatistics(); } } void MainWindow::on_reportModeRange_clicked() { if (p_profile->general->statReportMode() != STAT_MODE_RANGE) { p_profile->general->setStatReportMode(STAT_MODE_RANGE); GenerateStatistics(); } } void MainWindow::on_statEndDate_dateChanged(const QDate &date) { if (p_profile->general->statReportMode() == STAT_MODE_RANGE ) { p_profile->general->setStatReportRangeEnd(date); } else { p_profile->general->setStatReportDate(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 device(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) { // This uses the Prefs routines, which require p_profile to be set if ( p_profile != nullptr ) { AppSetting->setShowPersonalData(visible); if ( ! setupRunning ) GenerateStatistics(); } else { QMessageBox::information(this, "OSCAR", tr("You must select and open the profile you wish to modify"), QMessageBox::Ok); } } #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.5.1/oscar/mainwindow.h000066400000000000000000000321441450332542600171270ustar00rootroot00000000000000/* 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 devices, 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 device 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 init_reportModeUi(); void reset_reportModeUi(); 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; bool m_clinicalMode = false; volatile bool m_inRecalculation; void PopulatePurgeMenu(); //! \brief Destroy ALL the CPAP data for the selected device 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.5.1/oscar/mainwindow.ui000066400000000000000000003006561450332542600173230ustar00rootroot00000000000000 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 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 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 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 Show Standard Report Standard true Show Monthly Report Monthly Show Range Report Date Range 0 0 QDateTimeEdit::MonthSection true Select Report Date Report Date 0 0 QDateTimeEdit::MonthSection 1 true Qt::Horizontal QSizePolicy::MinimumExpanding 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 123 704 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 137 686 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 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 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 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" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; font-size:11pt; 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 137 686 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" /><style type="text/css"> p, li { white-space: pre-wrap; } </style></head><body style=" font-family:'Ubuntu'; font-size:11pt; 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 22 0 0 &File Exp&ort Data &View &Reset Graphs &Help Troubleshooting &Data &Advanced Purge Oximetry Data Purge ALL Device 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 - CPAP, APAP <html><head/><body><p>Standard graph order, good for CPAP, APAP, Basic BPAP</p></body></html> Advanced - BPAP, ASV <html><head/><body><p>Advanced graph order, good for BPAP w/BU, ASV, AVAPS, IVAPS</p></body></html> 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.5.1/oscar/mytextbrowser.h000066400000000000000000000005251450332542600177070ustar00rootroot00000000000000#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.5.1/oscar/newprofile.cpp000066400000000000000000000354751450332542600174720ustar00rootroot00000000000000/* 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 Devices 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.5.1/oscar/newprofile.h000066400000000000000000000031251450332542600171220ustar00rootroot00000000000000/* 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.5.1/oscar/newprofile.ui000066400000000000000000001050501450332542600173100ustar00rootroot00000000000000 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.5.1/oscar/oscar.pro000066400000000000000000000502421450332542600164320ustar00rootroot00000000000000#------------------------------------------------- # # 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-2023 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.13 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 \ highresolution.cpp \ Graphs/gGraph.cpp \ Graphs/gGraphView.cpp \ dailySearchTab.cpp \ daily.cpp \ saveGraphLayoutSettings.cpp \ overview.cpp \ common_gui.cpp \ cprogressbar.cpp \ exportcsv.cpp \ main.cpp \ mainwindow.cpp \ newprofile.cpp \ preferencesdialog.cpp \ # psettings.cpp \ reports.cpp \ sessionbar.cpp \ # updateparser.cpp \ version.cpp \ Graphs/gFlagsLine.cpp \ Graphs/gFooBar.cpp \ Graphs/glcommon.cpp \ Graphs/gLineChart.cpp \ Graphs/gLineOverlay.cpp \ Graphs/gSegmentChart.cpp \ Graphs/gspacer.cpp \ Graphs/gStatsLine.cpp \ Graphs/gSummaryChart.cpp \ Graphs/gAHIChart.cpp \ Graphs/gTTIAChart.cpp \ Graphs/gUsageChart.cpp \ Graphs/gSessionTimesChart.cpp \ Graphs/gPressureChart.cpp \ Graphs/gOverviewGraph.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/prisma_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/loader_plugins/resvent_loader.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 \ 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 } # The crypto libraries need to be optimized to avoid a 5x slowdown or worse. # Don't use this for everything, as it interferes with debugging. SOURCES_OPTIMIZE = \ SleepLib/thirdparty/botan_all.cpp \ SleepLib/crypto.cpp optimize.name = optimize optimize.input = SOURCES_OPTIMIZE optimize.dependency_type = TYPE_C optimize.variable_out = OBJECTS optimize.output = ${QMAKE_FILE_IN_BASE}$${first(QMAKE_EXT_OBJ)} optimize.commands = $${QMAKE_CXX} -c $(CXXFLAGS) -O3 $(INCPATH) -o ${QMAKE_FILE_OUT} ${QMAKE_FILE_IN} QMAKE_EXTRA_COMPILERS += optimize HEADERS += \ checkupdates.h \ highresolution.h \ dailySearchTab.h \ daily.h \ saveGraphLayoutSettings.h \ overview.h \ common_gui.h \ cprogressbar.h \ exportcsv.h \ mainwindow.h \ newprofile.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/gAHIChart.h \ Graphs/gTTIAChart.h \ Graphs/gUsageChart.h \ Graphs/gSessionTimesChart.h \ Graphs/gPressureChart.h \ Graphs/gOverviewGraph.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/prisma_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/loader_plugins/resvent_loader.h \ SleepLib/thirdparty/botan_all.h \ SleepLib/thirdparty/botan_windows.h \ SleepLib/thirdparty/botan_linux.h \ SleepLib/thirdparty/botan_macos.h \ SleepLib/crypto.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 \ 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 } message("CXXFLAGS pre-mods $$QMAKE_CXXFLAGS ") # 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") } gcc:!clang { message("Building for $$QMAKE_HOST.os") # this section removedi. stringop-overread was only trigger by mseries_loader:: OPen method #greaterThan(COMPILER_MAJOR, 10) : { # QMAKE_CFLAGS += -Wno-error=stringop-overread # QMAKE_CXXFLAGS += -Wno-error=stringop-overread # message("Making stringop-overread a non-error") #} } clang { message("Building for $$QMAKE_HOST.os") # this section removedi. all deprecated-copy errors have been removed #greaterThan(COMPILER_MAJOR, 9) : { # QMAKE_CFLAGS_WARN_ON += -Wno-error=deprecated-copy # QMAKE_CXXFLAGS_WARN_ON += -Wno-error=deprecated-copy # message("Making deprecated-copy a non-error") #} } # Make deprecation warnings just warnings # these two removed. all deprecated-declarations errors have been removed #QMAKE_CFLAGS += -Wno-error=deprecated-declarations #QMAKE_CXXFLAGS += -Wno-error=deprecated-declarations message("CXXFLAGS post-mods $$QMAKE_CXXFLAGS ") message("CXXFLAGS_WARN_ON $$QMAKE_CXXFLAGS_WARN_ON") 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 ## there is an error in qt. qlist.h uses an implicitly defined operator= ## allow this for debug QMAKE_CFLAGS += -Wno-error=deprecated-copy QMAKE_CXXFLAGS += -Wno-error=deprecated-copy !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/cryptotests.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/cryptotests.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.5.1/oscar/overview.cpp000066400000000000000000001013351450332542600171530ustar00rootroot00000000000000/* 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. */ #define TEST_MACROS_ENABLEDoff #include // Features enabled by conditional compilation. #define ENABLE_GENERAL_MODIFICATION_OF_CALENDARS #define ENABLE_START_END_WIDGET_DISPLAY_ACTUAL_GRAPH_RANGE #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/gSessionTimesChart.h" #include "Graphs/gPressureChart.h" #include "Graphs/gAHIChart.h" #include "Graphs/gUsageChart.h" #include "Graphs/gTTIAChart.h" #include "cprogressbar.h" #include "mainwindow.h" extern MainWindow *mainwin; qint64 convertDateToTimeRtn(const QDate &date,int hours,int min,int sec) { // date.startOfDay was introduced in 5.14. ubuntu 22.04 LTS used QT5.15 #if QT_VERSION > QT_VERSION_CHECK(5,15,0) return date.startOfDay().addSecs(((hours*60+min)*60)+sec).toMSecsSinceEpoch(); #else // this version works in QT 5.12 return QDateTime(date).addSecs(((hours*60+min)*60)+sec).toMSecsSinceEpoch(); #endif } qint64 convertDateToStartTime(const QDate &date) { return convertDateToTimeRtn(date,0,10,0); } qint64 convertDateToEndTime(const QDate &date) { return convertDateToTimeRtn(date,23,0,0); } QDate convertTimeToDate(qint64 time) { return QDateTime::fromMSecsSinceEpoch(time).date(); } Overview::Overview(QWidget *parent, gGraphView *shared) : QWidget(parent), ui(new Ui::Overview), m_shared(shared) { chartsToBeMonitored.clear(); chartsEmpty.clear();; ui->setupUi(this); // Set Date controls locale to 4 digit years QLocale locale = QLocale::system(); shortformat = locale.dateFormat(QLocale::ShortFormat); if (!shortformat.toLower().contains("yyyy")) { shortformat.replace("yy", "yyyy"); } 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); samePage=true; // Connect the signals to update which days have CPAP data when the month is changed 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 /////////////////////////////////////////////////////////////////////////////// settingsLoaded=false; 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")); dateErrorDisplay = new DateErrorDisplay(this); 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))); 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())); connect(GraphView, SIGNAL(XBoundsChanged(qint64 ,qint64)), this, SLOT(on_XBoundsChanged(qint64 ,qint64))); saveGraphLayoutSettings=nullptr; } Overview::~Overview() { disconnect(GraphView, SIGNAL(XBoundsChanged(qint64 ,qint64)), this, SLOT(on_XBoundsChanged(qint64 ,qint64))); 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))); disconnectgSummaryCharts() ; // Save graph orders and pin status, etc... GraphView->SaveSettings("Overview");//no trans delete ui; delete dateErrorDisplay; delete icon_on ; delete icon_off ; delete icon_up_down ; delete icon_warning ; if (saveGraphLayoutSettings!=nullptr) delete saveGraphLayoutSettings; } void Overview::ResetFont() { QFont font = QApplication::font(); font.setPointSizeF(font.pointSizeF()*1.3F); dateLabel->setFont(font); } void Overview::connectgSummaryCharts() { for (auto it = chartsToBeMonitored.begin(); it != chartsToBeMonitored.end(); ++it) { gSummaryChart* sc =it.key(); connect(sc, SIGNAL(summaryChartEmpty(gSummaryChart*,qint64,qint64,bool)), this, SLOT(on_summaryChartEmpty(gSummaryChart*,qint64,qint64,bool))); } } void Overview::disconnectgSummaryCharts() { for (auto it = chartsToBeMonitored.begin(); it != chartsToBeMonitored.end(); ++it) { gSummaryChart* sc =it.key(); disconnect(sc, SIGNAL(summaryChartEmpty(gSummaryChart*,qint64,qint64,bool)), this, SLOT(on_summaryChartEmpty(gSummaryChart*,qint64,qint64,bool))); } chartsToBeMonitored.clear(); chartsEmpty.clear();; } void Overview::on_summaryChartEmpty(gSummaryChart*sc,qint64 firstI,qint64 lastI,bool empty) { auto it = chartsToBeMonitored.find(sc); if (it==chartsToBeMonitored.end()) { return; } gGraph* graph=it.value(); if (empty) { // on next range change allow empty flag to be recalculated chartsEmpty.insert(sc,graph); } else { // The chart has some entry with data. chartsEmpty.remove(sc); updateGraphCombo(); } Q_UNUSED(firstI); Q_UNUSED(lastI); }; // 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. /////////////////////////////////////////////////////////////////////////////// if (chartsToBeMonitored.size()>0) { disconnectgSummaryCharts(); } chartsEmpty.clear();; chartsToBeMonitored.clear();; // 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); //chartsToBeMonitored.insert(ahi,AHI); UC = createGraph(STR_GRAPH_Usage, tr("Usage"), tr("Usage\n(hours)")); uc = new gUsageChart(); UC->AddLayer(uc); //chartsToBeMonitored.insert(uc,UC); 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); //chartsToBeMonitored.insert(pres,PR); TTIA = createGraph("TTIA", tr("Total Time in Apnea"), tr("Total Time in Apnea\n(Minutes)")); ttia = new gTTIAChart(); TTIA->AddLayer(ttia); //chartsToBeMonitored.insert(ttia,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(); gGraph *G = createGraph(chan->code(), name, chan->description()); gSummaryChart * sc = nullptr; if ((chan->type() == schema::FLAG) || (chan->type() == schema::MINOR_FLAG)) { sc = new gSummaryChart(chan->code(), chan->machtype()); // gts was MT_CPAP sc->addCalc(code, ST_CPH, schema::channel[code].defaultColor()); G->AddLayer(sc); chartsToBeMonitored.insert(sc,G); } else if (chan->type() == schema::SPAN) { sc = new gSummaryChart(chan->code(), MT_CPAP); sc->addCalc(code, ST_SPH, schema::channel[code].defaultColor()); G->AddLayer(sc); chartsToBeMonitored.insert(sc,G); } else if (chan->type() == schema::WAVEFORM) { if ((code==CPAP_AHI)||(code==CPAP_Pressure) ) { DEBUGF O("SKIPPING") NAME(code) Q(code); //skip if channel is for AHI. continue; } sc= new gSummaryChart(code, chan->machtype()); G->AddLayer(sc); chartsToBeMonitored.insert(sc,G); } else if (chan->type() == schema::UNKNOWN) { sc = new gSummaryChart(chan->code(), MT_CPAP); sc->addCalc(code, ST_CPH, schema::channel[code].defaultColor()); G->AddLayer(sc); chartsToBeMonitored.insert(sc,G); } if (sc!= nullptr) { sc ->reCalculate(); } } // if showInOverview() } // for chit #ifndef REMOVE_FITNESS /* To enable these changes: change the REMOTE_FITNESS define in appsettings.h */ // Note The following do not use gSummaryChart. They use gOverviewGraph instead. and can not be monitored. WEIGHT = createGraph(STR_GRAPH_Weight, STR_TR_Weight, STR_TR_Weight, YT_Weight); weight = new gOverviewGraph("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 gOverviewGraph("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(1-10)")); zombie = new gOverviewGraph("Feeling", GT_LINE); zombie->setMachineType(MT_JOURNAL); zombie->addSlice(Journal_ZombieMeter, QColor("black"), ST_SETAVG); ZOMBIE->AddLayer(zombie); #endif connectgSummaryCharts(); } // Recalculates Overview chart info void Overview::RebuildGraphs(bool reset) { qint64 minx, maxx; if (reset) { GraphView->GetXBounds(minx, maxx); } if (settingsLoaded) GraphView->SaveSettings("Overview"); settingsLoaded=false; minRangeStartDate=p_profile->LastDay(MT_CPAP); maxRangeEndDate=minRangeStartDate.addDays(-1); // force a range change; disconnectgSummaryCharts() ; GraphView->trashGraphs(true); // Remove all existing graphs CreateAllGraphs(); GraphView->LoadSettings("Overview"); settingsLoaded = true; if (reset) { GraphView->resetLayout(); GraphView->setDay(nullptr); 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(false); // Time scale false=24hourFormat 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++; } //DEBUGFW Q(numTotal) Q(numOff) Q(g->name()); } } ui->graphCombo->setItemIcon(0, numOff ? *icon_warning : *icon_up_down); QString graphText; int lastIndex = ui->graphCombo->count()-1; if (numOff == 0) { // all graphs are shown graphText = QObject::tr("%1 Graphs").arg(numTotal); ui->graphCombo->setItemText(lastIndex,STR_HIDE_ALL_GRAPHS); } else { // some graphs are hidden graphText = QObject::tr("%1 of %2 Graphs").arg(numTotal-numOff).arg(numTotal); if (numOff == numTotal) { // all graphs are hidden ui->graphCombo->setItemText(lastIndex,STR_SHOW_ALL_GRAPHS); } } ui->graphCombo->setItemText(0, graphText); } void Overview::updateGraphCombo() { ui->graphCombo->clear(); gGraph *g; ui->graphCombo->addItem(*icon_up_down, "", true); 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->addItem(*icon_on,STR_HIDE_ALL_GRAPHS,true); ui->graphCombo->setCurrentIndex(0); setGraphText(); updateCube(); } void Overview::RedrawGraphs() { GraphView->redraw(); } // Updates calendar format and profile data. void Overview::UpdateCalendarDay(QDateEdit *dateedit, QDate date,bool startDateWidget) { QCalendarWidget *calendar = dateedit->calendarWidget(); bool hascpap = p_profile->FindDay(date, MT_CPAP) != nullptr; bool hasoxi = p_profile->FindDay(date, MT_OXIMETER) != nullptr; QTextCharFormat normal; normal.setForeground(QBrush(Qt::blue, Qt::SolidPattern)); normal.setFontWeight(QFont::Bold); if (startDateWidget) { // reduce font size if range can not be reached if (date > uiEndDate) { normal.setFontWeight(QFont::Light); } } else if (date < uiStartDate) { // reduce font size if range can not be reached normal.setFontWeight(QFont::Light); } if (hascpap) { if (hasoxi) { normal.setForeground(QBrush(Qt::red, Qt::SolidPattern)); calendar->setDateTextFormat(date, normal); } else { normal.setForeground(QBrush(Qt::blue, Qt::SolidPattern)); calendar->setDateTextFormat(date, normal); } } else if (p_profile->GetDay(date)) { calendar->setDateTextFormat(date, normal); } else { // is invalid. normal.setForeground(QBrush(Qt::gray, Qt::SolidPattern)); normal.setFontWeight(QFont::Light); calendar->setDateTextFormat(date, normal); } calendar->setHorizontalHeaderFormat(QCalendarWidget::ShortDayNames); } void Overview::SetXBounds(qint64 start, qint64 end, short group , bool refresh ) { GraphView->SetXBounds( start , end ,group,refresh); } void Overview::SetXBounds(QDate & start, QDate& end, short group , bool refresh ) { SetXBounds(convertDateToStartTime(start),convertDateToEndTime(end),group,refresh); } void Overview::on_XBoundsChanged(qint64 start,qint64 end) { displayStartDate = convertTimeToDate(start); displayEndDate = convertTimeToDate(end); bool largerRange=false; if (displayStartDate>maxRangeEndDate || minRangeStartDate>displayEndDate) { // have non-overlaping ranges // Only occurs when custom mode is switched to/from a latest mode. custom mode to/from last week. // All other displays expand the existing range. // reset all empty flags to not empty largerRange=true; chartsEmpty = QHash( chartsToBeMonitored ); minRangeStartDate = displayStartDate; maxRangeEndDate = displayEndDate; } else { // new range overlaps with old range if (displayStartDatemaxRangeEndDate) { largerRange=true; maxRangeEndDate = displayEndDate; } } if (largerRange) { for (auto it= chartsEmpty.begin();it!=chartsEmpty.end();it++) { gSummaryChart* sc = it.key(); bool empty=sc->isEmpty(); if (empty) { sc ->reCalculate(); GraphView->updateScale(); GraphView->redraw(); GraphView->timedRedraw(150); } } chartsEmpty.clear(); updateGraphCombo(); } #if defined(ENABLE_START_END_WIDGET_DISPLAY_ACTUAL_GRAPH_RANGE) setRange(displayStartDate,displayEndDate, false); #endif } 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,true /*startWidget*/); } } 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,false /* not startWidget*/); } } void Overview::on_dateEnd_dateChanged(const QDate &date) { if (dateerror(false,date); return; } QDate d2(date); if (customMode) { p_profile->general->setCustomOverviewRangeEnd(d2); } setRange(uiStartDate,d2, true); if ( (uiStartDate.month() ==uiEndDate.month()) && (uiStartDate.year() ==uiEndDate.year()) ) { dateEnd_currentPageChanged(uiEndDate.year(),uiEndDate.month()); } } void Overview::on_dateStart_dateChanged(const QDate &date) { if (date>uiEndDate) { // change date back to last date. dateErrorDisplay->error(true,date); return; } QDate d1(date); if (customMode) { p_profile->general->setCustomOverviewRangeStart(d1); } setRange(d1, uiEndDate, true); if ( (uiStartDate.month() ==uiEndDate.month()) && (uiStartDate.year() ==uiEndDate.year()) ) { dateStart_currentPageChanged(uiStartDate.year(),uiStartDate.month()); } } // Zoom to 100% button clicked or called back from 100% zoom in popup menu void Overview::on_zoomButton_clicked() { // the Current behaviour is to zoom back to the last range created by on_rangeCombo_activation // This change preserves OSCAR behaviour on_rangeCombo_activated(p_profile->general->lastOverviewRange()); // type of range in last use } 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) { // 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 { // 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) ; #if !defined(ENABLE_GENERAL_MODIFICATION_OF_CALENDARS) ui->dateStartLabel->setEnabled(customMode); ui->dateEndLabel->setEnabled(customMode); ui->dateEnd->setEnabled(customMode); ui->dateStart->setEnabled(customMode); #endif 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; setRange(start, end); } DateErrorDisplay::DateErrorDisplay (Overview* overview) : m_overview(overview) { m_visible=false; m_timer = new QTimer(); connect(m_timer, SIGNAL(timeout()),this, SLOT(timerDone())); }; DateErrorDisplay::~DateErrorDisplay() { disconnect(m_timer, SIGNAL(timeout()),this, SLOT(timerDone())); delete m_timer; }; void DateErrorDisplay::cancel() { m_visible=false; m_timer->stop(); }; void DateErrorDisplay::timerDone() { m_visible=false; m_overview->resetUiDates(); m_overview->graphView()->m_parent_tooltip->cancel(); }; int Overview::calculatePixels(bool startDate,ToolTipAlignment& align) { // Center error message over start and end dates combined. // Other allignement were tested but this is the best for this problem. Q_UNUSED(startDate); int space=4; align=TT_AlignCenter; return ((4*space) + ui->label_3->width() + ui->rangeCombo->width() + ui->dateStartLabel->width() +ui->dateStart->width() ); } void DateErrorDisplay::error(bool startDate,const QDate& dateEntered) { m_startDate =m_overview->uiStartDate; m_endDate =m_overview->uiEndDate; ToolTipAlignment align=TT_AlignCenter; QString dateFormatted =dateEntered.toString(m_overview->shortformat); QString txt = QString(tr("ERROR\nThe start date MUST be before the end date")); txt.append("\n"); if (startDate) { txt.append(tr("The entered start date %1 is after the end date %2").arg(dateFormatted).arg(m_endDate.toString(m_overview->shortformat))); txt.append(tr("\nHint: Change the end date first")); } else { txt.append(tr("The entered end date %1 ").arg(dateFormatted)); txt.append(tr("is before the start date %1").arg(m_startDate.toString(m_overview->shortformat))); txt.append(tr("\nHint: Change the start date first")); } gGraphView* gv=m_overview->graphView(); int pixelsFromLeft = m_overview->calculatePixels(startDate,align); int pixelsAboveBottom = 0; int warningDurationMS =4000; int resetUiDatesTimerDelayMS =1000; //QFont* font=mediumfont; QFont* font=defaultfont; gv->m_parent_tooltip->display(gv,txt,pixelsAboveBottom,pixelsFromLeft, align, warningDurationMS , font); m_timer->setInterval( resetUiDatesTimerDelayMS ); m_timer->setSingleShot(true); m_timer->start(); }; void Overview::resetUiDates() { ui->dateStart->blockSignals(true); ui->dateStart->setMinimumDate(p_profile->FirstDay()); // first and last dates for ANY machine type ui->dateStart->setMaximumDate(p_profile->LastDay()); ui->dateStart->setDate(uiStartDate); ui->dateStart->blockSignals(false); ui->dateEnd->blockSignals(true); ui->dateEnd->setMinimumDate(p_profile->FirstDay()); // first and last dates for ANY machine type ui->dateEnd->setMaximumDate(p_profile->LastDay()); ui->dateEnd->setDate(uiEndDate); ui->dateEnd->blockSignals(false); } // Saves dates in UI, clicks zoom button, and updates combo box // 1. Updates the dates in the start / end date boxs // 2. optionally also changes display range for graphs. void Overview::setRange(QDate& start, QDate& end, bool updateGraphs/*zoom*/) { if (start>end) { // this is an ERROR and shold NEVER occur. return; } // first setting of the date (setDate) will cause pageChanged to be executed. // The pageChanged processing requires access to the other ui's date. // so save them to memory before the first call. uiStartDate =start; uiEndDate = end; //bool nextSamePage= start.daysTo(end)<=31; bool nextSamePage= (start.year() == end.year() && start.month() == end.month()) ; if (samePage>0 ||nextSamePage) { // The widgets do not signal pageChanged on opening - since the page hasn't changed. // however the highlighting may need to be changed. // The following forces a page change signal when calendar widget is opened. ui->dateStart->calendarWidget()->setCurrentPage(1970,1); ui->dateEnd->calendarWidget()->setCurrentPage(1970,1); samePage=nextSamePage; } resetUiDates(); if (updateGraphs) SetXBounds(uiStartDate,uiEndDate); updateGraphCombo(); } void Overview::showGraph(int index,bool show, bool updateGraph) { ui->graphCombo->setItemData(index,show,Qt::UserRole); ui->graphCombo->setItemIcon(index, show ? *icon_on : *icon_off); if (!updateGraph) return; QString graphName = ui->graphCombo->itemText(index); gGraph* graph=GraphView->findGraphTitle(graphName); if (graph) graph->setVisible(show); } void Overview::showAllGraphs(bool show) { //Skip over first button - label for comboBox int lastIndex = ui->graphCombo->count()-1; for (int i=1;i 0) { bool nextOn =!ui->graphCombo->itemData(index,Qt::UserRole).toBool(); int lastIndex = ui->graphCombo->count()-1; if ( index == lastIndex ) { // user just pressed hide show button - toggle states of the button and apply the new state showAllGraphs(nextOn); showGraph(index,nextOn,false); } else { showGraph(index,nextOn,true); } ui->graphCombo->showPopup(); } ui->graphCombo->setCurrentIndex(0); updateCube(); setGraphText(); GraphView->updateScale(); GraphView->redraw(); } void Overview::updateCube() { if ((GraphView->visibleGraphs() == 0)) { if (ui->graphCombo->count() > 0) { GraphView->setEmptyText(STR_Empty_NoGraphs); } else { GraphView->setEmptyText(STR_Empty_NoData); } } } void Overview::on_layout_clicked() { if (!saveGraphLayoutSettings) { saveGraphLayoutSettings= new SaveGraphLayoutSettings("overview",this); } if (saveGraphLayoutSettings) { saveGraphLayoutSettings->menu(GraphView); } } OSCAR-code-v1.5.1/oscar/overview.h000066400000000000000000000140751450332542600166240ustar00rootroot00000000000000/* 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 #include "SleepLib/profiles.h" #include "Graphs/gGraphView.h" #ifndef REMOVE_FITNESS #include "Graphs/gOverviewGraph.h" #endif #include "Graphs/gSummaryChart.h" #include "saveGraphLayoutSettings.h" #include #include #include namespace Ui { class Overview; } class Report; class Overview; class DateErrorDisplay:QObject { Q_OBJECT public: DateErrorDisplay (Overview* overview) ; ~DateErrorDisplay() ; bool visible() {return m_visible;}; void cancel(); void error(bool startDate,const QDate& date); protected: private: QTimer* m_timer; bool m_visible=false; Overview* m_overview; QDate m_startDate; QDate m_endDate; private slots: void timerDone(); }; enum YTickerType { YT_Number, YT_Time, YT_Weight }; /*! \class Overview \author Mark Watkins \brief Overview tab, showing overall summary data */ class Overview : public QWidget { friend class DateErrorDisplay; 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,bool updateGraphs=true); /*! \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; #ifndef REMOVE_FITNESS gOverviewGraph *bc, *sa, *us, *pr, *set, *ses, *ptb, *pulse, *spo2, *weight, *zombie, *bmi, *ahihr, *tgmv, *totlk; #endif gSummaryChart * stg, *uc, *ahi, * pres, *lk, *npb, *rr, *mv, *tv, *nll, *sn, *ttia; 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(); void on_XBoundsChanged(qint64 ,qint64); void on_summaryChartEmpty(gSummaryChart*,qint64,qint64,bool); //! \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_rangeCombo_activated(int index); void on_graphCombo_activated(int index); void on_LineCursorUpdate(double time); void on_RangeUpdate(double minx, double maxx); void setGraphText (); void on_layout_clicked(); private: void CreateAllGraphs(); void timedUpdateOverview(int ms=0); 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,bool startDateWidget); void updateCube(); Day *day; // dummy in this case bool checkRangeChanged(QDate& first, QDate& last); void connectgSummaryCharts() ; void disconnectgSummaryCharts() ; void SetXBounds(qint64 minx, qint64 maxx, short group = 0, bool refresh = true); void SetXBounds(QDate & start, QDate& end, short group =0 , bool refresh = true); void resetUiDates(); DateErrorDisplay* dateErrorDisplay; int calculatePixels(bool startDate,ToolTipAlignment& align); QString shortformat; // Start and of dates of the current graph display QDate displayStartDate; QDate displayEndDate; // min / max dates of the graph Range QDate minRangeStartDate; QDate maxRangeEndDate; QHash chartsToBeMonitored; QHash chartsEmpty; bool settingsLoaded ; // Actual dates displayed in Start/End Widgets. QDate uiStartDate; QDate uiEndDate; // Are start and end widgets displaying the same month. bool samePage; SaveGraphLayoutSettings* saveGraphLayoutSettings=nullptr; QString STR_HIDE_ALL_GRAPHS =QString(tr("Hide All Graphs")); QString STR_SHOW_ALL_GRAPHS =QString(tr("Show All Graphs")); void showGraph(int index,bool show, bool updateGraph); void showAllGraphs(bool show); }; #endif // OVERVIEW_H OSCAR-code-v1.5.1/oscar/overview.ui000066400000000000000000000156271450332542600170160ustar00rootroot00000000000000 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 Layout :/icons/cog.png:/icons/cog.png Save and Restore Graph Layout Settings Drop down to see list of graphs to switch on/off. QComboBox::AdjustToContents Graphs OSCAR-code-v1.5.1/oscar/oximeterimport.cpp000066400000000000000000001246041450332542600204000ustar00rootroot00000000000000/* 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 #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); QElapsedTimer 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::asprintf("%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::asprintf("%3i", pulse)); } else { ui->pulseDisplay->display("---"); } if (spo2 > 0) { ui->spo2Display->display(QString::asprintf("%2i", spo2)); } else { ui->spo2Display->display("--"); } ui->lcdDuration->display(QString::asprintf("%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 device 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 device"; mach->AddSession(session); qDebug() << "oximod - Saving device"; 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::asprintf("%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.5.1/oscar/oximeterimport.h000066400000000000000000000052561450332542600200460ustar00rootroot00000000000000/* 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.5.1/oscar/oximeterimport.ui000066400000000000000000002063111450332542600202270ustar00rootroot00000000000000 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 1 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 CMS50D+/E/F, Pulox PO-200/300 CMS50Fv3.7+/H/I, CMS50D+v4.6, Pulox PO-400/500 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 device. 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.5.1/oscar/oximetry.ui000066400000000000000000000340661450332542600170260ustar00rootroot00000000000000 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.5.1/oscar/pch.h000066400000000000000000000020001450332542600155110ustar00rootroot00000000000000#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.5.1/oscar/preferencesdialog.cpp000066400000000000000000001545431450332542600207770ustar00rootroot00000000000000/* 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. */ #define TEST_MACROS_ENABLEDoff #include #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" #include "highresolution.h" extern QFont *defaultfont; extern QFont *mediumfont; extern QFont *bigfont; extern MainWindow *mainwin; typedef QMessageBox::StandardButton StandardButton; typedef QMessageBox::StandardButtons StandardButtons; QHash channeltype; QString PreferencesDialog::clinicalHelp() { QStringList str; str <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 devices detected"), tr("Will you be using a ResMed brand device?"), 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 devices 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 devices, 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()); //change initialization from hard coded in schema to profile data. ui->oxiDesaturationThreshold->setValue(profile->oxi->oxiDesaturationThreshold()); ui->flagPulseAbove->setValue(profile->oxi->flagPulseAbove()); ui->flagPulseBelow->setValue(profile->oxi->flagPulseBelow()); ui->spo2Drop->setValue(profile->oxi->spO2DropPercentage()); ui->spo2DropDuration->setValue(profile->oxi->spO2DropDuration()); ui->pulseChange->setValue(profile->oxi->pulseChangeBPM()); ui->pulseChangeDuration->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->eventFlagSessionBar->setChecked(profile->appearance->eventFlagSessionBar()); ui->complianceHours->setValue(profile->cpap->complianceHours()); ui->clinicalMode->setChecked(profile->cpap->clinicalMode()); ui->clinicalTextEdit->setPlainText(clinicalHelp()); // clinicalMode and permissiveMode are radio buttons and must be set to opposite values. Once clinicalMode is used. // Radio Buttons illustrate the operating mode. ui->permissiveMode->setChecked(!profile->cpap->clinicalMode()); HighResolution::checkBox(false,ui->highResolution); 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->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()); #ifndef REMOVE_FITNESS ui->overviewLinecharts->setCurrentIndex(AppSetting->overviewLinechartMode()); #else ui->overviewLinecharts->hide(); ui->overviewLinechartsLabel->hide(); #endif 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; } } 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()); p_profile->appearance->setEventFlagSessionBar(ui->eventFlagSessionBar->isChecked()); p_profile->cpap->setClinicalMode(ui->clinicalMode->isChecked()); HighResolution::checkBox(true,ui->highResolution); 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->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()); #ifndef REMOVE_FITNESS AppSetting->setOverviewLinechartMode((OverviewLinechartModes)ui->overviewLinecharts->currentIndex()); #endif profile->oxi->setSpO2DropPercentage(ui->spo2Drop->value()); profile->oxi->setSpO2DropDuration(ui->spo2DropDuration->value()); profile->oxi->setPulseChangeBPM(ui->pulseChange->value()); profile->oxi->setPulseChangeDuration(ui->pulseChangeDuration->value()); profile->oxi->setOxiDiscardThreshold(ui->oxiDiscardThreshold->value()); profile->oxi->setOxiDesaturationThreshold(ui->oxiDesaturationThreshold->value()); profile->oxi->setFlagPulseAbove(ui->flagPulseAbove->value()); profile->oxi->setFlagPulseBelow(ui->flagPulseBelow->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(); profile->resetOxiChannelPref(); 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 devices 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_resetOxiMetryDefaults_clicked() { if (QMessageBox::question(this, STR_MessageBox_Warning, QObject::tr("Are you sure you want to reset all your oximetry settings to defaults?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes) { // reset ui with defaul values ui->spo2Drop->setValue( profile->oxi->defaultValue_OS_SPO2DropPercentage); ui->spo2DropDuration->setValue( profile->oxi->defaultValue_OS_SPO2DropDuration); ui->pulseChange->setValue( profile->oxi->defaultValue_OS_PulseChangeBPM); ui->pulseChangeDuration->setValue(profile->oxi->defaultValue_OS_PulseChangeDuration); ui->oxiDiscardThreshold->setValue(profile->oxi->defaultValue_OS_OxiDiscardThreshold); ui->oxiDesaturationThreshold->setValue( profile->oxi->defaultValue_OS_oxiDesaturationThreshold); ui->flagPulseAbove->setValue( profile->oxi->defaultValue_OS_flagPulseAbove ); ui->flagPulseBelow->setValue( profile->oxi->defaultValue_OS_flagPulseBelow ); if (Save() ) { // comment accept out to return to the preference tab // other wise the preference tab will close and return accept(); } } else { // restore values changed ui->spo2Drop->setValue(profile->oxi->spO2DropPercentage()); ui->spo2DropDuration->setValue(profile->oxi->spO2DropDuration()); ui->pulseChange->setValue(profile->oxi->pulseChangeBPM()); ui->pulseChangeDuration->setValue(profile->oxi->pulseChangeDuration()); ui->oxiDiscardThreshold->setValue(profile->oxi->oxiDiscardThreshold()); ui->oxiDesaturationThreshold->setValue(profile->oxi->defaultValue_OS_oxiDesaturationThreshold); ui->flagPulseAbove->setValue( profile->oxi->defaultValue_OS_flagPulseAbove ); ui->flagPulseBelow->setValue( profile->oxi->defaultValue_OS_flagPulseBelow ); } } 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(QString("%1 %2").arg(value/10.0f, 5,'f',1).arg(STR_UNIT_LPM)); } void PreferencesDialog::on_maskLeaks20Slider_valueChanged(int value) { ui->maskLeaks20Label->setText(QString("%1 %2").arg(value/10.0f, 5,'f',1).arg(STR_UNIT_LPM)); } void PreferencesDialog::on_calculateUnintentionalLeaks_toggled(bool) { } OSCAR-code-v1.5.1/oscar/preferencesdialog.h000066400000000000000000000061731450332542600204370ustar00rootroot00000000000000/* 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 #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(); QString clinicalHelp(); #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); void on_resetOxiMetryDefaults_clicked(); 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.5.1/oscar/preferencesdialog.ui000066400000000000000000004174101450332542600206250ustar00rootroot00000000000000 PreferencesDialog Qt::ApplicationModal 0 0 942 737 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 <html><head/><body><p><span style=" font-family:'Cantarell'; font-size:11pt;">Sessions shorter in duration than this will not be displayed</span><span style=" font-family:'Cantarell'; font-size:11pt; font-style:italic;">.</span></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 devices, ResMed S9 series devices 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 device model that has not yet been tested by OSCAR developers.</p></body></html> Warn when importing data from an untested device <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 device. (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 device 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 device detected event positioning. Resync Device Detected Events (Experimental) Qt::Vertical 20 40 #2 Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 0 0 Flow Restriction 0 0 true <html><head/><body><p><span style=" font-family:'Sans'; font-size:10pt;">Custom flagging is an experimental method of detecting events missed by the device. They are </span><span style=" font-family:'Sans'; font-size:10pt; text-decoration: underline;">not</span><span style=" font-family:'Sans'; font-size:10pt;"> included in AHI.</span></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 device events. 0 0 Event Duration QLayout::SetMinimumSize 5 5 0 0 General CPAP and Related Settings Show flags for device detected events that haven't been identified yet. Enable Unknown Events Channels AHI/Hour Graph Time Window 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 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 devices do not support changing these settings.</p></body></html> Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop true Qt::Vertical 20 40 Clinical 4 2 2 2 2 false true Clinical Settings false 4 8 4 4 5 Regard days with under this usage as "incompliant". 4 hours is usually considered compliant. Compliance defined as Qt::Vertical 20 40 false 0 0 Reset &Defaults 0 0 Select Oscar Operating Mode Clinical Mode does not allow disabled sessions.\nDisabled Session are not used for graphing or Statistics. Clinical Mode true false permissive Mode allows disabled sessions.\nDisabled Session are used for graphing and Statistics. Permissive Mode Hours 1 8.000000000000000 4.000000000000000 true &Oximetry 4 2 2 2 2 Oximetry Settings - Not Currently Functional 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 150 <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 Reset &Defaults 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:'Ubuntu'; 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;"> </p> <p style=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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=" margin-top:12px; margin-bottom:12px; 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 device, you can now also achieve sync. </span></p> <p align="justify" style=" margin-top:12px; margin-bottom:12px; 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 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 1000 5000 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 false <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 device serial number on device 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) For multiple sessions, displays a thin gray line for each session at the top of the Event Flag graph. Enables SessionBar in Event Flags Graph Enables High Resoluton Mode. Changes take effect when Oscar is restarted. Enables High Resolutiom Mode 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.5.1/oscar/profileselector.cpp000066400000000000000000000473611450332542600205160ustar00rootroot00000000000000/* 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 device 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.5.1/oscar/profileselector.h000066400000000000000000000033551450332542600201560ustar00rootroot00000000000000/* 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.5.1/oscar/profileselector.ui000066400000000000000000000243111450332542600203370ustar00rootroot00000000000000 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.5.1/oscar/rawdata.cpp000066400000000000000000000105241450332542600167270ustar00rootroot00000000000000/* 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.5.1/oscar/rawdata.h000066400000000000000000000031421450332542600163720ustar00rootroot00000000000000/* 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.5.1/oscar/reports.cpp000066400000000000000000000576751450332542600170240ustar00rootroot00000000000000/* 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. */ #define TEST_MACROS_ENABLEDoff #include #include #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->setPageOrientation(QPageLayout::Portrait); printer->setFullPage(false); // This has nothing to do with scaling printer->setCopyCount(1); printer->setPageMargins(QMarginsF(10, 10, 10, 10), QPageLayout::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->pageLayout().paintRectPixels( printer->resolution() ) ; 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.5.1/oscar/reports.h000066400000000000000000000015621450332542600164510ustar00rootroot00000000000000/* 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.5.1/oscar/reports.ui000066400000000000000000000021761450332542600166410ustar00rootroot00000000000000 Report 0 0 549 338 Form 0 0 -14 Qt::LeftToRight about:blank QWebView QWidget
    QWebView
    OSCAR-code-v1.5.1/oscar/saveGraphLayoutSettings.cpp000066400000000000000000000746401450332542600221540ustar00rootroot00000000000000/* user graph settings Implementation * * Copyright (c) 2022-2023 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. */ #define TEST_MACROS_ENABLEDoff #include #include #include #include #include #include #include "SleepLib/profiles.h" #include "saveGraphLayoutSettings.h" #define USE_FRAMELESS_WINDOW #define USE_PROFILE_SPECIFIC_FOLDERoff // off implies saved layouts worked for all profiles. SaveGraphLayoutSettings::SaveGraphLayoutSettings(QString title,QWidget* parent) : parent(parent),title(title) { fontSizeIncrease = 1; helpFontSizeIncrease = 1; createSaveFolder(); if (dir==nullptr) return; dir->setFilter(QDir::Files | QDir::Readable | QDir::Writable | QDir::NoSymLinks); QString descFileName = dirName+title.toLower()+".descriptions.txt"; descriptionMap = new DescriptionMap (dir,descFileName); createMenu() ; menuDialog->connect(menuAddFullBtn, SIGNAL(clicked()), this, SLOT (addFull_feature() )); menuDialog->connect(menuAddBtn, SIGNAL(clicked()), this, SLOT (add_feature() )); menuDialog->connect(menuRestoreBtn, SIGNAL(clicked()), this, SLOT (restore_feature() )); menuDialog->connect(menuUpdateBtn, SIGNAL(clicked()), this, SLOT (update_feature() )); menuDialog->connect(menuRenameBtn, SIGNAL(clicked()), this, SLOT (rename_feature() )); menuDialog->connect(menuDeleteBtn, SIGNAL(clicked()), this, SLOT (delete_feature() )); menuDialog->connect(menuHelpBtn, SIGNAL(clicked()), this, SLOT (help_feature() )); menuDialog->connect(menuExitBtn, SIGNAL(clicked()), this, SLOT (exit() )); menuDialog->connect(menuList, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(itemChanged(QListWidgetItem*) )); menuDialog->connect(menuList, SIGNAL(itemSelectionChanged()), this, SLOT(itemSelectionChanged() )); singleLineRe = new QRegularExpression( QString("^\\s*([^\\r\\n]{0,%1})").arg(1+maxDescriptionLen) ); fileNumRe = new QRegularExpression( QString("%1(\\d+)(.shg)?$").arg(fileBaseName) ); parseFilenameRe = new QRegularExpression(QString("^(%1[.](%2(\\d*)))[.]shg$").arg(title).arg(fileBaseName)); } SaveGraphLayoutSettings::~SaveGraphLayoutSettings() { if (dir==nullptr) {return;} menuDialog->disconnect(menuAddFullBtn, SIGNAL(clicked()), this, SLOT (addFull_feature() )); menuDialog->disconnect(menuAddBtn, SIGNAL(clicked()), this, SLOT (add_feature() )); menuDialog->disconnect(menuRestoreBtn, SIGNAL(clicked()), this, SLOT (restore_feature() )); menuDialog->disconnect(menuUpdateBtn, SIGNAL(clicked()), this, SLOT (update_feature() )); menuDialog->disconnect(menuRenameBtn, SIGNAL(clicked()), this, SLOT (rename_feature() )); menuDialog->disconnect(menuDeleteBtn, SIGNAL(clicked()), this, SLOT (delete_feature() )); menuDialog->disconnect(menuExitBtn, SIGNAL(clicked()), this, SLOT (exit() )); menuDialog->disconnect(menuHelpBtn, SIGNAL(clicked()), this, SLOT (help_feature() )); menuDialog->disconnect(menuList, SIGNAL(itemChanged(QListWidgetItem*)), this, SLOT(itemChanged(QListWidgetItem*) )); menuDialog->disconnect(menuList, SIGNAL(itemSelectionChanged()), this, SLOT(itemSelectionChanged() )); helpDestructor(); delete descriptionMap; delete singleLineRe; delete fileNumRe; delete parseFilenameRe; } void SaveGraphLayoutSettings::createSaveFolder() { // Insure that the save folder exists // Get the directory name for the save files //QString layoutFileFolder = "savedGraphLayoutSettings/"; QString layoutFileFolder = "layoutSettings/"; #if 0 // home directory for the current profile. QString baseName = p_profile->Get("{DataFolder}/"); #else // home directory for all profiles. // allows settings to be shared accross profiles. QString baseName = p_pref->Get("{home}/"); #endif dirName = baseName+layoutFileFolder; // Check if the save folder exists QDir* tmpDir = new QDir(dirName); if (!tmpDir->exists()) { QDir* baseDir=new QDir(baseName); if (!baseDir->exists()) { // Base folder does not exist - terminate return ; } // saved Setting folder does not exist. make it if (!baseDir->mkdir(dirName)) { // Did not create the folder. return ; } tmpDir = new QDir(dirName); // double check if save folder exists or not. if (!tmpDir->exists()) { return ; } } dir=tmpDir; } QPushButton* SaveGraphLayoutSettings::menuBtn(QString name, QIcon* icon, QString style,QSizePolicy::Policy hPolicy,QString tooltip) { return newBtnRtn(menuLayoutButtons, name, icon, style, hPolicy,tooltip) ; } QPushButton* SaveGraphLayoutSettings::helpBtn(QString name, QIcon* icon, QString style,QSizePolicy::Policy hPolicy,QString tooltip) { return newBtnRtn(helpLayoutButtons, name, icon, style, hPolicy,tooltip) ; } QPushButton* SaveGraphLayoutSettings::newBtnRtn(QHBoxLayout* layout,QString name, QIcon* icon, QString style,QSizePolicy::Policy hPolicy,QString tooltip) { QPushButton* button = new QPushButton(name, menuDialog); button->setIcon(*icon); button->setStyleSheet(style); button->setSizePolicy(hPolicy,QSizePolicy::Fixed); button->setToolTip(tooltip); button->setToolTipDuration(AppSetting->tooltipTimeout()); button->setFont(*mediumfont); layout->addWidget(button); return button; } void SaveGraphLayoutSettings::createStyleSheets() { styleOn= calculateButtonStyle(true,false); styleOff= calculateButtonStyle(false,false); styleExit= calculateButtonStyle(true,true); styleMessageBox= "QMessageBox { " "background-color:yellow;" //"color:black;" "color:red;" "border: 2px solid black;" "text-align: right;" "font-size: 16px;" "}" "QTextEdit {" "background-color:lightblue;" //"color:black;" "color:red;" "border: 9px solid black;" "text-align: center;" "vertical-align:top;" "}" ; styleDialog= "QDialog { " "border: 3px solid black;" "}"; } void SaveGraphLayoutSettings::createMenu() { menuListFont =*defaultfont; menuListFont.setPointSize(fontSizeIncrease+menuListFont.pointSize()); createStyleSheets(); #ifdef USE_FRAMELESS_WINDOW menuDialog= new QDialog(parent, Qt::FramelessWindowHint); menuDialog->setSizeGripEnabled(true); // allows lower right hand corner to resize dialog #else menuDialog= new QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint|Qt::WindowSystemMenuHint); menuDialog->setWindowTitle(tr("Manage Save Layout Settings")) ; #endif menuDialog->setStyleSheet(styleDialog); menuLayout = new QVBoxLayout(menuDialog); menuLayoutButtons = new QHBoxLayout(); menuAddFullBtn = menuBtn(tr("Add" ),m_icon_add , styleOff ,QSizePolicy::Minimum, tr("Add Feature inhibited. The maximum number of Items has been exceeded.") ); menuAddBtn = menuBtn(tr("Add" ),m_icon_add , styleOn ,QSizePolicy::Minimum, tr("creates new copy of current settings.")); menuRestoreBtn = menuBtn(tr("Restore"),m_icon_restore , styleOn ,QSizePolicy::Minimum, tr("Restores saved settings from selection.")); menuRenameBtn = menuBtn(tr("Rename" ),m_icon_rename , styleOn ,QSizePolicy::Minimum, tr("Renames the selection. Must edit existing name then press enter.")); menuUpdateBtn = menuBtn(tr("Update" ),m_icon_update , styleOn ,QSizePolicy::Minimum, tr("Updates the selection with current settings.")); menuDeleteBtn = menuBtn(tr("Delete" ),m_icon_delete , styleOn ,QSizePolicy::Minimum, tr("Deletes the selection.")); menuHelpBtn = menuBtn("" ,m_icon_help , styleOn ,QSizePolicy::Fixed , tr("Expanded Help menu.")); menuExitBtn = menuBtn("" ,m_icon_exit , styleExit,QSizePolicy::Fixed , tr("Exits the Layout menu.")); #ifndef USE_FRAMELESS_WINDOW menuExitBtn->hide(); #endif menuList = new QListWidget(menuDialog); menuList->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); menuList->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); menuList->setFont(menuListFont); menuLayout->addLayout(menuLayoutButtons); menuLayout->addWidget(menuList, 1); //menuList->setSizePolicy(QSizePolicy::Preferred,QSizePolicy::Preferred); menuList->setSizePolicy(QSizePolicy::Minimum,QSizePolicy::Minimum); }; void SaveGraphLayoutSettings::createHelp() { if(helpDialog) return; helpDialog= new QDialog(parent, Qt::FramelessWindowHint); helpDialog->setStyleSheet(styleDialog); menuDialog->setSizeGripEnabled(true); // allows lower right hand corner to resize dialog QFont helpInfoFont = menuListFont; helpInfoFont.setPointSize(helpFontSizeIncrease+helpInfoFont.pointSize()); QFont helpInfoLabelFont = helpInfoFont; helpInfoLabelFont.setPointSize(fontSizeIncrease+ helpInfoFont.pointSize()); QLabel* helpInfoHeaderLabel = new QLabel("helpInfoHeaderLabel",parent); helpInfoHeaderLabel->setText(QString( tr("

    Help Menu - Manage Layout Settings

    "))); helpInfoHeaderLabel->setFont(helpInfoLabelFont); QLabel* helpInfoLabel = new QLabel("helpInfo",parent); helpInfoLabel->setFont(helpInfoFont); helpInfoLabel->setText(helpInfo()) ; helpLayoutButtons = new QHBoxLayout(); helpLayoutButtons->addWidget(helpInfoHeaderLabel); helpInfoExitBtn= helpBtn("" ,m_icon_return , styleOn ,QSizePolicy::Fixed , tr("Exits the help menu.")); helpExitBtn = helpBtn("" ,m_icon_exit , styleExit,QSizePolicy::Fixed , tr("Exits the dialog menu.")); QVBoxLayout* helpLayout = new QVBoxLayout(helpDialog); helpLayout->addLayout(helpLayoutButtons); helpLayout->addWidget(helpInfoLabel, 1); helpDialog->connect(helpInfoExitBtn,SIGNAL(clicked()), this, SLOT (help_off_feature() )); helpDialog->connect(helpExitBtn, SIGNAL(clicked()), this, SLOT (help_exit_feature() )); } void SaveGraphLayoutSettings::helpDestructor() { if(!helpDialog) return; helpDialog->disconnect(helpInfoExitBtn,SIGNAL(clicked()), this, SLOT (help_off_feature() )); helpDialog->disconnect(helpExitBtn, SIGNAL(clicked()), this, SLOT (help_exit_feature() )); } QString SaveGraphLayoutSettings::helpInfo() { QStringList strList; strList<") <") <") <


    ") < ") <
    ") < ") <") <") <") <
    ") < ") <
    ") < ") <
    ") <") <
    ") <") <") <
    ") < ") <") <
    ") <
    ") < ") <
    ") < ") <
    ") < ") <

    ") <

    ") < ") < ") < ") <
    ") < ") < ") < ") <

    ") <

    • ") <
    • ") <
    • ") <") <") <") <
    • ") <
    "); return strList.join("\n"); }; const QString SaveGraphLayoutSettings::calculateStyleMessageBox(QFont* font , QString& s1, QString& s2) { QFontMetrics fm = QFontMetrics(*font); int width = fm.boundingRect(s1).size().width(); int width2 = fm.boundingRect(s2).size().width(); width = qMax(width,width2) + iconWidthMessageBox; QString style = QString("%1 QLabel{%2 min-width: %3px;}" ). arg(styleMessageBox). arg("text-align: center;" "color:black;"). arg(width); return style; } bool SaveGraphLayoutSettings::verifyItem(QListWidgetItem* item,QString text,QIcon* icon) { //if (verifyhelp() ) return false; if (item) return true; initminMenuListSize(); confirmAction( text ,"" , icon); return false; } #if 1 bool SaveGraphLayoutSettings::confirmAction(QString top ,QString bottom,QIcon* icon,QMessageBox::StandardButtons flags , QMessageBox::StandardButton adefault, QMessageBox::StandardButton success) { //QString topText=QString("
    %1
    ").arg(top); //QString bottomText=QString("
    %1
    ").arg(bottom); QString topText=QString("

    %1

    ").arg(top); QString bottomText=QString("

    %1

    ").arg(bottom); QMessageBox msgBox(menuDialog); msgBox.setText(topText); msgBox.setInformativeText(bottomText); if (icon!=nullptr) { msgBox.setIconPixmap(icon->pixmap(QSize(iconWidthMessageBox,iconWidthMessageBox))); } // may be good for help menu. a pull down box with box of data. msgBox.setDetailedText("some detailed string"); msgBox.setStandardButtons(flags); msgBox.setDefaultButton(adefault); msgBox.setWindowFlag(Qt::FramelessWindowHint,true); msgBox.setStyleSheet(calculateStyleMessageBox(&menuListFont,top,bottom)); // displaywidgets((QWidget*)&msgBox); bool ret= msgBox.exec()==success; return ret; } #else bool SaveGraphLayoutSettings::confirmAction(QString name ,QString question,QIcon* icon,QMessageBox::StandardButtons flags , QMessageBox::StandardButton adefault, QMessageBox::StandardButton success) { //bool SaveGraphLayoutSettings::confirmAction(QString name,QString question,QIcon* icon) Q_UNUSED(flags); Q_UNUSED(adefault); Q_UNUSED(success); QMessageBox msgBox(menuDialog); msgBox.setText(question); if (icon!=nullptr) { msgBox.setIconPixmap(icon->pixmap(QSize(50,50))); } msgBox.setStandardButtons(QMessageBox::Cancel | QMessageBox::Yes ); msgBox.setDefaultButton(QMessageBox::Cancel); msgBox.setStyleSheet(styleMessageBox); msgBox.setWindowFlag(Qt::FramelessWindowHint,true); return (msgBox.exec()==QMessageBox::Yes); Q_UNUSED(name); } #endif QString SaveGraphLayoutSettings::calculateButtonStyle(bool on,bool exitBtn){ QString btnStyle=QString("QPushButton{%1%2}").arg(on ?"color: black;background-color:white;" :"color: darkgray;background-color:#e8e8e8 ;" ).arg(!exitBtn ? "" : "background: transparent;" "border-radius: 8px;" "border: 2px solid transparent;" "max-width:1em;" "border:none;" ); QString toolTipStyle=" QToolTip { " "border: 1px solid black;" "border-width: 1px;" "padding: 4px;" "font: 14px ; color:black; background-color:yellow;" "}"; btnStyle.append(toolTipStyle); return btnStyle; } void SaveGraphLayoutSettings::looksOn(QPushButton* button,bool on){ //button->setEnabled(on); button->setStyleSheet(on?styleOn:styleOff); } void SaveGraphLayoutSettings::manageButtonApperance() { bool enable = menuList->currentItem(); looksOn(menuUpdateBtn,enable); looksOn(menuRestoreBtn,enable); looksOn(menuDeleteBtn,enable); looksOn(menuRenameBtn,enable); if (nextNumToUse<0) { // check if at Maximum limit // must always hide first menuAddBtn->hide(); menuAddFullBtn->show(); } else { // must always hide first menuAddFullBtn->hide(); menuAddBtn->show(); } } void SaveGraphLayoutSettings::add_feature() { if(!graphView) return; QString fileName = QString("%1%2").arg(fileBaseName).arg(nextNumToUse,fileNumMaxLength,10,QLatin1Char('0')); writeSettings(fileName); // create a default description - use formatted datetime. QString desc=QDateTime::currentDateTime().toString(); descriptionMap->add(fileName,desc); descriptionMap->save(); QListWidgetItem* item = updateFileList( fileName); if (item!=nullptr) { menuList->setCurrentItem(item,QItemSelectionModel::ClearAndSelect); menuList->editItem(item); } menuList->sortItems(); resizeMenu(); } void SaveGraphLayoutSettings::addFull_feature() { verifyItem( nullptr,tr("Maximum number of Items exceeded.") , m_icon_add); } void SaveGraphLayoutSettings::update_feature() { if(!graphView) return; QListWidgetItem * item=menuList->currentItem(); if (!verifyItem(item, tr("No Item Selected") , m_icon_update)) return ; if(!confirmAction( item->text(), tr("Ok to Update?") , m_icon_update) ) return; QString fileName = item->data(fileNameRole).toString(); writeSettings(fileName); }; void SaveGraphLayoutSettings::restore_feature() { if(!graphView) return; QListWidgetItem * item=menuList->currentItem(); if (!verifyItem(item, tr("No Item Selected") , m_icon_restore)) return ; QString fileName = item->data(fileNameRole).toString(); loadSettings(fileName); exit(); }; void SaveGraphLayoutSettings::rename_feature() { if(!graphView) return; QListWidgetItem * item=menuList->currentItem(); if (!verifyItem(item, tr("No Item Selected") , m_icon_rename)) return ; menuList->editItem(item); // SaveGraphLayoutSettings::itemChanged(QListWidgetItem *item) is called when edit changes the entry. // itemChanged will update the description map } void SaveGraphLayoutSettings::help_exit_feature() { helpDialog->close(); exit(); } void SaveGraphLayoutSettings::help_off_feature() { helpDialog->close(); } void SaveGraphLayoutSettings::help_feature() { initminMenuListSize(); createHelp(); if(!helpDialog) return; helpDialog->raise(); helpDialog->exec(); manageButtonApperance(); } void SaveGraphLayoutSettings::delete_feature() { if(!graphView) return; QListWidgetItem * item=menuList->currentItem(); if (!verifyItem(item, tr("No Item Selected") , m_icon_delete)) return ; if(!confirmAction(item->text(), tr("Ok To Delete?") ,m_icon_delete) ) return; QString fileName = item->data(fileNameRole).toString(); descriptionMap->remove(fileName); descriptionMap->save(); deleteSettings(fileName); delete item; if (nextNumToUse<0) { nextNumToUse=fileNum(fileName); } manageButtonApperance(); resizeMenu(); } void SaveGraphLayoutSettings::itemChanged(QListWidgetItem *item) { QString fileName=item->data(fileNameRole).toString(); QString desc= item->text(); // use only the first line in a multiline string. Can be set using cut and paste QRegularExpressionMatch match = singleLineRe->match(desc); if (match.hasMatch()) { // captured match is the first line and has been truncated desc=match.captured(1).trimmed(); // reoves spaces at end. } else { // no match. // an invalid name was entered. too much white space or empty desc=""; } if (desc.length()>maxDescriptionLen) { desc.append("..."); } if (desc.length() <=0) { // returns name back to previous saved name desc=descriptionMap->get(fileName); } else { descriptionMap->add(fileName,desc); descriptionMap->save(); } item->setText(desc); menuList->sortItems(); menuList->setCurrentItem(item); resizeMenu(); } void SaveGraphLayoutSettings::itemSelectionChanged() { initminMenuListSize(); manageButtonApperance(); } void SaveGraphLayoutSettings::initminMenuListSize() { if (minMenuDialogSize.width()==0) { menuDialogSize = menuDialog->size(); minMenuDialogSize = menuDialogSize; menuListSize = menuList->size(); minMenuListSize = menuListSize; dialogListDiff = menuDialogSize - menuListSize; dialogListDiff.setWidth (horizontalWidthAdjustment + dialogListDiff.width()); resizeMenu(); } }; void SaveGraphLayoutSettings::writeSettings(QString filename) { graphView->SaveSettings(title+"."+filename,dirName); }; void SaveGraphLayoutSettings::loadSettings(QString filename) { graphView->LoadSettings(title+"."+filename,dirName); }; void SaveGraphLayoutSettings::deleteSettings(QString filename) { QString fileName=graphView->settingsFilename (title+"."+filename,dirName) ; dir->remove(fileName); }; int SaveGraphLayoutSettings::fileNum(QString fileName) { QRegularExpressionMatch match = fileNumRe->match(fileName); int value=-1; if (match.hasMatch()) { value=match.captured(1).toInt(); } return value; } QSize SaveGraphLayoutSettings::maxSize(const QSize AA , const QSize BB ) { return QSize ( qMax(AA.width(),BB.width()) , qMax(AA.height(),BB.height() ) ); } bool SaveGraphLayoutSettings::sizeEqual(const QSize AA , const QSize BB ) { return (AA.width()==BB.width()) && ( AA.height()==BB.height()) ; } void SaveGraphLayoutSettings::resizeMenu() { if (minMenuDialogSize.width()==0) return; QSize newSize = calculateMenuDialogSize(); newSize.setWidth ( newSize.width()); menuDialogSize = menuDialog->size(); if ( sizeEqual(newSize , menuDialogSize)) { // no work to do return; }; if ( menuDialogSize.width() < newSize.width() ) { menuDialog->setMinimumWidth(newSize.width()); menuDialog->setMaximumWidth(QWIDGETSIZE_MAX); } else if ( menuDialogSize.width() > newSize.width() ) { menuDialog->setMaximumWidth(newSize.width()); menuDialog->setMinimumWidth(newSize.width()); } if ( menuDialogSize.height() < newSize.height() ) { menuDialog->setMinimumHeight(newSize.height()); menuDialog->setMaximumHeight(QWIDGETSIZE_MAX); } else if ( menuDialogSize.height() > newSize.height() ) { menuDialog->setMaximumHeight(newSize.height()); menuDialog->setMinimumHeight(newSize.height()); } menuDialogSize = newSize; } QSize SaveGraphLayoutSettings::calculateMenuDialogSize() { if (menuDialogSize.width()==0) return QSize(0,0); QListWidgetItem* item; widestItem=nullptr; QFontMetrics fm = QFontMetrics(menuListFont); // account for scrollbars. QSize returnValue = QSize( 0 , fm.height() ); // add an extra space at the bottom. width didn't work // Account for dialog Size returnValue += dialogListDiff; QSize size; for (int index = 0; index < menuList->count(); ++index) { item = menuList->item(index); if (!item) continue; size = fm.boundingRect(item->text()).size(); if (returnValue.width() < size.width()) { returnValue.setWidth( qMax( returnValue.width(),size.width())); widestItem=item; } returnValue.setHeight( returnValue.height()+size.height()); } returnValue.setWidth( horizontalWidthAdjustment + returnValue.width() ) ; returnValue = maxSize(returnValue, minMenuDialogSize); return returnValue; } QListWidgetItem* SaveGraphLayoutSettings::updateFileList(QString find) { QListWidgetItem* ret=nullptr; manageButtonApperance(); dir->refresh(); QFileInfoList filelist = dir->entryInfoList( QDir::Files | QDir::Readable | QDir::Writable | QDir::NoSymLinks,QDir::Name); // Restrict number of files. easy to find availble unused entry for add function. int row=0; int count=0; menuList->clear(); nextNumToUse=-1; descriptionMap->load(); for (int i = 0; i < filelist.size(); ++i) { QFileInfo fileInfo = filelist.at(i); QString fileName = fileInfo.fileName(); QRegularExpressionMatch match = parseFilenameRe->match(fileName); if (match.hasMatch()) { if (match.lastCapturedIndex()==3) { QString fileName=match.captured(2); if (nextNumToUse<0) { // check if an entry is availavle to use int fileNum=match.captured(3).toInt(); // find an available file name(number); if (fileNum!=count) { nextNumToUse=count; } } count++; QListWidgetItem *item = new QListWidgetItem(descriptionMap->get(fileName)); item->setData(fileNameRole,fileName); item->setFlags(item->flags() | Qt::ItemIsEditable); menuList->insertItem(row,item); row++; if (find!=nullptr && fileName==find) { ret=item; } } } } if (nextNumToUse<0) { // check if there is an existing empty slot // if not then the next available slot is at the end. CHeck if at max files. if (countsortItems(); return ret; } void SaveGraphLayoutSettings::exit() { menuDialog->close(); } void SaveGraphLayoutSettings::menu(gGraphView* graphView) { if (dir==nullptr) { //const char* err=qPrintable(QString("Cannot find directory %1").arg(dirName)); //qWarning(err); return; } this->graphView=graphView; updateFileList(); manageButtonApperance(); menuDialog->raise(); menuDialog->exec(); exit(); } //==================================================================================================== //==================================================================================================== // Descriptions map class with file storage DescriptionMap::DescriptionMap(QDir* dir, QString _filename) { filename = dir->absoluteFilePath(_filename); parseDescriptionsRe = new QRegularExpression(QString("^\\s*(\\w+)%1(.*)$").arg(delimiter) ); }; DescriptionMap::~DescriptionMap() { delete parseDescriptionsRe; }; void DescriptionMap::add(QString key,QString desc) { descriptions.insert(key,desc); }; void DescriptionMap::remove(QString key) { descriptions.remove(key); } QString DescriptionMap::get(QString key) { QString ret =descriptions.value(key,key); return ret; } void DescriptionMap::save() { QFile file(filename); file.open(QFile::WriteOnly); QTextStream out(&file); QMapIteratorit(descriptions); while (it.hasNext()) { it.next(); QString line=QString("%1%2%3\n").arg(it.key()).arg(delimiter).arg(it.value()); out <match(line); if (match.hasMatch()) { QString fileName = match.captured(1); QString desc = match.captured(2); add(fileName,desc); } else { DEBUGF QQ("MATCH ERROR",line); } } } //==================================================================================================== //==================================================================================================== #if 0 Different languages unicodes to test. optained from translation files 도움주신분들 已成功删除 删除 הצג את מחיצת הנתונים הצג את מחיצת הנתונים 已成功删除 عذرا ، لا يمكن تحديد موقع ملف. 已成功删除 عذرا ، لا يمكن تحديد موقع ملف. 删除 Toon gegevensmap عذرا ، لا يمكن تحديد موقع ملف. #endif OSCAR-code-v1.5.1/oscar/saveGraphLayoutSettings.h000066400000000000000000000133351450332542600216130ustar00rootroot00000000000000/* Overview GUI Headers * * Copyright (c) 2022-2023 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 SAVEGRAPHLAYOUTSETTINGS_H #define SAVEGRAPHLAYOUTSETTINGS_H #include #include #include #include #include #include #include #include #include #include #include "Graphs/gGraphView.h" class DescriptionMap { public: DescriptionMap(QDir* dir, QString filename) ; virtual ~DescriptionMap(); void add(QString key,QString desc); void remove(QString key); QString get(QString key); void load(); void save(); private: QString filename; QMap descriptions; const QRegularExpression* parseDescriptionsRe; const QChar delimiter = QChar(':'); }; class SaveGraphLayoutSettings : public QWidget { Q_OBJECT public: SaveGraphLayoutSettings(QString title, QWidget* parent) ; ~SaveGraphLayoutSettings(); void menu(gGraphView* graphView); protected: QIcon* m_icon_return = new QIcon(":/icons/return.png"); QIcon* m_icon_help = new QIcon(":/icons/question_mark.png"); QIcon* m_icon_exit = new QIcon(":/icons/exit.png"); QIcon* m_icon_delete = new QIcon(":/icons/trash_can.png"); QIcon* m_icon_update = new QIcon(":/icons/update.png"); QIcon* m_icon_restore = new QIcon(":/icons/restore.png"); QIcon* m_icon_rename = new QIcon(":/icons/rename.png"); QIcon* m_icon_add = new QIcon(":/icons/plus.png"); private: const static int fileNumMaxLength = 3; const static int maxFiles = 30; // Max supported design limited is 1000 10**fileNumMaxLength(3). const static int iconWidthMessageBox = 50; const static int maxDescriptionLen = 80; const QString fileBaseName = QString("layout"); const int fileNameRole = Qt::UserRole; int fontSizeIncrease = 0; int horizontalWidthAdjustment=60; // this seem to make menu size changes work. Testing says it is 60 but what causes it is unknown. QSize minMenuListSize = QSize(0,0); QSize minMenuDialogSize = QSize(0,0); QSize dialogListDiff = QSize(0,0); QSize menuDialogSize = QSize(0,0); QSize menuListSize = QSize(0,0); void initminMenuListSize(); QSize calculateMenuDialogSize(); QSize maxSize(const QSize AA , const QSize BB ) ; bool sizeEqual(const QSize AA , const QSize BB ) ; const QRegularExpression* singleLineRe; const QRegularExpression* fileNumRe; const QRegularExpression* parseFilenameRe; QWidget* parent; const QString title; gGraphView* graphView = nullptr; QFont menuListFont; QDialog* menuDialog; QListWidget* menuList; QPushButton* menuAddFullBtn; // Must be first item for workaround. QPushButton* menuAddBtn; QPushButton* menuDeleteBtn; QPushButton* menuRestoreBtn; QPushButton* menuUpdateBtn; QPushButton* menuRenameBtn; QPushButton* menuExitBtn; QPushButton* menuHelpBtn; QVBoxLayout* menuLayout; QHBoxLayout* menuLayoutButtons; void createHelp(); void helpDestructor(); QString helpInfo(); QDialog* helpDialog=nullptr; QPushButton* helpInfoExitBtn=nullptr; QPushButton* helpExitBtn=nullptr; int helpFontSizeIncrease = 0; QHBoxLayout* helpLayoutButtons = nullptr; QDir* dir = nullptr; QString dirName; int nextNumToUse; QListWidgetItem* updateFileList(QString find = QString()); QListWidgetItem* widestItem=nullptr; QString styleOn; QString styleOff; QString styleExit; QString styleMessageBox; QString styleDialog; QString calculateButtonStyle(bool on,bool border); void looksOn(QPushButton* button,bool on); DescriptionMap* descriptionMap; bool confirmAction(QString name,QString question,QIcon* icon, QMessageBox::StandardButtons flags = (QMessageBox::Cancel|QMessageBox::Yes) , QMessageBox::StandardButton adefault = QMessageBox::Cancel, QMessageBox::StandardButton success = QMessageBox::Yes ); bool verifyItem(QListWidgetItem* item,QString name,QIcon* icon) ; const QString calculateStyleMessageBox( QFont* font, QString& s1, QString& s2); void displaywidgets(QWidget* widget); QSize calculateParagraphSize(QString& text,QFont& font, QString& ); void createMenu(); void createStyleSheets(); void createSaveFolder(); QPushButton* newBtnRtn(QHBoxLayout*, QString name, QIcon* icon, QString style,QSizePolicy::Policy hPolicy,QString tooltip); QPushButton* menuBtn( QString name, QIcon* icon, QString style,QSizePolicy::Policy hPolicy,QString tooltip); QPushButton* helpBtn( QString name, QIcon* icon, QString style,QSizePolicy::Policy hPolicy,QString tooltip); void manageButtonApperance(); void resizeMenu(); int fileNum(QString fileName); void writeSettings(QString filename); void loadSettings(QString filename); void deleteSettings(QString filename); public slots: private slots: void add_feature(); void addFull_feature(); void restore_feature(); void rename_feature(); void update_feature(); void help_feature(); void help_off_feature(); void help_exit_feature(); void delete_feature(); void exit(); void itemChanged(QListWidgetItem *item); void itemSelectionChanged(); }; #endif // SAVEGRAPHLAYOUTSETTINGS_H OSCAR-code-v1.5.1/oscar/scripts/000077500000000000000000000000001450332542600162655ustar00rootroot00000000000000OSCAR-code-v1.5.1/oscar/scripts/build_number000066400000000000000000000000021450332542600206470ustar00rootroot000000000000001 OSCAR-code-v1.5.1/oscar/scripts/fetchUpstream.sh000077500000000000000000000000721450332542600214350ustar00rootroot00000000000000#!/bin/bash git fetch upstream git merge upstream/master OSCAR-code-v1.5.1/oscar/sessionbar.cpp000066400000000000000000000151231450332542600174540ustar00rootroot00000000000000/* 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.5.1/oscar/sessionbar.h000066400000000000000000000033351450332542600171230ustar00rootroot00000000000000/* 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.5.1/oscar/statistics.cpp000066400000000000000000002234521450332542600175040ustar00rootroot00000000000000/* 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. */ #define TEST_MACROS_ENABLEDoff #include #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" #include "SleepLib/profiles.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 = ""; // Device (formerly Rx) changes QString htmlMachines = ""; // Devices 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::asprintf("%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; { // Bug Fix. during testing, a crash occured due to a null machie value. in saveRxChanges. out << rx.machine->loaderName() QList toErase; for (auto ri = rxitems.begin(); ri != rxitems.end();++ri ) { RXItem rxitem = ri.value(); if (rxitem.machine==0) toErase.append(ri.key()); } for (auto date : toErase) { rxitems.remove(date) ; } } } 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::updateDisabledInfo() { QDate lastcpap = p_profile->LastGoodDay(MT_CPAP); QDate firstcpap = p_profile->FirstGoodDay(MT_CPAP); if (lastcpap > p_profile->general->statReportDate() ) { lastcpap = p_profile->general->statReportDate(); } disabledInfo.update( lastcpap, firstcpap ); } void DisabledInfo::update(QDate latestDate, QDate earliestDate) { clear(); if ( (!latestDate.isValid()) || (!earliestDate.isValid()) || (p_profile->cpap->clinicalMode()) ) return; qint64 complianceHours = 3600000.0 * p_profile->cpap->complianceHours(); // conbvert to ms totalDays = 1+earliestDate.daysTo(latestDate); for (QDate date = latestDate ; date >= earliestDate ; date=date.addDays(-1) ) { Day* day = p_profile->GetDay(date); if (!day) { daysNoData++; continue;}; // find basic statistics for a day int numDisabled=0; qint64 sessLength = 0; qint64 dayLength = 0; qint64 enabledLength = 0; QList sessions = day->getSessions(MT_CPAP,true); for (auto & sess : sessions) { sessLength = sess->length(); //if (sessLength<0) sessLength=0; // some sessions have negative length. Sould solve this issue dayLength += sessLength; if (sess->enabled(true)) { enabledLength += sessLength; } else { numDisabled ++; totalDurationOfDisabledSessions += sessLength; if (maxDurationOfaDisabledsession < sessLength) maxDurationOfaDisabledsession = sessLength; } } // calculate stats for all days // calculate if compliance for a day changed. if ( complianceHours <= enabledLength ) { daysInCompliance ++; } else { if (complianceHours < dayLength ) { numDaysDisabledSessionChangedCompliance++; } else { daysOutOfCompliance ++; } } // update disabled info for all days if ( numDisabled > 0 ) { numDisabledsessions += numDisabled; numDaysWithDisabledsessions++; }; } // convect ms to minutes maxDurationOfaDisabledsession/=60000 ; totalDurationOfDisabledSessions/=60000 ; }; QString DisabledInfo::display(int type) { /* Permissive mode: some sessions are excluded from this report, as follows: Total disabled sessions: xx, found in yy days. Duration of longest disabled session: aa minutes, Total duration of all disabled sessions: bb minutes. +tr("Date: %1 AHI: %2").arg(it.value().toString(Qt::SystemLocaleShortDate)).arg(it.key(), 0, 'f', 2) + "
    "; */ switch (type) { default : case 0: //return QString(QObject::tr("Permissive mode is set (Preferences/Clinical), disabled sessions are excluded from this report")); //return QString(QObject::tr("Permissive mode allows disabled sessions")); return QString(QObject::tr("Permissive Mode")); case 1: if (numDisabledsessions>0) { return QString(QObject::tr("Total disabled sessions: %1, found in %2 days") .arg(numDisabledsessions) .arg(numDaysWithDisabledsessions)); } else { return QString(QObject::tr("Total disabled sessions: %1") .arg(numDisabledsessions) ); } case 2: return QString(QObject::tr( "Duration of longest disabled session: %1 minutes, Total duration of all disabled sessions: %2 minutes.") .arg(maxDurationOfaDisabledsession, 0, 'f', 1) .arg(totalDurationOfDisabledSessions, 0, 'f', 1)); } } 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 "device" names for device types. Statistics::Statistics(QObject *parent) : QObject(parent) { rows.push_back(StatisticsRow(tr("CPAP Statistics"), SC_HEADING, MT_CPAP)); if (!p_profile->cpap->clinicalMode()) { updateDisabledInfo(); rows.push_back(StatisticsRow(disabledInfo.display(0),SC_WARNING ,MT_CPAP)); rows.push_back(StatisticsRow(disabledInfo.display(1),SC_WARNING2,MT_CPAP)); if (disabledInfo.size()>0) { rows.push_back(StatisticsRow(disabledInfo.display(2),SC_WARNING2,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 Device 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; } Period(QDate first,QDate last,bool& finished, int advance , bool month,QString name) { if (finished) return; // adds date range to header. // replaces the following // periods.push_back(Period(qMax(last.addDays(-6), first), last, tr("Last Week"))); QDate next; if (month) { // note add days or addmonths returns the start of the next day or the next month. // must shorten one day for Month. next = last.addMonths(advance).addDays(+1); } else { next = last.addDays(advance); } if (next<=first) { finished = true; next = first; } name = name + "
    " + next.toString(Qt::SystemLocaleShortDate) ; if (advance!=0) { name = name + " - " + last.toString(Qt::SystemLocaleShortDate); } this->header = name; this->start = next ; this->end = last ; } Period& operator=(const Period&) = default; ~Period() {}; QDate start; QDate end; QString header; }; const QString warning_color="#ffffff"; const QString heading_color="#ffffff"; const QString subheading_color="#e0e0e0"; //const int rxthresh = 5; // Sort devices 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() << "Device" << m->brand() << "series" << m->series() << "model" << m->model() << "model number" << m->modelnumber(); QDate d1 = m->FirstDay(); QDate d2 = m->LastDay(); if (d2 > p_profile->general->statReportDate() ) { d2 = p_profile->general->statReportDate(); } 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("Device Information") + "
    %1%2%3%4%5
    %1%2%3%4%5
    "; html += "
    "; } return html; } QString Statistics::GenerateRXChanges() { // Generate list only if there are CPAP devices 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(); if (rx.start > p_profile->general->statReportDate() ) continue; QDate rxend=rx.end; if (rxend > p_profile->general->statReportDate() ) rxend = p_profile->general->statReportDate(); 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(rxend.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(rxend.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 Device 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 devices 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); if (lastcpap > p_profile->general->statReportDate() ) { lastcpap = p_profile->general->statReportDate(); } 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); if (last > p_profile->general->statReportDate() ) { last = p_profile->general->statReportDate(); } // Clear the periods (columns) periods.clear(); if (p_profile->general->statReportMode() == STAT_MODE_STANDARD) { // note add days or addmonths returns the start of the next day or the next month. // must shorten one day for each. Month executed in Period method bool finished = false; // used to detect end of data - when less than a year of data. periods.push_back(Period(first,last,finished, 0, false ,tr("Most Recent"))); periods.push_back(Period(first,last,finished, -6, false ,tr("Last Week"))); periods.push_back(Period(first,last,finished, -29,false, tr("Last 30 Days"))); periods.push_back(Period(first,last,finished, -6,true, tr("Last 6 Months"))); periods.push_back(Period(first,last,finished, -12,true,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
    yyyy"))); 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(); if (first > last) { first = last; } 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); if (last > p_profile->general->statReportDate() ) { last = p_profile->general->statReportDate(); } 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("Database has No %1 data available.").arg(machine)); } else if (value == 1) { html+=QString("").arg(periods.size()+1). arg(tr("Database has %1 day of %2 Data on %3") .arg(value) .arg(machine) .arg(last.toString(MedDateFormat))); } else { html+=QString("").arg(periods.size()+1). arg(tr("Database has %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 if (row.calc == SC_WARNING) { //html+=QString(""). // arg(warning_color).arg(periods.size()+1).arg(row.src); html+=QString(""). arg(warning_color).arg(periods.size()+1).arg(row.src); continue; } else if (row.calc == SC_WARNING2) { html+=QString(""). arg(warning_color).arg(periods.size()+1).arg(row.src); 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()); } // Defined percentages for columns for diffent modes. QString line; int np = periods.size(); int width; // both create header column and 5 data columns for a total of 100 int dataWidth = 14; int headerWidth = 30; if (p_profile->general->statReportMode() == STAT_MODE_MONTHLY) { // both create header column and 13 data columns for a total of 100 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
    %3
    %3
    %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.setPageOrientation(QPageLayout::Portrait); printer.setFullPage(false); // Print only on printable area of page and not in non-printable margins printer.setCopyCount(1); QMarginsF minMargins = printer.pageLayout().margins(QPageLayout::Millimeter); printer.setPageMargins( QMarginsF( fmax(10,minMargins.left()), fmax(10,minMargins.top()), fmax(10,minMargins.right()), fmax(12,minMargins.bottom())), QPageLayout::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 = "" "Device 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 = 1+first.daysTo(last); int daysUsed = p_profile->countDays(MT_CPAP, first, last); int daysSkipped = totalDays - daysUsed; int compliant = p_profile->countCompliantDays(MT_CPAP, first, last); int lowUsed = daysUsed - compliant; float comperc = (100.0 / float(totalDays)) * float(compliant); html += ""+tr("CPAP Usage")+"
    "; html += first.toString(Qt::SystemLocaleShortDate) + " - " + last.toString(Qt::SystemLocaleShortDate) + "
    "; if (daysSkipped > 0) { html += tr("Total Days: %1").arg(totalDays) + "
    "; html += tr("Days Not Used: %1").arg(daysSkipped) + "
    "; } html += tr("Days Used: %1").arg(daysUsed) + "
    "; html += tr("Days %1 %2 Hours: %3").arg(">=") .arg(p_profile->cpap->complianceHours(),0,'f',1) .arg(compliant) + "
    "; html += tr("Days %1 %2 Hours: %3").arg("<").arg(p_profile->cpap->complianceHours(),0,'f',1) .arg(lowUsed) + "
    "; 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->GetDay(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->GetDay(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->GetDay(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->GetDay(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->GetDay(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 Device 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 Device 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; } QDate lastdate; QDate firstdate; void Statistics::updateReportDate() { if (p_profile) { QDate last = p_profile->LastDay(); QDate first = p_profile->FirstDay(); if (last == lastdate && first == firstdate) return; p_profile->general->setStatReportRangeStart(first); p_profile->general->setStatReportRangeEnd(last); p_profile->general->setStatReportDate(last); lastdate = last; firstdate = first; } } OSCAR-code-v1.5.1/oscar/statistics.h000066400000000000000000000152621450332542600171470ustar00rootroot00000000000000/* 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" class DisabledInfo { public: QString display(int); void update(QDate latest, QDate earliest) ; int size() {return numDisabledsessions;}; private: int totalDays ; int daysNoData ; int daysOutOfCompliance ; int daysInCompliance ; int numDisabledsessions ; int numDaysWithDisabledsessions ; int numDaysDisabledSessionChangedCompliance ; double maxDurationOfaDisabledsession ; double totalDurationOfDisabledSessions ; public: void clear () { totalDays = 0; daysNoData = 0; daysOutOfCompliance = 0; daysInCompliance = 0; numDisabledsessions = 0; numDaysWithDisabledsessions = 0; maxDurationOfaDisabledsession = 0; numDaysDisabledSessionChangedCompliance = 0; totalDurationOfDisabledSessions = 0; }; }; //! \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 , SC_WARNING , SC_WARNING2 }; /*! \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; } StatisticsRow& operator=(const StatisticsRow&) = default; ~StatisticsRow() {}; 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 device 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 (device) 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); QString GenerateHTML(); QString UpdateRecordsBox(); static void printReport(QWidget *parent = nullptr); void updateDisabledInfo(); static void updateReportDate(); protected: void loadRXChanges(); void saveRXChanges(); void updateRXChanges(); 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; DisabledInfo disabledInfo; signals: public slots: }; #endif // SUMMARY_H OSCAR-code-v1.5.1/oscar/test_macros.h000066400000000000000000000155111450332542600172750ustar00rootroot00000000000000/* Test macros 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. */ /* These functions will display formatted debug information. The macro TEST_MACROS_ENABLED will enable these macros to display information When The macro TEST_MACROS_ENABLED is undefined then these marcos will expand to white space. When these macos are used then debugging is disabled When only these macos are used then debugging is disabled. SO for production code rename TEST_MACROS_ENABLED to TEST_MACROS_ENABLEDoff ########################################### The the following to source cpp files to turn on the debug macros for use. #define TEST_MACROS_ENABLED #include To turn off the the test macros. #define TEST_MACROS_ENABLEDoff #include ########################################### */ #ifndef TEST_MACROS_ENABLED_ONCE #define TEST_MACROS_ENABLED_ONCE #ifdef TEST_MACROS_ENABLED #include #include #define DEBUGQ qDebug().noquote() #define DEBUGW qWarning().noquote() #define DEBUGL DEBUGQ <10) return nullptr; if (mode==debugDisplay) { DEBUGF O(indent) O(widget) ; } else if (mode==returnQTextEdit){ if(QTextEdit* te = dynamic_cast(widget)) { return widget ; }; } const QList list = widget->children(); for (int i = 0; i < list.size(); ++i) { QWidget *next_widget = dynamic_cast(list.at(i)); if (!next_widget) continue; QWidget* found=findQTextEditdisplaywidgets(next_widget,mode,objectName,recurseCount); if (found) return found; } if (mode==debugDisplay && recurseCount==1) DEBUGF O("==================================================="); return nullptr; } } #endif // TEST_ROUTIMES_ENABLED #endif // TEST_MACROS_ENABLED_ONCE OSCAR-code-v1.5.1/oscar/tests/000077500000000000000000000000001450332542600157405ustar00rootroot00000000000000OSCAR-code-v1.5.1/oscar/tests/AutoTest.h000066400000000000000000000030011450332542600176530ustar00rootroot00000000000000// 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.5.1/oscar/tests/cryptotests.cpp000066400000000000000000000136401450332542600210530ustar00rootroot00000000000000/* Cryptographic Abstraction 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 "cryptotests.h" #include "SleepLib/crypto.h" //#define BENCHMARK_CRYPTO 1 void CryptoTests::testAES256() { // From FIPS-197 C.3 QByteArray expected_plaintext = QByteArray::fromHex("00112233445566778899aabbccddeeff"); QByteArray key = QByteArray::fromHex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f"); QByteArray ciphertext = QByteArray::fromHex("8ea2b7ca516745bfeafc49904b496089"); QByteArray plaintext; CryptoResult result = decrypt_aes256(key, ciphertext, plaintext); Q_ASSERT(result == OK); Q_ASSERT(plaintext == expected_plaintext); } // From https://luca-giuzzi.unibs.it/corsi/Support/papers-cryptography/gcm-spec.pdf typedef struct AES256GCMVector_t { const char* key; const char* p; const char* iv; const char* c; const char* tag; } AES256GCMVector_t; static const int s_AES256GCMVectorCount = 3; static const AES256GCMVector_t s_AES256GCMVectors[s_AES256GCMVectorCount] = { // Test Case 13 { "0000000000000000000000000000000000000000000000000000000000000000", "", "000000000000000000000000", "", "530f8afbc74536b9a963b4f1c4cb738b" }, // Test Case 14 { "0000000000000000000000000000000000000000000000000000000000000000", "00000000000000000000000000000000", "000000000000000000000000", "cea7403d4d606b6e074ec5d3baf39d18", "d0d1c8a799996bf0265b98b5d48ab919" }, // Test Case 15 { "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308", "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255", "cafebabefacedbaddecaf888", "522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662898015ad", "b094dac5d93471bdec1a502270e3cc6c" } }; void CryptoTests::testAES256GCM() { QByteArray empty; for (int i = 0; i < s_AES256GCMVectorCount; i++) { const AES256GCMVector_t* v = &s_AES256GCMVectors[i]; QByteArray key = QByteArray::fromHex(v->key); QByteArray expected_plaintext = QByteArray::fromHex(v->p); QByteArray iv = QByteArray::fromHex(v->iv); QByteArray ciphertext = QByteArray::fromHex(v->c); QByteArray tag = QByteArray::fromHex(v->tag); QByteArray plaintext; CryptoResult result = decrypt_aes256_gcm(key, iv, ciphertext, tag, plaintext); Q_ASSERT(result == OK); Q_ASSERT(plaintext == expected_plaintext); tag = QByteArray::fromHex(s_AES256GCMVectors[(i+1) % s_AES256GCMVectorCount].tag); result = decrypt_aes256_gcm(key, iv, ciphertext, tag, plaintext); Q_ASSERT(result == InvalidTag); Q_ASSERT(plaintext == empty); } } void CryptoTests::testAES256GCMencrypt() { QByteArray empty; for (int i = 0; i < s_AES256GCMVectorCount; i++) { const AES256GCMVector_t* v = &s_AES256GCMVectors[i]; QByteArray key = QByteArray::fromHex(v->key); QByteArray plaintext = QByteArray::fromHex(v->p); QByteArray iv = QByteArray::fromHex(v->iv); QByteArray expected_ciphertext = QByteArray::fromHex(v->c); QByteArray expected_tag = QByteArray::fromHex(v->tag); QByteArray ciphertext; QByteArray tag; CryptoResult result = encrypt_aes256_gcm(key, iv, plaintext, ciphertext, tag); Q_ASSERT(result == OK); Q_ASSERT(ciphertext == expected_ciphertext); Q_ASSERT(tag == expected_tag); } } void CryptoTests::testPBKDF2_SHA256() { // From RFC 7914 section 11 QByteArray passphrase("passwd"); QByteArray salt("salt"); int iterations = 1; QByteArray expected_key = QByteArray::fromHex( "55 ac 04 6e 56 e3 08 9f ec 16 91 c2 25 44 b6 05" "f9 41 85 21 6d de 04 65 e6 8b 9d 57 c2 0d ac bc" "49 ca 9c cc f1 79 b6 45 99 16 64 b3 9d 77 ef 31" "7c 71 b8 45 b1 e3 0b d5 09 11 20 41 d3 a1 97 83"); QByteArray derived_key(expected_key.size(), 0); CryptoResult result = pbkdf2_sha256(passphrase, salt, iterations, derived_key); Q_ASSERT(result == OK); Q_ASSERT(derived_key == expected_key); } void CryptoTests::testPRS1Benchmarks() { #if BENCHMARK_CRYPTO static const int AES_ITERATIONS = 500; static const int PBKDF2_ITERATIONS = 100; QTime time; qDebug() << "Timing AESGCM..."; time.start(); for (int i = 0; i < AES_ITERATIONS; i++) { // On average, a full directory of 500 PRS1 files is about 10-20MB, so use 32kB/file as representative. QByteArray ciphertext(32768, 0); QByteArray key = QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000"); QByteArray iv = QByteArray::fromHex("000000000000000000000000"); QByteArray tag = QByteArray::fromHex("51dedf58b6a5299bff9d06e041efe725"); QByteArray plaintext; CryptoResult result = decrypt_aes256_gcm(key, iv, ciphertext, tag, plaintext); Q_ASSERT(result == OK); } int elapsed = time.restart(); qDebug() << "AESGCM x" << AES_ITERATIONS << "=" << elapsed << "ms," << ((float)elapsed / AES_ITERATIONS) << "ms/file"; qDebug() << "Timing PBKDF2..."; time.restart(); for (int i = 0; i < PBKDF2_ITERATIONS; i++) { QByteArray passphrase("passwd"); QByteArray salt("salt"); QByteArray derived_key(32, 0); CryptoResult result = pbkdf2_sha256(passphrase, salt, 10000, derived_key); Q_ASSERT(result == OK); } elapsed = time.restart(); qDebug() << "PBKDF2 x" << PBKDF2_ITERATIONS << "=" << elapsed << "ms," << ((float)elapsed / PBKDF2_ITERATIONS) << "ms/file"; #endif } OSCAR-code-v1.5.1/oscar/tests/cryptotests.h000066400000000000000000000010241450332542600205110ustar00rootroot00000000000000/* Cryptographic Abstraction 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 CryptoTests : public QObject { Q_OBJECT private slots: void testAES256(); void testAES256GCM(); void testAES256GCMencrypt(); void testPBKDF2_SHA256(); void testPRS1Benchmarks(); }; DECLARE_TEST(CryptoTests) OSCAR-code-v1.5.1/oscar/tests/deviceconnectiontests.cpp000066400000000000000000000152501450332542600230510ustar00rootroot00000000000000/* 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.5.1/oscar/tests/deviceconnectiontests.h000066400000000000000000000010021450332542600225040ustar00rootroot00000000000000/* 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.5.1/oscar/tests/dreemtests.cpp000066400000000000000000000053321450332542600206260ustar00rootroot00000000000000/* 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.5.1/oscar/tests/dreemtests.h000066400000000000000000000010701450332542600202660ustar00rootroot00000000000000/* 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.5.1/oscar/tests/prs1tests.cpp000066400000000000000000000407241450332542600204230ustar00rootroot00000000000000/* 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.5.1/oscar/tests/prs1tests.h000066400000000000000000000011161450332542600200600ustar00rootroot00000000000000/* 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.5.1/oscar/tests/rawdatatests.cpp000066400000000000000000000202201450332542600211460ustar00rootroot00000000000000/* 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.5.1/oscar/tests/rawdatatests.h000066400000000000000000000014771450332542600206300ustar00rootroot00000000000000/* 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.5.1/oscar/tests/resmedtests.cpp000066400000000000000000000067011450332542600210120ustar00rootroot00000000000000/* 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.5.1/oscar/tests/resmedtests.h000066400000000000000000000011361450332542600204540ustar00rootroot00000000000000/* 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.5.1/oscar/tests/sessiontests.cpp000066400000000000000000000301061450332542600212120ustar00rootroot00000000000000/* 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.5.1/oscar/tests/sessiontests.h000066400000000000000000000006501450332542600206600ustar00rootroot00000000000000/* 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.5.1/oscar/tests/versiontests.cpp000066400000000000000000000035751450332542600212260ustar00rootroot00000000000000/* 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.5.1/oscar/tests/versiontests.h000066400000000000000000000006561450332542600206700ustar00rootroot00000000000000/* 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.5.1/oscar/tests/viatomtests.cpp000066400000000000000000000045611450332542600210340ustar00rootroot00000000000000/* 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.5.1/oscar/tests/viatomtests.h000066400000000000000000000010771450332542600205000ustar00rootroot00000000000000/* 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.5.1/oscar/tests/zeotests.cpp000066400000000000000000000052741450332542600203340ustar00rootroot00000000000000/* 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.5.1/oscar/tests/zeotests.h000066400000000000000000000010521450332542600177670ustar00rootroot00000000000000/* 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.5.1/oscar/translation.cpp000066400000000000000000000220121450332542600176350ustar00rootroot00000000000000/* 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["af"] = "Afrikaans"; langNames["ar"] = "\xd8\xb9\xd8\xb1\xd8\xa8\xd9\x8a (Arabic)"; langNames["bg"] = "\xd0\xb1\xd1\x8a\xd0\xbb\xd0\xb3\xd0\xb0\xd1\x80\xd1\x81\xd0\xba\xd0\xb8 (Bulgarian)"; langNames["da"] = "Dansk"; langNames["de"] = "Deutsch"; langNames["el"] = "\xce\x95\xce\xbb\xce\xbb\xce\xb7\xce\xbd\xce\xb9\xce\xba\xce\xac (Greek)"; langNames["en_UK"] = "English (UK)"; langNames["es"] = "Español"; langNames["es_MX"] = "Español (Mexico)"; langNames["fi"] = "Suomen kieli"; langNames["fil"] = "Filipino"; langNames["fr"] = "Français"; langNames["he"] = "\xd7\xa2\xd7\x91\xd7\xa8\xd7\x99\xd7\xaa (Hebrew)"; langNames["hu"] = "Magyar"; langNames["it"] = "Italiano"; langNames["ja"] = "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e (Japanese)"; langNames["ko"] = "\xed\x95\x9c\xea\xb5\xad\xec\x96\xb4 (Korean)"; langNames["nl"] = "Nederlands"; langNames["no"] = "Norsk"; langNames["pl"] = "Polski"; langNames["pt"] = "Português"; langNames["pt_BR"] = "Português (Brazil)"; langNames["ro"] = "Românește"; langNames["ru"] = "\xd1\x80\xd1\x83\xd1\x81\xd1\x81\xd0\xba\xd0\xb8\xd0\xb9 (Russian)"; langNames["th"] = "\xe0\xb8\xa0\xe0\xb8\xb2\xe0\xb8\xa9\xe0\xb8\xb2\xe0\xb9\x84\xe0\xb8\x97\xe0\xb8\xa2 (Thai)"; langNames["tr"] = "Türkçe"; 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 (Chinese Simpl)"; 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 (Chinese Trad)"; 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(12); 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.5.1/oscar/translation.h000066400000000000000000000011251450332542600173040ustar00rootroot00000000000000/* 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.5.1/oscar/update_gitinfo.bat000077500000000000000000000026061450332542600202760ustar00rootroot00000000000000@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_gitinfo.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.5.1/oscar/update_gitinfo.sh000077500000000000000000000020371450332542600201400ustar00rootroot00000000000000#!/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.5.1/oscar/updateparser.cpp000066400000000000000000000262001450332542600200010ustar00rootroot00000000000000/* 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.5.1/oscar/version.cpp000066400000000000000000000164711450332542600170000ustar00rootroot00000000000000/* 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.5.1/oscar/version.h000066400000000000000000000036071450332542600164420ustar00rootroot00000000000000/* 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.5.1/oscar/welcome.cpp000066400000000000000000000346561450332542600167530ustar00rootroot00000000000000/* 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 devices 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 device 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 if (daysto == 0) daystring += tr("today"); 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 device 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 (day->channelHasData(CPAP_EEPAP)) { epapDataChanID = CPAP_EEPAP; } 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 device 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 device 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 device 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); } else if (cpapmode == MODE_TRILEVEL_AUTO_VARIABLE_PDIFF){ EventDataType ipap = day->percentile(pressChanID, perc/100.0); EventDataType eepap = day->percentile(epapDataChanID, perc/100.0); html += tr("Your EEPAP pressure was under %1 %2 for %3% of the time.").arg(eepap).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.5.1/oscar/welcome.h000066400000000000000000000015301450332542600164010ustar00rootroot00000000000000/* 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.5.1/oscar/welcome.ui000066400000000000000000000517421450332542600166010ustar00rootroot00000000000000 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 device.</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.5.1/oscar/win_icon.rc000066400000000000000000000000631450332542600167300ustar00rootroot00000000000000IDI_ICON1 ICON DISCARDABLE "./icons/bob-v3.0.ico" OSCAR-code-v1.5.1/oscar/zip.cpp000066400000000000000000000251751450332542600161160ustar00rootroot00000000000000/* 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.5.1/oscar/zip.h000066400000000000000000000045401450332542600155540ustar00rootroot00000000000000/* 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.5.1/utf8.sh000077500000000000000000000004671450332542600147230ustar00rootroot00000000000000#!/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