Trees

The trees GATE writes and the in-memory representation of their data.

Tree names

GateTree enum and parser.

This module defines the GateTree enum and provides a function to parse strings into GateTree enum members.

class opengate_gate_tree.tree.gatetree.GateTree(*values)[source]
opengate_gate_tree.tree.gatetree.parse_gate_tree(name)[source]

Parse a string into a GateTree enum member.

Parameters:

name (str) – Name of the gate tree member.

Returns:

Corresponding GateTree enum member.

Return type:

GateTree

Raises:

ValueError – If the name does not correspond to any GateTree member.

Tree data

NumPy-backed representation of a GATE tree.

The module defines TreeData, the in-memory representation shared by every reader and writer in the package. Data is stored as NumPy arrays, which is the common denominator of the supported output formats.

Two kinds of branches are supported:

  • scalar branches, stored as one-dimensional arrays

  • fixed-width array branches, stored as two-dimensional arrays of shape (entries, width); GATE uses them for branches such as volumeID

Branches of varying length per entry are not supported and are rejected by the readers before a TreeData instance is built.

Arrays are referenced, not copied, so building a TreeData from an already loaded tree does not duplicate its memory. The mapping of columns is read-only, but the arrays themselves stay writable.

A pandas.DataFrame view is available through TreeData.to_dataframe(). Because a data frame holds scalar cells, a fixed-width array branch is expanded there into one column per component, named <branch>_<index>. The expansion is one way: reading such a frame back with TreeData.from_dataframe() keeps the expanded columns separate.

Public objects:

TreeData

Immutable set of branch columns extracted from a single GATE tree.

class opengate_gate_tree.tree.treedata.TreeData(tree, columns)[source]

Branch columns extracted from a single GATE tree.

Equality is intentionally not defined: comparing NumPy arrays with == yields arrays rather than booleans, so a generated __eq__ would raise instead of answering. Compare the fields explicitly when needed.

Parameters:
tree

Tree the columns were extracted from.

Type:

GateTree

columns

Branch name to column mapping. Exposed as a read-only mapping; the insertion order is the branch order of the data.

Type:

Mapping[str, numpy.ndarray]

property branch_names: tuple[str, ...]

Branch names in their original order.

property entry_count: int

Number of entries stored in every branch.

property dtypes: Mapping[str, dtype[Any]]

Branch name to NumPy data type mapping.

property array_branches: Mapping[str, int]

Fixed-width array branches mapped to their width.

__len__()[source]

Return the number of entries.

Return type:

int

__getitem__(name)[source]

Return the column of a single branch.

Parameters:

name (str) – Branch name.

Returns:

Column of the requested branch.

Return type:

numpy.ndarray

Raises:

BranchNotFoundError – If the branch is not present.

select(names)[source]

Return a new instance holding only the requested branches.

Repeated names are kept once, at the position of their first occurrence.

Parameters:

names (Sequence[str]) – Branch names to keep, in the requested order.

Returns:

New instance sharing the selected columns.

Return type:

TreeData

Raises:

BranchNotFoundError – If any requested branch is not present.

to_dataframe()[source]

Return the data as a pandas data frame.

Scalar branches become one column each. Fixed-width array branches are expanded into one column per component, named <branch>_<index> with indices counted from zero, placed where the original branch was.

Returns:

Data frame holding every branch as a scalar column.

Return type:

pandas.DataFrame

Raises:

ValueError – If expanding an array branch would collide with another column.

classmethod from_dataframe(tree, frame)[source]

Build an instance from a pandas data frame.

Every column becomes a scalar branch and the index is dropped. Columns produced by expanding an array branch stay separate; they are not folded back into a two-dimensional branch.

Parameters:
Returns:

Instance holding one branch per data frame column.

Return type:

TreeData

Raises:

ValueError – If the columns are a MultiIndex, or if two columns end up with the same branch name.

Branch selection

Branch selection for GATE trees.

The module turns a requested list of branch names into the list that is actually read. Which branches exist is decided by the file being read, not by a fixed list built into the package, so the checks here cover the shape of the request only. Whether a branch exists is checked against the opened file by opengate_gate_tree.io.validation.

Public functions:

validate_branch_selection(requested: Sequence[str]) -> None

Check that a branch selection is well formed.

normalize_branch_selection(requested: Sequence[str], available: Sequence[str]) -> list[str]

Turn a requested selection into the list of branches to read.

opengate_gate_tree.tree.branch.validate_branch_selection(requested)[source]

Check that a branch selection is well formed.

The check does not need the file, so it can run before one is opened. Repeated names are allowed; they are collapsed by normalize_branch_selection().

Parameters:

requested (Sequence[str]) – Requested branch names.

Raises:

ValueError – If any requested name is empty or contains only whitespace.

Return type:

None

opengate_gate_tree.tree.branch.normalize_branch_selection(requested, available)[source]

Turn a requested selection into the list of branches to read.

An empty selection means every branch of the tree. Repeated names are kept once, at the position of their first occurrence, because asking for the same branch twice is not a mistake worth refusing.

Parameters:
  • requested (Sequence[str]) – Requested branch names. Empty means every branch.

  • available (Sequence[str]) – Branch names present in the tree, in file order.

Returns:

Branch names to read, in the order they should appear.

Return type:

list[str]

Raises:

ValueError – If any requested name is empty or contains only whitespace.

Hits tree structures

Variants of the “Hits” tree and the naming of system identifier branches.

Which branches GATE writes into the “Hits” tree depends on the simulation: on whether hits are attached to a system, on whether the Compton camera output is enabled, on whether septal penetration is counted, and on which output module wrote the file.

