From 300f64d00aceb4f8948fd3701a694f201b05fbcf Mon Sep 17 00:00:00 2001 From: "Peter J. Jones" Date: Tue, 4 Aug 2026 13:38:16 +0200 Subject: [PATCH 1/7] test: Sync test files with the Rust implementation --- test/files/Example_Processed.img.mzpeak | Bin 261169 -> 281503 bytes test/files/has_uv.mzpeak | Bin 531079 -> 595577 bytes test/files/small.chunked.mzpeak | Bin 2318486 -> 2333593 bytes .../small.dir/chromatograms_data.parquet | Bin 10580 -> 10580 bytes .../small.dir/chromatograms_metadata.parquet | Bin 22944 -> 10864 bytes .../chromatograms_metadata_precursors.parquet | Bin 0 -> 2759 bytes ...romatograms_metadata_selected_ions.parquet | Bin 0 -> 1806 bytes test/files/small.dir/mzpeak_index.json | 527 +++++++++++++----- test/files/small.dir/spectra_data.parquet | Bin 1683335 -> 1683335 bytes test/files/small.dir/spectra_metadata.parquet | Bin 48083 -> 22259 bytes .../spectra_metadata_precursors.parquet | Bin 0 -> 13544 bytes .../small.dir/spectra_metadata_scans.parquet | Bin 0 -> 16124 bytes .../spectra_metadata_selected_ions.parquet | Bin 0 -> 10597 bytes test/files/small.mzpeak | Bin 2040563 -> 2055616 bytes test/files/small.numpress.mzpeak | Bin 845893 -> 861217 bytes 15 files changed, 393 insertions(+), 134 deletions(-) create mode 100644 test/files/small.dir/chromatograms_metadata_precursors.parquet create mode 100644 test/files/small.dir/chromatograms_metadata_selected_ions.parquet create mode 100644 test/files/small.dir/spectra_metadata_precursors.parquet create mode 100644 test/files/small.dir/spectra_metadata_scans.parquet create mode 100644 test/files/small.dir/spectra_metadata_selected_ions.parquet diff --git a/test/files/Example_Processed.img.mzpeak b/test/files/Example_Processed.img.mzpeak index 6033462e6707c324f2fdc28e282ad849fca2b76b..3b348f30b171020b23f2a6f3679b96a4dc95f239 100644 GIT binary patch delta 15036 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 QCp!4A3*P=hzqsc03M)vEw0(gPnKIt*YMCcnR}f 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 VmAWB8cs3jUt0a(g6g~s4{x={On1uiU delta 38467 zcmeHwdwf*Y)pjOl&ddZ+12QKGkPreKqg;|qZW9G@CX@SZ5<+fOW9}j3W&*kO5~RG8 zmr{z@vav-(i-?GT2m~o2AR?lrs1zw8qE$xzjaFI7W6<{EZuPAGA=5 zfwj7gWBe2RSi>vaq%jG8(5L)DhWm#E8?jHy8>@ea;CCAodj1t|+;aBO)k_M{pG@edVRD0~o{!3swoIDYDC|x

gUr`-mjA^z+9*iQ~~ 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 s)YDp1Fw_Ztqe_)pRLZrMpQ1&7R1xo5`u- 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 EzkH}R3;+NC delta 29294 zcmd^od0-RQwYMb8#()#Rpz(sdU75iP#*(~e0Y;X0Y{|xZNW3rDmTkP-SxC}A2_Xsb zBV0lW1s>(&rIb1;1wI}n36DpcJW5GEn&gp`l9WejUJ8UzN_m8S=iHf*MzU;Z`~CMR zku@{-p5IySx#!+HbMElk!I#vV-hELWXfN>5CaCb!$Gx|FOW|1WGl8oE)vE(Fs{?&j z2WnRb`mPT2TOBxMb)f(1z^SVPr>zd0zB({qb>Mxg1MgoQIAe9-%+-OjRtEyr?A3vD zRtM@<2Xa3R4BG!Ib<4q@|0<|aqs}_G;KQIR_^bbQ(0+}2=->w*2OY-q^=E^g_n}vQ z7gS5fmKdVw%U7a8>DalTkGx!)L5=5we(9yak8b-s=wr`R5I`LK^i~w-|t)E}XOP((^;=A9@r9KW)Rm|Q>Ie+2`7 z@B)dZW8X&2a$R??<@M|zAa%#PH=rN=5rl02Q`8I)vQ0xyrF83_RWH{)A=$~S3j{mO z@(Tzy*4DnGnic(8bkuv%Q}g(|Tyvgf6#ow#T{=n|eMltT`gK$vz<}Li>47}5Qa%QA zakM3$d@NsREg(5y)3E|FjJAy{B+Rz5J@quwM&_v1^h6O!1DZQUBo)q5Y~;bfO^#3B zay)Xt@gD~q|9PP4(*qr!(yU@4gp_@HpzYIg$ER;N9{H)`*9U%d_JG))Q|X&;lQ7y{ zOt`@Db!e2G7`$CCx08ZEUEW(HuRQv_b%_57sje_SdNbRVu4SulAcxg#r>nfBZJ_-}F2&b8)&jFZF2l^Q3p8 zvR^(=4tT})(QB0W^k3JJxgi0%fN>0ZAL;SG znL2-+78I#fSpfl!GxsjVDHBqwfbAUJ92weA35~rm;x5SlSTP^1OI@zprN;kJuW7nq z=sL6CkUrDqsMf0eK*c@l$$~|dx;9o2zo8P@iYVWaX<4feByt^vi|LDiw|4#tLMPF6YV`EoZ=>{+ zmrmgLAnL@955VgGCJ~bZPhL7fuK>36_5X@8_}8jJ0yQDQan$rOiK8F;kR(m2icZ&( zQ2gb!goLMJ|7NwcYG}KVVD_Wagm9`@sMDw=Jl1_d`h5#%@ch@(?@j4V zEAn^(euE?)>X2Z-S0Q#SVAs-T{74Xe7i$ivy#4BFogdNp@gQKc%JbMov_?gL7{B=Gjt8MJxWT_Hk{iRbp{8KqkQ?gZQnH-kmXgr82@DR+8|cvp^i!zMd9P2W zmOVB1~gf*J4>ySQEMiy$IdB(~}>6B)vQgIpKXhb=Q!V&>~u>?M~ z^pz<@r`fHdPnDDK34RUO)Sp2 z{oqxSK$pKvOj3ncUM4|)`q*q0RQ{32@f7j8m4e7~v}>?R@dh%RUiBq@_k6m+kNEqj z^j7+LIgsx2BYqQ{`SZ2b5XR)_1~Pw|J_OIfvU+-X0}+y3=b-6YwMTO~D?^ACH4Pqb z2ubB1f=xo4Ht}fmTKf4h5|l7uu@^rPJF8kU4(fZiV=$n|U)<=JKN44HhG!V2*4K&ta_OPaF!=`<&k!|BR`Z5hh5XCF|U zt>P{!Pr3IWR1|Xi9#Wo-KFrQgh8wQKvx}5Q>im*;GNn+49p-FD~U+7ex*RQGlTyBflMH4nvo1C}mQ$(cu4+u_yy zkLk>-;Ih<4yJ~bxq)MZ;$_8FjryG(A_SWm#q{HJ4>`)@!*Qo233NAJ2(5XVW_0Ws- z-1=5V9l~Ab@bs+}O0spU+I7n%x(gk;R_XATgV_V?pX+oU9_vzC>1LPH(qD8d3r?(5 zp1skdJiFbi>yvn#>DQHWD$)KI2k{t0{)EYRXT(`+&l9?IiD1){&coYJ>K>4;UUay*Wnlmq@vHBah|S19gjcjJjc_$-%;zDCHT|NsdbxW57@LuERbMs z!>QbTkFQlrW^#fbPV@aq@-xGB~@;i#-i(B1v-2S>~on=_>rEP9yH{4Ipecx58 z(}vvMq|Obf>*?tltm$oNZF1D<{8cHMVE^9kriQ+reus{yi@zap0kaF2)tHP%V~WXC zLnEdW5)dq1Nls0vp>5v~eE^Z`nBhvzbYfT~*GWmqsF4wC`i8rk=BbwIAyVq5&C>)Y zILXp$I=Tj%di!d+>U*1d2AUdc93wUToh$lkq$3j9rn{-Gy?35|>E78(VAUvcoG)`UCfC&0 z^)|6eUgh%#PEPrZDT>-FI{TVBdsp-g%Su%-o0^hLDjiZSka(EWQ)>FU`szArR&;gN zH1zlMG55-P6_3)Z4qFv!$k|sTqxX@ccJqy=IQLV!G-EG10{{ zh-n(Mw6?dQu2UCGUko!4dZ?0wJ{%`WVKk+JPttulVwO4E-DIw5UeVD9X6^0k0rBK^M{VVH&oHMkJy&$LfzP4F<5`*0 z>7pv~Fbvg;`$>ooD0z@pk*Vqcv$I*iFLzXvAmU~x*7V5CRS%l}^L9hfOwW3BWdIoq znA+Xb1P1Tz>d{R@n>BUBiaTcZ5P-CYdEYS0MM$M)! zL)gKG{)x1yXM)KCdYd{xQ*dukmc9^X$Xq4cJS{o7Mp^3B!O-SG5?5ogsivW|uBW95 zo2jo! z2ua@n>lY8iNPBsFqm?;0Z`AK2f!HCPo%bPmBS3(5drtoyBf(%-?NGQ z-bADhq8n<7Z~Op@JOHBNHh+vBv-V$Pv80xDL($VE?~$qWuZ^$(K5FDQ(5N61JPv6e zLwd)FBq`{Dbh?%V_?K{v{7w^p9f2ZZ71A6B3_W+#iJ=P7`GvS4BxpV{QF|THDU~?S zSdYCSDlVEZ+}P_+2S3|44xQ z5w#2oz|J$U<4!QiLA-#?U>@RoAeCR6Isw=X26o2@CN-27FhBZqD0uFcH2y0F7D}&1 z5pBFuvi1Lo7C-bOSxx3FUZHM=l3AM5Y0Pijl-mOk9CytL;pwvu3YJRpJ)F+3PiGCr zq*$P^^-(6o-hCdf=k!^I5J^9t3+=uwgTFcf%vlC=;chUwdVqQM0sc&ecWG`gn5{qd zkfze*LZHuFy8W-@KFNd?rt`h=F#n5(QL&&Dv7@=;-DY?P-jwkq z5AupvyOqq8%yo7VzjqO$MkKi7jed%Tzv0AVPVE(q>H4z>@azqX>FEf6JLeKV-&>3MlZ)vqO@zeDy7n;5Q1MD7P8PHBXdEca6w|11ZMM-4_0)Fy2o$e}-ls+b{d+xY;iKtJV-c zt(L3g+W65-{xkX@A@k&!^?sn{xQjsTHnYxk3HAg$Sedp>%N^75pJekV=q^IC3dEcJ z7_fmpnA`SKwXReduw&SoGN8Aszo(%I)*p>cL*fi$3U@%mAIsy{=IT>bG5(2g*Hm3g z4AgisNNa_*C79}l-2;chax!v|zId6vZB5vJT3<^cytdvj=4(%1ywS$O|#HG=6_MnK!M3-&u^}YNycc zGf8-%gtCjF9Co5e{ZTZfbLHT;uuSCE z1mZ1&S#ZmM@$KNA;&;{X7ayZ@Ye;moq{4PUfS>~iVoz{q`$!E5)7J7AY8cySne73_ z_Og?$Jd;Je>APFSS!}@cooeo|nqO1LAFrkVRYR=v2iUVERS9_Lhm7y0e^u)$m0`VA zhLEPPj#}cct>^F5F=MeQtb_s}D((_PxeO?7(koD4`WmQh;5RiOfH)_aCyK!U?l4Vu zACUpX`Kv6%eE(YRF|M26*T`lfO3qt;i*`HbB$Vt{DU-6<>x}86YVLrVztqHUYohz> zNE+XaCa}U|IBpCO!nsX9Q|lg625Jf6brODGEw^67jcNGxt^Ap0np{sFo6pFZ0Z%dV z>p;F&baHnIUWcW_FY3t*?F#<66-;-WF3bbwOz*FNTF-5NTMnO64`IwZ=?6_DX#5SP zNY5Kb%&iWox%bulx^{kR8?9&{$tWUxYC*%b=g~$V0h4T|;iOEYyHnnooKbTZ)%=AH z{$e}*gFBOLjLFt_WG3=#0~>;Al7vgDSy~5w(!rd_!lOOAx80vinwewvWN@|zQ3MgDtO~t|OSQo#s3!x%nVO9a3TJ{$v@c^RdzHoww zvkE7KRo%sA5b^F*w?)>+JXo)F(k;L>)f{a%zrP#D>gahALvHkQ#t$gnXALgOO)cO| zEBVbU-3j+G!tMVyF(HNd!3=E=e_N!?ms!5gD9<@5>LSo^b73vd;eXn`_OH0N_WA| zT+!RrQO9lw55lP5H8}1jEP>mjjNdLF?tl+}XM{g8qz~3E+PT~stiAE$=~hlpAKyfx zaWY&w9d~;-jB#Afb@F?j;IBOn{8Xx^7mI&^-^*0N?*!v_ z=;wbIKk>G)i`}Lt`SVYBV;3i@_Zee%&B;z`{2`Z3>5sP%-nrbkZ)!Q$%HMvHKlLQ* z$ZTL)xJEP(4ubc4vTWiy+KXg<6qXzJO0m7R-xxEZGX)0qfA|vv;YvZf>KRho`6fooChh z)Lm*Cr8ejiH!;@Y9(rQuYuY*WPteC0- zaH7cP&=O4KaX!NY26vgB2Le}f9Xvc1eh8Ge1j3_LNN)7gX;crzjx)k-rxCm~H(*BM zh;ttbcv#JyQS8R*s5NrBk|bUy?A4RK&F?%UaO0qzP*)1;Pg0v#be01;4g_N;&TUx?ox19ak~I5nA-FE5zJx-(4IZ6C_g ziR(Cbtk|iw91ew$Ga}wtR?uH#(tPBkm!zq5M+nAXCXR%VYr>xgARGyCWfdong6l4! z)nEEQ^2*8^((5ErG7;pc@W0sR1!fO}Ier(IkU5wn!Wysob*fP=%g-n3P zij?j~oZig)tg{26di$qn9jDh-`^49tmYdTc^d?)bkW#}kzZjrIU(a~Q$CBJgF>wgRw{+K=Q=025 zGFH@840Si=b~M9xuo~@IG}1x8J{1(4RMKCcmbt-<*zt`i_jgdUJkveNKn5 zsXW76RWW2j%4}ObeR?4YUKO7*keCQ{nJM5GT22s3%Zr)|ONGHgt6(hb$mp)`Ea|B4 zEE0L6EweL<%1r4)P(D~#S~!F>LXOn|CSk3d!!E#|;yh;999uIqjC@hzWodv528#>G zT0B%~F6yeP%aLPW#c})CnG^SC$2wn+>rQv!* zkZ*emxF20dO+O_dUQ9Lw+iu~rQMmcmaz09MFGRY=D|-^(Mkc$f?rFo1y~lb zV*%yJ!WKl&7jmtjaC`&Oi}rNUn^}iou8kdNs?d%GP8;c@Q755WWDX)xlC7C`#}o6^ zq7k%QUaFn`Ho~9}u_j|FYwz6h{<=!0vQTJiqvzwnb@Ob{rv~w&rN64e)PbT1*==2{ zQ~6}U!K`0`54S>n`vO4EEA0X?2Lb)(5s-- zGSZj}e(69juy!*q_NnXdq%#vpn9ovMCp9JzxcA+j+DunP87hJW*50jENhRHlj%;IH zMMggusw|b-|C#7`Gy{`49qm_u0i>uHb;JUp!-f_BE~0sisbH;SV8P5|vvxyaVd{Wn zo^&krG*6MOM>J2qt;dUbMpjDZfdgiq6;L0|y-A0)~W9w5Uo>a8(`M)v38}>r=kp%K6!0oon`6$fRDx(TNNw;pDd^ACl|JL zG}l!YLBJACSeeuaZBR1MXv#1(}#;83b(B zyrfDqy&7uZ0wDklSDVX?m1a{avda?aSD}XBspv6+&}JK9E#lW~8v&K+J97=8J_B&k z9!-LlT82?z$p~28j(-|*^E;}N%7+`w86Y4;<(%?l2m~k*t2#aq0JM?Tu7p0G?+JkC zMYbfUz-V0~TX_qP3+y~s0AXz{(0?<_EZ~?W0yb_jp0keMfF0l30(`B5Xxe^kH`^fg zf9AO5h@WijgP>zLNoEx??-tt@&;*My4Scl=D~Zw7B}6p~A}lZ!dh~`)te@W2C-sJO z#7MOzNxh*Bn5CDcNxebHN<)VQUViI)1=Cg>&>{d3XDC)#QF}AZo z5h;bDQd9uDA)B$`uTWAfdlEP@D`(xc-jUtM<{g4HJzX45!mYi?A`|;SPIg``nj$Nbvku8H5J#GXtgtVo z>KPC$yV3`!X*rIsNGo<_U+AC?XpMgv*C^bQ&0~`8;AI#K~kjY@18CXvo zF6?bUlg?~W4U>$j@{GX-M@Abs!w7a}sXL@+4A6in2I606hq<W5qM;-MFax|X!Ue9E zk1)_fUkNZg>a!9mv0D=PBfuyz4=S)*62o>_E+m$wvI$X;trVqTc5>a45E^LWb`l;U z6voSqtGFdmVjp0A!KVlg@Ro$eGDyF_og`0bu!A3`jLFKOP-NJYgtOAIRz#V`Qq}cLlhh*c4m7Ln|sX5v>~2kP85wv=o}wM1al!Qz@DFB zi139_V^3o@nkowt!9}1l8#CayA1R$g*0SLuQ$`m|R=OZM!!j#lMFXtd8Xb7|WFo}` zp%Kup#**O5CJ*#JPjpxl_QI4Je59lqZbHGLGT&TX0mqfy@~|yi0fugzR>OD+=y?At zsoxIHJcMd0W)`JjswCx9tt6!(hs7nqVh3{r;i3#A9L8{~B+N6p6N0T_F$nsCbI5lS zEP2pmrd?$IBGJ4k4-2+1HoL=Fs}PPa!ldSN~S_r_pghfgKQvEzP2(kyH&3*4uW zZlM%HbPhGlen7m%)C>)iI4HRpRLt`9w9;%aQxU8}%E4rVI0r1FJ4{4Rzw9EBQ<;1@ z9qj^rVmB$A&c+m6$9FW*_dg5@J-B)$;W>KeYE-D!CpkTxnxYM1n%gb3E|*l(?p#vf z{F4=}I`NE3Wfw|J?k~x@x%sowze=BQY6>%i(BHmh2$;6L3LeG5XL}WW>i1*`ydUW8 z-;*>SjY@-WpTT!AEx1G$vGZ5Hh1W2_yM%n{`bz}g-|^;pNCn=1iA2c75p?mF1Ygip z{3UyTll0U62Y9N@-rwZ4*uAp***`$~_W;5Bo04Q@6uQ!9zl5f8sy_KCsE_a6aaH$r z!Cg4%?C(+bE;R0vOfL98TG~Rd{DGLM;}7H~^p7!y*f~C>d6^->_*Z`C1$fmn{v;UR zdXaV+d^%O7(zw4eK?6^-=*r84Xt9jGd6~??_gQ>?S$yS)mtQ?KT_GVFcx#2?S4V5F z5ML+d8Wnx!iuk?}FTY{l;3tEHmtnkeReTwSm)}V2JYQcWbG4gL1-B!Pp1wk~+GZ?x z%Ad@I=eFJ~pn(T8%J+VtRqpLtHsW~_PkfPTu7H964)cWmJw&=y_pHag>if6VquMJZ zkUo8d{P^J7ugOmJD%eH-Rfu!1`pHn|;oiCIP}-foUhgdU%5``$%z1ThID1_H?n>Vf z;XJ%99!el@L^|I*urW$}y8wHQ0_>-fPB~~fJ@*+A3&pc{3{JRR@Qy0Dh;`P^Q`R{; z&xyQ;R~D?FuROczc6KI4e2oLb?T>Y8vN6tic-?h)Cf<2<{{rXX#sqdK>32QBDc&aw zorg!n*L+BIt|dAp{S03Ek%u};db+*%W0&h~AGmkF(PQhrA8y?bXlU)}a@6&8we-|E z=)>QT1zxu5>!t0(sb<)+{oQX!*aC0659Hn2-uBK%9Q@rk?iEjoLwhH7V{pL|T}vrgPy0; zJ$8S()4)DJ*ACF>^t-po{KPe~wxI7-#8{UhVaVyK-Z4_$DA$w#<#Cx(va*N7F7B|) za~d7^7Iao}-&z7%-;puID{TN30Z#UYg9&;#y?j&AP7wlF5p>N_M> zX}nX&`q~4N_@%U^^WW|elalaDBz)t+yAvk=)tzw7G9diIgV4>j#NC#-Z8KHj8at70 z3%mciE;ZiW{LYXL=Nlq{JFW3+S#HraXZUF*3T@uIA-vBU@>lY#{G~5 zD4A8yAJu!{A42>!nST$yxEsR-e=>~U7RKOXGi32kA^eyO?}AIdmQ9G&RsynvzZ>lnXB=f`#Ho=GUUPy& zjqk2#{>DUDdl73_j=P0D1T#u9t)0i8jYgT=oBmiO=ypzq7}xYn7~DL;#+b~b^X}T} z=TN52x$ZJ4TOA<9y%S=q@8S7d^E?cG9fkTZcj8ueZk3RfTR)#a$9sTmr~;66d6R;~ zNc30d^T*~h`W&b0?M3=yGQAx7U3t^+mM!nm|4a;@5aa8{IR2u@saWQ89XY)(b8@arl`k}#WOsuI zdp(Xn5yupGOom-o4Y1c_*nenegUI7dJij}h@mMPJ*p5856ij9$?>iSn#Qh8SH47MG zfsA+n5s%4;(sU}utrchCyVsn16)T zUg31XEiyd<`CTiNrf%NIVfTS#r*};JsL4q>E%FPb-BUCvZAhTB- zVfJCtNWYYrl8$^C34#aFp&-tQvhyUVoHmYx9DeGIt0b^z55Mw!$z6!VZNW3yjB6F_$b(CCFJKVCWZyt%wnjAiM~adOQlRb zrZCtT@m)`3;dw7h!M*FFd7w%HHkb{*NX#RYGijiunY7vN&rTt|KfAfg+}hgUXzaiw zHb__KfOPXAmKzn_Gq6<5O^_bG7T~Yz8GlldwWOH}2E&}W-ko~Nir^QdEz?G$0Z0^F{ z1Naqk1`*fDj#*M>zN%9x$ z;5Wk~BjN@c%LwKWdTgMvlu6l%!CC3x+3cyiB3q>yk_9nWQY+XXB@K4agh4JRMA04a zfUtA~6IL9HELJ1nvPbbOT^5WW6eks9uNU*CSaNk2p1Zac3B{QFXSbJ%MzJDBMni_Q zt*8=H{*v2_l2O`_fu~Uhp)%x@h6T4ElBZG3fXs|C3R=VUv3@_83=BhLrteF=Zc!3QEnao8=V^U>+G7 zAHML($6r@BgN+D{6KzDm^v=#|$cL`41b?STen6tMLLTUbTdU?JWBQ($2@NTx6__|w zGyvNcx~mxAur9E!g_9S0n4=W@KI}3JUzb?_SNMvX!H<8#}yzu4&BdZSv>Pl zy%rMw2E?6jxoxSUkG4W3>?FaBm^0^@^;rPPcet%DoF&+$vxL|xb4DM_;=vp=NGLW- z2tJT?N9{2rNCSyoZ-1X8`&lqoZiJ5M3=x`4EBydc<}giCOoaxS!IYlnXps}|AkR)r zyvytr6YqKuEThepco$OG2E(#vw-tm$X*b}Yx751=$gE>FNlg@Y18OW~V)9*qn0%K` zpNu2Zw3!V6^FkaUKJ7p;7DvqYHim@W;2bjei19O@HK7OOL9#7X$~OmrPY2hEs7 z6FNwmCc{X76JZFa&vg*arxUK?E-!i*>Zl9p=??OM8g>piNQBx!;~kI|k={Um7HNo} zO%6aQfov>Re+!;jOXKOAzamLAI1b)Xmnb!$Z6sc7z<8*Py=-1)KW2tW9UuD=x`Qjq zxF@R>L%tNUh$#eLziWnJZfPQOgNz)A$%*OE?-an+s$s|yZ>!6Njb|0v#(Hz(FzjG+ z<-Ef7Q3wr~;FTtdgw$dryvGDD+I=Z1f^J`A@V#puR(U!Yap5dO6*P-m+Ep~Cdcoo) zDt1%^y$Y8QPX~cFFt_JPxSPMj~Pb%BjJVgb;G@c*3}rg@y|T^ zVu!(>KJj&wKdtS7B(1$Qz;|npVJen}(Kkj6_fvb1q0D(&?E+cyIQ(mb2dsvfwDz}f zYr=2Tz#W{v(NOL~XYMCc4%R(xxL-~6uNZ!J@VieKntbSye*#B>*Nlf9Y=6%1X28L( zRv8}AhE9u&d~({8_5!sg!N>g{S)`9sLlOR?g!Cjel)C<{!I;g4)M(egH7Kg+34jS` z^vACmIG@cwBDDDT#PEDzs$fkaeRe&wcFLyj86J8R|FuIju3TxB wTctk1pmL1hEAGYj|150l?dnvkW`gxaAoy0{EAk4gdfE diff --git a/test/files/small.dir/chromatograms_data.parquet b/test/files/small.dir/chromatograms_data.parquet index 1716b16b6a39c20458141a9c396cffaf16bb9549..614135b8b068748b4a36835617892ba0e4cacc48 100644 GIT binary patch delta 27 jcmcZ-bR}qmo#f^vavaQ?6J?y4Hrq)mGjCp_xPl!3maz$_ delta 24 gcmcZ-bR}qmo#f_DIb~)>oy~v5yjVA{QCz_e0DUnDL;wH) diff --git a/test/files/small.dir/chromatograms_metadata.parquet b/test/files/small.dir/chromatograms_metadata.parquet index ef616d4be681b53f0de12e08ec1d59ec399e044a..e6ea0cb4942b1f2012f7c885efadd162259f8728 100644 GIT binary patch literal 10864 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 C6e+j> literal 22944 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 C;9`pa diff --git a/test/files/small.dir/chromatograms_metadata_precursors.parquet b/test/files/small.dir/chromatograms_metadata_precursors.parquet new file mode 100644 index 0000000000000000000000000000000000000000..27615fa5bca03a6dfd39dd86e8532208ee88f6d2 GIT binary patch literal 2759 zcmdT`-E!JS6jnVXO+B7TCk-v0NvFB-RoV!okU*ywT_F%q8v|nl;)c;50$V>s5(x85 zeS^MCA0!t&=PU{~w$qv3b;n4%d;Y$2_S<81S-+Ie9e?_%d1ORUm`-AA=J-bbP{`*_ z4hlzcxQr}kG7oHLee&w`&+mSJAqZUdd=WX;GK$0Kp7;n&Z;9;_;q(>Ql@AKv&g0NC zl6e?RQt%q4`Da4@8R-A8FPJ1o~tXb;lhXv-$|S( z&L8BTp8kfIa)q2XkCXfX!pqGQ#|KC-eX=sVrGxOW5?{}Q#BrTS$REP$$KSD%Pbya?F*Ds4)(CT zuu+lKT7-}0pW|JA&WF`1C?}9wd1emDJ&P$Hil6yclwM}fSQ+K%HF#BKy43M3xw9~( zcd%Bihw{6j_>bq;>TbZ_RpowudKMpCZ&2PuSGr$anc84xx-1pDP=E5D;j7=uPq{|9 zf=AQr6fNIdjzMjR@z!L-6UK9*wd$ijNa)DmO|HlOdZmvDe;wsiWO9l=>NQg8ZmObK z#SK2Fe2nlK!*X#vv^~q^{%pSy+q~A~${6)Mlj{SI8MCy;jXo@Swl=u2059=bYh2ly zC+VX;uk|_A0}tUAAK(x&{%R@aaE)u3_(l9o#i=N2UHLv=P2%c~uMHLQsk}q|eZJ2A zg|AceFI}Mh7x;RG@_oLZVZ6uBQ)MXM=W9awPysW(+~Mnclt0W@_o4iXSky1v$95O4 zTDk$M@8@@muXPpW`+O~maCIKfPYF@J&({&gdzh~uQ6IoQ#@80gHB2Yd0KO6^AO`dS zsDI2wbP}k@dls+|rwcRadt+$;R~U~{k@VrZG^SGHLsRe2;2QVZ_FNUHkdpyN?* ztHB)>E98{d6U=*Ov|d8aBDRBV$R@ruZ(~a98Qh_j=~ZR8lh>wfPfhR-Sf{2m_AO~Z zM}G=;eA~~l2sofW(EWj_j`f0)QkRBxhM(8#Vf_7><=cpx5E-Km^sh$I7*>}f*cvTqF_?<3o6602*eO{-XJvU+#OOH`Tt^V+j8(QSU0wm+4|nyN-KiItV7=(( zVh9+7A8-$DbZG$C%h%%(w-E|2tXUNLMiRR4l{K-#WsnrUDhY*S`0V<)bdq19`1K0D gz2Hmeb@{_v`EBX=(@Xd+{Lg>FUx$Lwg-_Uj0P(}(Jpcdz literal 0 HcmV?d00001 diff --git a/test/files/small.dir/chromatograms_metadata_selected_ions.parquet b/test/files/small.dir/chromatograms_metadata_selected_ions.parquet new file mode 100644 index 0000000000000000000000000000000000000000..f060e43d43565dd5abdce2726ec2047444609236 GIT binary patch literal 1806 zcma)7-E!Ji5LOrxryizholys7a*-QfwT%qeiOD20IWi!ifC0xy=!TIF#{#S$q90`Q zBzcHFS|6bo-Ccn*xIfb~aHQR{XTNX1J(31&RF?ClKVP;ARu~59G~#pNS@I8(oIlPL z3sI1STuc}KOxzv6>i+)n(~%@)YcE!z;7J$-;op`IVe5-XxPr%GHeCe%)Z0jM4ioaZ z6*^)X#a1jN`8l}0UkU3LQpCcK7V*6#SC8 z&H$f9ak%iEEs87whXXqZTw(cHz;_nsB8q_5E(Uq#ePFWZNc=^-b^Iii3Z-9)1vVO8 z>hCkji(d~F9Oq7*F*bxhj^7wd*@eS8=z=R_D*3?#o3S=p;M!fmq96|2Sd;Y_>kT0c ze&D{MuoR5?5D_Sqx~u`{1oI#;jgcM@17d;s*txP+l*>U?QSQkJiD{^HBgHfgv~F?I zaAGGo0T9^BpB1HctK29`6Ky0$qtSf8V;2p~PkdVA#4@t-mxy<_kJo(I&saEkrzk0| ztI0$!yOg&38SA;m`-l9)>m6Ul`k#T%Iq1pQMDH(6qrb8AT7vfJKh?*n{(rAetz*-A z$p2A&+5As)Tcd7ftTZ`=q)C;-A zxOO58mGCswoW;dxB4uI{!OyY=0ZP3Lh^ znS)j{*QoYSCZHF=wdp*OUM+IKH0K$MxuZ$y8??XREH{06ZA_+Jr z!1k2Y)2t{EiS&O|{{`7ytiQHHhAV2JrraAZXGUjZS38jV?AA0KOr~T0y2AjUQojB0YGeR_0#cMV(D;jKahC_)#hz@p%|{Rvb9+f}ipr@#Er?vQ#X= p8~;lp;$Oq)^%`FL@DhG~_U=vfO}X?3d=&oYPs$xg(g41Me*wQd;LQL4 literal 0 HcmV?d00001 diff --git a/test/files/small.dir/mzpeak_index.json b/test/files/small.dir/mzpeak_index.json index 6663228..c2959fd 100644 --- a/test/files/small.dir/mzpeak_index.json +++ b/test/files/small.dir/mzpeak_index.json @@ -3,31 +3,400 @@ { "name": "spectra_data.parquet", "entity_type": "spectrum", - "data_kind": "data arrays" + "data_kind": "data_arrays", + "column_mapping": [], + "parameters": [] }, { "name": "spectra_peaks.parquet", "entity_type": "spectrum", - "data_kind": "peaks" + "data_kind": "peaks", + "column_mapping": [], + "parameters": [] }, { "name": "spectra_metadata.parquet", "entity_type": "spectrum", - "data_kind": "metadata" + "data_kind": "metadata", + "column_mapping": [ + { + "name": "ms level", + "path": "ms_level", + "accession": "MS:1000511", + "unit": null + }, + { + "name": "scan polarity", + "path": "scan_polarity", + "accession": "MS:1000465", + "unit": null + }, + { + "name": "spectrum representation", + "path": "spectrum_representation", + "accession": "MS:1000525", + "unit": null + }, + { + "name": "spectrum type", + "path": "spectrum_type", + "accession": "MS:1000559", + "unit": null + }, + { + "name": "lowest observed m/z", + "path": "lowest_observed_mz", + "accession": "MS:1000528", + "unit": "MS:1000040" + }, + { + "name": "highest observed m/z", + "path": "highest_observed_mz", + "accession": "MS:1000527", + "unit": "MS:1000040" + }, + { + "name": "number of data points", + "path": "number_of_data_points", + "accession": "MS:1003060", + "unit": null + }, + { + "name": "number of peaks", + "path": "number_of_peaks", + "accession": "MS:1003059", + "unit": null + }, + { + "name": "base peak m/z", + "path": "base_peak_mz", + "accession": "MS:1000504", + "unit": "MS:1000040" + }, + { + "name": "base peak intensity", + "path": "base_peak_intensity", + "accession": "MS:1000505", + "unit": "MS:1000131" + }, + { + "name": "total ion current", + "path": "total_ion_current", + "accession": "MS:1000285", + "unit": "MS:1000131" + } + ], + "parameters": [] + }, + { + "name": "spectra_metadata_scans.parquet", + "entity_type": "spectrum", + "data_kind": "scans", + "column_mapping": [ + { + "name": "scan start time", + "path": "scan_start_time", + "accession": "MS:1000016", + "unit": "UO:0000031" + }, + { + "name": "preset scan configuration", + "path": "preset_scan_configuration", + "accession": "MS:1000616", + "unit": null + }, + { + "name": "filter string", + "path": "filter_string", + "accession": "MS:1000512", + "unit": null + }, + { + "name": "ion injection time", + "path": "ion_injection_time", + "accession": "MS:1000927", + "unit": "UO:0000028" + }, + { + "name": "scan window lower limit", + "path": "scan_windows.scan_window_lower_limit", + "accession": "MS:1000501", + "unit": "MS:1000040" + }, + { + "name": "scan window upper limit", + "path": "scan_windows.scan_window_upper_limit", + "accession": "MS:1000500", + "unit": "MS:1000040" + } + ], + "parameters": [] + }, + { + "name": "spectra_metadata_precursors.parquet", + "entity_type": "spectrum", + "data_kind": "precursors", + "column_mapping": [ + { + "name": "isolation window target m/z", + "path": "isolation_window.isolation_window_target", + "accession": "MS:1000827", + "unit": "MS:1000040" + }, + { + "name": "isolation window lower offset", + "path": "isolation_window.isolation_window_lower_offset", + "accession": "MS:1000828", + "unit": "MS:1000040" + }, + { + "name": "isolation window upper offset", + "path": "isolation_window.isolation_window_upper_offset", + "accession": "MS:1000829", + "unit": "MS:1000040" + } + ], + "parameters": [] + }, + { + "name": "spectra_metadata_selected_ions.parquet", + "entity_type": "spectrum", + "data_kind": "selected_ions", + "column_mapping": [ + { + "name": "selected ion m/z", + "path": "selected_ion_mz", + "accession": "MS:1000744", + "unit": "MS:1000040" + }, + { + "name": "charge state", + "path": "charge_state", + "accession": "MS:1000041", + "unit": null + }, + { + "name": "intensity", + "path": "peak_intensity", + "accession": "MS:1000042", + "unit": "MS:1000131" + } + ], + "parameters": [] }, { "name": "chromatograms_metadata.parquet", "entity_type": "chromatogram", - "data_kind": "metadata" + "data_kind": "metadata", + "column_mapping": [ + { + "name": "scan polarity", + "path": "scan_polarity", + "accession": "MS:1000465", + "unit": null + }, + { + "name": "chromatogram type", + "path": "chromatogram_type", + "accession": "MS:1000626", + "unit": null + }, + { + "name": "number of data points", + "path": "number_of_data_points", + "accession": "MS:1003060", + "unit": null + } + ], + "parameters": [] + }, + { + "name": "chromatograms_metadata_precursors.parquet", + "entity_type": "chromatogram", + "data_kind": "precursors", + "column_mapping": [ + { + "name": "isolation window target m/z", + "path": "isolation_window.isolation_window_target", + "accession": "MS:1000827", + "unit": "MS:1000040" + }, + { + "name": "isolation window lower offset", + "path": "isolation_window.isolation_window_lower_offset", + "accession": "MS:1000828", + "unit": "MS:1000040" + }, + { + "name": "isolation window upper offset", + "path": "isolation_window.isolation_window_upper_offset", + "accession": "MS:1000829", + "unit": "MS:1000040" + } + ], + "parameters": [] + }, + { + "name": "chromatograms_metadata_selected_ions.parquet", + "entity_type": "chromatogram", + "data_kind": "selected_ions", + "column_mapping": [ + { + "name": "selected ion m/z", + "path": "selected_ion_mz", + "accession": "MS:1000744", + "unit": "MS:1000040" + }, + { + "name": "charge state", + "path": "charge_state", + "accession": "MS:1000041", + "unit": null + }, + { + "name": "intensity", + "path": "peak_intensity", + "accession": "MS:1000042", + "unit": "MS:1000131" + } + ], + "parameters": [] }, { "name": "chromatograms_data.parquet", "entity_type": "chromatogram", - "data_kind": "data arrays" + "data_kind": "data_arrays", + "column_mapping": [], + "parameters": [] } ], "metadata": { "scan_settings_list": [], + "version": "0.9.0", + "data_processing_method_list": [ + { + "id": "pwiz_Reader_Thermo_conversion", + "methods": [ + { + "order": 0, + "parameters": [ + { + "accession": "MS:1000544", + "name": "Conversion to mzML", + "unit": null, + "value": null + } + ], + "software_reference": "pwiz" + } + ] + }, + { + "id": "mzpeak_conversion1", + "methods": [ + { + "order": 1, + "parameters": [ + { + "accession": null, + "name": "conversion options", + "unit": null, + "value": "-y -z -u small.mzML -o small.mzpeak" + } + ], + "software_reference": "mzpeak_prototyping_convert1" + } + ] + } + ], + "file_description": { + "contents": [ + { + "accession": "MS:1000579", + "name": "MS1 spectrum", + "unit": null, + "value": null + }, + { + "accession": "MS:1000580", + "name": "MSn spectrum", + "unit": null, + "value": null + } + ], + "source_files": [ + { + "id": "RAW1", + "location": "file:///C:\\Users\\Joshua\\Dev\\learn-rust\\ffirawfilereader\\tests\\data", + "name": "small.RAW", + "parameters": [ + { + "accession": "MS:1000569", + "name": "SHA-1", + "unit": null, + "value": "b43e9286b40e8b5dbc0dfa2e428495769ca96a96" + }, + { + "accession": "MS:1000563", + "name": "Thermo RAW format", + "unit": null, + "value": null + }, + { + "accession": "MS:1000768", + "name": "Thermo nativeID format", + "unit": null, + "value": null + } + ] + } + ] + }, + "software_list": [ + { + "id": "Xcalibur", + "parameters": [ + { + "accession": "MS:1000532", + "name": "Xcalibur", + "unit": null, + "value": null + } + ], + "version": "1.1 Beta 7" + }, + { + "id": "pwiz", + "parameters": [ + { + "accession": "MS:1000615", + "name": "ProteoWizard software", + "unit": null, + "value": null + } + ], + "version": "3.0.23307" + }, + { + "id": "mzpeak_prototyping_convert1", + "parameters": [ + { + "accession": "MS:1000799", + "name": "custom unreleased software tool", + "unit": null, + "value": "mzpeak_prototyping_convert" + } + ], + "version": "0.1.0" + } + ], + "run": { + "default_data_processing_id": "pwiz_Reader_Thermo_conversion", + "default_instrument_id": 0, + "default_source_file_id": "RAW1", + "id": "small", + "start_time": "2005-07-20T19:44:22Z" + }, "instrument_configuration_list": [ { "components": [ @@ -54,8 +423,8 @@ "order": 2, "parameters": [ { - "accession": "MS:1000079", - "name": "fourier transform ion cyclotron resonance mass spectrometer", + "accession": "MS:1000083", + "name": "radial ejection linear ion trap", "unit": null, "value": null } @@ -66,15 +435,15 @@ "order": 3, "parameters": [ { - "accession": "MS:1000624", - "name": "inductive detector", + "accession": "MS:1000253", + "name": "electron multiplier", "unit": null, "value": null } ] } ], - "id": 0, + "id": 1, "parameters": [ { "accession": "MS:1000448", @@ -116,8 +485,8 @@ "order": 2, "parameters": [ { - "accession": "MS:1000083", - "name": "radial ejection linear ion trap", + "accession": "MS:1000079", + "name": "fourier transform ion cyclotron resonance mass spectrometer", "unit": null, "value": null } @@ -128,15 +497,15 @@ "order": 3, "parameters": [ { - "accession": "MS:1000253", - "name": "electron multiplier", + "accession": "MS:1000624", + "name": "inductive detector", "unit": null, "value": null } ] } ], - "id": 1, + "id": 0, "parameters": [ { "accession": "MS:1000448", @@ -154,92 +523,20 @@ "software_reference": "Xcalibur" } ], - "run": { - "default_data_processing_id": "pwiz_Reader_Thermo_conversion", - "default_instrument_id": 0, - "default_source_file_id": "RAW1", - "id": "small", - "start_time": "2005-07-20T19:44:22Z" - }, - "data_processing_method_list": [ + "cv_list": [ { - "id": "pwiz_Reader_Thermo_conversion", - "methods": [ - { - "order": 0, - "parameters": [ - { - "accession": "MS:1000544", - "name": "Conversion to mzML", - "unit": null, - "value": null - } - ], - "software_reference": "pwiz" - } - ] + "full_name": "Proteomics Standards Initiative Mass Spectrometry Ontology", + "id": "MS", + "uri": "http://purl.obolibrary.org/obo/ms/4.1.249/psi-ms.obo", + "version": "4.1.249" }, { - "id": "mzpeak_conversion1", - "methods": [ - { - "order": 1, - "parameters": [ - { - "accession": null, - "name": "conversion options", - "unit": null, - "value": "-y -z -u small.mzML -o small.mzpeak" - } - ], - "software_reference": "mzpeak_prototyping_convert1" - } - ] + "full_name": "Units of measurement ontology", + "id": "UO", + "uri": "http://purl.obolibrary.org/obo/uo/releases/2026-01-16/uo.obo", + "version": "2026-01-16" } ], - "file_description": { - "contents": [ - { - "accession": "MS:1000579", - "name": "MS1 spectrum", - "unit": null, - "value": null - }, - { - "accession": "MS:1000580", - "name": "MSn spectrum", - "unit": null, - "value": null - } - ], - "source_files": [ - { - "id": "RAW1", - "location": "file:///C:\\Users\\Joshua\\Dev\\learn-rust\\ffirawfilereader\\tests\\data", - "name": "small.RAW", - "parameters": [ - { - "accession": "MS:1000569", - "name": "SHA-1", - "unit": null, - "value": "b43e9286b40e8b5dbc0dfa2e428495769ca96a96" - }, - { - "accession": "MS:1000563", - "name": "Thermo RAW format", - "unit": null, - "value": null - }, - { - "accession": "MS:1000768", - "name": "Thermo nativeID format", - "unit": null, - "value": null - } - ] - } - ] - }, "sample_list": [ { "id": "_x0031_", @@ -253,44 +550,6 @@ } ] } - ], - "software_list": [ - { - "id": "Xcalibur", - "parameters": [ - { - "accession": "MS:1000532", - "name": "Xcalibur", - "unit": null, - "value": null - } - ], - "version": "1.1 Beta 7" - }, - { - "id": "pwiz", - "parameters": [ - { - "accession": "MS:1000615", - "name": "ProteoWizard software", - "unit": null, - "value": null - } - ], - "version": "3.0.23307" - }, - { - "id": "mzpeak_prototyping_convert1", - "parameters": [ - { - "accession": "MS:1000799", - "name": "custom unreleased software tool", - "unit": null, - "value": "mzpeak_prototyping_convert" - } - ], - "version": "0.1.0" - } ] } } \ No newline at end of file diff --git a/test/files/small.dir/spectra_data.parquet b/test/files/small.dir/spectra_data.parquet index e297aa55ab161e5f8bbf686e7c62dba7963e97fc..8d0f7502370d09f3521278b3978172bc3064fa66 100644 GIT binary patch delta 96 zcmV-m0H6Pd-)4v3W`KkNgaU*Egam{Iga(8Mgb0KQgbIWUgbaiYgbsucgb;)ggc5`k zgcO7ogcgJsgc!6LRHp%Ww^gSZbpiorx67#+bOV>Kt{GsrpRE}X0hd3m8DN(WuNint CGa<+T delta 96 zcmV-m0H6Pd-)4v3W`KkNgaU*Egam{Iga(8Mgb0KQgbIWUgbaiYgbsucgb;)ggc5`k zgcO7ogcgJsgc!6LRHp%Xw^gSZbpioVx67#+bOV>Kt{GsrpRE}X0hd3m8DN(WuNinq C?;*1Q diff --git a/test/files/small.dir/spectra_metadata.parquet b/test/files/small.dir/spectra_metadata.parquet index 8ce8000c3f017ad8c3b320be09b7afbc9964adb0..c4a028966c3c24c9d6754d57ed8009ee2f5bae84 100644 GIT binary patch delta 6398 zcmb7I3v64}8NNqPjs;m^YAyh$uW^`4DG(ZEPsw>bjgiwdjR#g=er~++cT?YIAbFUre z0o!hs>+|@&|9$>*)j$3t=ZohRkM36}NMX^|qffsp4amJn={1*wHaJqm`rn-3}$y*u+alFsjz6K4Ss7VRv@ zN-O_ABU{pcV;@}y(IRiGyR^;6j_+mflVecHt}kSmLUn$z&gXlTDg2t5&%Lqs)kfxP z_&1+R-YTph4{R@2@T73dkcMY`Ov(D3l47NTVHo(yfgd)fgk!eytcLFuiHR;2IUIlV#N?Sdgn%E0#uQsRU31b{V1v= zj7FCF?GlgbHY$A(1)`|Bm7LzT>VKH5sR2UCVK9KQzI9rxaFN38r4UTrc3x$(GE5!$ zS9K!g;6P`IhC5r%j>*FVo7wy1;pr`z;gxNf;o%*u0RWIrWM13B*2wS++RX5@jt*ta zRUO+PV=n2lH&SN|+0$dj?Ac9ehDtuFrW-hX-IN(VQbk4^PY1VV$60b;J-rpP1r)>kSD$dmONMG^U3OA+~~ zKHpI=F*X>8k55MWEw z;lZ2@D*#AEqkjYEE76q9SMiJn;$&jf>q?4=-Y(EK!*GZ9vK)8N&e|9R- zg)rRgUbc*T%g$a>P(+T5$RoYY^|FlfDC1ZQTSF0R(lUP2+*>Nicn1L&TVy3^5d~yV zOPh?ihKR>nWf5sJFlk;HF;DGiWgUwQEg&7OPOYTn_}yUXu{IfzHe-dPk}q1FI*D`c zZqR$RO|~q}St;M-c$-r;R>Pg{WO)R}&uKIB|n zl$83|_tz6$k5YA7#~;uEvqIye+8FM5KUn%|n!}W^>XNiFHTiR(K;6JK@K<%@Mu5#S z@+@lldzvMOsG0hAI$l5;yVR;ndj6yyRF##()k~Fe7g5)9U2Jvskw6wl-U+UzuAMRP zGls>i>&W_e1=j7VV@Ce^67&pw$#IW&%R1zv8zQM*4LRCfq`GP3FB)m+v>|ntLnnRU z&|BSXU3S8dRzb>o)I@Vvq4J|@{zam{t8^27BuEc;7HJ3Wl4i)N48T|;im4D_HIzUS<63B3x-vc$tt+V2QcV8OG_!$yF+!Vj-Ou) zei6aPdY6MYky2NI@`#!Ln7q_mo)zwk$hf$K(G2$7Mjm%K9aXoz6uxj|1!!&HA z-we(tNcsdHZsb4M4O+`94=;Gi9S?xoV;;64`w&Y+=VTsv5sv;R zjr=Q(%ZO)@czH!)YH$sCbwsJUw1+>rXBqJ_5|4NnYox`p>6@A|p0+arKOpv|7qSN%U#5(z&hH0Nbzvdg+7v;#kn`ic9lqoW}>l=236oex1i99pMQVOk_C4B)Ci>WX_Ju4%Wy9B~^+$81rmIf?2$xNF51KHcsv zYCp9U{S&9m39UAY>XHdhD+Sep>ggu3nBp`hJ16liWucnq#&z!0AF>dj+b*s?f>$eQ~rS39W> zF|(GK?li24bcqsBE$%4WA~Rj>`Bw|6j?^AAcWtX zB1G(|z@_&21; z+J|@u!#AbKGK_!~Q`FQ`3n{8g`6ITtm!3*%48e9x#KHk`_0TyLK~J<%wgv(zv8JTX z1t!qBq>K)cX`%Cu+8T&PR_nlh+K^i0jzkw|a;ZKlE30GCw1}agJ2d9EheLsAc*x^! zo%XoHQuAmAZ?uOhSBtSKI3{6df_M)ZcDMG_!c4%WC3&|k^aaZl?o$QLuE_yf+JaC6 zA}$;%EjBnICQ{{2diwNXYMp4U!#c|q;J19HIM?Y+lKL6LZfmkmt2M{8%g>>GL7(Ws zeY^|=duzgP*dO-vRSTma0`(0$lb{bHEhdA_tz-U3KoHtT>c|TRigWD=Be^+KT53X& z)TC)n5G{l-gq9X9ss5om*P1kw&PPg9mL&9@y1QUU@Y-$I>8An_^N82&NDlOwqHaSN zx{f%dh00%?IQb@Vv?CwbbQhrbE9(5l?vUSRh9+ei@)%vyfrx98Ho~?M#MA6!#K~_D zv&{qe8V4y4&I_NN!1@MGM&gN$Gm0 zcZMfoOM1aEZ=Y@4ZP3@iKG@g;S*3;${$L?DodB6q+o9W5Y6CfX@>adrf@c6av!)p9gMg}sk!aR zD$K628O)$@lAEb?Rqam%;fe~{%n45vZic8g?6sNWgMcwRYq2}tZgOIL(K#^*@f?A& z(feIy-Jn~f@@Z?X!>T3a2a8i7YI*t&kkez=C*4ro;N}D}@WPgQMzsLZr}0kr zj3qs%S1vYW!LUod>oJ@ny`*8W4;QV&f)n=1v0oI{(%TRjT`lP|0b+*{M#am;hX~pW zp!`R@?%}!Jzucsrd*g+?+_{6#TXN?5Pu-q7_lGk-Sw*JL-=?S{Pn~~YHS_i^hH(nr G`u_l_d|Sc* literal 48083 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 Cp{eQs literal 0 HcmV?d00001 diff --git a/test/files/small.dir/spectra_metadata_scans.parquet b/test/files/small.dir/spectra_metadata_scans.parquet new file mode 100644 index 0000000000000000000000000000000000000000..7a410f5f013621712418dd470c4ecc743e2d5418 GIT binary patch literal 16124 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 bD!(w_44(_bXX&5gf9iJlo1rH7yZ8SE3=~}G literal 0 HcmV?d00001 diff --git a/test/files/small.dir/spectra_metadata_selected_ions.parquet b/test/files/small.dir/spectra_metadata_selected_ions.parquet new file mode 100644 index 0000000000000000000000000000000000000000..1c6d8a0e8fb7919385a273ffb3726a32279060bb GIT binary patch literal 10597 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 literal 0 HcmV?d00001 diff --git a/test/files/small.mzpeak b/test/files/small.mzpeak index 859d5b86e4384f51d0ae55c99a0238ad52e971fa..23363b70c84fd3d15b86a8909fb31a39b45deb7b 100644 GIT binary patch delta 28217 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 delta 29518 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

    %;WLpMm@sW9Q1?I(0%uL-kYvhX+? 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 F{eN!UDg^)l delta 29192 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

    thi=Ca0??waawQ3vd-q^9c5w_ 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 From e1b180dc98a41906a3075f48df71c628e21b1dfb Mon Sep 17 00:00:00 2001 From: "Peter J. Jones" Date: Wed, 5 Aug 2026 09:22:58 +0200 Subject: [PATCH 2/7] schema: Add support for top-level columns that are not in a group --- include/mzpeak/schema/group.h | 32 +++++++++---- src/metadata/spectrum.cpp | 2 +- src/schema/group.cpp | 86 +++++++++++++++++++---------------- src/util/executor.cpp | 17 ++++--- src/util/parquet.cpp | 10 ++-- src/util/query.cpp | 4 +- test/executor_test.cpp | 4 +- test/group_test.cpp | 39 +--------------- test/planner_test.cpp | 43 ++---------------- 9 files changed, 98 insertions(+), 139 deletions(-) diff --git a/include/mzpeak/schema/group.h b/include/mzpeak/schema/group.h index 5617752..c6a2e14 100644 --- a/include/mzpeak/schema/group.h +++ b/include/mzpeak/schema/group.h @@ -18,6 +18,7 @@ top-level directory of this repository. // Forward declarations. namespace parquet::schema { +class Node; class GroupNode; } // namespace parquet::schema @@ -87,16 +88,10 @@ class Group final { */ index_type absolute_index() const; - /** - * The name of this field using underscores to replace spaces and - * other special characters. - */ - const std::string& name() const; - /** * The name of this field as recognized by parquet. */ - const std::string& schema_name() const; + const std::string& name() const; /** * The structural type this field represents. @@ -129,7 +124,6 @@ class Group final { index_type rel_index_; index_type abs_index_; std::string schema_name_; - std::string clean_name_; std::optional cv_type_; std::optional cv_unit_; std::optional type_; @@ -140,6 +134,10 @@ class Group final { /// Fields are stored in a map for quick look-up using their name. using field_map_t = std::map>; + /// Constructor for the root group to hold all of the top-level + /// columns that are not in a separate struct/group. + explicit Group(const parquet::schema::GroupNode&); + /// Constructor from a parquet schema descriptor. explicit Group(const parquet::schema::GroupNode&, index_type index, @@ -176,8 +174,26 @@ class Group final { */ const field_map_t& fields() const; + /** + * Return true if this is the root group. + * + * There is only one root group and its fields represent the + * top-level columns that are not themselves members of a group or + * struct. + */ + bool is_root() const; + + /** + * Return a schema path to the given field. Mostly useful for error + * messages. + */ + std::string path(const Field&) const; + private: + void make_fields(const parquet::schema::GroupNode& node, index_type offset); + std::string name_; + bool is_root_; index_type index_; field_map_t fields_; }; diff --git a/src/metadata/spectrum.cpp b/src/metadata/spectrum.cpp index 007671a..07349c6 100644 --- a/src/metadata/spectrum.cpp +++ b/src/metadata/spectrum.cpp @@ -23,7 +23,7 @@ using namespace MzPeak::Util; /******************************************************************************/ Spectrum::Spectrum(std::shared_ptr table, uint64_t index) : table_(std::move(table)) - , group_(table_ == nullptr ? nullptr : table_->group("spectrum")) + , group_(table_ == nullptr ? nullptr : table_->group("root")) , index_(index) , ms_level_() , delta_model_() diff --git a/src/schema/group.cpp b/src/schema/group.cpp index f14b2ff..061db07 100644 --- a/src/schema/group.cpp +++ b/src/schema/group.cpp @@ -78,37 +78,10 @@ Group::Field::Field(const std::string_view& column_name, : rel_index_(rel_index) , abs_index_(abs_index) , schema_name_(column_name) - , clean_name_(column_name) , cv_type_() , cv_unit_() , type_() { - using std::operator""sv; - - auto tokens = column_name | std::views::split("_"sv) | - std::ranges::to>(); - - if (tokens.size() < 3) { - return; - } - - auto name_begin = tokens.begin(); - auto name_end = tokens.end(); - - if (*name_begin == "MS" || *name_begin == "UO") { - cv_type_ = CVType(*name_begin, *(name_begin + 1)); - name_begin += 2; - } - - auto unit = std::ranges::find_last(name_begin, name_end, "unit"sv); - - if (unit.begin() != name_begin && unit.begin() != name_end && - std::ranges::distance(unit.begin(), name_end) == 3) { - cv_unit_ = CVUnit(*(unit.begin() + 1), *(unit.begin() + 2)); - name_end = unit.begin(); - } - - clean_name_ = join_(name_begin, name_end); } /******************************************************************************/ @@ -118,10 +91,7 @@ Group::index_type Group::Field::relative_index() const { return rel_index_; } Group::index_type Group::Field::absolute_index() const { return abs_index_; } /******************************************************************************/ -const std::string& Group::Field::name() const { return clean_name_; } - -/******************************************************************************/ -const std::string& Group::Field::schema_name() const { return schema_name_; } +const std::string& Group::Field::name() const { return schema_name_; } /******************************************************************************/ Group::Field::Kind Group::Field::kind() const { return kind_; } @@ -144,37 +114,60 @@ const std::optional& Group::Field::type() const { return type_; } /******************************************************************************/ void Group::Field::type(Util::Type type) { type_ = type; } +/******************************************************************************/ +Group::Group(const parquet::schema::GroupNode& node) + : name_("root") + , is_root_(true) + , index_(0) + , fields_() +{ + // This is the root group so only collect non-group top-level columns. + make_fields(node, 0); +} + /******************************************************************************/ Group::Group(const parquet::schema::GroupNode& node, index_type index, index_type offset) : name_(node.name()) + , is_root_(false) , index_(index) , fields_() +{ + make_fields(node, offset); +} + +/******************************************************************************/ +void Group::make_fields(const parquet::schema::GroupNode& node, index_type offset) { for (index_type i : std::views::iota(0, node.field_count())) { auto child = node.field(i); - std::shared_ptr field = - std::make_shared(child->name(), i, offset + i); - if (child->is_primitive()) { + std::shared_ptr field = + std::make_shared(child->name(), i, offset + i); + fields_[field->name()] = field; + auto prim = std::static_pointer_cast(child); field->kind_ = Field::Kind::Scalar; field->type_ = Util::type_from_parquet(*prim); - } else { + } else if (child->is_group()) { auto grp = std::static_pointer_cast(child); - if (field->name() == "parameters" && grp->logical_type()->is_list()) { - field->kind_ = Group::Field::Kind::Params; - } else { + if (grp->field_count() == 1) { + std::shared_ptr field = + std::make_shared(grp->name(), i, offset + i); + fields_[field->name()] = field; + auto grp_type = field_type_from_parquet(grp); field->kind_ = grp_type.first; field->type_ = grp_type.second; + + if (field->name() == "parameters") { + field->kind_ = Group::Field::Kind::Params; + } } } - - fields_[field->name()] = field; } } @@ -218,4 +211,17 @@ Group::field(const CVType&& cvt) const /******************************************************************************/ const Group::field_map_t& Group::fields() const { return fields_; } +/******************************************************************************/ +bool Group::is_root() const { return is_root_; } + +/******************************************************************************/ +std::string Group::path(const Field& field) const +{ + if (is_root_) { + return field.name(); + } else { + return name() + "." + field.name(); + } +} + } // namespace MzPeak::Schema diff --git a/src/util/executor.cpp b/src/util/executor.cpp index d4344f5..7c3298b 100644 --- a/src/util/executor.cpp +++ b/src/util/executor.cpp @@ -119,14 +119,17 @@ std::shared_ptr Executor::Impl::array(std::shared_ptr& batch, const Schema::Column& field) { - std::shared_ptr col(batch->column(field.first->index())); - - if (col && col->type_id() == arrow::Type::STRUCT) { - auto sa = std::static_pointer_cast(col); - return sa->field(field.second->relative_index()); + if (field.first->is_root()) { + return batch->column(field.second->absolute_index()); } else { - throw ParquetError("column not in batch: " + field.first->name() + "." + - field.second->name()); + std::shared_ptr col(batch->column(field.first->index())); + + if (col && col->type_id() == arrow::Type::STRUCT) { + auto sa = std::static_pointer_cast(col); + return sa->field(field.second->relative_index()); + } else { + throw ParquetError("column not in batch: " + field.first->path(*field.second)); + } } } diff --git a/src/util/parquet.cpp b/src/util/parquet.cpp index 8f5c44b..962b726 100644 --- a/src/util/parquet.cpp +++ b/src/util/parquet.cpp @@ -106,12 +106,16 @@ void Parquet::Impl::parse_schema() auto root = fmd->schema()->group_node(); int32_t offset = 0; + std::shared_ptr root_group = std::make_shared(*root); + + if (!root_group->fields().empty()) { + (*groups_)[root_group->name()] = root_group; + } + for (int32_t i : std::views::iota(0, root->field_count())) { auto node = root->field(i); - // TODO: Should we emit a warning if there is a top-level - // primitive column? - if (node->is_group()) { + if (node->is_group() && !node->logical_type()->is_list()) { std::shared_ptr group = std::static_pointer_cast(node); diff --git a/src/util/query.cpp b/src/util/query.cpp index a1d023d..e3fa539 100644 --- a/src/util/query.cpp +++ b/src/util/query.cpp @@ -62,7 +62,7 @@ Query Query::Builder::validate(Query::Predicate&& p) const { if (!p.dest.second->type().has_value()) { std::string msg("cannot query field with unknown type: "); - msg += p.dest.first->name() + "." + p.dest.second->name(); + msg += p.dest.first->path(*p.dest.second); throw TypeError(msg); } @@ -239,7 +239,7 @@ Trampoline> EvalHelper::eval(const Query& query, if constexpr (std::is_same_v) { if (!tree.dest.second->type().has_value()) { std::string msg("invalid query on field with unknown data type: "); - msg += tree.dest.first->name() + "." + tree.dest.second->name(); + msg += tree.dest.first->path(*tree.dest.second); throw TypeError(msg); } diff --git a/test/executor_test.cpp b/test/executor_test.cpp index 248c850..9fc9d10 100644 --- a/test/executor_test.cpp +++ b/test/executor_test.cpp @@ -71,10 +71,10 @@ BOOST_AUTO_TEST_CASE(can_read_uint8_t) auto parquet = index.parquet(*entry); - auto index_field = parquet->field("spectrum", "index"); + auto index_field = parquet->field("root", "index"); BOOST_TEST(index_field.has_value()); - auto ms_level = parquet->field("spectrum", "ms_level"); + auto ms_level = parquet->field("root", "ms_level"); BOOST_TEST(ms_level.has_value()); auto query = Util::Query::Builder(*index_field).eq(0ul); diff --git a/test/group_test.cpp b/test/group_test.cpp index bc7752b..294fb4a 100644 --- a/test/group_test.cpp +++ b/test/group_test.cpp @@ -10,43 +10,8 @@ top-level directory of this repository. #include #include "mzpeak/open.h" -#include "mzpeak/schema/group.h" #include "mzpeak/util/parquet.h" -/******************************************************************************/ -BOOST_AUTO_TEST_CASE(can_parse_column_names) -{ - using namespace MzPeak::Schema; - - // All components. - Group::Field a("MS_1000528_lowest_observed_mz_unit_MS_1000040", 0, 2); - BOOST_TEST(a.relative_index() == 0); - BOOST_TEST(a.absolute_index() == 2); - BOOST_TEST(a.name() == "lowest_observed_mz"); - - BOOST_TEST(a.cv_type().has_value()); - BOOST_TEST((a.cv_type()->code() == "MS")); - BOOST_TEST((a.cv_type()->accession() == "1000528")); - BOOST_TEST((a.cv_type()->to_string() == "MS:1000528")); - - BOOST_TEST((a.cv_unit().has_value())); - BOOST_TEST((a.cv_unit().has_value() && a.cv_unit()->code() == "MS")); - BOOST_TEST((a.cv_unit()->accession() == "1000040")); - - // No unit. - Group::Field b("MS_1000016_scan_start_time", 0, 0); - BOOST_TEST(b.name() == "scan_start_time"); - BOOST_TEST((b.cv_type().has_value() && b.cv_type()->code() == "MS")); - BOOST_TEST((b.cv_type().has_value() && b.cv_type()->accession() == "1000016")); - BOOST_TEST((!b.cv_unit().has_value())); - - // Nmae only. - Group::Field c("mz", 0, 0); - BOOST_TEST(c.name() == "mz"); - BOOST_TEST(!c.cv_type().has_value()); - BOOST_TEST(!c.cv_unit().has_value()); -} - /******************************************************************************/ BOOST_AUTO_TEST_CASE(can_load_all_groups) { @@ -61,9 +26,9 @@ BOOST_AUTO_TEST_CASE(can_load_all_groups) auto parquet = mzpeak.parquet(*entry); auto groups = parquet->groups(); - BOOST_TEST((groups->size() == 4)); + BOOST_TEST((groups->size() == 1)); - auto spectrum_index = parquet->field("spectrum", "index"); + auto spectrum_index = parquet->field("root", "index"); BOOST_TEST(spectrum_index.has_value()); BOOST_TEST((spectrum_index->second->type().has_value())); BOOST_TEST((spectrum_index->second->type().value() == MzPeak::Util::Type::UInt64)); diff --git a/test/planner_test.cpp b/test/planner_test.cpp index 0d1a412..57f4fe5 100644 --- a/test/planner_test.cpp +++ b/test/planner_test.cpp @@ -56,57 +56,22 @@ BOOST_AUTO_TEST_CASE(can_use_two_columns) using namespace MzPeak; auto index = MzPeak::open("../test/files/small.mzpeak"); - auto entry = std::ranges::find(index.files(), "spectra_metadata.parquet", + auto entry = std::ranges::find(index.files(), "spectra_metadata_scans.parquet", &Schema::File::file_name); BOOST_TEST((entry != index.files().end())); auto parquet = index.parquet(*entry); - auto index_field = parquet->field("scan", "source_index"); + auto index_field = parquet->field("root", "source_index"); BOOST_TEST(index_field.has_value()); - auto start_time_field = parquet->field("scan", "scan_start_time"); + auto start_time_field = parquet->field("root", "scan_start_time"); BOOST_TEST(start_time_field.has_value()); auto query = Util::Query::Builder(*index_field) .eq(3) - .and_then(Util::Query::Builder(*start_time_field).gt(0)); - - Util::Planner planner = parquet->planner(query); - - auto plan = planner.plan(); - BOOST_TEST(plan.ranges.size() == 1ul); - - // Ug, this file is too small to exercise the planner. - auto first = plan.ranges[0]; - BOOST_TEST(first.row_group == 0); - BOOST_TEST(first.offset == 0); - BOOST_TEST(first.length == 48); -} - -/******************************************************************************/ -BOOST_AUTO_TEST_CASE(can_access_multiple_groups) -{ - using namespace MzPeak; - auto index = MzPeak::open("../test/files/small.mzpeak"); - - auto entry = std::ranges::find(index.files(), "spectra_metadata.parquet", - &Schema::File::file_name); - - BOOST_TEST((entry != index.files().end())); - - auto parquet = index.parquet(*entry); - - auto index_field = parquet->field("scan", "source_index"); - BOOST_TEST(index_field.has_value()); - - auto level_field = parquet->field("spectrum", "ms_level"); - BOOST_TEST(level_field.has_value()); - - auto query = Util::Query::Builder(*index_field) - .eq(30) - .and_then(Util::Query::Builder(*level_field).ge(1)); + .and_then(Util::Query::Builder(*start_time_field).gt(0.0)); Util::Planner planner = parquet->planner(query); From 96f8943014350fd7db60cd39c26b1e354f953cf1 Mon Sep 17 00:00:00 2001 From: "Peter J. Jones" Date: Wed, 5 Aug 2026 13:39:23 +0200 Subject: [PATCH 3/7] transform: Update test to match recent CV term swap --- test/array_index_test.cpp | 44 +++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/test/array_index_test.cpp b/test/array_index_test.cpp index 3589bf4..da9eea7 100644 --- a/test/array_index_test.cpp +++ b/test/array_index_test.cpp @@ -31,24 +31,32 @@ BOOST_AUTO_TEST_CASE(can_get_array_index) BOOST_TEST(index->prefix() == "point"); BOOST_TEST(index->entries().size() == 2ul); - std::optional transform_cv(Schema::CV::from_string("MS:1003901")); - BOOST_TEST((transform_cv.has_value())); - std::optional transform(transform_cv.value()); - - // NOTE: Due to a sort after parsing the index, the m/z array gets - // moved to the end of the index. - BOOST_TEST((index->entries()[1].array_name == "m/z array")); - BOOST_TEST((index->entries()[1].buffer_format == Schema::BufferFormat::Point)); - BOOST_TEST((index->entries()[1].context == Schema::EntityType::Spectrum)); - BOOST_TEST((index->entries()[1].path == "point.mz")); - BOOST_TEST((index->entries()[1].data_type == - Schema::PSI::DataType(Schema::PSI::DataType::Float64))); - BOOST_TEST((index->entries()[1].array_type == Schema::PSI::ArrayType::Mz)); - BOOST_TEST((index->entries()[1].unit == "MS:1000040")); - BOOST_TEST((index->entries()[1].buffer_priority)); - BOOST_TEST((index->entries()[1].sorting_rank == std::optional{0})); - BOOST_TEST((index->entries()[1].data_processing_id == std::nullopt)); - BOOST_TEST((index->entries()[1].transform == transform)); + const auto mz_it = std::ranges::find(index->entries(), "m/z array", + &Data::ArrayIndex::Entry::array_name); + BOOST_TEST((mz_it != index->entries().end())); + + const std::optional interpolation( + Schema::PSI::Transform::ZeroIntensityInterpolation); + const Schema::PSI::DataType f64(Schema::PSI::DataType::Float64); + + BOOST_TEST((mz_it->buffer_format == Schema::BufferFormat::Point)); + BOOST_TEST((mz_it->context == Schema::EntityType::Spectrum)); + BOOST_TEST((mz_it->path == "point.mz")); + BOOST_TEST((mz_it->data_type == f64)); + BOOST_TEST((mz_it->array_type == Schema::PSI::ArrayType::Mz)); + BOOST_TEST((mz_it->unit == "MS:1000040")); + BOOST_TEST((mz_it->buffer_priority)); + BOOST_TEST((mz_it->sorting_rank == std::optional{0})); + BOOST_TEST((mz_it->data_processing_id == std::nullopt)); + BOOST_TEST((mz_it->transform == interpolation)); + + const auto intensity_it = std::ranges::find(index->entries(), "intensity array", + &Data::ArrayIndex::Entry::array_name); + BOOST_TEST((intensity_it != index->entries().end())); + + const std::optional trim( + Schema::PSI::Transform::ZeroIntensityTrim); + BOOST_TEST((intensity_it->transform == trim)); std::size_t count = index->num_entities().value_or(0); BOOST_TEST(count == 48ul); From 189016544adf93a6e5a0c4695a49c7eb032c415f Mon Sep 17 00:00:00 2001 From: "Peter J. Jones" Date: Wed, 5 Aug 2026 16:01:33 +0200 Subject: [PATCH 4/7] refactor: Extract Index::Impl to a new class: Util::Manager Since we need to load metadata Parquet files on demand we need a way to pass around an object that knows how to do that. I thought about doing it with the top-level `Index` class but that would create some circular dependencies and be a bit messy. --- bin/mzp-inspect.cpp | 5 +- include/mzpeak/index.h | 35 ++++++---- include/mzpeak/metadata/spectrum.h | 17 +---- include/mzpeak/spectra.h | 8 +-- include/mzpeak/spectrum.h | 28 ++++---- include/mzpeak/util/manager.h | 48 ++++++++++++++ meson.build | 2 + src/index.cpp | 103 ++++------------------------- src/metadata/spectrum.cpp | 18 ++--- src/metadata/table.cpp | 3 +- src/spectra.cpp | 7 +- src/spectrum.cpp | 37 ++++++++--- src/util/manager.cpp | 81 +++++++++++++++++++++++ test/array_index_test.cpp | 5 +- test/executor_test.cpp | 5 +- test/group_test.cpp | 7 +- test/index_test.cpp | 7 +- test/parquet_test.cpp | 3 +- test/planner_test.cpp | 5 +- test/spectra_test.cpp | 2 + 20 files changed, 247 insertions(+), 179 deletions(-) create mode 100644 include/mzpeak/util/manager.h create mode 100644 src/util/manager.cpp diff --git a/bin/mzp-inspect.cpp b/bin/mzp-inspect.cpp index f2ab8c4..8c894c7 100644 --- a/bin/mzp-inspect.cpp +++ b/bin/mzp-inspect.cpp @@ -14,6 +14,7 @@ top-level directory of this repository. #include "mzpeak.h" #include "mzpeak/schema/group.h" +#include "mzpeak/util/manager.h" // IWYU pragma: keep /******************************************************************************/ namespace po = boost::program_options; @@ -22,14 +23,14 @@ namespace po = boost::program_options; std::unique_ptr open_parquet_file(MzPeak::Index& index, const std::string& file) { - auto it = std::ranges::find(index.files(), file, &MzPeak::Schema::File::file_name); + auto it = index.find(file); if (it == index.files().end()) { std::println(stderr, "file \"{}\" is not in the mzPeak file index", file); return nullptr; } - return index.parquet(*it); + return index.manager()->parquet(*it); } /******************************************************************************/ diff --git a/include/mzpeak/index.h b/include/mzpeak/index.h index b3dc12a..93dfdb8 100644 --- a/include/mzpeak/index.h +++ b/include/mzpeak/index.h @@ -8,14 +8,24 @@ directory of this repository. #pragma once -#include "mzpeak/io/archive.h" -#include "mzpeak/schema/file.h" -#include "mzpeak/spectra.h" -#include "mzpeak/util/parquet.h" +#include +#include namespace MzPeak { -// Internal implementation. +namespace IO { +class Archive; +} + +namespace Schema { +class File; +} + +namespace Util { +class Manager; +} + +class Spectra; /** * Read-only access to the index inside a MzPeak archive. @@ -25,27 +35,28 @@ class Index { /// Constructor. Index(std::unique_ptr); - /// Destructor. - ~Index(); - /** * Return a list of files found in the index. */ const std::vector& files() const; + /** + * Find a file in the mzPeak archive with the given name. + */ + std::vector::const_iterator find(const std::string_view&) const; + /** * Access the spectra in the file. */ Spectra spectra() const; /** - * Open a Parquet file directly. + * Access the low-level MzPeak Manager object. */ - std::unique_ptr parquet(const Schema::File&) const; + std::shared_ptr manager() const; protected: - struct Impl; - std::unique_ptr impl_; + std::shared_ptr manager_; }; } // namespace MzPeak diff --git a/include/mzpeak/metadata/spectrum.h b/include/mzpeak/metadata/spectrum.h index a686c2a..7dcb41b 100644 --- a/include/mzpeak/metadata/spectrum.h +++ b/include/mzpeak/metadata/spectrum.h @@ -13,27 +13,18 @@ top-level directory of this repository. #include namespace MzPeak::Util { -class Slice; -} - -namespace MzPeak::Schema { -class Group; +class Parquet; } namespace MzPeak::Metadata { -class Table; - /** * Metadata from the spectrum table. */ class Spectrum final { public: /// Constructor. - Spectrum(std::shared_ptr
    , uint64_t); - - /// Destructor. - ~Spectrum() = default; + Spectrum(std::unique_ptr, uint64_t); /** * Return the spectrum level (MS:1000511). @@ -50,10 +41,6 @@ class Spectrum final { const std::vector& delta_model() const { return delta_model_; } private: - std::shared_ptr
    table_; - std::shared_ptr group_; - uint64_t index_; - std::optional ms_level_; std::vector delta_model_; }; diff --git a/include/mzpeak/spectra.h b/include/mzpeak/spectra.h index ddf7f8e..231ceba 100644 --- a/include/mzpeak/spectra.h +++ b/include/mzpeak/spectra.h @@ -16,8 +16,8 @@ namespace MzPeak::Data { class Signals; } // namespace MzPeak::Data -namespace MzPeak::Metadata { -class Table; +namespace MzPeak::Util { +class Manager; } namespace MzPeak { @@ -28,12 +28,12 @@ namespace MzPeak { class Spectra final : public Util::EnumerableProxy { public: /// Low-level constructor from a Parquet file. - explicit Spectra(std::unique_ptr, std::unique_ptr); + explicit Spectra(std::unique_ptr, std::shared_ptr); private: // Internal data access. std::shared_ptr data_; - std::shared_ptr meta_; + std::shared_ptr manager_; // Function to fetch a specific spectrum. Spectrum fetch(uint64_t); diff --git a/include/mzpeak/spectrum.h b/include/mzpeak/spectrum.h index e85dc70..0df37ae 100644 --- a/include/mzpeak/spectrum.h +++ b/include/mzpeak/spectrum.h @@ -9,17 +9,21 @@ top-level directory of this repository. #pragma once #include +#include #include "mzpeak/data/array_index.h" -#include "mzpeak/data/encoding.h" -#include "mzpeak/data/signals.h" -#include "mzpeak/metadata/spectrum.h" -#include "mzpeak/metadata/table.h" -#include "mzpeak/util/slice.h" namespace MzPeak { -// Forward declaration. +namespace Data { +class Signals; +} // namespace Data + +namespace Util { +class Slice; +class Manager; +} // namespace Util + class Spectra; /** @@ -27,9 +31,6 @@ class Spectra; */ class Spectrum final { public: - /// The type of decoder used. - using decoder_type = Data::Encoding::Decoder; - /// Destructor. ~Spectrum() = default; @@ -54,18 +55,17 @@ class Spectrum final { /// Internal constructor. Spectrum(uint64_t index, + std::shared_ptr, std::shared_ptr, const std::vector&, - std::unique_ptr, - std::shared_ptr); + std::unique_ptr); private: uint64_t index_; - std::shared_ptr md_table_; - Metadata::Spectrum md_spec_; - decoder_type decoder_; + std::shared_ptr manager_; std::vector mz_; std::vector intensity_; + uint8_t ms_level_; }; } // namespace MzPeak diff --git a/include/mzpeak/util/manager.h b/include/mzpeak/util/manager.h new file mode 100644 index 0000000..ca88079 --- /dev/null +++ b/include/mzpeak/util/manager.h @@ -0,0 +1,48 @@ +/* + +This file is part of the mzpeak project. It is subject to the license +specified in the LICENSE file which can be found in the top-level +directory of this repository. + +*/ + +#pragma once + +#include + +#include "mzpeak/io/archive.h" +#include "mzpeak/schema/file.h" +#include "mzpeak/util/parquet.h" + +namespace MzPeak::Util { + +/** + * FIXME: Write documentation! + */ +class Manager final { +public: + /// Constructor. + Manager(std::unique_ptr); + + /** + * Return a vector of files that are located in the mzPeak archive. + */ + const std::vector& files() const; + + /** + * Find a file given its name. + */ + std::vector::const_iterator + find_file(const std::string_view& name) const; + + /** + * Open a Parquet file from the mzPeak archive. + */ + std::unique_ptr parquet(const Schema::File&) const; + +private: + std::shared_ptr archive_; + std::vector files_; +}; + +} // namespace MzPeak::Util diff --git a/meson.build b/meson.build index 59b0653..62a13f7 100644 --- a/meson.build +++ b/meson.build @@ -43,6 +43,7 @@ lib_sources = [ 'src/spectrum.cpp', 'src/util/arrow.cpp', 'src/util/executor.cpp', + 'src/util/manager.cpp', 'src/util/parquet.cpp', 'src/util/planner.cpp', 'src/util/projection.cpp', @@ -85,6 +86,7 @@ install_headers([ 'include/mzpeak/util/delta_estimator.h', 'include/mzpeak/util/enumerable_proxy.h', 'include/mzpeak/util/executor.h', + 'include/mzpeak/util/manager.h', 'include/mzpeak/util/parquet.h', 'include/mzpeak/util/planner.h', 'include/mzpeak/util/projection.h', diff --git a/src/index.cpp b/src/index.cpp index db9b02a..9c41888 100644 --- a/src/index.cpp +++ b/src/index.cpp @@ -12,122 +12,43 @@ directory of this repository. #include "mzpeak/exception.h" #include "mzpeak/index.h" #include "mzpeak/io/archive.h" -#include "mzpeak/metadata/table.h" +#include "mzpeak/spectra.h" +#include "mzpeak/util/manager.h" -/* - * Boost JSON: - * https://www.boost.org/doc/libs/latest/libs/json/doc/html/index.html - */ namespace MzPeak { -/******************************************************************************/ -const static char* INDEX_FILE_NAME = "mzpeak_index.json"; - -/******************************************************************************/ -namespace json = boost::json; - -/******************************************************************************/ -struct Index::Impl { - - /// Constructor. - Impl(std::unique_ptr archive) - : archive_(std::move(archive)) - , files_() - { - parse_index(); - } - - /// Parse the JSON that makes up the MzPeak index. - void parse_index(); - - // The archive we are reading files out of. - std::unique_ptr archive_; - - // Parsed file entries. - std::vector files_; - - // Return an iterator to the requested file. - std::vector::const_iterator find_file(const std::string_view& name) - { - return std::ranges::find(files_, name, &Schema::File::file_name); - } -}; - /******************************************************************************/ Index::Index(std::unique_ptr archive) - : impl_(std::make_unique(std::move(archive))) + : manager_(std::make_shared(std::move(archive))) { } /******************************************************************************/ -Index::~Index() = default; +const std::vector& Index::files() const { return manager_->files(); } /******************************************************************************/ -const std::vector& Index::files() const { return impl_->files_; } - -/******************************************************************************/ -void Index::Impl::parse_index() +std::vector::const_iterator +Index::find(const std::string_view& name) const { - auto file = archive_->read_file(INDEX_FILE_NAME); - uint8_t buffer[64 * 1024]; - std::optional bytes; - - json::stream_parser parser; - boost::system::error_code ec; - - do { - bytes = file->read(buffer, sizeof(buffer)); - - if (bytes.has_value() && *bytes > 0) { - parser.write(reinterpret_cast(buffer), *bytes, ec); - } - - } while (bytes.has_value() && !ec); - - if (!ec) parser.finish(ec); - if (ec) throw MzPeak::JsonError(ec.message()); - - json::value v = parser.release(); - json::object o = v.as_object(); - - if (const auto it = o.find("files"); it != o.end() && it->value().is_array()) { - const json::array files(it->value().as_array()); - files_.reserve(files.size()); - - for (const auto& file_obj : files) { - if (file_obj.is_object()) { - files_.push_back(Schema::File(file_obj.as_object())); - } - } - } + return manager_->find_file(name); } /******************************************************************************/ Spectra Index::spectra() const { - auto data_it = impl_->find_file("spectra_data.parquet"); - auto meta_it = impl_->find_file("spectra_metadata.parquet"); + auto data_it = manager_->find_file("spectra_data.parquet"); - if (data_it == impl_->files_.end()) { + if (data_it == manager_->files().end()) { throw ParquetError("missing files: spectra_data.parquet"); } std::unique_ptr data = - std::make_unique(parquet(*data_it)); - std::unique_ptr meta = nullptr; + std::make_unique(manager_->parquet(*data_it)); - if (meta_it != impl_->files_.end()) { - meta = std::make_unique(parquet(*meta_it)); - } - - return Spectra(std::move(data), std::move(meta)); + return Spectra(std::move(data), manager_); } /******************************************************************************/ -std::unique_ptr Index::parquet(const Schema::File& file) const -{ - std::unique_ptr data(impl_->archive_->read_file(file.file_name)); - return std::make_unique(std::move(data), file); -} +std::shared_ptr Index::manager() const { return manager_; } } // namespace MzPeak diff --git a/src/metadata/spectrum.cpp b/src/metadata/spectrum.cpp index 07349c6..f7f1e4a 100644 --- a/src/metadata/spectrum.cpp +++ b/src/metadata/spectrum.cpp @@ -21,22 +21,18 @@ using namespace MzPeak::Schema; using namespace MzPeak::Util; /******************************************************************************/ -Spectrum::Spectrum(std::shared_ptr
    table, uint64_t index) - : table_(std::move(table)) - , group_(table_ == nullptr ? nullptr : table_->group("root")) - , index_(index) - , ms_level_() +Spectrum::Spectrum(std::unique_ptr parquet, uint64_t index) + : ms_level_() , delta_model_() { - if (table_ == nullptr || group_ == nullptr) { - throw ParquetError("metadata file missing or does not have the spectrum group"); - } + Table table(std::move(parquet)); + std::shared_ptr group = table.group("root"); Projection projection; - auto level_field = projection.project(group_, Group::CVType("MS", "1000511")); - auto delta_field = projection.project(group_, "mz_delta_model"); + auto level_field = projection.project(group, Group::CVType("MS", "1000511")); + auto delta_field = projection.project(group, "mz_delta_model"); - std::unique_ptr slice = table_->indexed(index_, group_, projection); + std::unique_ptr slice = table.indexed(index, group, projection); if (level_field.has_value()) { using ms_level_t = decltype(ms_level_)::value_type; diff --git a/src/metadata/table.cpp b/src/metadata/table.cpp index 14a2dd2..cf9dfe2 100644 --- a/src/metadata/table.cpp +++ b/src/metadata/table.cpp @@ -56,7 +56,8 @@ std::shared_ptr Table::group(const std::string_view& name) const auto it = map->find(std::string(name)); if (it == map->end()) { - return nullptr; + // FIXME: Replace with the InvalidFormat exception. + throw ParquetError("schema is missing the " + std::string(name) + " group"); } else { return it->second; } diff --git a/src/spectra.cpp b/src/spectra.cpp index db8603f..f784038 100644 --- a/src/spectra.cpp +++ b/src/spectra.cpp @@ -9,7 +9,6 @@ top-level directory of this repository. #include #include "mzpeak/data/signals.h" -#include "mzpeak/metadata/table.h" #include "mzpeak/spectra.h" #include "mzpeak/spectrum.h" #include "mzpeak/util/enumerable_proxy.h" @@ -18,11 +17,11 @@ namespace MzPeak { /******************************************************************************/ Spectra::Spectra(std::unique_ptr data, - std::unique_ptr meta) + std::shared_ptr manager) : EnumerableProxy( 0, std::bind(std::mem_fn(&Spectra::fetch), this, std::placeholders::_1)) , data_(std::move(data)) - , meta_(std::move(meta)) + , manager_(std::move(manager)) { // Update the record count. resize(data_->record_count()); @@ -40,7 +39,7 @@ Spectrum Spectra::fetch(uint64_t index) std::ranges::to>(); std::unique_ptr slice = data_->select(dims, data_->index().eq(index)); - return Spectrum(index, data_, dims, std::move(slice), meta_); + return Spectrum(index, manager_, data_, dims, std::move(slice)); } } // namespace MzPeak diff --git a/src/spectrum.cpp b/src/spectrum.cpp index 0d679bb..1176ffc 100644 --- a/src/spectrum.cpp +++ b/src/spectrum.cpp @@ -9,30 +9,47 @@ top-level directory of this repository. #include #include +#include "mzpeak/data/encoding.h" +#include "mzpeak/metadata/spectrum.h" #include "mzpeak/spectrum.h" +#include "mzpeak/util/manager.h" namespace MzPeak { +/******************************************************************************/ +const static char* PRIMARY_METADATA_FILE = "spectra_metadata.parquet"; + /******************************************************************************/ Spectrum::Spectrum(uint64_t index, + std::shared_ptr manager, std::shared_ptr data, const std::vector& dims, - std::unique_ptr slice, - std::shared_ptr metadata) + std::unique_ptr slice) : index_(index) - , md_table_(std::move(metadata)) - , md_spec_(md_table_, index_) - , decoder_(std::move(data), - std::move(slice), - Util::DeltaEstimator(md_spec_.delta_model())) + , manager_(std::move(manager)) , mz_() , intensity_() + , ms_level_(0) { + auto meta_it = manager_->find_file(PRIMARY_METADATA_FILE); + + if (meta_it == manager_->files().end()) { + throw ParquetError("missing necessary mzpeak file: " + + std::string(PRIMARY_METADATA_FILE)); + } + + Metadata::Spectrum meta(manager_->parquet(*meta_it), index_); + ms_level_ = meta.ms_level().value_or(0); + + Data::Encoding::Decoder decoder( + std::move(data), std::move(slice), + Util::DeltaEstimator(meta.delta_model())); + for (auto& dim : dims) { if (dim.array_type == Schema::PSI::ArrayType::Mz) { - decoder_.decimal(dim, mz_); + decoder.decimal(dim, mz_); } else if (dim.array_type == Schema::PSI::ArrayType::Intensity) { - decoder_.decimal(dim, intensity_); + decoder.decimal(dim, intensity_); } else { // FIXME: should we throw an exception here? continue; @@ -47,6 +64,6 @@ const std::vector& Spectrum::mz() const { return mz_; } const std::vector& Spectrum::intensity() const { return intensity_; } /******************************************************************************/ -uint8_t Spectrum::ms_level() const { return md_spec_.ms_level().value_or(0u); } +uint8_t Spectrum::ms_level() const { return ms_level_; } } // namespace MzPeak diff --git a/src/util/manager.cpp b/src/util/manager.cpp new file mode 100644 index 0000000..62e9744 --- /dev/null +++ b/src/util/manager.cpp @@ -0,0 +1,81 @@ +/* + +This file is part of the mzpeak project. It is subject to the license +specified in the LICENSE file which can be found in the top-level +directory of this repository. + +*/ + +#include "mzpeak/util/manager.h" + +namespace MzPeak::Util { + +/******************************************************************************/ +const static char* INDEX_FILE_NAME = "mzpeak_index.json"; + +/******************************************************************************/ +namespace json = boost::json; + +/******************************************************************************/ +void parse_index(std::shared_ptr& archive, + std::vector& files) +{ + auto file = archive->read_file(INDEX_FILE_NAME); + uint8_t buffer[64 * 1024]; + std::optional bytes; + + json::stream_parser parser; + boost::system::error_code ec; + + do { + bytes = file->read(buffer, sizeof(buffer)); + + if (bytes.has_value() && *bytes > 0) { + parser.write(reinterpret_cast(buffer), *bytes, ec); + } + } while (bytes.has_value() && !ec); + + if (!ec) parser.finish(ec); + if (ec) throw MzPeak::JsonError(ec.message()); + + json::value v = parser.release(); + json::object o = v.as_object(); + + if (const auto it = o.find("files"); it != o.end() && it->value().is_array()) { + const json::array file_list(it->value().as_array()); + files.reserve(file_list.size()); + + for (const auto& file_obj : file_list) { + if (file_obj.is_object()) { + files.push_back(Schema::File(file_obj.as_object())); + } + } + } +} + +/******************************************************************************/ +Manager::Manager(std::unique_ptr archive) + : archive_(std::move(archive)) + , files_() +{ + parse_index(archive_, files_); +} + +/******************************************************************************/ +const std::vector& Manager::files() const { return files_; } + +/******************************************************************************/ +std::vector::const_iterator +Manager::find_file(const std::string_view& name) const +{ + return std::ranges::find(files_, name, &Schema::File::file_name); +} + +/******************************************************************************/ +std::unique_ptr Manager::parquet(const Schema::File& file) const +{ + std::unique_ptr data(archive_->read_file(file.file_name)); + return std::make_unique(std::move(data), file); +} + +} // namespace MzPeak::Util diff --git a/test/array_index_test.cpp b/test/array_index_test.cpp index da9eea7..1fda385 100644 --- a/test/array_index_test.cpp +++ b/test/array_index_test.cpp @@ -11,6 +11,7 @@ directory of this repository. #include "mzpeak/data/signals.h" #include "mzpeak/open.h" +#include "mzpeak/util/manager.h" /******************************************************************************/ BOOST_AUTO_TEST_CASE(can_get_array_index) @@ -24,7 +25,7 @@ BOOST_AUTO_TEST_CASE(can_get_array_index) BOOST_TEST((entry != mzpeak.files().end())); - auto parquet = mzpeak.parquet(*entry); + auto parquet = mzpeak.manager()->parquet(*entry); Data::Signals data(std::move(parquet)); std::shared_ptr index(data.array_index()); @@ -73,7 +74,7 @@ BOOST_AUTO_TEST_CASE(can_read_mz_array) BOOST_TEST((entry != mzpeak.files().end())); - auto parquet = mzpeak.parquet(*entry); + auto parquet = mzpeak.manager()->parquet(*entry); Data::Signals data(std::move(parquet)); diff --git a/test/executor_test.cpp b/test/executor_test.cpp index 9fc9d10..b32d80b 100644 --- a/test/executor_test.cpp +++ b/test/executor_test.cpp @@ -11,6 +11,7 @@ in the LICENSE file found in the top-level directory of this project. #include "mzpeak/open.h" #include "mzpeak/util/executor.h" +#include "mzpeak/util/manager.h" // IWYU pragma: keep #include "mzpeak/util/parquet.h" #include "mzpeak/util/planner.h" #include "mzpeak/util/query.h" @@ -26,7 +27,7 @@ BOOST_AUTO_TEST_CASE(can_find_spectrum) BOOST_TEST((entry != index.files().end())); - auto parquet = index.parquet(*entry); + auto parquet = index.manager()->parquet(*entry); auto index_field = parquet->field("point", "spectrum_index"); BOOST_TEST(index_field.has_value()); @@ -69,7 +70,7 @@ BOOST_AUTO_TEST_CASE(can_read_uint8_t) BOOST_TEST((entry != index.files().end())); - auto parquet = index.parquet(*entry); + auto parquet = index.manager()->parquet(*entry); auto index_field = parquet->field("root", "index"); BOOST_TEST(index_field.has_value()); diff --git a/test/group_test.cpp b/test/group_test.cpp index 294fb4a..efeaf12 100644 --- a/test/group_test.cpp +++ b/test/group_test.cpp @@ -10,6 +10,7 @@ top-level directory of this repository. #include #include "mzpeak/open.h" +#include "mzpeak/util/manager.h" // IWYU pragma: keep #include "mzpeak/util/parquet.h" /******************************************************************************/ @@ -18,12 +19,10 @@ BOOST_AUTO_TEST_CASE(can_load_all_groups) using namespace MzPeak::Util; auto mzpeak = MzPeak::open("../test/files/small.mzpeak"); - auto entry = std::ranges::find(mzpeak.files(), "spectra_metadata.parquet", - &MzPeak::Schema::File::file_name); - + auto entry = mzpeak.find("spectra_metadata.parquet"); BOOST_TEST((entry != mzpeak.files().end())); - auto parquet = mzpeak.parquet(*entry); + auto parquet = mzpeak.manager()->parquet(*entry); auto groups = parquet->groups(); BOOST_TEST((groups->size() == 1)); diff --git a/test/index_test.cpp b/test/index_test.cpp index eaade54..3ad2d9b 100644 --- a/test/index_test.cpp +++ b/test/index_test.cpp @@ -13,6 +13,7 @@ directory of this repository. #include "mzpeak/index.h" #include "mzpeak/open.h" +#include "mzpeak/schema/file.h" /******************************************************************************/ BOOST_AUTO_TEST_CASE(can_parse_json) @@ -26,11 +27,9 @@ BOOST_AUTO_TEST_CASE(can_parse_json) BOOST_AUTO_TEST_CASE(is_associated_with) { auto index = MzPeak::open("../test/files/small.mzpeak"); - const auto& files = index.files(); - - const auto& spectra = std::ranges::find(files, "spectra_data.parquet", - &MzPeak::Schema::File::file_name); + const auto& files = index.files(); + const auto& spectra = index.find("spectra_data.parquet"); BOOST_TEST((spectra != files.end()), "missing spectra_data.parquet"); auto matches = [&](const auto& other) -> bool { diff --git a/test/parquet_test.cpp b/test/parquet_test.cpp index 0d86171..e772eed 100644 --- a/test/parquet_test.cpp +++ b/test/parquet_test.cpp @@ -10,6 +10,7 @@ directory of this repository. #include #include "mzpeak/open.h" +#include "mzpeak/util/manager.h" // IWYU pragma: keep #include "mzpeak/util/parquet.h" /******************************************************************************/ @@ -24,7 +25,7 @@ BOOST_AUTO_TEST_CASE(can_get_kv_string) BOOST_TEST((entry != mzpeak.files().end())); - auto parquet = mzpeak.parquet(*entry); + auto parquet = mzpeak.manager()->parquet(*entry); auto fmd = parquet->file_metadata(); auto et = parquet->index_file().entity_type; auto key = MzPeak::Schema::entity_type_to_string(et) + "_array_index"; diff --git a/test/planner_test.cpp b/test/planner_test.cpp index 57f4fe5..477b70c 100644 --- a/test/planner_test.cpp +++ b/test/planner_test.cpp @@ -9,6 +9,7 @@ in the LICENSE file found in the top-level directory of this project. #include #include "mzpeak/open.h" +#include "mzpeak/util/manager.h" // IWYU pragma: keep #include "mzpeak/util/parquet.h" #include "mzpeak/util/planner.h" @@ -23,7 +24,7 @@ BOOST_AUTO_TEST_CASE(can_locate_correct_rows) BOOST_TEST((entry != index.files().end())); - auto parquet = index.parquet(*entry); + auto parquet = index.manager()->parquet(*entry); auto index_field = parquet->field("point", "spectrum_index"); BOOST_TEST(index_field.has_value()); @@ -61,7 +62,7 @@ BOOST_AUTO_TEST_CASE(can_use_two_columns) BOOST_TEST((entry != index.files().end())); - auto parquet = index.parquet(*entry); + auto parquet = index.manager()->parquet(*entry); auto index_field = parquet->field("root", "source_index"); BOOST_TEST(index_field.has_value()); diff --git a/test/spectra_test.cpp b/test/spectra_test.cpp index d8047d5..bd3993c 100644 --- a/test/spectra_test.cpp +++ b/test/spectra_test.cpp @@ -9,6 +9,8 @@ directory of this repository. #define BOOST_TEST_MODULE Spectra #include +#include + #include "mzpeak/open.h" #include "mzpeak/spectra.h" #include "mzpeak/spectrum.h" From 49ff9a76ed9057629dbeec05ae2b77526b02fea0 Mon Sep 17 00:00:00 2001 From: "Peter J. Jones" Date: Thu, 6 Aug 2026 10:58:23 +0200 Subject: [PATCH 5/7] Capture the new column_mapping object in the index JSON file Also refactor the Schema::File struct to be a class. --- bin/mzp-inspect.cpp | 2 +- include/mzpeak/schema/file.h | 34 ++++++++++++----- src/data/signals.cpp | 2 +- src/metadata/table.cpp | 4 +- src/schema/file.cpp | 73 +++++++++++++++++++++++++++++++----- src/util/manager.cpp | 2 +- src/util/parquet.cpp | 6 +-- test/index_test.cpp | 6 +-- test/parquet_test.cpp | 2 +- 9 files changed, 101 insertions(+), 30 deletions(-) diff --git a/bin/mzp-inspect.cpp b/bin/mzp-inspect.cpp index 8c894c7..5b008c7 100644 --- a/bin/mzp-inspect.cpp +++ b/bin/mzp-inspect.cpp @@ -40,7 +40,7 @@ int print_array_index(MzPeak::Index& index, const std::string& file) if (parquet == nullptr) return 1; auto fmd = parquet->file_metadata(); - auto et = parquet->index_file().entity_type; + auto et = parquet->index_file().entity_type(); auto key = MzPeak::Schema::entity_type_to_string(et) + "_array_index"; auto json = parquet->kv_string(fmd, key); diff --git a/include/mzpeak/schema/file.h b/include/mzpeak/schema/file.h index 618ffe3..527ce40 100644 --- a/include/mzpeak/schema/file.h +++ b/include/mzpeak/schema/file.h @@ -17,13 +17,20 @@ directory of this repository. namespace MzPeak::Schema { namespace json = boost::json; -struct File { +/** + * A description of a file in the mzPeak archive. + */ +class File final { +public: + struct Column { + std::string name; + std::string path; + std::optional accession; + std::optional unit; + }; /// Constructor from a file name. - explicit File(const std::string& name) - : file_name(name) - { - } + explicit File(const std::string& name); /// Conversion from JSON. explicit File(const json::object&); @@ -34,16 +41,25 @@ struct File { bool is_associated_with(const File&) const; /// Equality operator. - bool operator==(const File&) const = default; + bool operator==(const File&) const; /// The name of this file. - std::string file_name; + const std::string& file_name() const { return file_name_; } /// This file's data kind. - DataKind data_kind = DataKind::Other; + DataKind data_kind() const { return data_kind_; } /// This file's entity type. - EntityType entity_type = EntityType::Other; + EntityType entity_type() const { return entity_type_; } + + /// Column definitions for this file. + const std::vector& columns() const { return columns_; } + +private: + std::string file_name_; + DataKind data_kind_ = DataKind::Other; + EntityType entity_type_ = EntityType::Other; + std::vector columns_; }; } // namespace MzPeak::Schema diff --git a/src/data/signals.cpp b/src/data/signals.cpp index 4da918b..6144bc1 100644 --- a/src/data/signals.cpp +++ b/src/data/signals.cpp @@ -36,7 +36,7 @@ struct Signals::Impl { std::shared_ptr Signals::Impl::parse_array_index() const { Util::Parquet::file_metadata_t fmd(parquet_->file_metadata()); - EntityType entity_type = parquet_->index_file().entity_type; + EntityType entity_type = parquet_->index_file().entity_type(); std::string num_key(Schema::entity_type_to_string(entity_type) + "_count"); std::optional num_entities(parquet_->kv_size_t(fmd, num_key)); diff --git a/src/metadata/table.cpp b/src/metadata/table.cpp index cf9dfe2..46197c6 100644 --- a/src/metadata/table.cpp +++ b/src/metadata/table.cpp @@ -31,8 +31,8 @@ Table::Impl::Impl(std::unique_ptr parquet) { auto file = parquet_->index_file(); - if (file.data_kind != Schema::DataKind::Metadata) { - std::string msg("file is not a metadata file: " + file.file_name); + if (file.data_kind() != Schema::DataKind::Metadata) { + std::string msg("file is not a metadata file: " + file.file_name()); throw ParquetError(msg); } } diff --git a/src/schema/file.cpp b/src/schema/file.cpp index 6a59650..37f3cac 100644 --- a/src/schema/file.cpp +++ b/src/schema/file.cpp @@ -10,28 +10,83 @@ directory of this repository. namespace MzPeak::Schema { +/******************************************************************************/ +void parse_columns(const json::array& input, std::vector& output) +{ + auto get_string = [](const json::object& ob, + std::string_view key) -> std::optional { + auto it = ob.find(key); + + if (it != ob.end() && it->value().is_string()) { + return std::string(it->value().as_string()); + } else { + return {}; + } + }; + + output.reserve(input.size()); + + for (const auto& column : input) { + if (!column.is_object()) { + // FIXME: emit a warning + continue; + } + + const auto& colobj(column.as_object()); + + File::Column fc = { + .name = get_string(colobj, "name").value_or(""), + .path = get_string(colobj, "path") + .or_else(std::bind(get_string, colobj, "name")) + .value_or(""), + .accession = get_string(colobj, "accession"), + .unit = get_string(colobj, "unit"), + }; + + output.push_back(fc); + } +} + +/******************************************************************************/ +File::File(const std::string& name) + : file_name_(name) +{ +} + /******************************************************************************/ File::File(const json::object& o) - : file_name(o.at("name").as_string()) - , data_kind(data_kind_from_string(o.at("data_kind").as_string())) - , entity_type(entity_type_from_string(o.at("entity_type").as_string())) + : file_name_(o.at("name").as_string()) + , data_kind_(data_kind_from_string(o.at("data_kind").as_string())) + , entity_type_(entity_type_from_string(o.at("entity_type").as_string())) + , columns_() { + auto cs = o.find("column_mapping"); + + if (cs != o.end() && cs->value().is_array()) { + parse_columns(cs->value().as_array(), columns_); + } } /******************************************************************************/ bool File::is_associated_with(const File& other) const { - std::string::size_type underscore(file_name.find("_")); + std::string::size_type underscore(file_name_.find("_")); if (underscore == std::string::npos) return false; - if (other.file_name.size() < underscore) return false; + if (other.file_name_.size() < underscore) return false; - if (file_name.compare(0, underscore, other.file_name, 0, underscore) != 0) { + if (file_name_.compare(0, underscore, other.file_name_, 0, underscore) != 0) { return false; } - return (data_kind == DataKind::DataArray && - other.data_kind == DataKind::Metadata) || - (data_kind == DataKind::Metadata && other.data_kind == DataKind::DataArray); + return (data_kind_ == DataKind::DataArray && + other.data_kind_ == DataKind::Metadata) || + (data_kind_ == DataKind::Metadata && + other.data_kind_ == DataKind::DataArray); } +/******************************************************************************/ +bool File::operator==(const File& other) const +{ + return file_name_ == other.file_name_; +}; } // namespace MzPeak::Schema diff --git a/src/util/manager.cpp b/src/util/manager.cpp index 62e9744..77c00b7 100644 --- a/src/util/manager.cpp +++ b/src/util/manager.cpp @@ -74,7 +74,7 @@ Manager::find_file(const std::string_view& name) const /******************************************************************************/ std::unique_ptr Manager::parquet(const Schema::File& file) const { - std::unique_ptr data(archive_->read_file(file.file_name)); + std::unique_ptr data(archive_->read_file(file.file_name())); return std::make_unique(std::move(data), file); } diff --git a/src/util/parquet.cpp b/src/util/parquet.cpp index 962b726..22f393e 100644 --- a/src/util/parquet.cpp +++ b/src/util/parquet.cpp @@ -66,7 +66,7 @@ struct Parquet::Impl { auto status = reader_builder.Open(std::move(raf)); if (!status.ok()) { - std::string msg("while opening file: " + file.file_name + ": "); + std::string msg("while opening file: " + file_.file_name() + ": "); throw ParquetError(msg + status.ToString()); } @@ -74,7 +74,7 @@ struct Parquet::Impl { status = reader_builder.Build(&reader); if (!status.ok()) { - std::string msg("while reading file: " + file.file_name + ": "); + std::string msg("while reading file: " + file_.file_name() + ": "); throw ParquetError(msg + status.ToString()); } @@ -86,7 +86,7 @@ struct Parquet::Impl { void error(const std::string& error) { - std::string msg("file accessing " + file_.file_name + ": " + error); + std::string msg("file accessing " + file_.file_name() + ": " + error); throw ParquetError(msg); } diff --git a/test/index_test.cpp b/test/index_test.cpp index 3ad2d9b..3d55823 100644 --- a/test/index_test.cpp +++ b/test/index_test.cpp @@ -37,10 +37,10 @@ BOOST_AUTO_TEST_CASE(is_associated_with) }; for (const auto& other : files | std::views::filter(matches)) { - BOOST_TEST_CONTEXT(spectra->file_name << " should not be associated with " - << other.file_name) + BOOST_TEST_CONTEXT(spectra->file_name() + << " should not be associated with " << other.file_name()) { - BOOST_TEST((other.file_name == "spectra_metadata.parquet")); + BOOST_TEST((other.file_name() == "spectra_metadata.parquet")); } } } diff --git a/test/parquet_test.cpp b/test/parquet_test.cpp index e772eed..6ad40c3 100644 --- a/test/parquet_test.cpp +++ b/test/parquet_test.cpp @@ -27,7 +27,7 @@ BOOST_AUTO_TEST_CASE(can_get_kv_string) auto parquet = mzpeak.manager()->parquet(*entry); auto fmd = parquet->file_metadata(); - auto et = parquet->index_file().entity_type; + auto et = parquet->index_file().entity_type(); auto key = MzPeak::Schema::entity_type_to_string(et) + "_array_index"; auto json = parquet->kv_string(fmd, key); From d9c81e3c881f4870426d54bc3ef515024aca71d1 Mon Sep 17 00:00:00 2001 From: "Peter J. Jones" Date: Thu, 6 Aug 2026 10:59:23 +0200 Subject: [PATCH 6/7] Transfer the column_map information into the schema parser --- include/mzpeak/schema/group.h | 7 +++++-- src/schema/group.cpp | 35 +++++++++++++++++++++++++++++------ src/util/parquet.cpp | 5 +++-- 3 files changed, 37 insertions(+), 10 deletions(-) diff --git a/include/mzpeak/schema/group.h b/include/mzpeak/schema/group.h index c6a2e14..9b4550e 100644 --- a/include/mzpeak/schema/group.h +++ b/include/mzpeak/schema/group.h @@ -14,6 +14,7 @@ top-level directory of this repository. #include #include "mzpeak/schema/cv.h" +#include "mzpeak/schema/file.h" #include "mzpeak/util/types.h" // Forward declarations. @@ -136,10 +137,11 @@ class Group final { /// Constructor for the root group to hold all of the top-level /// columns that are not in a separate struct/group. - explicit Group(const parquet::schema::GroupNode&); + explicit Group(const parquet::schema::GroupNode&, const Schema::File&); /// Constructor from a parquet schema descriptor. explicit Group(const parquet::schema::GroupNode&, + const Schema::File&, index_type index, index_type offset); @@ -190,7 +192,8 @@ class Group final { std::string path(const Field&) const; private: - void make_fields(const parquet::schema::GroupNode& node, index_type offset); + void + make_fields(const parquet::schema::GroupNode&, const Schema::File&, index_type); std::string name_; bool is_root_; diff --git a/src/schema/group.cpp b/src/schema/group.cpp index 061db07..78e1702 100644 --- a/src/schema/group.cpp +++ b/src/schema/group.cpp @@ -16,6 +16,14 @@ top-level directory of this repository. namespace MzPeak::Schema { +/******************************************************************************/ +template std::optional string_to_cv_child(std::string_view s) +{ + return CV::from_string(s).and_then([](const auto& cv) -> std::optional { + return T(cv.code(), cv.accession()); + }); +} + /******************************************************************************/ // clang doesn't support views::join yet :( std::string join_(auto begin, auto end) @@ -115,18 +123,19 @@ const std::optional& Group::Field::type() const { return type_; } void Group::Field::type(Util::Type type) { type_ = type; } /******************************************************************************/ -Group::Group(const parquet::schema::GroupNode& node) +Group::Group(const parquet::schema::GroupNode& node, const Schema::File& file) : name_("root") , is_root_(true) , index_(0) , fields_() { // This is the root group so only collect non-group top-level columns. - make_fields(node, 0); + make_fields(node, file, 0); } /******************************************************************************/ Group::Group(const parquet::schema::GroupNode& node, + const Schema::File& file, index_type index, index_type offset) : name_(node.name()) @@ -134,19 +143,33 @@ Group::Group(const parquet::schema::GroupNode& node, , index_(index) , fields_() { - make_fields(node, offset); + make_fields(node, file, offset); } /******************************************************************************/ -void Group::make_fields(const parquet::schema::GroupNode& node, index_type offset) +void Group::make_fields(const parquet::schema::GroupNode& node, + const Schema::File& file, + index_type offset) { + auto link = [&](std::shared_ptr& field) -> void { + fields_[field->name()] = field; + + std::string col_path = path(*field); + const auto it = std::ranges::find(file.columns(), col_path, &File::Column::path); + + if (it != file.columns().end()) { + field->cv_type_ = it->accession.and_then(&string_to_cv_child); + field->cv_unit_ = it->unit.and_then(&string_to_cv_child); + } + }; + for (index_type i : std::views::iota(0, node.field_count())) { auto child = node.field(i); if (child->is_primitive()) { std::shared_ptr field = std::make_shared(child->name(), i, offset + i); - fields_[field->name()] = field; + link(field); auto prim = std::static_pointer_cast(child); field->kind_ = Field::Kind::Scalar; @@ -157,7 +180,7 @@ void Group::make_fields(const parquet::schema::GroupNode& node, index_type offse if (grp->field_count() == 1) { std::shared_ptr field = std::make_shared(grp->name(), i, offset + i); - fields_[field->name()] = field; + link(field); auto grp_type = field_type_from_parquet(grp); field->kind_ = grp_type.first; diff --git a/src/util/parquet.cpp b/src/util/parquet.cpp index 22f393e..fab820d 100644 --- a/src/util/parquet.cpp +++ b/src/util/parquet.cpp @@ -106,7 +106,8 @@ void Parquet::Impl::parse_schema() auto root = fmd->schema()->group_node(); int32_t offset = 0; - std::shared_ptr root_group = std::make_shared(*root); + std::shared_ptr root_group = + std::make_shared(*root, file_); if (!root_group->fields().empty()) { (*groups_)[root_group->name()] = root_group; @@ -120,7 +121,7 @@ void Parquet::Impl::parse_schema() std::static_pointer_cast(node); std::shared_ptr s = - std::make_shared(*group, i, offset); + std::make_shared(*group, file_, i, offset); (*groups_)[s->name()] = s; offset += group->field_count(); } From 6195f0a1cefe246154f6bd43d67238b0724046e1 Mon Sep 17 00:00:00 2001 From: "Peter J. Jones" Date: Thu, 6 Aug 2026 10:59:40 +0200 Subject: [PATCH 7/7] Remove extra allocation for the old spectrum index entry The entry was removed but I never updated the allocation line. --- src/data/array_index.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/data/array_index.cpp b/src/data/array_index.cpp index 9d2cff6..6324057 100644 --- a/src/data/array_index.cpp +++ b/src/data/array_index.cpp @@ -44,7 +44,7 @@ ArrayIndex::ArrayIndex(EntityType entity_type, const json::object& obj) if (entries != obj.end() && entries->value().is_array()) { auto entries_ary(entries->value().as_array()); - entries_.reserve(entries_ary.size() + 1); + entries_.reserve(entries_ary.size()); for (const auto& entry_obj : entries_ary) { if (entry_obj.is_object()) {