Skip to main content

dump

Emit whole events as JSON — one object per line (NDJSON). Where scan projects a single bank into a flat table, dump gives you every present bank at once, so it's the bridge to any JSON-speaking tool: jq, pandas, DuckDB, awk, a notebook.

hipoq dump rec.hipo --events 0..1 | jq .
{
"event": 0,
"tag": 0,
"REC::Calorimeter": [
{ "pindex": 0, "energy": 0.8, "sector": 1 },
{ "pindex": 0, "energy": 0.9, "sector": 4 },
{ "pindex": 1, "energy": 0.3, "sector": 2 }
],
"REC::Event": [ { "helicity": 1 } ],
"REC::Particle": [
{ "pid": 11, "px": 0.5, "py": 0.0, "pz": 2.0, "charge": -1 },
{ "pid": 2212, "px": 0.3, "py": 0.1, "pz": 1.0, "charge": 1 }
]
}

Each line is one event: an event index, then each present bank as an array of {column: value} row objects, the columns in schema order.

Banks come out sorted by name, not in the order the dictionary declares them — above, the dictionary's order is REC::Particle, REC::Event, REC::Calorimeter. Sorting keeps a line's shape stable across files whose dictionaries were written in different orders, which is what a jq filter or a pandas load needs. Within a bank the columns follow the schema.

Options

FlagMeaning
--events A..Bevent span, end-exclusive. Omit for the whole file
--bank Binclude only these banks (repeatable, comma-ok). Empty = all present
--where Ekeep only events passing a cut (repeatable, ANDed)
--format Fndjson (default), json, or table; csv is refused — see shape vs encoding

In ndjson values stay numericpid is not decoded, even with global --pdg — so the stream is clean for downstream parsing. --format table is the human-facing form and does honour --pdg, exactly as scan's table does.

As tables, one per bank per event

--format table renders each bank of each event as its own table — that bank's columns, every row, nothing padded:

hipoq dump rec.hipo --events 0..1 --format table --bank REC::Particle,REC::Calorimeter
event 0 · REC::Calorimeter (3 rows)
┌─────┬────────┬────────┬────────┐
│ row ┆ pindex ┆ energy ┆ sector │
╞═════╪════════╪════════╪════════╡
│ 0 ┆ 0 ┆ 0.8 ┆ 1 │
└─────┴────────┴────────┴────────┘

event 0 · REC::Particle (2 rows)
┌─────┬──────┬─────┬─────┬─────┬────────┐
│ row ┆ pid ┆ px ┆ py ┆ pz ┆ charge │
╞═════╪══════╪═════╪═════╪═════╪════════╡
│ 0 ┆ 11 ┆ 0.5 ┆ 0.0 ┆ 2.0 ┆ -1 │
└─────┴──────┴─────┴─────┴─────┴────────┘

This is the shape for reading one event; scan --bank A,B is the shape for extracting a column across many. The difference is the union: a scan of two banks puts them in one table with blank cells where a bank lacks a column, while here each bank keeps its own columns and nothing is blank.

--bank selects which banks get a table (repeatable, comma-ok; empty = all present), and global --pdg decodes pid to symbols in the table the same way it does for scan.

csv is refused; json is not

--format json gives the same event objects as the NDJSON stream, wrapped in one array — for a reader that wants a single document rather than a stream.

csv has no such form, and for a reason specific to it: CSV is one header over uniform rows, and an event is several banks with different row counts and different columns. Padding to the longest bank, or emitting only the first, would be a guess about what you meant. scan --bank A,B --format csv is the flat projection.

The tag field

Every line carries the event's own tag, from the event header:

hipoq dump run.hipo | jq -r 'select(.tag == 3) | .event'

It is present even when zero, on purpose. A key that appeared only on tagged events would make the stream's schema depend on its contents — jq '.tag' would return null on some lines, and read_json(lines=True) would give you a column that exists for some files and not others. It costs ten bytes against ~155 KiB of event on a CLAS12 DST.

Filtering is cheaper done up front with --tag, which reads the header and inflates nothing, rather than by dumping everything and selecting in jq.

NDJSON, not a JSON array

Output is newline-delimited JSON: one self-contained object per line, not a single big array. This streams (you can start processing before the file is fully read) and plays nicely with line-oriented tools:

hipoq dump rec.hipo | head -1 | jq . # first event, pretty
hipoq dump rec.hipo | wc -l # event count that reached stdout

Selecting banks and events

# only the particle and event banks, first 1000 events
hipoq dump rec.hipo --events 0..1000 --bank REC::Particle,REC::Event

# only events with an electron
hipoq dump rec.hipo --where "REC::Particle.pid == 11"

The --where cut uses the full expression language, so cross-bank count() / any() conditions work here too.

Into a dataframe

The common pattern — filter in hipoq, analyze in Python:

hipoq dump rec.hipo --where "REC::Particle.pid==11" > electrons.ndjson
import pandas as pd
df = pd.read_json("electrons.ndjson", lines=True)
# df["REC::Particle"] holds a list of row-dicts per event; explode/normalize as needed

See the programmatic access guide for fuller jq / pandas / DuckDB recipes.

dump vs scan

Use scan --format ndjson when you want one bank as flat rows (one JSON object per bank row). Use dump when you want the whole event (one JSON object per event, every bank nested inside).

Cost

dump writes every bank of every event, which on a CLAS12 DST is about 155 KiB of JSON per event. It streams — one event is serialised and dropped, so memory does not grow with the range — and runs at 247 MB/s, or 660 µs per event, on run 22083. A whole run file is roughly 6.6 minutes and about 95 GB of JSON.

With --bank it is far cheaper, because only the requested banks are read and the event's structure list is never walked: 25 µs per event for one bank. If you want one bank across many events, scan is usually the better shape anyway — it gives you rows rather than nested objects. See Output formats.