When a system is used, GATE adds one branch per level of the system hierarchy. Their names depend on the type of the system, and two system types can share one set of names, so what a set of names identifies is the naming scheme, not the system itself. In the classic ROOT output GATE always writes six such branches, whatever the depth of the system; the GateToTree output writes as many as the system is deep, which makes its identifier block a prefix of the scheme.

Public objects:

HitsTreeVariant

Structure of a “Hits” tree.

GateSystemType

Naming scheme of the system identifier branches.

SYSTEM_ID_BRANCHES

Identifier branch names of every scheme, ordered from the top level down.

SYSTEM_ALIASES

System types that share one naming scheme.

system_id_depth(system, branch_names) -> int

Number of leading identifier branches of a scheme that are present.

find_system_type(branch_names) -> GateSystemType | None

Naming scheme the branches follow, or None when it cannot be told.

class opengate_gate_tree.tree.hits.variant.HitsTreeVariant(*values)[source]

Structure of a “Hits” tree.

class opengate_gate_tree.tree.hits.variant.GateSystemType(*values)[source]

Naming scheme of the system identifier branches.

A member stands for the names GATE gives the identifier branches, not for a single system type: the systems listed in SYSTEM_ALIASES under one member cannot be told apart by branch names alone.

opengate_gate_tree.tree.hits.variant.system_id_depth(system, branch_names)[source]

Return how many leading identifier branches of a scheme are present.

Counting stops at the first missing name, because GATE writes the levels of a system from the top down: a gap means the branches belong to another scheme rather than to a deeper level of this one.

Parameters:
  • system (GateSystemType) – Naming scheme to measure against.

  • branch_names (Sequence[str]) – Branch names of the tree.

Returns:

Number of leading identifier branches present, from 0 to the depth of the scheme.

Return type:

int

opengate_gate_tree.tree.hits.variant.find_system_type(branch_names)[source]

Return the naming scheme the identifier branches follow.

The scheme reaching furthest into its own names wins. Schemes overlap at their first level, so a tree carrying only a shared name such as gantryID matches several of them equally well; that is reported as “cannot be told” rather than resolved by picking one.

Parameters:

branch_names (Sequence[str]) – Branch names of the tree.

Returns:

Matching scheme, or None when no identifier branch is present or several schemes match equally well.

Return type:

GateSystemType | None

Branch schemas of the supported “Hits” tree variants.

A schema is the list of branches a variant of the tree holds, in the order GATE writes them, each with the type it is written as. Variants that use a system carry a block of identifier branches whose names depend on the system (opengate_gate_tree.tree.hits.variant), so the schema of such a variant is completed with the naming scheme in use.

The branch lists were measured on simulation output, one file per variant, and the tests compare them against those files. Two of them are written out here, and the rest are stated as the differences the reference material describes: the identifier block, the septal penetration counter, and the Compton camera columns.

Types follow the name of the branch, which is the same rule GATE follows: a branch called edep is single precision wherever it appears. In the GateToTree layout volumeID is split into ten scalar branches named volumeID[0] to volumeID[9], which are integers like any other branch; only the classic volumeID is a fixed-width array.

Public objects:

BranchKind

Kind of value a branch holds.

BranchSpec

Name and type of a single branch.

branch_spec(name) -> BranchSpec

Specification of a branch of the given name.

expected_branches(variant, system, system_depth) -> tuple[BranchSpec, …]

Branches a variant holds, in file order.

supported_variants() -> tuple[HitsTreeVariant, …]

Variants the package supports.

takes_system_depth(variant) -> bool

Whether the number of identifier branches follows the depth of the system.

uses_system(variant) -> bool

Whether a variant carries system identifier branches.

variant_reference(variant) -> str

Label the reference material gives a variant.

class opengate_gate_tree.tree.hits.schema.BranchKind(*values)[source]

Kind of value a branch holds.

The array kinds stand for a fixed-width array branch, one row of values per entry. No structure of the “Hits” tree holds an array of floating point values, but the kinds are also used to describe data the package did not read from a GATE file.

class opengate_gate_tree.tree.hits.schema.BranchSpec(name, kind, dtype)[source]

Name and type of a single branch.

Parameters:
name

Branch name, as GATE writes it.

Type:

str

kind

Kind of value the branch holds.

Type:

BranchKind

dtype

Type name, such as "int32", "float64", "text" or "int32[10]" for a fixed-width array branch.

Type:

str

opengate_gate_tree.tree.hits.schema.branch_spec(name)[source]

Return the specification of a branch of the given name.

Parameters:

name (str) – Branch name.

Returns:

Name and type of the branch.

Return type:

BranchSpec

opengate_gate_tree.tree.hits.schema.supported_variants()[source]

Return the variants the package supports, in reference order.

Return type:

tuple[HitsTreeVariant, …]

opengate_gate_tree.tree.hits.schema.variant_reference(variant)[source]

Return the label the reference material gives a variant.

Parameters:

variant (HitsTreeVariant) – Variant to label.

Returns:

Label such as "A1" or "B1".

Return type:

str

opengate_gate_tree.tree.hits.schema.uses_system(variant)[source]

Return whether a variant carries system identifier branches.

Parameters:

variant (HitsTreeVariant) – Variant to check.

Returns:

True when the schema of the variant needs a naming scheme to be completed.

Return type:

bool

opengate_gate_tree.tree.hits.schema.takes_system_depth(variant)[source]

Return whether the identifier block is as deep as the system.

The classic ROOT output always writes six identifier branches, filling the levels the system does not reach with placeholder names, while the GateToTree output writes one per level of the system.

Parameters:

variant (HitsTreeVariant) – Variant to check.

Returns:

True when the length of the identifier block is not fixed.

Return type:

bool

opengate_gate_tree.tree.hits.schema.expected_branches(variant, system=None, system_depth=None)[source]

Return the branches a variant holds, in file order.

Parameters:
  • variant (HitsTreeVariant) – Variant to describe.

  • system (GateSystemType | None) – Naming scheme of the identifier branches. Required for a variant that uses a system, rejected for one that does not.

  • system_depth (int | None) – Number of identifier branches, for the GateToTree output only, where the block is as deep as the system. Defaults to the full depth of the scheme. The classic ROOT output always writes six identifier branches, so passing a depth for it is rejected.

Returns:

Branches of the variant, in the order GATE writes them.

Return type:

tuple[BranchSpec, …]

Raises:

ValueError – If the naming scheme is missing or given where it does not belong, or if the depth is out of range or given for a variant of fixed depth.

Recognition of the structure a “Hits” tree has.

Which structure a tree has is decided by a handful of marker branches rather than by comparing the whole branch list against a schema. Marker branches say what the simulation did: whether hits were attached to a system, whether the Compton camera output was enabled, whether septal penetration was counted, and which output module wrote the file.

Splitting recognition from validation is deliberate. A file from a GATE build that adds or drops a branch still recognises as the structure it is, so opengate_gate_tree.tree.hits.validation can report what is missing from it, instead of the package answering “unknown structure” to everything that is not an exact match.

Two structures are recognised and refused: the per-collection GateToTree output with the Compton camera columns, and the output of the Compton camera actor. Neither has a reference file that holds any data, so their schemas cannot be confirmed; naming them in the error beats leaving the user with “not recognised”.

Public objects:

HitsTreeDetection

Structure recognised in a tree.

detect_hits_variant(branch_names, tree_name) -> HitsTreeDetection

Recognise the structure of a tree, or refuse it.

find_hits_variant(branch_names) -> HitsTreeDetection | None

Recognise the structure of a tree, answering None when it is not one of the supported ones.

find_complete_hits_variant(branch_names) -> HitsTreeDetection | None

Recognise a tree that holds every branch of its structure.

expected_branches_of(detection) -> tuple[BranchSpec, …]

Branches the recognised structure describes.

summarise_hits_tree(detection) -> str

One line stating what a tree was recognised as.

describe_hits_tree(detection, dtypes, entry_count) -> str

Human readable description of a recognised tree, branch by branch.

class opengate_gate_tree.tree.hits.detection.HitsTreeDetection(variant, system, system_depth, tree_name, branch_count)[source]

Structure recognised in a “Hits” tree.

Parameters:
variant

Structure the tree has.

Type:

HitsTreeVariant

system

Naming scheme of the system identifier branches, or None when the structure carries none.

Type:

GateSystemType | None

system_depth

Number of identifier branches present, or None when the structure carries none.

Type:

int | None

tree_name

Name the tree is stored under, when it is known.

Type:

str | None

branch_count

Number of branches the tree holds.

Type:

int

opengate_gate_tree.tree.hits.detection.detect_hits_variant(branch_names, tree_name=None)[source]

Recognise the structure of a “Hits” tree.

Parameters:
  • branch_names (Sequence[str]) – Branch names of the tree.

  • tree_name (str | None) – Name the tree is stored under, reported back and named in errors.

Returns:

Structure of the tree.

Return type:

HitsTreeDetection

Raises:

UnknownHitsVariantError – If the branches match no supported structure, or match one the package does not support.

opengate_gate_tree.tree.hits.detection.find_hits_variant(branch_names)[source]

Recognise the structure of a tree without refusing unknown ones.

Used where a tree is a candidate rather than a request, such as when the tree holding the hits is looked for by its structure.

Parameters:

branch_names (Sequence[str]) – Branch names of the tree.

Returns:

Structure of the tree, or None when it is not a supported one.

Return type:

HitsTreeDetection | None

opengate_gate_tree.tree.hits.detection.summarise_hits_tree(detection)[source]

Return one line stating what a tree was recognised as.

Used where the structure is worth reporting but the branches are not, such as the log of a run.

Parameters:

detection (HitsTreeDetection) – Structure recognised in the tree.

Returns:

Single line naming the variant, the tree and the identifier scheme.

Return type:

str

opengate_gate_tree.tree.hits.detection.find_complete_hits_variant(branch_names)[source]

Recognise a tree that holds every branch of the structure it matches.

Recognition on its own is lenient by design: a marker branch is enough, so that a tree which almost matches a structure can be told what is wrong with it. That is the wrong rule for deciding which tree of a file holds the hits, where a single branch named like a marker would be enough to drag an unrelated tree in. Here the branches of the structure have to be there.

Branches beyond the structure are allowed, as everywhere else: a GATE build adding one still writes hits.

Parameters:

branch_names (Sequence[str]) – Branch names of the tree.

Returns:

Structure of the tree, or None when the tree is not a supported structure or does not hold all of it.

Return type:

HitsTreeDetection | None

opengate_gate_tree.tree.hits.detection.expected_branches_of(detection)[source]

Return the branches the recognised structure describes.

Parameters:

detection (HitsTreeDetection) – Structure recognised in a tree.

Returns:

Branches of the structure, in the order GATE writes them.

Return type:

tuple[BranchSpec, …]

opengate_gate_tree.tree.hits.detection.describe_hits_tree(detection, dtypes=None, entry_count=None)[source]

Describe a recognised tree in a form meant to be read.

Parameters:
  • detection (HitsTreeDetection) – Structure recognised in the tree.

  • dtypes (Mapping[str, str] | None) – Branch names mapped to the type each of them was read with, in file order. When omitted, the branches and types of the schema are described instead, which needs no file to be open.

  • entry_count (int | None) – Number of entries, when it is known.

Returns:

Description spanning several lines.

Return type:

str

Checking a “Hits” tree against the structure it was recognised as.

Recognition names the structure from a few marker branches; validation asks whether the tree really holds what that structure describes. The two are kept apart so that a tree which almost matches gets a report of what is wrong with it, instead of being turned away as unrecognised.

