oxihipo (Python)
New to CLAS12? Start with the CLAS12 analysis tutorial — eight pages from your first
open()to DIS kinematics,pindexdetector joins, and invariant/missing-mass spectra, with runnable code and sample data.
Fast, columnar reading and writing of HIPO (CLAS12) files, powered by the
Rust oxihipo core. A HIPO bank reads like a
uproot jagged branch, and columns come back as
Awkward arrays — built zero-copy from buffers the
Rust side fills with the GIL released. Writing is columnar too: create a new
file, or recreate to decorate an existing one with a derived bank.
import oxihipo as ox
f = ox.open("run5042.hipo") # file | dir | glob | list of paths
f.num_entries # event count
f.keys() # ['REC::Particle', 'REC::Event', ...]
p = f.arrays("REC::Particle", ["pid", "px", "py", "pz"])
p.px # jagged: p[event].px indexes particles
ak.sum(p.px, axis=1) # per-event reductions, no Python loop
Runnable scripts live in examples/ — every one works against the
bundled sample with no arguments:
quickstart.py |
open a file, inspect it, read columns |
analysis.py |
a columnar analysis with Awkward (cuts, reductions) |
streaming.py |
iterate a chain bigger than RAM |
parallel.py |
workers=N multi-process reading |
writing.py |
write a file: jagged, T#N array, and scalar columns |
decorate.py |
attach a derived bank to a cooked file |
event_tags.py |
tags: filter by name, tag-and-skim, retag in place |
interop.py |
NumPy / pandas / Arrow → polars, duckdb |
rdataframe.py |
feed ROOT's RDataFrame |
tutorial_sample.py |
generate the CLAS12-shaped sample for the tutorial |
bench_*.py |
read, compression, and RDataFrame benchmarks |
Reading
| call | returns |
|---|---|
f.arrays(bank, [cols]) |
ak.Array — jagged record N * var * {col: T} |
f.arrays([bankA, bankB]) / f.arrays(filter_name="REC::*") |
record with one field per bank |
f.array(bank, col) |
one column, N * var * T |
f.numpy(bank, col) |
(values, offsets, inner_len) — plain NumPy, no Awkward import |
f.event_tags() |
per-event tag (EH_TAG) as uint32[n_events] — aligned 1:1 with arrays() |
f["REC::Particle"] |
a bank proxy: .keys(), .typenames(), .array(col), ["col"] |
f["REC::Particle/px"] |
the px column |
Common knobs (on arrays / array / numpy / iterate):
entry_start=,entry_stop=— restrict to a global event range.filter_name="REC::*"— glob overbank/bank/columnkeys.library=—"ak"(default,ak.Array),"np"(dictof object-dtypendarray),"pd"(pandas, one frame per bank),"arrow"(pyarrow.Table, onelarge_listcolumn per field — for polars / duckdb). A non-matchingfilter_name/ empty bank list yields an empty result, not an error.threads=—0= all cores (default),1= sequential,n=n-thread pool.workers=— read withNprocesses for I/O-bound filesystems; see Parallel reading.
Streaming (bigger than RAM)
iterate yields the chain in fully-materialized chunks; each is dropped before
the next is read, so resident memory stays ≈ one chunk.
for chunk in f.iterate("REC::Particle", ["px"], step_size="200 MB"):
hist.fill(ak.flatten(chunk.px))
for chunk, report in f.iterate("REC::Particle", step_size=1_000_000, report=True):
... # report.entry_start / report.entry_stop / report.file_path
# multi-file, never opens it all at once:
for chunk in ox.iterate("/data/run5042/*.hipo", "REC::Particle", step_size="1 GB"):
...
step_size is an event count (int) or a byte budget ("200 MB", "1 GB");
chunks are aligned to record and file boundaries.
Parallel reading (multi-process)
On a parallel filesystem (JLab ifarm /volatile, Lustre) a single process
saturates well below the filesystem's aggregate bandwidth — the limit is
per-process, not per-node. workers=N splits the chain into N disjoint,
record-aligned event ranges, reads them from N separate processes, and
stitches the result — turning one I/O stream into N.
# whole-array read, N processes, stitched into one ak.Array:
a = ox.arrays("/volatile/run5042/*.hipo", "REC::Particle", ["px", "py", "pz"], workers=8)
# streaming, ~N reads in flight (resident memory ≈ N chunks), yielded in order:
for chunk in ox.iterate("/volatile/run5042/*.hipo", "REC::Particle", step_size="1 GB", workers=8):
...
- Works with everything else:
filter_name,entry_start/entry_stop,library=, and.filtered(...)all carry through to the workers. - Without an explicit
threads=, the machine's cores are split across the workers (total ≈ all cores); on an I/O-bound farm the surplus decode threads simply wait on the read. - This helps only when I/O is the bottleneck. On a local, already-cached
disk the limit is decode/bandwidth, not I/O, so
workers>1just adds process and IPC overhead — keep the defaultworkers=1there. - Each
arrays(workers=N)/iterate(workers=N)call spins up its own worker pool, so pay the spawn cost once: prefer a singleiterate(...)over a many-file chain to a loop of smallarrays()calls.
Required: any script that passes
workers=must be guarded byif __name__ == "__main__":. Workers are spawned (not forked — forking after Rust's thread pool exists is unsafe), so each re-imports your script; without the guard it would re-run at import. Seeexamples/parallel.py.
Filtering and skimming
g = f.filtered(require=["REC::Particle"]) # events carrying a bank
g = f.filtered(record_tag=[0x42]) # by record tag
g = f.filtered(event_tag=[1, 4]) # by per-event tag (EH_TAG)
g = f.filtered(event_tag="dvcs") # by tag name (if the file has a registry)
summary = g.skim("electrons.hipo", compression="lz4percolumn") # SkimSummary(events, records, bytes)
filtered() returns a new chain; the filter reduces what arrays() / skim()
yield (its num_entries stays the pre-filter total, as in uproot).
Writing
create opens a new file; recreate decorates an existing one. Both return a
columnar Writer with an uproot-style new_bank / extend / close API —
columns are written zero-copy from NumPy or Awkward, with the GIL released.
with ox.create("out.hipo", compression="lz4percolumn") as w:
w.new_bank("NEW::bank", {"px": "F", "pid": "I", "cov": "F#3"}) # scalars + T#N arrays
w.extend({"NEW::bank": { # a batch of events
"px": ak.Array([[1.0, 2.0], [], [3.0]]), # jagged: rows per event
"pid": ak.Array([[11, -11], [], [211]]),
"cov": ak.Array([[[1, 2, 3], [4, 5, 6]], [], [[7, 8, 9]]]), # 3-vector per row
}})
new_bank(bank, {col: typechar})— declare a bank;typechar∈B/S/I/L/F/D, optionally#Nfor a fixed-length array column ("F#3"). The uniqueitemauto-assigns.extend({bank: data})— append a batch.datais anak.Arrayrecord (whatarrays(bank)returns) or a dict of columns — a jaggedak.Arrayper column, or a 1-D NumPy array for a scalar-per-event bank. Call it in a loop to stream large outputs in bounded memory.close()(or leaving thewith) writes the trailer index and returns aSkimSummary.
Decorate — add a bank to a cooked file without rewriting the physics banks (an ML score, a computed kinematic):
f = ox.open("dst.hipo")
scores = model.predict(f.arrays("REC::Particle")).astype("float32") # one per event
w = ox.recreate("dst.hipo", "decorated.hipo") # or dst=None to replace in place
w.new_bank("ML::pred", {"score": "F"})
w.extend({"ML::pred": {"score": scores}}) # aligned 1:1 with the source events
w.close()
Every source event is copied verbatim (existing banks, array columns included),
with the new banks attached; they must cover all source events (close errors
otherwise). Full guide:
Writing.
RDataFrame (ROOT)
rdataframe hands a selection to ROOT's
RDataFrame through Awkward's generated
RDataSource — a jagged bank column becomes an RVec<T>, a T#N array column
a nested RVec, no copy of the view. Column names are the bank/column keys
sanitized to C++ identifiers (REC::Particle/px → REC_Particle_px).
df = ox.rdataframe("run5042.hipo", "REC::Particle", ["px", "py", "pid"])
h = df.Define("pt", "sqrt(REC_Particle_px*REC_Particle_px"
" + REC_Particle_py*REC_Particle_py)").Histo1D("pt")
# bigger than RAM: one RDataFrame per chunk, merge histograms across chunks
total = None
for chunk in ox.iterate_rdataframe("run5042.hipo", "REC::Particle", ["px"], step_size="1 GB"):
h = chunk.Histo1D(("pt", "", 100, 0, 10), "REC_Particle_px").GetValue()
total = h.Clone() if total is None else (total.Add(h) or total)
total.SetDirectory(0)
Needs a working ROOT/PyROOT (not on PyPI — conda-forge or system) plus
awkward; pip install oxihipo[root] covers the awkward side. filter_name,
entry_start/entry_stop, and .filtered(...) all carry through. See
examples/rdataframe.py and the
RDataFrame guide.
The bridge is a no-copy view — rdataframe costs ~1 ms over the bare
arrays read. But the RDF loop is single-threaded here (implicit MT doesn't work
with the Awkward-generated source), so on a simple kernel it runs slower than the
vectorized Awkward equivalent: use it to reuse RDF/C++ code, not for speed. Numbers
- reproduction:
examples/bench_rdataframe.pyand the RDataFrame guide's Performance section.
Discovery
f.keys() # bank names
f.keys(recursive=True) # 'bank/column' keys
f.keys(filter_name="REC::*") # globbed
f.typenames() # {'REC::Particle/px': 'float32', 'REC::Track/cov': 'float32[3]'}
"REC::Particle" in f
How it works
The whole per-event loop runs in Rust with the GIL released. One pass over
the file materializes each requested column into a flat NumPy buffer plus one
shared int64 offsets buffer per bank — exactly an Awkward
ListOffsetArray / Index64 layout — moved into NumPy zero-copy. The Python
layer only wraps those buffers (NumpyArray / RegularArray for T#N array
columns / ListOffsetArray), so nothing is copied past decompression and Python
never iterates events. Errors map onto a Python exception tree
(KeyError for a missing bank/column, TypeError for a dtype mismatch,
OSError for I/O, oxihipo.CorruptFileError for a malformed record).
Performance
Reading through the binding runs within ~10% of native Rust — the per-event
decode is Rust behind a released GIL, and columns move into NumPy zero-copy. On
a 9.1 GB CLAS12 file (598k events, Apple M4 Pro, all cores),
f.arrays("REC::Particle", ["px","py","pz","pid"]) reads at ~5.6 GB/s vs Rust's
6.3 GB/s. Details + reproduction:
Python vs Rust benchmark
and examples/bench_columns.py.
Install
pip install oxihipo # wheels for Linux / macOS / Windows, CPython >= 3.13
That is the whole install: every backend ships by default, so library="ak",
"pd", "np" and "arrow" all work out of the box. The imports stay lazy, so
import oxihipo costs nothing for a backend you never call.
The one piece pip cannot supply is ROOT itself — see Dependencies.
Build from source
Requires the Rust toolchain and maturin.
cd py
maturin develop --release # build + install into the active venv
# or: maturin build --release # produce an abi3 wheel under target/wheels
The extension is built with pyo3 0.29 and rust-numpy 0.29, with an
abi3-py313 floor — so one abi3 wheel per OS/arch works across CPython ≥ 3.13.
pyo3 0.29 supports current CPython natively; only for an interpreter newer than
it knows do you need PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1.
Dependencies
pip install oxihipo installs all of these:
| package | powers |
|---|---|
numpy >= 1.24 |
the columnar buffers themselves; library="np" |
awkward >= 2.6 |
array / arrays (library="ak"), and the pandas + ROOT paths |
pandas >= 2.0 |
library="pd" |
pyarrow >= 14 |
library="arrow", assembled directly — no awkward on the polars / duckdb path |
ROOT is the exception. rdataframe / iterate_rdataframe need a working
ROOT/PyROOT, which is not on PyPI — install it via conda-forge
(conda install -c conda-forge root) or your system. The oxihipo[root] extra
covers only the awkward side, which you already have.
The [awkward], [pandas], [arrow] and [all] extras still resolve, so old
install commands keep working, but they are no-ops now.
Nothing above is imported at import oxihipo time — each backend is imported on
first use, so an unused one costs only disk. If you need the minimal footprint,
pip install --no-deps oxihipo numpy still gives you the numpy() /
read_columns() paths.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file oxihipo-0.3.0.tar.gz.
File metadata
- Download URL: oxihipo-0.3.0.tar.gz
- Upload date:
- Size: 327.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
de5601317ecf47bc0bef6ba63d0a208c9d17788d12990e50533a15dc88c702e0
|
|
| MD5 |
4fd0f1c89f00f7e8aced264ac42b5de1
|
|
| BLAKE2b-256 |
9ad173dafc128a0dead1efe71661799ad2aa359de48f990de2b211dcd8c47066
|
Provenance
The following attestation bundles were made for oxihipo-0.3.0.tar.gz:
Publisher:
wheels.yml on mathieuouillon/oxihipo
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
oxihipo-0.3.0.tar.gz -
Subject digest:
de5601317ecf47bc0bef6ba63d0a208c9d17788d12990e50533a15dc88c702e0 - Sigstore transparency entry: 2241133252
- Sigstore integration time:
-
Permalink:
mathieuouillon/oxihipo@a5968980720bb05a10d0947c5fe3078939a7565c -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/mathieuouillon
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
wheels.yml@a5968980720bb05a10d0947c5fe3078939a7565c -
Trigger Event:
push
-
Statement type:
File details
Details for the file oxihipo-0.3.0-cp313-abi3-win_amd64.whl.
File metadata
- Download URL: oxihipo-0.3.0-cp313-abi3-win_amd64.whl
- Upload date:
- Size: 615.4 kB
- Tags: CPython 3.13+, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c777cc9f0680074131d36ba3eb3690ac5ea6f3aa52dda820c7d074795dca225e
|
|
| MD5 |
a2d07a7a96e626659a911f46fd84c8cd
|
|
| BLAKE2b-256 |
78c31cb0844c68b6c05da1c4583bae601eb223e27f00d70799a530f3e7822928
|
Provenance
The following attestation bundles were made for oxihipo-0.3.0-cp313-abi3-win_amd64.whl:
Publisher:
wheels.yml on mathieuouillon/oxihipo
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
oxihipo-0.3.0-cp313-abi3-win_amd64.whl -
Subject digest:
c777cc9f0680074131d36ba3eb3690ac5ea6f3aa52dda820c7d074795dca225e - Sigstore transparency entry: 2241134230
- Sigstore integration time:
-
Permalink:
mathieuouillon/oxihipo@a5968980720bb05a10d0947c5fe3078939a7565c -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/mathieuouillon
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
wheels.yml@a5968980720bb05a10d0947c5fe3078939a7565c -
Trigger Event:
push
-
Statement type:
File details
Details for the file oxihipo-0.3.0-cp313-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: oxihipo-0.3.0-cp313-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 751.1 kB
- Tags: CPython 3.13+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
02e4773631a06a157dca001b782896eb5da62ba09f95977e2d8207d849a0fdbb
|
|
| MD5 |
780447f25bab2bfd680ca41fdbdee748
|
|
| BLAKE2b-256 |
e6db5188607bf22050b4d01390733870332e57e1512a1d6e3ecd30af3a449b64
|
Provenance
The following attestation bundles were made for oxihipo-0.3.0-cp313-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
wheels.yml on mathieuouillon/oxihipo
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
oxihipo-0.3.0-cp313-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
02e4773631a06a157dca001b782896eb5da62ba09f95977e2d8207d849a0fdbb - Sigstore transparency entry: 2241133869
- Sigstore integration time:
-
Permalink:
mathieuouillon/oxihipo@a5968980720bb05a10d0947c5fe3078939a7565c -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/mathieuouillon
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
wheels.yml@a5968980720bb05a10d0947c5fe3078939a7565c -
Trigger Event:
push
-
Statement type:
File details
Details for the file oxihipo-0.3.0-cp313-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: oxihipo-0.3.0-cp313-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 724.1 kB
- Tags: CPython 3.13+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
42f85b0203935788fc5ac8c199a882703d4ebbef11b943182272784da58569b1
|
|
| MD5 |
f0580a5df0f5bfe3fe7428507ce6f68e
|
|
| BLAKE2b-256 |
805805077f90b3e1b0d45746332dd46d7391eaa1857d407939e3c0f9fc5d27c5
|
Provenance
The following attestation bundles were made for oxihipo-0.3.0-cp313-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:
Publisher:
wheels.yml on mathieuouillon/oxihipo
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
oxihipo-0.3.0-cp313-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -
Subject digest:
42f85b0203935788fc5ac8c199a882703d4ebbef11b943182272784da58569b1 - Sigstore transparency entry: 2241133776
- Sigstore integration time:
-
Permalink:
mathieuouillon/oxihipo@a5968980720bb05a10d0947c5fe3078939a7565c -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/mathieuouillon
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
wheels.yml@a5968980720bb05a10d0947c5fe3078939a7565c -
Trigger Event:
push
-
Statement type:
File details
Details for the file oxihipo-0.3.0-cp313-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: oxihipo-0.3.0-cp313-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 666.0 kB
- Tags: CPython 3.13+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
324ea575e7ac308070ff61251e17fe0c1113b1e6711d59301d972ceb12437009
|
|
| MD5 |
c3d7d9ed88a31d2b80f63e51a2cdf1a8
|
|
| BLAKE2b-256 |
58d0367bf00a55630187dbdbc43674dcd5d755cce13b4fa5dea74915c3697b9f
|
Provenance
The following attestation bundles were made for oxihipo-0.3.0-cp313-abi3-macosx_11_0_arm64.whl:
Publisher:
wheels.yml on mathieuouillon/oxihipo
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
oxihipo-0.3.0-cp313-abi3-macosx_11_0_arm64.whl -
Subject digest:
324ea575e7ac308070ff61251e17fe0c1113b1e6711d59301d972ceb12437009 - Sigstore transparency entry: 2241133608
- Sigstore integration time:
-
Permalink:
mathieuouillon/oxihipo@a5968980720bb05a10d0947c5fe3078939a7565c -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/mathieuouillon
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
wheels.yml@a5968980720bb05a10d0947c5fe3078939a7565c -
Trigger Event:
push
-
Statement type:
File details
Details for the file oxihipo-0.3.0-cp313-abi3-macosx_10_12_x86_64.whl.
File metadata
- Download URL: oxihipo-0.3.0-cp313-abi3-macosx_10_12_x86_64.whl
- Upload date:
- Size: 714.3 kB
- Tags: CPython 3.13+, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3bda9b9c06070a1786c716a0d8e0004cfc597aa2194843b3bf0c83ca205c1f91
|
|
| MD5 |
9394103f7bf61a1bf507f188c77039e6
|
|
| BLAKE2b-256 |
ec4603f59bb458871b7f640c704fcf115510326a08ae9ebfc60cf2aea972452f
|
Provenance
The following attestation bundles were made for oxihipo-0.3.0-cp313-abi3-macosx_10_12_x86_64.whl:
Publisher:
wheels.yml on mathieuouillon/oxihipo
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
oxihipo-0.3.0-cp313-abi3-macosx_10_12_x86_64.whl -
Subject digest:
3bda9b9c06070a1786c716a0d8e0004cfc597aa2194843b3bf0c83ca205c1f91 - Sigstore transparency entry: 2241134054
- Sigstore integration time:
-
Permalink:
mathieuouillon/oxihipo@a5968980720bb05a10d0947c5fe3078939a7565c -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/mathieuouillon
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
wheels.yml@a5968980720bb05a10d0947c5fe3078939a7565c -
Trigger Event:
push
-
Statement type: