-
Notifications
You must be signed in to change notification settings - Fork 250
feat: python based tracking geometry display #5303
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| """Pure-Python helpers shipped with Acts (geometry views, utilities).""" | ||
|
|
||
| from .geometry_identifier import GeometryIdentifierMasks | ||
| from .projection2d import ( | ||
| auto_axis_limits, | ||
| project_polyhedron_vertices, | ||
| surface_to_2d_patches, | ||
| ) | ||
| from .tracking_surfaces import ( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. More a style question, do we want to import all these functions into the |
||
| collect_material_surfaces, | ||
| collect_sensitive_surfaces, | ||
| filter_surfaces_by_layer_ids, | ||
| filter_surfaces_by_volume_ids, | ||
| group_surfaces_by_volume_layer, | ||
| parse_layer_selection_spec, | ||
| parse_subsystem_region_spec, | ||
| sort_surfaces_by_geometry_id, | ||
| volume_ids_for_selection, | ||
| ) | ||
|
|
||
| __all__ = [ | ||
| "GeometryIdentifierMasks", | ||
| "auto_axis_limits", | ||
| "collect_material_surfaces", | ||
| "collect_sensitive_surfaces", | ||
| "filter_surfaces_by_layer_ids", | ||
| "filter_surfaces_by_volume_ids", | ||
| "group_surfaces_by_volume_layer", | ||
| "parse_layer_selection_spec", | ||
| "parse_subsystem_region_spec", | ||
| "project_polyhedron_vertices", | ||
| "sort_surfaces_by_geometry_id", | ||
| "surface_to_2d_patches", | ||
| "volume_ids_for_selection", | ||
| ] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm not sure, don't we have this in C++, and can bind the geometry identifier? Do we need a reimplementation in Python? |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| """GeometryIdentifier bit layout (see ``Acts::GeometryIdentifier`` in C++).""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from dataclasses import dataclass | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class GeometryIdentifierMasks: | ||
| """Masks and shifts match ``Core/include/Acts/Geometry/GeometryIdentifier.hpp``.""" | ||
|
|
||
| volume_mask: int = 0xFF00000000000000 | ||
| boundary_mask: int = 0x00FF000000000000 | ||
| layer_mask: int = 0x0000FFF000000000 | ||
| approach_mask: int = 0x0000000FF0000000 | ||
| sensitive_mask: int = 0x000000000FFFFF00 | ||
| extra_mask: int = 0x00000000000000FF | ||
|
|
||
| @property | ||
| def volume_shift(self) -> int: | ||
| return (self.volume_mask & -self.volume_mask).bit_length() - 1 | ||
|
|
||
| @property | ||
| def boundary_shift(self) -> int: | ||
| return (self.boundary_mask & -self.boundary_mask).bit_length() - 1 | ||
|
|
||
| @property | ||
| def layer_shift(self) -> int: | ||
| return (self.layer_mask & -self.layer_mask).bit_length() - 1 | ||
|
|
||
| @property | ||
| def approach_shift(self) -> int: | ||
| return (self.approach_mask & -self.approach_mask).bit_length() - 1 | ||
|
|
||
| @property | ||
| def sensitive_shift(self) -> int: | ||
| return (self.sensitive_mask & -self.sensitive_mask).bit_length() - 1 | ||
|
|
||
| @property | ||
| def extra_shift(self) -> int: | ||
| return (self.extra_mask & -self.extra_mask).bit_length() - 1 | ||
|
|
||
|
|
||
| DEFAULT_MASKS = GeometryIdentifierMasks() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| """2D projections of surface polyhedra (xy, zr, zphi).""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import math | ||
| from typing import Iterable, List, Literal, Sequence, Tuple | ||
|
|
||
| import numpy as np | ||
|
|
||
| ViewName = Literal["xy", "zr", "zphi"] | ||
|
|
||
|
|
||
| def project_point( | ||
| view: ViewName, | ||
| x: float, | ||
| y: float, | ||
| z: float, | ||
| ) -> Tuple[float, float]: | ||
| if view == "xy": | ||
| return x, y | ||
| if view == "zr": | ||
| r = math.hypot(x, y) | ||
| return z, r | ||
| if view == "zphi": | ||
| phi = math.atan2(y, x) | ||
| return z, phi | ||
| raise ValueError(view) | ||
|
|
||
|
|
||
| def project_polyhedron_vertices( | ||
| view: ViewName, | ||
| vertices, | ||
| ) -> np.ndarray: | ||
| """Project ACTS ``Polyhedron.vertices`` (Vector3 sequence) to Nx2 float array.""" | ||
|
|
||
| pts = np.zeros((len(vertices), 2)) | ||
| for i, v in enumerate(vertices): | ||
| pts[i] = project_point(view, float(v[0]), float(v[1]), float(v[2])) | ||
| return pts | ||
|
|
||
|
|
||
| def _dedupe_loop(idxs: Sequence[int]) -> List[int]: | ||
| if not idxs: | ||
| return [] | ||
| out = list(idxs) | ||
| if out[0] == out[-1]: | ||
| out = out[:-1] | ||
| return out | ||
|
|
||
|
|
||
| def surface_to_2d_patches( | ||
| view: ViewName, | ||
| polyhedron, | ||
| *, | ||
| prefer_triangles: bool = False, | ||
| ) -> List[np.ndarray]: | ||
| """Return closed 2D polygons (each Kx2) for matplotlib. | ||
|
|
||
| By default uses the native ``Polyhedron.faces`` (polygon mesh). With | ||
| ``prefer_triangles=True``, uses ``triangularMesh`` when non-empty. | ||
| """ | ||
|
|
||
| if prefer_triangles and polyhedron.triangularMesh: | ||
| faces_source = polyhedron.triangularMesh | ||
| else: | ||
| faces_source = polyhedron.faces | ||
| patches: List[np.ndarray] = [] | ||
| verts3 = polyhedron.vertices | ||
|
|
||
| for face in faces_source: | ||
| idxs = _dedupe_loop(face) | ||
| ring = np.zeros((len(idxs), 2)) | ||
| for j, k in enumerate(idxs): | ||
| v = verts3[k] | ||
| ring[j] = project_point(view, float(v[0]), float(v[1]), float(v[2])) | ||
| if len(ring) >= 3: | ||
| patches.append(ring) | ||
| return patches | ||
|
|
||
|
|
||
| def auto_axis_limits( | ||
| polygons: Iterable[np.ndarray], | ||
| *, | ||
| margin_ratio: float = 0.05, | ||
| ) -> Tuple[float, float, float, float]: | ||
| """Return xmin, ymin, xmax, ymax over all polygon vertices.""" | ||
|
|
||
| xs: List[float] = [] | ||
| ys: List[float] = [] | ||
| for poly in polygons: | ||
| if poly.size == 0: | ||
| continue | ||
| xs.extend(poly[:, 0].tolist()) | ||
| ys.extend(poly[:, 1].tolist()) | ||
| if not xs: | ||
| return -1.0, -1.0, 1.0, 1.0 | ||
| xmin, xmax = min(xs), max(xs) | ||
| ymin, ymax = min(ys), max(ys) | ||
| dx = xmax - xmin if xmax > xmin else 1.0 | ||
| dy = ymax - ymin if ymax > ymin else 1.0 | ||
| mx = margin_ratio * dx | ||
| my = margin_ratio * dy | ||
| return xmin - mx, ymin - my, xmax + mx, ymax + my |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm maybe this is too early, but I would find it nice to split geometry-specific and generic parts.
One could e.g. have a function/class in a non-executable script takes the subsystem-volume-id dicts and then separate the ODD / generic detector specific scripts in one or two dedicated scripts
E.g.,
Python/Utilities/display_tracking_geometry.pyExamples/Scripts/Python/display_odd.pyExamples/Scripts/Python/display_generic.py