What counts as a failure is not symmetric:

  • a branch the structure describes but the tree does not hold, or one whose type differs, is an error, because the data is not what it was taken for;

  • a branch the tree holds beyond the structure is a warning. The reference files come from a GATE build carrying patches, and adding a branch is an ordinary thing for a GATE build to do. Refusing such a file would turn the package away from exactly the simulations it is written for.

Public functions:

validate_hits_tree(branch_names, dtypes, detection) -> HitsTreeDetection

Check a tree against the structure it was recognised as.

opengate_gate_tree.tree.hits.validation.validate_hits_tree(branch_names, dtypes, detection=None)[source]

Check a tree against the structure it was recognised as.

Parameters:
  • branch_names (Sequence[str]) – Branch names of the tree, in file order.

  • dtypes (Mapping[str, str]) – Branch names mapped to the type each of them is stored with, in the vocabulary of BranchSpec.

  • detection (HitsTreeDetection | None) – Structure recognised in the tree. Recognised here when not given.

Returns:

Structure the tree was checked against.

Return type:

HitsTreeDetection

Raises:

What the gammas of a PositroniumSource were, as read from the “Hits” tree.

A PositroniumSource writes four branches saying where each gamma came from: which source model emitted it, through which decay channel, what kind of gamma it is, and which channel of the configured mixture it belongs to. GATE stores all four as integers, and their meaning is defined by the enums of GateEmittedGammaInformation.hh:

  • sourceType — which model emitted the gamma (GATE: SourceKind)

  • decayType — which decay channel it came through (GATE: DecayModel)

  • gammaType — what kind of gamma it is (GATE: GammaKind)

  • decayIndex — component of the sampled decay, or -1 when the row carries no decay metadata (no enum in GATE)

The classes here are named after the branches rather than after the enums of GATE, because a branch name is what the reader of a file works with. Each class names its counterpart, so the way back to the source of the values stays short.

They derive from enum.IntEnum, which means their members are the integers GATE wrote. A column read from a file can be compared against them with nothing in between:

prompt = data["gammaType"] == GammaType.PROMPT

A plain enum.Enum would not work here, and would not say so: comparing a column against one of its members yields a mask of False rather than an error.

Public objects:

SourceType

Model that emitted a gamma.

DecayType

Decay channel a gamma came through.

GammaType

Kind of gamma.

POSITRONIUM_BRANCHES

Branches whose values these classes describe.

DECAY_INDEX_BRANCH, NO_POSITRONIUM_METADATA

Branch holding the component of the sampled decay, and the value it holds for a row that carries no decay metadata.

positronium_enum(branch) -> type[IntEnum] | None

Class describing the values of a branch, when one describes them.

decode_positronium_value(branch, value) -> IntEnum

What one value of a branch means.

decode_positronium_column(branch, column) -> numpy.ndarray

What every value of a column means.

has_positronium_metadata(decay_index) -> numpy.ndarray

Which rows carry the decay metadata of a PositroniumSource.

class opengate_gate_tree.tree.hits.positronium.SourceType(*values)[source]

Model that emitted a gamma.

Counterpart of GateEmittedGammaInformation::SourceKind.

class opengate_gate_tree.tree.hits.positronium.DecayType(*values)[source]

Decay channel a gamma came through.

Counterpart of GateEmittedGammaInformation::DecayModel.

class opengate_gate_tree.tree.hits.positronium.GammaType(*values)[source]

Kind of gamma.

Counterpart of GateEmittedGammaInformation::GammaKind.

opengate_gate_tree.tree.hits.positronium.positronium_enum(branch)[source]

Return the class describing the values of a branch.

Parameters:

branch (str) – Branch name.

Returns:

Class describing the values of the branch, or None when the package describes none. decayIndex has no class of its own: its values are channel numbers, and which channel a number means depends on how the source was configured.

Return type:

type[IntEnum] | None

opengate_gate_tree.tree.hits.positronium.has_positronium_metadata(decay_index)[source]

Return which rows carry the decay metadata of a PositroniumSource.

The name says what the value guarantees, which is less than “written by a PositroniumSource”. GATE writes a component number for every gamma such a source emits — including the ones from a direct annihilation component, since it numbers those like the rest — and leaves the branch at -1 for a gamma of another source or for a particle the metadata never reached. In practice the two coincide, but the branch does not say so, and what a gamma itself was is said by sourceType.

Parameters:

decay_index (numpy.typing.ArrayLike) – Column of the decayIndex branch.

Returns:

Boolean mask, True where the row carries a component number rather than the value standing for no metadata.

Return type:

numpy.ndarray

Raises:

ValueError – If the column is not one-dimensional or does not hold whole numbers. A comparison against a value that is not a column answers with a single truth value instead of one per row, which selects everything without saying so.

opengate_gate_tree.tree.hits.positronium.decode_positronium_value(branch, value)[source]

Return what one value of a branch means.

Parameters:
  • branch (str) – Branch name.

  • value (int) – Value written by GATE.

Returns:

Member of the class describing the branch.

Return type:

IntEnum

Raises:

ValueError – If the package describes no values for the branch, or the value is not one of them. A question about a single value has one answer or none: reading it as the value standing for “not defined” would report something the file does not say.

opengate_gate_tree.tree.hits.positronium.decode_positronium_column(branch, column)[source]

Return what every value of a column means.

A value the package does not know becomes None rather than stopping the reading: a GATE build can write one, and an analysis of the rows that are understood is still worth having. The values that were not understood are reported in the log, with how often each of them occurs.

Parameters:
  • branch (str) – Branch name.

  • column (numpy.typing.ArrayLike) – Column of that branch.

Returns:

Array of members of the class describing the branch, with None wherever the value is not one of them.

Return type:

numpy.ndarray

Raises:

