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.
- 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:
- 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 asvolumeID
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.- 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]
- __getitem__(name)[source]¶
Return the column of a single branch.
- Parameters:
name (str) – Branch name.
- Returns:
Column of the requested branch.
- Return type:
- 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:
- 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:
- 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:
tree (GateTree) – Tree the data belongs to.
frame (pandas.DataFrame) – Data frame to convert.
- Returns:
Instance holding one branch per data frame column.
- Return type:
- 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:
- Returns:
Branch names to read, in the order they should appear.
- Return type:
- 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
Nonewhen 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_ALIASESunder 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:
- 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
gantryIDmatches 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
Nonewhen 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 (str)
kind (BranchKind)
dtype (str)
- kind¶
Kind of value the branch holds.
- Type:
- 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:
- 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:
- 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:
Truewhen the schema of the variant needs a naming scheme to be completed.- Return type:
- 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
GateToTreeoutput writes one per level of the system.- Parameters:
variant (HitsTreeVariant) – Variant to check.
- Returns:
Truewhen the length of the identifier block is not fixed.- Return type:
- 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
GateToTreeoutput 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
Nonewhen 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 (HitsTreeVariant)
system (GateSystemType | None)
system_depth (int | None)
tree_name (str | None)
branch_count (int)
- variant¶
Structure the tree has.
- Type:
- system¶
Naming scheme of the system identifier branches, or
Nonewhen the structure carries none.- Type:
GateSystemType | None
- opengate_gate_tree.tree.hits.detection.detect_hits_variant(branch_names, tree_name=None)[source]¶
Recognise the structure of a “Hits” tree.
- Parameters:
- Returns:
Structure of the tree.
- Return type:
- 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
Nonewhen 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:
- 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
Nonewhen 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:
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:
- Raises:
UnknownHitsVariantError – If the structure of the tree is not a supported one.
HitsTreeValidationError – If a branch the structure describes is missing or stored with another type.
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
Nonewhen the package describes none.decayIndexhas 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
decayIndexbranch.- Returns:
Boolean mask,
Truewhere the row carries a component number rather than the value standing for no metadata.- Return type:
- 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:
- 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
Nonerather 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
Nonewherever the value is not one of them.- Return type:
- 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 theTypeErrorpandas raises.
- Returns:
Boolean column of the same length and index as
values.- Return type:
- 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:
- 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:
- 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.
- 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:
- 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.
- 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:
- 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.
- 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:
- Raises:
KeyError – If the frame holds no
runIDcolumn.
- opengate_gate_tree.tree.filters.by_run(frame, run_id)[source]¶
Return the rows of a run.
See
is_from_run()for the parameters.
- 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"] == 5is 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:
- Raises:
KeyError – If the frame holds no
runIDor noeventIDcolumn.
- 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.
- 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:
- Raises:
ValueError – If
decayIndexdoes 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
decayIndexcolumn.
- 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.
- 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
sourceTypebranch.*types (SourceType) – One or more members to look for.
- Returns:
Boolean column of the same length and index as
values.- Return type:
- 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:
values (Series)
types (SourceType)
- Return type:
- 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 thedecayTypebranch.
- opengate_gate_tree.tree.filters.select_by_decay_type(values, *types)[source]¶
Return the values that name one of the given decay channels.
- 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 thegammaTypebranch.
- opengate_gate_tree.tree.filters.select_by_gamma_type(values, *types)[source]¶
Return the values that name one of the given kinds of gamma.
- 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
processNamebranch.*names (str) – One or more process names, as GATE writes them.
- Returns:
Boolean column of the same length and index as
values.- Return type:
- 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.
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.gategives.- GateFrameAccessor
What
DataFrame.gategives.
- class opengate_gate_tree.tree.accessors.GateSeriesAccessor(values)[source]¶
Filters of one column of a GATE tree.
- Parameters:
values (Series)
- is_source_type(*types)[source]¶
Return which values name one of the given source types.
- Parameters:
types (SourceType)
- Return type:
- select_by_source_type(*types)[source]¶
Return the values that name one of the given source types.
- Parameters:
types (SourceType)
- Return type:
- class opengate_gate_tree.tree.accessors.GateFrameAccessor(frame)[source]¶
Filters reading several columns of a GATE tree at once.
- Parameters:
frame (DataFrame)
- is_in_sphere(centre, radius, columns=('posX', 'posY', 'posZ'))[source]¶
Return which rows lie in a sphere.
- in_sphere(centre, radius, columns=('posX', 'posY', 'posZ'))[source]¶
Return the rows that lie in a sphere.
- 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.
- 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.
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:
- 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:
- kind¶
Kind of value the branch holds.
- Type:
- 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.
Nonefor a text branch, and for a numeric one holding no usable value.- Type:
float | 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_countis what says how many the column really held.
- 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.
- track_key¶
Branches the tracks were counted by. Empty when they could not be counted at all.
- time_min, time_max
Range of the times.
- Type:
float | None
- class opengate_gate_tree.tree.statistics.TreeStatistics(tree, entry_count, branches, detection=None, hits_summary=None)[source]¶
Summary of an extracted tree.
- Parameters:
tree (GateTree)
entry_count (int)
branches (tuple[BranchStatistics, ...])
detection (HitsTreeDetection | None)
hits_summary (HitsSummary | None)
- branches¶
Summary of every branch, in the order of the data.
- Type:
- 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:
- 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:
- 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: