From 556433700a259215c62c63212532eaa5747f6a23 Mon Sep 17 00:00:00 2001
From: yoonsuk huh <44041125+ynskHuh@users.noreply.github.com>
Date: Mon, 27 Jul 2026 15:41:19 +0900
Subject: [PATCH 01/18] Add MRCP tetrahedral phantom support
---
TETRAHEDRAL_MESH_RENAMING_20260727.md | 85 +++
compile_10_1.md | 73 ++
.../g4_bindings/pyG4Material.cpp | 12 +
core/opengate_core/g4_bindings/pyG4Tet.cpp | 77 ++
core/opengate_core/opengate_core.cpp | 26 +
.../GateTetrahedralMeshParameterisation.cpp | 471 +++++++++++++
.../GateTetrahedralMeshParameterisation.h | 88 +++
.../filters/GateCopyNumberFilter.cpp | 60 ++
.../filters/GateCopyNumberFilter.h | 38 +
.../filters/pyGateCopyNumberFilter.cpp | 20 +
.../opengate_lib/pyGateTetrahedralMesh.cpp | 87 +++
modified_10_1.md | 338 +++++++++
mrcp_10_1_all_modified_files_20260519.md | 664 ++++++++++++++++++
mrcp_10_1_compile_build_20260519.md | 273 +++++++
mrcp_10_1_full_summary_20260519.md | 335 +++++++++
mrcp_10_1_modified_files_20260519.md | 360 ++++++++++
mrcp_10_1_port.md | 79 +++
mrcp_10_1_run_guide_20260519.md | 312 ++++++++
mrcp_10_1_v15_refactor_20260519.md | 346 +++++++++
opengate/actors/filters.py | 42 +-
opengate/contrib/phantoms/mrcp.py | 317 +++++++++
opengate/contrib/phantoms/mrcp_utils.py | 654 +++++++++++++++++
opengate/geometry/solids.py | 98 ++-
opengate/geometry/volumes.py | 132 ++++
opengate/managers.py | 2 +
25 files changed, 4975 insertions(+), 14 deletions(-)
create mode 100644 TETRAHEDRAL_MESH_RENAMING_20260727.md
create mode 100644 compile_10_1.md
create mode 100644 core/opengate_core/g4_bindings/pyG4Tet.cpp
create mode 100644 core/opengate_core/opengate_lib/GateTetrahedralMeshParameterisation.cpp
create mode 100644 core/opengate_core/opengate_lib/GateTetrahedralMeshParameterisation.h
create mode 100644 core/opengate_core/opengate_lib/filters/GateCopyNumberFilter.cpp
create mode 100644 core/opengate_core/opengate_lib/filters/GateCopyNumberFilter.h
create mode 100644 core/opengate_core/opengate_lib/filters/pyGateCopyNumberFilter.cpp
create mode 100644 core/opengate_core/opengate_lib/pyGateTetrahedralMesh.cpp
create mode 100644 modified_10_1.md
create mode 100644 mrcp_10_1_all_modified_files_20260519.md
create mode 100644 mrcp_10_1_compile_build_20260519.md
create mode 100644 mrcp_10_1_full_summary_20260519.md
create mode 100644 mrcp_10_1_modified_files_20260519.md
create mode 100644 mrcp_10_1_port.md
create mode 100644 mrcp_10_1_run_guide_20260519.md
create mode 100644 mrcp_10_1_v15_refactor_20260519.md
create mode 100644 opengate/contrib/phantoms/mrcp.py
create mode 100644 opengate/contrib/phantoms/mrcp_utils.py
diff --git a/TETRAHEDRAL_MESH_RENAMING_20260727.md b/TETRAHEDRAL_MESH_RENAMING_20260727.md
new file mode 100644
index 0000000000..46d5de7c40
--- /dev/null
+++ b/TETRAHEDRAL_MESH_RENAMING_20260727.md
@@ -0,0 +1,85 @@
+# Tetrahedral mesh 명명 변경 보고서
+
+- 작성일: 2026-07-27
+- 작업 브랜치: `codex/mrcp-tet-10.1-port`
+- 작업 저장소: `/Users/yhuh/Study/installation/gate/Gate-10.1/opengate`
+- 목적: 메쉬 전체를 뜻하던 `G4Tet` 계열 이름을 `TetrahedralMesh`로 명확히 하고, `TetGen`은 입력 파일 형식을 나타내는 이름에만 사용한다.
+
+## 명명 원칙
+
+1. 사용자에게 보이는 OpenGATE volume type은 `TetrahedralMesh`로 한다.
+2. Python 구현 클래스는 OpenGATE 관례에 맞춰 `TetrahedralMeshVolume`로 한다.
+3. TetGen의 `.node`/`.ele` 파일을 읽는 C++ 함수에만 `from_tetgen`을 표시한다.
+4. Geant4의 단일 사면체 solid 클래스인 `G4Tet`과 그 바인딩 `pyG4Tet.cpp`는 변경하지 않는다.
+5. 이번 기능은 아직 upstream에 공개되지 않았으므로 혼동되는 기존 공개 별칭 `G4Tet`/`G4TetVolume`은 유지하지 않는다.
+
+## 변경 내용
+
+| 기존 함수·이름 | 변경 함수·이름 | 위치 | 주요 내용 |
+|---|---|---|---|
+| 단일 파일 `g4_bindings/GateTetMeshG4Tet.cpp` | `GateTetrahedralMeshParameterisation.h`, `GateTetrahedralMeshParameterisation.cpp`, `pyGateTetrahedralMesh.cpp` | `core/opengate_core/opengate_lib/` | OpenGATE 자체 기능의 관례에 맞춰 클래스 선언, mesh 구현, Python 바인딩을 세 파일로 분리했다. |
+| `GateTetNestedParam` | `GateTetrahedralMeshParameterisation` | `opengate_lib/GateTetrahedralMeshParameterisation.h`: 27행 부근
`opengate_lib/GateTetrahedralMeshParameterisation.cpp`: 185행 부근 | 각 tetrahedron의 solid, material, region 및 시각 속성을 copy number별로 제공하는 parameterisation 클래스다. |
+| `g_param_store` | `g_tetrahedral_mesh_parameterisations` | `opengate_lib/GateTetrahedralMeshParameterisation.cpp`: 276행 부근 | 실행 중 parameterisation 객체가 소멸하지 않도록 보관하는 전역 저장소다. |
+| `build_tet_mesh_g4tet_internal` | `build_tetrahedral_mesh_impl` | `opengate_lib/GateTetrahedralMeshParameterisation.cpp`: 281, 350행 부근 | TetGen 파싱, `G4Tet` 생성, material 매핑과 `G4PVParameterised` 생성을 담당하는 내부 구현이다. |
+| `build_tet_mesh_g4tet` | `build_tetrahedral_mesh_from_tetgen` | 선언: `GateTetrahedralMeshParameterisation.h`: 68행 부근
구현: `.cpp`: 309행 부근
등록: `pyGateTetrahedralMesh.cpp`: 48행 부근 | material 포인터와 표시 속성을 받아 TetGen mesh를 생성하는 주 builder다. |
+| `build_tet_mesh_g4tet_compat` | `build_tetrahedral_mesh_from_tetgen_material_names` | 선언: `GateTetrahedralMeshParameterisation.h`: 80행 부근
구현: `.cpp`: 327행 부근
등록: `pyGateTetrahedralMesh.cpp`: 77행 부근 | material 이름 사전을 material 포인터 사전으로 변환한 뒤 같은 내부 builder를 호출한다. |
+| `init_GateTetMeshG4Tet` | `init_GateTetrahedralMesh` | `opengate_lib/pyGateTetrahedralMesh.cpp`: 46행 부근
`core/opengate_core/opengate_core.cpp`: 312, 595행 부근 | Python 등록 전용 파일에서 tetrahedral mesh builder를 `opengate_core` module에 등록한다. |
+| `G4TetVolume` | `TetrahedralMeshVolume` | `opengate/geometry/volumes.py`: 1589, 1872행 부근 | OpenGATE에서 tetrahedral mesh 전체를 나타내는 volume 클래스다. `process_cls` 등록과 C++ builder 호출도 함께 변경했다. |
+| `G4TetSolid` | `TetrahedralMeshEnvelopeSolid` | `opengate/geometry/solids.py`: 813, 859행 부근 | mesh를 담는 envelope box를 준비하는 보조 solid다. 실제 반환물이 단일 `G4Tet`이 아니라 envelope임을 이름에 표시했다. |
+| import 및 등록 키 `G4TetVolume`, `G4Tet` | import 및 등록 키 `TetrahedralMeshVolume` | `opengate/managers.py`: 76, 1115행 부근 | 문자열 volume type을 Python 클래스로 연결한다. OpenGATE의 `type + "Volume"` 규칙에 따라 `add_volume("TetrahedralMesh", ...)`가 동작한다. |
+| `simulation.add_volume("G4Tet", name)` | `simulation.add_volume("TetrahedralMesh", name)` | `opengate/contrib/phantoms/mrcp.py`: 173행 부근 | MRCP_AF 또는 MRCP_AM phantom을 새 공개 volume type으로 생성한다. |
+| VS Code 검사 `build_tet_mesh_g4tet` | VS Code 검사 `build_tetrahedral_mesh_from_tetgen` | `/Users/yhuh/Documents/Opengate10.1/OpenGATE-MRCP.code-workspace`: 76행 부근 | VS Code binding 확인 task가 새 공개 C++ builder의 등록 여부를 검사하도록 변경했다. |
+
+## 사용자 API
+
+변경 후 기본 사용법:
+
+```python
+phantom = simulation.add_volume("TetrahedralMesh", "mrcp")
+```
+
+MRCP helper 사용법:
+
+```python
+phantom = add_mrcp_phantom(
+ simulation,
+ phantom_type="MRCP_AM",
+ node_file="custom.node",
+ ele_file="custom.ele",
+ material_file="custom.material",
+)
+```
+
+`phantom_type="MRCP_AM"`을 선택해도 사용자가 파일명을 직접 지정하면 `custom.*` 값이 그대로 사용된다. 기본값인 `MRCP_AF.node`, `MRCP_AF.ele`, `MRCP_AF.material`일 때만 AM 기본 파일명으로 자동 치환된다.
+
+## 유지한 이름
+
+| 파일/심볼 | 유지 이유 |
+|---|---|
+| `core/opengate_core/g4_bindings/pyG4Tet.cpp` | Geant4의 실제 단일 tetrahedron solid인 `G4Tet` 바인딩이므로 이름이 정확함 |
+| `opengate_core.G4Tet` | mesh type이 아니라 한 개의 Geant4 solid를 뜻하므로 유지 |
+| `TetGen` 및 `from_tetgen` | `.node`/`.ele` 입력 형식 또는 입력 backend를 정확히 설명하는 위치에만 유지 |
+
+## 검증 결과
+
+| 검증 항목 | 결과 |
+|---|---|
+| Python 파일 `py_compile` | 통과 |
+| VS Code workspace JSON 구문 | 통과 |
+| CMake 재구성 | 통과 |
+| C++ Release 빌드 | 통과 (`Built target opengate_core`) |
+| `opengate_core.G4Tet` 유지 | `True` |
+| `build_tetrahedral_mesh_from_tetgen` 등록 | `True` |
+| `build_tetrahedral_mesh_from_tetgen_material_names` 등록 | `True` |
+| 기존 `build_tet_mesh_g4tet` 제거 | 확인 |
+| `simulation.add_volume("TetrahedralMesh", "mrcp")` | `TetrahedralMeshVolume` 생성 확인 |
+| MRCP v15 `--mode build` | 통과 |
+| MRCP_AF mesh 로딩 | 총 tetrahedra `8,582,677`, scoring 대상 `48,012` |
+| Geant4 engine 초기화 | 3파일 분리 후 통과, 약 `17.99 s` |
+
+## 참고 사항
+
+- 최초 컴파일에서 원본 미추적 C++ 파일 상단에 있던 단독 백틱 문자(U+0060) 문법 오류를 발견해 제거했다.
+- 3파일 분리 후 첫 컴파일에서 Geant4 11.4의 `G4VTouchable` type alias와 불필요한 전방 선언의 충돌을 확인해 해당 선언을 제거했다.
+- CMake 경고는 기존 minimum-version deprecation 경고이며 이번 이름 변경에 따른 오류가 아니다.
+- 커밋, push, GitHub pull request는 수행하지 않았다.
diff --git a/compile_10_1.md b/compile_10_1.md
new file mode 100644
index 0000000000..0be489b24b
--- /dev/null
+++ b/compile_10_1.md
@@ -0,0 +1,73 @@
+# Compile Notes For MRCP Port On OpenGATE 10.1.0
+
+Date: 2026-05-19
+
+Work directory:
+
+```bash
+cd /Users/yhuh/Study/installation/gate/Gate-10.1/opengate/core/build
+```
+
+Successful configure command, adapted from the Gate-10.0.3 setup:
+
+```bash
+/Applications/CMake-4.2.3.app/Contents/bin/cmake \
+ -DGeant4_DIR="/Users/yhuh/Study/installation/geant/geant4114/build" \
+ -DOPENGATE_USE_ITK=ON \
+ -DITK_DIR="/Users/yhuh/Study/installation/gate/Forgate10_tet/opengate/itk/ITK/build" \
+ -DPython_EXECUTABLE="/opt/anaconda3/envs/tetGatev2/bin/python" \
+ -DPython3_EXECUTABLE="/opt/anaconda3/envs/tetGatev2/bin/python" \
+ -DPython_ROOT_DIR="/opt/anaconda3/envs/tetGatev2" \
+ -DPython3_ROOT_DIR="/opt/anaconda3/envs/tetGatev2" \
+ -DPython_INCLUDE_DIR="/opt/anaconda3/envs/tetGatev2/include/python3.12" \
+ -DPython_LIBRARY="/opt/anaconda3/envs/tetGatev2/lib/libpython3.12.dylib" \
+ -DCMAKE_PREFIX_PATH="/opt/anaconda3/envs/tetGatev2;/opt/homebrew/opt/fftw;/opt/homebrew" \
+ -DOPENGATE_USE_VISU=ON \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DCMAKE_SHARED_LINKER_FLAGS="-L/opt/homebrew/opt/fftw/lib" \
+ ..
+```
+
+Recommended build command:
+
+```bash
+env LIBRARY_PATH=/opt/homebrew/opt/fftw/lib:/opt/homebrew/lib \
+ DYLD_LIBRARY_PATH=/opt/homebrew/opt/fftw/lib:/opt/homebrew/lib \
+ /Applications/CMake-4.2.3.app/Contents/bin/cmake --build . --config Release -j4
+```
+
+After build, check the new filter binding:
+
+```bash
+python -c "import opengate_core as g4; print(hasattr(g4, 'GateCopyNumberFilter'))"
+```
+
+Expected output:
+
+```text
+True
+```
+
+Actual binding check passed:
+
+```text
+GateCopyNumberFilter True True
+G4Tet True
+build_tet_mesh_g4tet True
+```
+
+Example build-mode check:
+
+```bash
+env PYTHONPATH=/Users/yhuh/Study/installation/gate/Gate-10.1/opengate/core/build:/Users/yhuh/Study/installation/gate/Gate-10.1/opengate \
+ DYLD_LIBRARY_PATH=/opt/homebrew/opt/fftw/lib:/opt/homebrew/lib \
+ /opt/anaconda3/envs/tetGatev2/bin/python \
+ ./only_heart_ct_260320_v7_v14_20260429.py --mode build
+```
+
+Result:
+
+```text
+tet mesh ready: total tetrahedra=8582677, selected scoring tetrahedra=48012
+engine initialize = 25.01 s
+```
diff --git a/core/opengate_core/g4_bindings/pyG4Material.cpp b/core/opengate_core/g4_bindings/pyG4Material.cpp
index 36b1f66942..308d1a228c 100644
--- a/core/opengate_core/g4_bindings/pyG4Material.cpp
+++ b/core/opengate_core/g4_bindings/pyG4Material.cpp
@@ -33,6 +33,18 @@ void init_G4Material(py::module &m) {
.def("AddElementByNumberOfAtoms", &G4Material::AddElementByNumberOfAtoms)
.def("AddElementByMassFraction", &G4Material::AddElementByMassFraction)
+ // Expose both native G4Material::AddElement overloads. The G4double
+ // overload interprets its second argument as a mass fraction, whereas
+ // the G4int overload interprets it as the number of atoms. Explicit
+ // overload selection is required because both methods have the same
+ // C++ name and must remain distinguishable to pybind11.
+ .def("AddElement",
+ py::overload_cast(&G4Material::AddElement),
+ py::arg("element"), py::arg("fraction"))
+ .def("AddElement",
+ py::overload_cast(&G4Material::AddElement),
+ py::arg("element"), py::arg("natoms"))
+
.def("GetElementFraction",
[](G4Material &ma, int i) -> double {
return ma.GetFractionVector()[i];
diff --git a/core/opengate_core/g4_bindings/pyG4Tet.cpp b/core/opengate_core/g4_bindings/pyG4Tet.cpp
new file mode 100644
index 0000000000..74abee5095
--- /dev/null
+++ b/core/opengate_core/g4_bindings/pyG4Tet.cpp
@@ -0,0 +1,77 @@
+// pyG4Tet.cpp (FIXED v2 for Geant4 11.1.x)
+// - Avoids calling G4Tet::CheckDegeneracy (non-static in this Geant4)
+// - Provides a simple geometric degeneracy test based on tetra volume
+//
+// Place this file into: opengate_core/g4_bindings/pyG4Tet.cpp
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include "G4Tet.hh"
+#include "G4ThreeVector.hh"
+#include "G4String.hh"
+
+namespace py = pybind11;
+
+// Simple degeneracy check: |6V| = | dot( (b-a), cross((c-a),(d-a)) ) |
+static bool tet_is_degenerate(const G4ThreeVector &a,
+ const G4ThreeVector &b,
+ const G4ThreeVector &c,
+ const G4ThreeVector &d,
+ double eps = 1e-12) {
+ const auto ab = b - a;
+ const auto ac = c - a;
+ const auto ad = d - a;
+ const double sixV = ab.dot(ac.cross(ad));
+ return std::abs(sixV) <= eps;
+}
+
+void init_G4Tet(py::module &m) {
+
+ py::class_>(m, "G4Tet")
+ // NOTE: Geant4 11.1.x constructor is:
+ // G4Tet(name, p1,p2,p3,p4, G4bool* degeneracyFlag = nullptr)
+ .def(py::init([](const std::string &name,
+ const std::array &p1,
+ const std::array &p2,
+ const std::array &p3,
+ const std::array &p4) {
+ G4bool deg = false;
+ auto *t = new G4Tet(G4String(name.c_str()),
+ G4ThreeVector(p1[0], p1[1], p1[2]),
+ G4ThreeVector(p2[0], p2[1], p2[2]),
+ G4ThreeVector(p3[0], p3[1], p3[2]),
+ G4ThreeVector(p4[0], p4[1], p4[2]),
+ °);
+ // If Geant4 says degenerate, delete and throw
+ if (deg) {
+ delete t;
+ throw std::runtime_error("G4Tet: degenerate tetrahedron (Geant4 degeneracyFlag=true)");
+ }
+ return t;
+ }),
+ py::arg("name"), py::arg("p1"), py::arg("p2"), py::arg("p3"), py::arg("p4"))
+ .def("GetCubicVolume", &G4Tet::GetCubicVolume)
+ .def("Inside", &G4Tet::Inside);
+
+ // Standalone helper: purely geometric degeneracy test
+ m.def("tet_is_degenerate",
+ [](const std::array &p1,
+ const std::array &p2,
+ const std::array &p3,
+ const std::array &p4,
+ double eps) {
+ return tet_is_degenerate(G4ThreeVector(p1[0], p1[1], p1[2]),
+ G4ThreeVector(p2[0], p2[1], p2[2]),
+ G4ThreeVector(p3[0], p3[1], p3[2]),
+ G4ThreeVector(p4[0], p4[1], p4[2]),
+ eps);
+ },
+ py::arg("p1"), py::arg("p2"), py::arg("p3"), py::arg("p4"),
+ py::arg("eps") = 1e-12);
+}
diff --git a/core/opengate_core/opengate_core.cpp b/core/opengate_core/opengate_core.cpp
index e317eedc17..46cafe78b1 100644
--- a/core/opengate_core/opengate_core.cpp
+++ b/core/opengate_core/opengate_core.cpp
@@ -296,6 +296,11 @@ void init_G4TriangularFacet(py::module &m);
void init_G4QuadrangularFacet(py::module &m);
+// Added binding declaration: expose Geant4's single tetrahedron solid
+// (G4Tet) to Python. This is distinct from the OpenGATE tetrahedral mesh
+// builder declared below.
+void init_G4Tet(py::module &);
+
// geometry/volume
void init_G4PVPlacement(py::module &);
@@ -400,6 +405,14 @@ void init_GateBooleanFilter(py::module &);
void init_GateAttributeComparisonFilter(py::module &);
+// Added filter binding declaration: select steps by the copy number of their
+// pre-step touchable.
+void init_GateCopyNumberFilter(py::module &);
+
+// Added OpenGATE geometry binding declaration: build a parameterised
+// tetrahedral mesh from TetGen .node and .ele files.
+void init_GateTetrahedralMesh(py::module &);
+
// Gate actors
void init_GateDoseActor(py::module &m);
@@ -714,6 +727,10 @@ PYBIND11_MODULE(opengate_core, m) {
init_G4TriangularFacet(m);
init_G4QuadrangularFacet(m);
+ // Added module registration: make g4.G4Tet and its helper binding available
+ // after importing opengate_core.
+ init_G4Tet(m);
+
init_G4PVPlacement(m);
init_G4TouchableHistory(m);
init_G4NavigationHistory(m);
@@ -782,6 +799,15 @@ PYBIND11_MODULE(opengate_core, m) {
init_GateTrackCreatorProcessFilter(m);
init_GateBooleanFilter(m);
init_GateAttributeComparisonFilter(m);
+
+ // Added module registration: expose g4.GateCopyNumberFilter to the Python
+ // CopyNumberFilter wrapper in opengate/actors/filters.py.
+ init_GateCopyNumberFilter(m);
+
+ // Added module registration: expose the TetGen-based parameterised mesh
+ // builders implemented in opengate_lib.
+ init_GateTetrahedralMesh(m);
+
init_itk_image(m);
init_GateImageNestedParameterisation(m);
init_GateRepeatParameterisation(m);
diff --git a/core/opengate_core/opengate_lib/GateTetrahedralMeshParameterisation.cpp b/core/opengate_core/opengate_lib/GateTetrahedralMeshParameterisation.cpp
new file mode 100644
index 0000000000..b96c4ab084
--- /dev/null
+++ b/core/opengate_core/opengate_lib/GateTetrahedralMeshParameterisation.cpp
@@ -0,0 +1,471 @@
+/* --------------------------------------------------
+ G4Tet-based tetrahedral mesh implementation for OpenGATE
+ - Builds ONE parameterised volume (G4PVParameterised + G4VNestedParameterisation)
+ - IMPORTANT (GeomVol0002): the parameterised PV MUST be the ONLY daughter of mother_lv.
+ -------------------------------------------------- */
+
+#include "GateTetrahedralMeshParameterisation.h"
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include