ValueError – If the package describes no values for the branch, or the column is not a one-dimensional column of whole numbers. A column of another type would be read by truncating its values, which is the silent substitution this function exists to avoid.

Filters and selectors

Filters and selectors for the data of a “Hits” tree.

The functions here work on the pandas view of extracted data. They come in pairs, and the pair is the same everywhere:

  • is_<something> answers with a boolean column of the same length and the same index as its input, which is what combines with other conditions (&, |) and what indexes other columns;

  • the other name of the pair answers with the rows themselves, which is what chains.

Only what pandas has no name for is added. Comparing, isin and combining masks already work on a column read from a GATE file, so the package does not restate them; a filter earns its place by naming something the data means - a closed range, a shape in space, the identity of an event, the meaning of a code.

Public functions:

is_in_range(values, low, high, inclusive) -> pandas.Series

Which values fall in a range.

in_range(values, low, high, inclusive) -> pandas.Series

The values that fall in a range.

is_in_box(frame, centre, sides, columns) -> pandas.Series

Which rows lie in a box.

in_box(frame, centre, sides, columns) -> pandas.DataFrame

The rows that lie in a box.

is_in_sphere(frame, centre, radius, columns) -> pandas.Series

Which rows lie in a sphere.

in_sphere(frame, centre, radius, columns) -> pandas.DataFrame

The rows that lie in a sphere.

is_in_cylinder(frame, centre, radius, z_range, inner_radius, columns) -> pandas.Series

Which rows lie in a cylinder or a ring.

in_cylinder(frame, centre, radius, z_range, inner_radius, columns) -> pandas.DataFrame

The rows that lie in a cylinder or a ring.

is_from_run(frame, run_id) -> pandas.Series

Which rows come from a run.

by_run(frame, run_id) -> pandas.DataFrame

The rows of a run.

is_from_event(frame, run_id, event_id) -> pandas.Series

Which rows come from an event.

by_event(frame, run_id, event_id) -> pandas.DataFrame

The rows of an event.

has_decay_metadata(frame) -> pandas.Series

Which rows carry the decay metadata of a PositroniumSource.

with_decay_metadata(frame) -> pandas.DataFrame

The rows that carry it.

is_source_type(values, *types) -> pandas.Series

Which values name one of the given source types.

select_by_source_type(values, *types) -> pandas.Series

The values that name one of them.

is_decay_type(values, *types), select_by_decay_type(values, *types)

The same for the decay channel.

is_gamma_type(values, *types), select_by_gamma_type(values, *types)

The same for the kind of gamma.

is_process(values, *names) -> pandas.Series

Which values name one of the given processes.

select_by_process(values, *names) -> pandas.Series

The values that name one of them.

opengate_gate_tree.tree.filters.is_in_range(values, low, high, inclusive='both')[source]

Return which values fall in a range.

Parameters:
  • values (pandas.Series) – Column to test.

  • low (float) – Ends of the range.

  • high (float) – Ends of the range.

  • inclusive ({"both", "neither", "left", "right"}) – Which ends belong to the range. The vocabulary is the one of pandas.Series.between(), and so is everything else about the comparison: a reader of pandas needs no second convention, a missing value falls outside the range, and a column that cannot be compared against the ends at all - text against numbers - raises the TypeError pandas raises.

Returns:

Boolean column of the same length and index as values.

Return type:

pandas.Series

opengate_gate_tree.tree.filters.in_range(values, low, high, inclusive='both')[source]

Return the values that fall in a range.

Parameters:
  • values (pandas.Series) – Column to select from.

  • low (float) – Ends of the range.

  • high (float) – Ends of the range.

  • inclusive ({"both", "neither", "left", "right"}) – Which ends belong to the range.

Returns:

The values in the range, with the index they had.

Return type:

pandas.Series

opengate_gate_tree.tree.filters.is_in_box(frame, centre, sides, columns=('posX', 'posY', 'posZ'))[source]

Return which rows lie in a box.

The box is described the way the other shapes are: by where it sits and how big it is. Each side reaches half its length either way from the centre, so a box of sides 100 centred on the origin runs from -50 to 50.

The faces belong to the box: a hit sitting exactly on one is inside. The other convention would drop hits on a boundary, and a simulation puts them there.

Parameters:
  • frame (pandas.DataFrame) – Rows to test.

  • centre (Sequence[float]) – Centre of the box, one value per column.

  • sides (Sequence[float] | float) – Length of each side, one per column, or a single length for a cube.

  • columns (Sequence[str]) – Columns holding the coordinates, in the order the centre gives them.

Returns:

Boolean column of the same length and index as frame.

Return type:

pandas.Series

Raises:
  • ValueError – If the centre does not give one value per column, the sides do not give one length per column, a side is negative, or any of them is not a finite number.

  • KeyError – If the frame holds no column of one of those names.

opengate_gate_tree.tree.filters.in_box(frame, centre, sides, columns=('posX', 'posY', 'posZ'))[source]

Return the rows that lie in a box.

See is_in_box() for the parameters and for which points count as inside.

Parameters:
Return type:

DataFrame

opengate_gate_tree.tree.filters.is_in_sphere(frame, centre, radius, columns=('posX', 'posY', 'posZ'))[source]

Return which rows lie in a sphere.

The surface belongs to the sphere, for the reason the faces belong to a box.

Parameters:
  • frame (pandas.DataFrame) – Rows to test.

  • centre (Sequence[float]) – Centre of the sphere, one value per column.

  • radius (float) – Radius of the sphere.

  • columns (Sequence[str]) – Columns holding the coordinates.

Returns:

Boolean column of the same length and index as frame.

Return type:

pandas.Series

Raises:
  • ValueError – If the centre does not give one value per column, the radius is negative, or either is not a finite number.

  • KeyError – If the frame holds no column of one of those names.

