Skip to content

Commit

Permalink
feat: create SVG python bindings for new detector (acts-project#2884)
Browse files Browse the repository at this point in the history
This PR introduces python bindings for the new detector and gives convenience functions to select volumes, restrict views and color them eventually.

An example is show cased in `detector_creation` which would need `thirdparty/OpenDataDetector` with version `v3.0.3`.

This would produce things like this:

![Screenshot 2024-01-19 at 16 45 40](https://github.com/acts-project/acts/assets/26623879/ccad9896-a7af-4478-bff1-5c7ac3cd43e6)

![Screenshot 2024-01-19 at 16 45 56](https://github.com/acts-project/acts/assets/26623879/86943e84-70c2-4ee0-9ab3-46813cda7cc9)
  • Loading branch information
asalzburger authored and LaraCalic committed Feb 10, 2024
1 parent 5c63bcb commit 993cbf0
Show file tree
Hide file tree
Showing 6 changed files with 274 additions and 22 deletions.
7 changes: 7 additions & 0 deletions Core/include/Acts/Geometry/Extent.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,13 @@ class Extent {
/// @return true if the rhs is contained
bool contains(const Extent& rhs, BinningValue bValue = binValues) const;

/// Contains check for a single point
///
/// @param vtx the point that is check if it is contained
///
/// @return true if the rhs is contained
bool contains(const Vector3& vtx) const;

/// Intersection checks
///
/// @param rhs the extent that is check for intersection
Expand Down
27 changes: 19 additions & 8 deletions Core/src/Geometry/Extent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,17 @@ void Acts::Extent::setEnvelope(const ExtentEnvelope& envelope) {
m_envelope = envelope;
}

bool Acts::Extent::contains(const Vector3& vtx) const {
Extent checkExtent;
for (const auto& bv : s_binningValues) {
if (constrains(bv)) {
ActsScalar vtxVal = VectorHelpers::cast(vtx, bv);
checkExtent.set(bv, vtxVal, vtxVal);
}
}
return contains(checkExtent);
}

bool Acts::Extent::contains(const Extent& rhs, BinningValue bValue) const {
// Helper to check including a constraint bit set check
auto checkContainment = [&](BinningValue bvc) -> bool {
Expand All @@ -115,8 +126,8 @@ bool Acts::Extent::contains(const Extent& rhs, BinningValue bValue) const {

// Check all
if (bValue == binValues) {
for (int ibv = 0; ibv < (int)binValues; ++ibv) {
if (!checkContainment((BinningValue)ibv)) {
for (const auto& bv : s_binningValues) {
if (!checkContainment(bv)) {
return false;
}
}
Expand All @@ -137,8 +148,8 @@ bool Acts::Extent::intersects(const Extent& rhs, BinningValue bValue) const {

// Check all
if (bValue == binValues) {
for (int ibv = 0; ibv < (int)binValues; ++ibv) {
if (checkIntersect((BinningValue)ibv)) {
for (const auto& bv : s_binningValues) {
if (checkIntersect(bv)) {
return true;
}
}
Expand Down Expand Up @@ -174,10 +185,10 @@ bool Acts::Extent::operator==(const Extent& e) const {
std::string Acts::Extent::toString(const std::string& indent) const {
std::stringstream sl;
sl << indent << "Extent in space : " << std::endl;
for (std::size_t ib = 0; ib < static_cast<std::size_t>(binValues); ++ib) {
if (constrains((BinningValue)ib)) {
sl << indent << " - value :" << std::setw(10) << binningValueNames()[ib]
<< " | range = [" << m_range[ib].min() << ", " << m_range[ib].max()
for (const auto& bv : s_binningValues) {
if (constrains(bv)) {
sl << indent << " - value :" << std::setw(10) << binningValueNames()[bv]
<< " | range = [" << m_range[bv].min() << ", " << m_range[bv].max()
<< "]" << std::endl;
}
}
Expand Down
11 changes: 8 additions & 3 deletions Examples/Python/src/Geometry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -183,13 +183,18 @@ void addExperimentalGeometry(Context& ctx) {

using namespace Acts::Experimental;

// Detector definition
py::class_<Detector, std::shared_ptr<Detector>>(m, "Detector");

// Detector volume definition
py::class_<DetectorVolume, std::shared_ptr<DetectorVolume>>(m,
"DetectorVolume");

// Detector definition
py::class_<Detector, std::shared_ptr<Detector>>(m, "Detector")
.def("number_volumes",
[](Detector& self) { return self.volumes().size(); });

// Portal definition
py::class_<Portal, std::shared_ptr<Portal>>(m, "Portal");

{
// The surface hierarchy map
using SurfaceHierarchyMap =
Expand Down
204 changes: 197 additions & 7 deletions Examples/Python/src/Svg.cpp
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
// This file is part of the Acts project.
//
// Copyright (C) 2022 CERN for the benefit of the Acts project
// Copyright (C) 2022-2024 CERN for the benefit of the Acts project
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include "Acts/Detector/Detector.hpp"
#include "Acts/Detector/DetectorVolume.hpp"
#include "Acts/Detector/Portal.hpp"
#include "Acts/Geometry/GeometryContext.hpp"
#include "Acts/Geometry/TrackingGeometry.hpp"
#include "Acts/Plugins/ActSVG/DetectorVolumeSvgConverter.hpp"
#include "Acts/Plugins/ActSVG/IndexedSurfacesSvgConverter.hpp"
#include "Acts/Plugins/ActSVG/LayerSvgConverter.hpp"
#include "Acts/Plugins/ActSVG/PortalSvgConverter.hpp"
#include "Acts/Plugins/ActSVG/SurfaceSvgConverter.hpp"
#include "Acts/Plugins/ActSVG/SvgUtils.hpp"
#include "Acts/Plugins/ActSVG/TrackingGeometrySvgConverter.hpp"
#include "Acts/Plugins/Python/Utilities.hpp"
#include "Acts/Utilities/Enumerate.hpp"
#include "ActsExamples/EventData/GeometryContainers.hpp"
#include "ActsExamples/EventData/SimHit.hpp"
#include "ActsExamples/EventData/SimSpacePoint.hpp"
#include "ActsExamples/Io/Svg/SvgPointWriter.hpp"
#include "ActsExamples/Io/Svg/SvgTrackingGeometryWriter.hpp"

#include <memory>
#include <string>
#include <tuple>
#include <vector>

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
Expand All @@ -30,12 +40,108 @@ using namespace pybind11::literals;
using namespace Acts;
using namespace ActsExamples;

namespace {

using ViewAndRange = std::tuple<std::string, Acts::Extent>;

// Helper function to be picked in different access patterns
actsvg::svg::object viewDetectorVolume(const Svg::ProtoVolume& pVolume,
const std::string& identification,
const ViewAndRange& viewAndRange) {
actsvg::svg::object svgDet;
svgDet._id = identification;
svgDet._tag = "g";

auto [view, viewRange] = viewAndRange;

// The surfaces to be drawn
std::vector<Svg::ProtoVolume::surface_type> surfaces;
// If there is view range restriction, filter the surfaces
if (viewRange.constrains()) {
for (const auto& vs : pVolume._v_surfaces) {
bool inRange = false;
for (const auto& v : vs._vertices) {
if (viewRange.contains(v)) {
inRange = true;
break;
}
}
if (inRange) {
surfaces.push_back(vs);
}
}
} else {
// take all instead
surfaces = pVolume._v_surfaces;
}
// Now draw all the surfaces
for (const auto& vs : surfaces) {
if (view == "xy") {
svgDet.add_object(Svg::View::xy(vs, identification));
} else if (view == "zr") {
svgDet.add_object(Svg::View::zr(vs, identification));
} else {
throw std::invalid_argument("Unknown view type");
}
}
return svgDet;
}

// Helper function to be picked in different access patterns
void viewDetector(
const Acts::GeometryContext& gctx,
const Acts::Experimental::Detector& detector,
const std::string& identification,
const std::vector<std::tuple<int, Svg::DetectorVolumeConverter::Options>>&
volumeIdxOpts,
const std::vector<ViewAndRange>& viewAndRanges, const std::string& saveAs) {
// The svg object to be returned

std::vector<actsvg::svg::object> svgDetViews;
svgDetViews.reserve(viewAndRanges.size());
for (unsigned int i = 0; i < viewAndRanges.size(); ++i) {
actsvg::svg::object svgDet;
svgDet._id = identification;
svgDet._tag = "g";
svgDetViews.push_back(svgDet);
}

for (const auto& [vidx, vopts] : volumeIdxOpts) {
// Get the volume and convert it
const auto& v = detector.volumes()[vidx];
auto [pVolume, pGrid] =
Svg::DetectorVolumeConverter::convert(gctx, *v, vopts);

for (auto [iv, var] : Acts::enumerate(viewAndRanges)) {
auto [view, range] = var;
// Get the view and the range
auto svgVolView = viewDetectorVolume(
pVolume, identification + "_vol" + std::to_string(vidx) + "_" + view,
var);
svgDetViews[iv].add_object(svgVolView);
}
}

for (auto [iv, var] : Acts::enumerate(viewAndRanges)) {
auto [view, range] = var;
Svg::toFile({svgDetViews[iv]}, saveAs + "_" + view + ".svg");
}
}

} // namespace

namespace Acts::Python {
void addSvg(Context& ctx) {
auto [m, mex] = ctx.get("main", "examples");

auto svg = m.def_submodule("svg");

// Some basics
py::class_<actsvg::svg::object>(svg, "object");

// Core components, added as an acts.svg submodule
{
auto c = py::class_<Svg::Style>(m, "SvgStyle").def(py::init<>());
auto c = py::class_<Svg::Style>(svg, "Style").def(py::init<>());
ACTS_PYTHON_STRUCT_BEGIN(c, Svg::Style);
ACTS_PYTHON_MEMBER(fillColor);
ACTS_PYTHON_MEMBER(fillOpacity);
Expand All @@ -47,23 +153,105 @@ void addSvg(Context& ctx) {
ACTS_PYTHON_STRUCT_END();
}

// How surfaces should be drawn: Svg Surface options & drawning
{
auto c = py::class_<Svg::SurfaceConverter::Options>(m, "SvgSurfaceOptions")
auto c = py::class_<Svg::SurfaceConverter::Options>(svg, "SurfaceOptions")
.def(py::init<>());
ACTS_PYTHON_STRUCT_BEGIN(c, Svg::SurfaceConverter::Options);
ACTS_PYTHON_MEMBER(style);
ACTS_PYTHON_MEMBER(templateSurface);
ACTS_PYTHON_STRUCT_END();

// Define the proto surface
py::class_<Svg::ProtoSurface>(svg, "ProtoSurface");
// Convert an Acts::Surface object into an acts::svg::proto::surface
svg.def("convertSurface", &Svg::SurfaceConverter::convert);

// Define the view functions
svg.def("viewSurface", [](const Svg::ProtoSurface& pSurface,
const std::string& identification,
const std::string& view = "xy") {
if (view == "xy") {
return Svg::View::xy(pSurface, identification);
} else if (view == "zr") {
return Svg::View::zr(pSurface, identification);
} else if (view == "zphi") {
return Svg::View::zphi(pSurface, identification);
} else if (view == "zrphi") {
return Svg::View::zrphi(pSurface, identification);
} else {
throw std::invalid_argument("Unknown view type");
}
});
}

// How portals should be drawn: Svg Portal options & drawning
{
auto c = py::class_<Svg::PortalConverter::Options>(svg, "PortalOptions")
.def(py::init<>());

ACTS_PYTHON_STRUCT_BEGIN(c, Svg::PortalConverter::Options);
ACTS_PYTHON_MEMBER(surfaceOptions);
ACTS_PYTHON_MEMBER(linkLength);
ACTS_PYTHON_MEMBER(volumeIndices);
ACTS_PYTHON_STRUCT_END();

// Define the proto portal
py::class_<Svg::ProtoPortal>(svg, "ProtoPortal");
// Convert an Acts::Experimental::Portal object into an
// acts::svg::proto::portal
svg.def("convertPortal", &Svg::PortalConverter::convert);

// Define the view functions
svg.def("viewPortal", [](const Svg::ProtoPortal& pPortal,
const std::string& identification,
const std::string& view = "xy") {
if (view == "xy") {
return Svg::View::xy(pPortal, identification);
} else if (view == "zr") {
return Svg::View::zr(pPortal, identification);
} else {
throw std::invalid_argument("Unknown view type");
}
});
}

// How detector volumes are drawn: Svg DetectorVolume options & drawning
{
auto c = py::class_<Svg::DetectorVolumeConverter::Options>(
svg, "DetectorVolumeOptions")
.def(py::init<>());

ACTS_PYTHON_STRUCT_BEGIN(c, Svg::DetectorVolumeConverter::Options);
ACTS_PYTHON_MEMBER(portalIndices);
ACTS_PYTHON_MEMBER(portalOptions);
ACTS_PYTHON_MEMBER(surfaceOptions);
ACTS_PYTHON_STRUCT_END();

// Define the proto volume & indexed surface grid
py::class_<Svg::ProtoVolume>(svg, "ProtoVolume");
py::class_<Svg::ProtoIndexedSurfaceGrid>(svg, "ProtoIndexedSurfaceGrid");

// Convert an Acts::Experimental::DetectorVolume object into an
// acts::svg::proto::volume
svg.def("convertDetectorVolume", &Svg::DetectorVolumeConverter::convert);

// Define the view functions
svg.def("viewDetectorVolume", &viewDetectorVolume);
}

// How a detector is drawn: Svg Detector options & drawning
{ svg.def("viewDetector", &viewDetector); }

// Legacy geometry drawing
{
using DefinedStyle = std::pair<GeometryIdentifier, Svg::Style>;
using DefinedStyleSet = std::vector<DefinedStyle>;

auto sm = py::class_<GeometryHierarchyMap<Svg::Style>>(m, "SvgStyleMap")
auto sm = py::class_<GeometryHierarchyMap<Svg::Style>>(svg, "StyleMap")
.def(py::init<DefinedStyleSet>(), py::arg("elements"));

auto c = py::class_<Svg::LayerConverter::Options>(m, "SvgLayerOptions")
auto c = py::class_<Svg::LayerConverter::Options>(svg, "LayerOptions")
.def(py::init<>());
ACTS_PYTHON_STRUCT_BEGIN(c, Svg::LayerConverter::Options);
ACTS_PYTHON_MEMBER(name);
Expand All @@ -85,18 +273,20 @@ void addSvg(Context& ctx) {

auto lom =
py::class_<GeometryHierarchyMap<Svg::LayerConverter::Options>>(
m, "SvgLayerOptionMap")
svg, "LayerOptionMap")
.def(py::init<DefinedLayerOptionsSet>(), py::arg("elements"));

auto c = py::class_<Svg::TrackingGeometryConverter::Options>(
m, "SvgTrackingGeometryOptions")
svg, "TrackingGeometryOptions")
.def(py::init<>());
ACTS_PYTHON_STRUCT_BEGIN(c, Svg::TrackingGeometryConverter::Options);
ACTS_PYTHON_MEMBER(prefix);
ACTS_PYTHON_MEMBER(layerOptions);
ACTS_PYTHON_STRUCT_END();
}

// Components from the ActsExamples - part of acts.examples

{
using Writer = ActsExamples::SvgTrackingGeometryWriter;
auto w = py::class_<Writer, std::shared_ptr<Writer>>(
Expand Down
Loading

0 comments on commit 993cbf0

Please sign in to comment.