Skip to main content

scan

Project columns of a single bank across an event range — the ROOT TTree::Scan of HIPO. It's the command you'll use most.

hipoq scan rec.hipo --bank REC::Particle --cols pid,px,py,pz --events 0..100
event tag row pid px py pz
0 0 0 11 0.51 -0.02 2.31
0 0 1 2212 0.30 0.11 0.98
1 0 0 11 0.44 0.05 1.87
...

Each output row is one bank row, prefixed with its event index, the event's tag, and the row number within the event.

tag is always there, even when every event has zero

It sits with event because both describe the event rather than the row, and it repeats down a bank's rows exactly as event does.

Emitting it only when some event happened to be tagged would make the CSV header depend on the file's contents — a script reading column 3 would get row on one run and pid on another. A constant column is the lesser problem. ⚠️ This did shift the column positions after event.

Options

FlagMeaning
--bank B(required) bank to project (repeatable, comma-ok)
--cols a,b,…columns to show (repeatable, comma-ok). Empty = every column of the bank
--events A..Bevent span, end-exclusive (0..100). Omit for from the start
--limit Nmaximum output rows (default 100)
--where Ekeep only events passing a cut (repeatable, ANDed)
--expr N=Eadd a computed column (repeatable)
--format Ftable (default), csv, ndjson, or json

Global --pdg decodes pid to symbols in table output (data formats stay numeric).

Choosing columns

Omit --cols to get every column of the bank — array columns included, rendered as a JSON array per row; name them to control order and width:

hipoq scan rec.hipo --bank REC::Particle # every column
hipoq scan rec.hipo --bank REC::Particle --cols pid,px,py,pz # just these, in order

Several banks at once

--bank is repeatable, and comma-ok. The rows of each bank are stacked under their event, in the order you named them, and the output gains a bank column:

hipoq scan rec.hipo --bank REC::Particle,REC::Calorimeter --cols pid,energy
event,tag,bank,row,pid,energy
0,0,REC::Particle,0,11,
0,0,REC::Particle,1,2212,
0,0,REC::Calorimeter,0,,0.8
0,0,REC::Calorimeter,1,,0.9

The value columns are the union across the banks, in order of first appearance. A column a bank does not have is blank for that bank's rows — blank rather than zero, because zero is a value energy could legitimately take. row restarts at 0 for each bank, so it still pairs with pindex directly.

They are stacked, not joined

Two banks have different row counts in the same event — four particles, seven calorimeter hits — so there is no row correspondence to print, and inventing one would be a guess. For the pindex join use a cut across banks, dump for the whole event as one object, or the TUI's x panel.

The bank column appears only when you name more than one bank, so a single-bank scan is byte-identical to what it always was. That depends on the arguments — which you wrote — never on how many banks the file happens to hold.

--expr is refused with several banks: a computed column is evaluated against one driver bank, and p (which needs px,py,pz) means nothing for REC::Calorimeter. Scan one bank at a time when computing columns.

Column names not present in the schema are silently skipped, so you can keep a generous --cols list across files with slightly different banks.

Event range and limit

--events is end-exclusive; --limit caps the number of output rows (not events), which matters because one event yields several rows:

hipoq scan rec.hipo --bank REC::Particle --events 500..600 --limit 1000

Filtering with --where

Keep only events that pass a cut — the same expression language used everywhere:

hipoq scan rec.hipo --bank REC::Particle --cols pid,px,py,pz \
--where "REC::Particle.pid == 11"

The cut selects events; all rows of the bank in a passing event are shown.

Computed columns with --expr

Add derived columns — a NAME=EXPRESSION, or a bare preset name:

# momentum, polar angle, sampling fraction
hipoq scan rec.hipo --bank REC::Particle --cols pid \
--expr p --expr theta_deg --expr sampling_fraction

# a custom transverse mass
hipoq scan rec.hipo --bank REC::Particle --expr "mt=sqrt(px*px+py*py)"

Computed columns are evaluated per row of the scanned bank (which is the expression's driver, so cross-bank references resolve by pindex). They appear in every output format. See computed columns for the full list of presets and the reverse-gather trick.

Formats

ndjson is the one to pipe into jq or a dataframe:

hipoq scan rec.hipo --bank REC::Particle --cols pid,px,py,pz --format ndjson | jq .

Array columns (like a covariance cov) come out as arrays in every format; in ndjson/json they are real nested JSON arrays. For the full comparison of table / csv / ndjson / json, see output formats.

Whole events, not a projection

scan projects the banks you name into one flat table, with a union of their columns. To get every bank of an event, use dump: as NDJSON for one object per event, or dump --format table for one table per bank per event, each keeping its own columns.

Exporting more than the default 100 rows

--limit caps the output; raise it for a real export:

hipoq scan run.hipo --bank REC::Particle --limit 2000000 --format csv > particles.csv

The data formats (csv, ndjson, json) stream — each row is written and dropped, so peak memory does not grow with the row count. --format table holds every row instead, because a column is as wide as its widest cell, and above 100,000 rows it says so and points at the cheaper formats. See Output formats for measured numbers.