opengate_gate_tree.tree.filters.in_sphere(frame, centre, radius, columns=('posX', 'posY', 'posZ'))[source]

Return the rows that lie in a sphere.

See is_in_sphere() for the parameters.

Parameters:
Return type:

DataFrame

opengate_gate_tree.tree.filters.is_in_cylinder(frame, centre, radius, z_range=None, inner_radius=0.0, columns=('posX', 'posY', 'posZ'))[source]

Return which rows lie in a cylinder, or in a ring.

The cylinder runs along the third of the columns, so another axis is a matter of naming the columns in another order rather than of another parameter: columns=("posX", "posZ", "posY") stands it along y.

An inner radius turns the cylinder into a ring, which is how a layer of a detector is usually asked for. The surfaces belong to the shape.

Parameters:
  • frame (pandas.DataFrame) – Rows to test.

  • centre (Sequence[float]) – Where the axis crosses the plane of the first two columns.

  • radius (float) – Outer radius.

  • z_range (tuple[float, float] | None) – Ends of the cylinder along its axis. Unbounded when omitted.

  • inner_radius (float) – Inner radius, which makes the shape a ring.

  • columns (Sequence[str]) – Columns holding the coordinates, the axis last.

Returns:

Boolean column of the same length and index as frame.

Return type:

pandas.Series

Raises:
  • ValueError – If the centre does not give one value per plane column, the axial window is not two ends, a radius is negative, any of them is not a finite number, or the inner radius is larger than the outer one - which is a ring that could hold nothing, and reads more like two arguments swapped than like a question.

  • KeyError – If the frame holds no column of one of those names.

opengate_gate_tree.tree.filters.in_cylinder(frame, centre, radius, z_range=None, inner_radius=0.0, columns=('posX', 'posY', 'posZ'))[source]

Return the rows that lie in a cylinder, or in a ring.

See is_in_cylinder() for the parameters.

Parameters:
Return type:

DataFrame

opengate_gate_tree.tree.filters.is_from_run(frame, run_id)[source]

Return which rows come from a run.

Parameters:
  • frame (pandas.DataFrame) – Rows to test.

  • run_id (int) – Run to look for, as GATE numbered it.

Returns:

Boolean column of the same length and index as frame.

Return type:

pandas.Series

Raises:

KeyError – If the frame holds no runID column.

opengate_gate_tree.tree.filters.by_run(frame, run_id)[source]

Return the rows of a run.

See is_from_run() for the parameters.

Parameters:
Return type:

DataFrame

opengate_gate_tree.tree.filters.is_from_event(frame, run_id, event_id)[source]

Return which rows come from an event.

An event is named by both identifiers. GATE numbers events within a run, so a file holding more than one run holds an event 5 in each of them, and they are different decays. There is deliberately no filter taking the event identifier alone: writing frame["eventID"] == 5 is one comparison, and it should look like the guess it is.

Parameters:
  • frame (pandas.DataFrame) – Rows to test.

  • run_id (int) – Run and event to look for, as GATE numbered them.

  • event_id (int) – Run and event to look for, as GATE numbered them.

Returns:

Boolean column of the same length and index as frame.

Return type:

pandas.Series

Raises:

KeyError – If the frame holds no runID or no eventID column.

opengate_gate_tree.tree.filters.by_event(frame, run_id, event_id)[source]

Return the rows of an event.

See is_from_event() for the parameters and for why an event needs both identifiers.

Parameters:
Return type:

DataFrame

opengate_gate_tree.tree.filters.has_decay_metadata(frame)[source]

Return which rows carry the decay metadata of a PositroniumSource.

The frame-wide counterpart of has_positronium_metadata(), which answers about a column. Both say the same thing, and it is narrower than “written by a PositroniumSource”: such a source writes the metadata for every gamma it emits, and GATE leaves it out for a gamma of another source or for a particle it never reached.

Parameters:

frame (pandas.DataFrame) – Rows to test.

Returns:

Boolean column of the same length and index as frame.

Return type:

pandas.Series

Raises:
  • ValueError – If decayIndex does not hold whole numbers. The branch numbers the components of a source, and a column of another kind - a frame read back from CSV, or one a concatenation turned into floats - cannot be compared against the value standing for “no metadata” without saying so.

  • KeyError – If the frame holds no decayIndex column.

opengate_gate_tree.tree.filters.with_decay_metadata(frame)[source]

Return the rows that carry the decay metadata of a PositroniumSource.

See has_decay_metadata() for what the answer covers.

Parameters:

frame (DataFrame)

Return type:

DataFrame

opengate_gate_tree.tree.filters.is_source_type(values, *types)[source]

Return which values name one of the given source types.

Parameters:
  • values (pandas.Series) – Column of the sourceType branch.

  • *types (SourceType) – One or more members to look for.

Returns:

Boolean column of the same length and index as values.

Return type:

pandas.Series

Raises:

ValueError – If no member was given, or one of them belongs to another class. The classes share their numbers - a source type of 2 is a positronium and a gamma type of 2 is an annihilation gamma - so a member of the wrong class would select the right rows for the wrong reason, or the wrong rows outright.

opengate_gate_tree.tree.filters.select_by_source_type(values, *types)[source]

Return the values that name one of the given source types.

See is_source_type() for the parameters.

Parameters:
Return type:

Series

opengate_gate_tree.tree.filters.is_decay_type(values, *types)[source]

Return which values name one of the given decay channels.

See is_source_type(); this one reads the decayType branch.

Parameters:
Return type:

Series

opengate_gate_tree.tree.filters.select_by_decay_type(values, *types)[source]

Return the values that name one of the given decay channels.

Parameters:
Return type:

Series

opengate_gate_tree.tree.filters.is_gamma_type(values, *types)[source]

Return which values name one of the given kinds of gamma.

See is_source_type(); this one reads the gammaType branch.

Parameters:
Return type:

Series

opengate_gate_tree.tree.filters.select_by_gamma_type(values, *types)[source]

Return the values that name one of the given kinds of gamma.

Parameters:
Return type:

Series

opengate_gate_tree.tree.filters.is_process(values, *names)[source]

Return which values name one of the given processes.

Parameters:
  • values (pandas.Series) – Column of the processName branch.

  • *names (str) – One or more process names, as GATE writes them.

Returns:

Boolean column of the same length and index as values.

Return type:

pandas.Series

Raises:

ValueError – If no name was given.

opengate_gate_tree.tree.filters.select_by_process(values, *names)[source]

Return the values that name one of the given processes.

See is_process() for the parameters.

Parameters:
Return type:

Series

The gate namespace on a pandas column and on a pandas frame.

Importing the package registers two accessors, so that a filter reads as something the data does rather than as something done to it:

frame["edep"].gate.in_range(0.2, 0.4)
frame.gate.in_cylinder(centre=(0, 0), radius=500.0, inner_radius=409.0)

The split follows what a filter needs to know. A range is a question about one column, so it lives on the column; a shape is a question about three of them at once, and the identity of an event about two, so those live on the frame.

Every method calls the function of the same name in opengate_gate_tree.tree.filters and adds nothing. The accessors are a way of writing, and nothing is reachable only through them.

Registering a name in pandas is a change to something the whole process shares, and it happens when this package is imported. Should gate already be taken, pandas says so with a warning of its own, which is not silenced here: it reports a real collision in somebody’s code.

Public objects:

ACCESSOR_NAME

The name both accessors are registered under.

GateSeriesAccessor

What Series.gate gives.

GateFrameAccessor

What DataFrame.gate gives.

class opengate_gate_tree.tree.accessors.GateSeriesAccessor(values)[source]

Filters of one column of a GATE tree.

Parameters:

values (Series)

is_in_range(low, high, inclusive='both')[source]

Return which values fall in a range.

Parameters:
Return type:

Series

in_range(low, high, inclusive='both')[source]

Return the values that fall in a range.

Parameters:
Return type:

Series

is_source_type(*types)[source]

Return which values name one of the given source types.

Parameters:

types (SourceType)

Return type:

Series

select_by_source_type(*types)[source]

Return the values that name one of the given source types.

Parameters:

types (SourceType)

Return type:

Series

is_decay_type(*types)[source]

Return which values name one of the given decay channels.

Parameters:

types (DecayType)

Return type:

Series

select_by_decay_type(*types)[source]

Return the values that name one of the given decay channels.

Parameters:

types (DecayType)

Return type:

Series

is_gamma_type(*types)[source]

Return which values name one of the given kinds of gamma.

Parameters:

types (GammaType)

Return type:

Series

select_by_gamma_type(*types)[source]

Return the values that name one of the given kinds of gamma.

Parameters:

types (GammaType)

Return type:

Series

is_process(*names)[source]

Return which values name one of the given processes.

Parameters:

names (str)

Return type:

Series

select_by_process(*names)[source]

Return the values that name one of the given processes.

Parameters:

names (str)

Return type:

Series

class opengate_gate_tree.tree.accessors.GateFrameAccessor(frame)[source]

Filters reading several columns of a GATE tree at once.

Parameters:

frame (DataFrame)

is_in_box(centre, sides, columns=('posX', 'posY', 'posZ'))[source]

Return which rows lie in a box.

Parameters:
Return type:

Series

in_box(centre, sides, columns=('posX', 'posY', 'posZ'))[source]

Return the rows that lie in a box.

Parameters:
Return type:

DataFrame

is_in_sphere(centre, radius, columns=('posX', 'posY', 'posZ'))[source]

Return which rows lie in a sphere.

Parameters:
Return type:

Series

in_sphere(centre, radius, columns=('posX', 'posY', 'posZ'))[source]

Return the rows that lie in a sphere.

Parameters:
Return type:

DataFrame

is_in_cylinder(centre, radius, z_range=None, inner_radius=0.0, columns=('posX', 'posY', 'posZ'))[source]

Return which rows lie in a cylinder, or in a ring.

Parameters:
Return type:

Series

in_cylinder(centre, radius, z_range=None, inner_radius=0.0, columns=('posX', 'posY', 'posZ'))[source]

Return the rows that lie in a cylinder, or in a ring.

Parameters:
Return type:

DataFrame

is_from_run(run_id)[source]

Return which rows come from a run.

Parameters:

run_id (int)

Return type:

Series

by_run(run_id)[source]

Return the rows of a run.

Parameters:

run_id (int)

Return type:

DataFrame

is_from_event(run_id, event_id)[source]

Return which rows come from an event.

Parameters:
Return type:

Series

by_event(run_id, event_id)[source]

Return the rows of an event.

Parameters:
Return type:

DataFrame

has_decay_metadata()[source]

Return which rows carry the decay metadata of a PositroniumSource.

Return type:

Series

with_decay_metadata()[source]

Return the rows that carry the decay metadata of a PositroniumSource.

Return type:

DataFrame

Merging

Merging trees a file stores under several names into one dataset.

GATE can split the hits of a simulation across several trees: one per run, or one per sensitive detector. Each of them is a whole tree of the same structure, so a merge is a concatenation of columns, in the order the trees appear in the file.

Nothing else happens to the data. Rows are not sorted, identifiers are not renumbered, and rows sharing a run and an event are not collapsed:

  • sorting would restore no original order, because GATE writes hits in the order of the tracks within an event rather than by time;

  • identifiers are what ties a row back to the simulation that produced it, and to the “Singles” and “Coincidences” trees written next to the hits;

  • a run and an event repeating across trees is one event recorded in two detectors, which is the very thing a merge is performed to see.

The name of the tree each row came from is recorded in an added column. In a file split per sensitive detector, the runs and the events of the two trees are the same, so without it nothing in the data says which detector recorded a deposit.

Public objects:

SOURCE_TREE_BRANCH

Name of the column recording where a row came from.

merge_tree_data(parts, source_names, add_source_branch) -> TreeData

Merge trees of one structure into a single dataset.

opengate_gate_tree.tree.merge.merge_tree_data(parts, source_names=None, add_source_branch=True)[source]

Merge trees of one structure into a single dataset.

Parameters:
  • parts (Sequence[TreeData]) – Trees to merge, in the order their rows should follow.

  • source_names (Sequence[str] | None) – Name of the tree each part came from. Required while the source column is recorded, one name per part.

  • add_source_branch (bool) – Whether to record where each row came from, in a column named sourceTreeName. Turn it off when the result has to match the branches of the structure exactly, and when merging data that already carries the column, such as a merged dataset written out and read back.

Returns:

The parts, one after another.

Return type:

TreeData

Raises:
  • ValueError – If no part was given, or the names do not account for every part.

  • TreeMergeError – If the parts describe different trees, hold different branches, store a branch with a different type or width, or already carry the source column while it is being recorded.

Statistics

Summaries of the data extracted from a tree.

Statistics answer what a file holds before anything is done with it: how many entries, how many events behind them, what range each branch covers, which process names appear and how often.

The per-branch part is not specific to hits and stays that way: the other trees of a GATE file are summarised the same way once they are supported. The part that reads the numbers as physics is separate and is filled in only for hits, and only from the branches that were actually extracted.

Events and tracks are counted by the identifiers that name them together. GATE numbers events within a run and tracks within an event, so an identifier on its own counts far too few of them: every event has a track 1. The package leaves those identifiers as they are, so a summary has to do the composing.

Public objects:

BranchStatistics

Summary of a single branch.

HitsSummary

Summary of what the hits describe, beyond their columns.

TreeStatistics

Summary of an extracted tree.

compute_statistics(data, detection) -> TreeStatistics

Summarise extracted data.

format_statistics(statistics) -> str

Render a summary for reading.

statistics_to_dict(statistics) -> dict

Render a summary for a file.

class opengate_gate_tree.tree.statistics.BranchStatistics(name, dtype, kind, entries, non_finite_count=None, minimum=None, maximum=None, mean=None, std=None, unique_count=None, top_values=())[source]

Summary of a single branch.

Parameters:
name

Branch name.

Type:

str

dtype

Type the branch is held with.

Type:

str

kind

Kind of value the branch holds.

Type:

BranchKind

entries

Number of entries of the branch.

Type:

int

non_finite_count

Number of values that are not a finite number, for a floating point branch. Both “not a number” and the infinities are counted: neither can be written to a report, and an infinity would carry into the mean and turn the spread into “not a number”.

Type:

int | None

minimum, maximum, mean, std

Range and spread of a numeric branch, ignoring values that are not finite. None for a text branch, and for a numeric one holding no usable value.

Type:

float | None

unique_count

Number of distinct values, for a text or whole number branch.

Type:

int | None

top_values

Most frequent values, with their counts, for a branch whose values can be named: a text branch, and a branch of the PositroniumSource whose numbers stand for something. There, every value with a name is reported and the ones without are capped, so unique_count is what says how many the column really held.

Type:

tuple[tuple[str, int], …]

class opengate_gate_tree.tree.statistics.HitsSummary(event_key=(), event_count=None, run_count=None, track_key=(), track_count=None, total_edep=None, time_min=None, time_max=None, source_trees=())[source]

Summary of what the hits describe, beyond their columns.

Parameters:
event_key

Branches the events were counted by. Empty when they could not be counted at all.

Type:

tuple[str, …]

event_count

Number of distinct events.

Type:

int | None

run_count

Number of distinct runs.

Type:

int | None

track_key

Branches the tracks were counted by. Empty when they could not be counted at all.

Type:

tuple[str, …]

track_count

Number of distinct tracks.

Type:

int | None

total_edep

Sum of the deposited energy, over the values that are finite.

Type:

float | None

time_min, time_max

Range of the times.

Type:

float | None

source_trees

Trees the entries came from, for a merged dataset.

Type:

tuple[str, …]

class opengate_gate_tree.tree.statistics.TreeStatistics(tree, entry_count, branches, detection=None, hits_summary=None)[source]

Summary of an extracted tree.

Parameters:
tree

Tree the data was extracted from.

Type:

GateTree

entry_count

Number of entries.

Type:

int

branches

Summary of every branch, in the order of the data.

Type:

tuple[BranchStatistics, …]

detection

Structure the tree was recognised as, when it is known.

Type:

HitsTreeDetection | None

hits_summary

Summary of the hits, for a “Hits” tree.

Type:

HitsSummary | None

opengate_gate_tree.tree.statistics.compute_statistics(data, detection=None)[source]

Summarise extracted data.

Parameters:
  • data (TreeData) – Data to summarise.

  • detection (HitsTreeDetection | None) – Structure the tree was recognised as, reported along with the numbers.

Returns:

Summary of the data.

Return type:

TreeStatistics

opengate_gate_tree.tree.statistics.statistics_to_dict(statistics)[source]

Render a summary as plain values, ready to be written to a file.

Parameters:

statistics (TreeStatistics) – Summary to render.

Returns:

Summary as dictionaries, lists, strings and numbers. Values that are not a number are reported as null, so that the result is valid JSON.

Return type:

dict

opengate_gate_tree.tree.statistics.format_statistics(statistics)[source]

Render a summary for reading.

Parameters:

statistics (TreeStatistics) – Summary to render.

Returns:

Description spanning several lines.

Return type:

str