Skip to content

Commit 26d5c8a

Browse files
authored
Merge pull request #469 from OpenGATE/spatial_blur_for_param_volumes
Consolidation3
2 parents ab7678a + 5611aea commit 26d5c8a

File tree

52 files changed

+278
-183
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+278
-183
lines changed

core/opengate_core/opengate_lib/GateSimulationStatisticsActor.cpp

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -54,18 +54,16 @@ void GateSimulationStatisticsActor::StartSimulationAction() {
5454
fStartRunTimeIsSet = false;
5555

5656
// initialise the counts
57-
fCounts["run_count"] = 0;
58-
fCounts["event_count"] = 0;
59-
fCounts["track_count"] = 0;
60-
fCounts["step_count"] = 0;
57+
fCounts["runs"] = 0;
58+
fCounts["events"] = 0;
59+
fCounts["tracks"] = 0;
60+
fCounts["steps"] = 0;
6161
}
6262

6363
py::dict GateSimulationStatisticsActor::GetCounts() {
6464
auto dd = py::dict(
65-
"run_count"_a = fCounts["run_count"],
66-
"event_count"_a = fCounts["event_count"],
67-
"track_count"_a = fCounts["track_count"],
68-
"step_count"_a = fCounts["step_count"],
65+
"runs"_a = fCounts["runs"], "events"_a = fCounts["events"],
66+
"tracks"_a = fCounts["tracks"], "steps"_a = fCounts["steps"],
6967
"duration"_a = fCountsD["duration"], "init"_a = fCountsD["init"],
7068
"start_time"_a = fCountsStr["start_time"],
7169
"stop_time"_a = fCountsStr["stop_time"], "track_types"_a = fTrackTypes);
@@ -124,10 +122,10 @@ void GateSimulationStatisticsActor::EndOfSimulationWorkerAction(
124122
G4AutoLock mutex(&GateSimulationStatisticsActorMutex);
125123
threadLocal_t &data = threadLocalData.Get();
126124
// merge all threads (need mutex)
127-
fCounts["run_count"] += data.fRunCount;
128-
fCounts["event_count"] += data.fEventCount;
129-
fCounts["track_count"] += data.fTrackCount;
130-
fCounts["step_count"] += data.fStepCount;
125+
fCounts["runs"] += data.fRunCount;
126+
fCounts["events"] += data.fEventCount;
127+
fCounts["tracks"] += data.fTrackCount;
128+
fCounts["steps"] += data.fStepCount;
131129
if (fTrackTypesFlag) {
132130
for (auto v : data.fTrackTypes) {
133131
if (fTrackTypes.count(v.first) == 0)

core/opengate_core/opengate_lib/digitizer/GateDigitizerSpatialBlurringActor.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,13 @@ void GateDigitizerSpatialBlurringActor::BlurCurrentThreeVectorValue() {
108108
l.fNavigator->LocateGlobalPointAndUpdateTouchable(vec, &fTouchableHistory);
109109
auto vid = GateUniqueVolumeID::New(&fTouchableHistory);
110110
phys_vol = vid->GetVolumeDepthID().back().fVolume;
111+
// If the volume is parameterised, we consider the parent volume to compute
112+
// the extent (otherwise the keep in solid will consider one single instance
113+
// of the repeated solid, instead of the whole parameterised volume).
114+
if (phys_vol->IsParameterised()) {
115+
auto n = vid->GetVolumeDepthID().size();
116+
phys_vol = vid->GetVolumeDepthID()[n - 2].fVolume;
117+
}
111118
}
112119

113120
// consider local position

docs/source/user_guide_2_4_actors.md

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ Several tests depict usage of DoseActor: test008, test009, test021, test035, etc
3434

3535
````python
3636
dose = sim.add_actor("DoseActor", "dose")
37-
dose.output = output_path / "test008-edep.mhd"
38-
dose.mother = "waterbox"
37+
dose.output_filename = output_path / "test008-edep.mhd"
38+
dose.attached_to = "waterbox"
3939
dose.size = [99, 99, 99]
4040
mm = gate.g4_units.mm
4141
dose.spacing = [2 * mm, 2 * mm, 2 * mm]
@@ -50,7 +50,7 @@ A PhaseSpaceActor stores any set of particles reaching a given volume during the
5050

5151
```python
5252
phsp = sim.add_actor("PhaseSpaceActor", "PhaseSpace")
53-
phsp.mother = plane.name
53+
phsp.attached_to = plane.name
5454
phsp.attributes = [
5555
"KineticEnergy",
5656
"Weight",
@@ -64,7 +64,7 @@ phsp.attributes = [
6464
"LocalTime",
6565
"EventPosition",
6666
]
67-
phsp.output = "test019_hits.root"
67+
phsp.output_filename = "test019_hits.root"
6868
f = sim.add_filter("ParticleFilter", "f")
6969
f.particle = "gamma"
7070
phsp.filters.append(f)
@@ -105,21 +105,28 @@ Note: all three conditions may be combined (if one condition is True, the partic
105105

106106
### Hits-related actors (digitizer)
107107

108-
In legacy Gate, the digitizer module is a set of tools used to simulate the behaviour of the scanner detectors and signal processing chain. The tools consider a list of interactions occurring in the detector (e.g. in the crystal), named as "hits collections". Then, this collection of hits is processed and filtered by different modules to end up with a final digital value. To start a digitizer chain, we must start defining a `HitsCollectionActor`, explained in the next sections.
108+
The digitizer module is a set of tools used to simulate the behaviour of the scanner detectors and signal processing chain. The tools consider a list of interactions occurring in the detector (e.g. in the crystal), named as "hits collections". This collection of hits is then processed and filtered by different modules to produce a final digital value. To initiate a digitizer chain, you begin by defining a `HitsCollectionActor`, as explained in the following sections.
109+
110+
Common features of all digitizer actors:
111+
112+
- Most digitizers have a root output (with the exception for `DigitizerProjectionActor`, which outputs an image). This output can be written to disk with `my_digitizer.root_output.write_to_disk = True`. Multiple digitizers can share the same root output file, with each storing data in a separate branch named after the actor.
113+
114+
- `authorize_repeated_volumes`: Set this to True if you want the digitizer to work with repeated volumes. This is useful, for example, when the digitizer is attached to a region with repeated crystal volumes (as in a PET system). However, if you are repeating some SPECT heads, you may not want the digitizer to record hits from both heads in the same file (in which case, set the flag to False).
115+
109116

110117
#### DigitizerHitsCollectionActor
111118

112119
The `DigitizerHitsCollectionActor` is an actor that collects hits occurring in a given volume (or one of its daughters). Every time a step occurs in the volume a list of attributes is recorded. The list of attributes is defined by the user as follows:
113120

114121
```python
115122
hc = sim.add_actor('DigitizerHitsCollectionActor', 'Hits')
116-
hc.mother = ['crystal1', 'crystal2']
117-
hc.output = 'test_hits.root'
123+
hc.attached_to = ['crystal1', 'crystal2']
124+
hc.output_filename = 'test_hits.root'
118125
hc.attributes = ['TotalEnergyDeposit', 'KineticEnergy', 'PostPosition',
119126
'CreatorProcess', 'GlobalTime', 'VolumeName', 'RunID', 'ThreadID', 'TrackID']
120127
```
121128

122-
In this example, the actor is attached (`mother` option) to several volumes (`crystal1` and `crystal2` ) but most of the time, one single volume is sufficient. This volume is important: every time an interaction (a step) is occurring in this volume, a hit will be created. The list of attributes is defined with the given array of attribute names. The names of the attributes are as close as possible to the Geant4 terminology. They can be of a few types: 3 (ThreeVector), D (double), S (string), I (int), U (unique volume ID, see DigitizerAdderActor section). The list of available attributes is defined in the file `core/opengate_core/opengate_lib/GateDigiAttributeList.cpp` and can be printed with:
129+
In this example, the actor is attached (`attached_to` option) to several volumes (`crystal1` and `crystal2` ) but most of the time, one single volume is sufficient. This volume is important: every time an interaction (a step) is occurring in this volume, a hit will be created. The list of attributes is defined with the given array of attribute names. The names of the attributes are as close as possible to the Geant4 terminology. They can be of a few types: 3 (ThreeVector), D (double), S (string), I (int), U (unique volume ID, see `DigitizerAdderActor` section). The list of available attributes is defined in the file `core/opengate_core/opengate_lib/GateDigiAttributeList.cpp` and can be printed with:
123130

124131
```python
125132
import opengate_core as gate_core
@@ -192,29 +199,29 @@ The two actors used to convert some `hits` to one `digi` are "DigitizerHitsAdder
192199
This actor groups the hits per different volumes according to the option `group_volume` (by default, this is the deeper volume that contains the hit). All hits (in the same event) occurring in the same volume are gathered into one single digi according to one of the two available policies:
193200

194201
- EnergyWeightedCentroidPosition:
195-
- the final energy ("TotalEnergyDeposit") is the sum of all deposited energy
196-
- the position ("PostPosition") is the energy-weighted centroid position
197-
- the time ("GlobalTime") is the time of the earliest hit
202+
- the final energy (`TotalEnergyDeposit`") is the sum of all deposited energy
203+
- the position (`PostPosition`) is the energy-weighted centroid position
204+
- the time (`GlobalTime`) is the time of the earliest hit
198205

199206
- EnergyWinnerPosition
200-
- the final energy ("TotalEnergyDeposit") is the energy of the hit with the largest deposited energy
201-
- the position ("PostPosition") is the position of the hit with the largest deposited energy
202-
- the time ("GlobalTime") is the time of the earliest hit
207+
- the final energy (`TotalEnergyDeposit`) is the energy of the hit with the largest deposited energy
208+
- the position (`PostPosition`) is the position of the hit with the largest deposited energy
209+
- the time (`GlobalTime`) is the time of the earliest hit
203210

204211
```python
205212
sc = sim.add_actor("DigitizerAdderActor", "Singles")
206-
sc.output = 'test_hits.root'
213+
sc.output_filename = 'test_hits.root'
207214
sc.input_digi_collection = "Hits"
208215
sc.policy = "EnergyWeightedCentroidPosition"
209216
# sc.policy = "EnergyWinnerPosition"
210217
sc.group_volume = crystal.name
211218
```
212219

213-
Note that this actor is only triggered at the end of an event, so the `mother` volume to which it is attached has no effect. Examples are available in test 037.
220+
Note that this actor is only triggered at the end of an event, so the `attached_to` volume to which it is attached has no effect. Examples are available in test 037.
214221

215222
#### DigitizerReadoutActor
216223

217-
This actor is the same as the previous one (DigitizerHitsAdderActor) with one additional option: the resulting positions of the digi are set in the center of the defined volumes (discretized). We keep two different actors (Adder and Readout) to be close to the previous legacy GATE versions. The additional option `discretize_volume` indicates the volume name in which the discrete position will be taken.
224+
This actor is the same as the previous one (`DigitizerHitsAdderActor`) with one additional option: the resulting positions of the digi are set in the center of the defined volumes (discretized). We keep two different actors (Adder and Readout) to be close to the previous legacy GATE versions. The additional option `discretize_volume` indicates the volume name in which the discrete position will be taken.
218225

219226
```python
220227
sc = sim.add_actor("HitsReadoutActor", "Singles")
@@ -239,7 +246,7 @@ For Linear: `blur_reference_value`, `blur_reference_value` and `blur_slope` EQU
239246

240247
```python
241248
bc = sim.add_actor("DigitizerBlurringActor", "Singles_with_blur")
242-
bc.output = "output.root"
249+
bc.output_filename = "output.root"
243250
bc.input_digi_collection = "Singles_readout"
244251
bc.blur_attribute = "GlobalTime"
245252
bc.blur_method = "Gaussian"
@@ -306,7 +313,7 @@ As parameters, Coincidence Sorter expects as input:
306313

307314
#### Policies
308315

309-
When more than two singles are found in coincidence, several type of behavior could be implemented. GATE allows to model 5 different policies to treat multiple coincidences that can be used. Mutliple coincidences or "multicoincidence" are composed of at least three singles detected in the same **time window** that could form coincidence. The list of policies along with their explanation are given in Table below. The 5 policies, same as in [Gate9.X](https://opengate.readthedocs.io/en/latest/digitizer_and_detector_modeling.html#id43), were selected for the implementation as the most used. If an option that you need is missing, please, don't hesitate to report it in [Issues](https://github.com/OpenGATE/opengate/issues).
316+
When more than two singles are found in coincidence, several type of behavior could be implemented. GATE allows to model 5 different policies to treat multiple coincidences that can be used. Multiple coincidences or "multicoincidence" are composed of at least three singles detected in the same **time window** that could form coincidence. The list of policies along with their explanation are given in Table below. The 5 policies, same as in [Gate9.X](https://opengate.readthedocs.io/en/latest/digitizer_and_detector_modeling.html#id43), were selected for the implementation as the most used. If an option that you need is missing, please, don't hesitate to report it in [Issues](https://github.com/OpenGATE/opengate/issues).
310317

311318

312319
**Available multiple policies and associated meaning**. When a multiple coincidence involving n *singles* is processed, it is first decomposed into a list of n·(n−1) pairs which are analyzed individually.
@@ -368,7 +375,7 @@ The Compton splitting actor generates N particles, each with a weight equal to t
368375

369376
```python
370377
compt_splitting_actor = sim.add_actor("ComptSplittingActor", "ComptSplitting")
371-
compt_splitting_actor.mother = W_tubs.name
378+
compt_splitting_actor.attached_to = W_tubs.name
372379
compt_splitting_actor.splitting_factor = nb_split
373380
compt_splitting_actor.russian_roulette = True
374381
compt_splitting_actor.rotation_vector_director = True

opengate/actors/miscactors.py

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from ..exception import warning
99

1010

11-
def _setter_hook_output_filename(self, output_filename):
11+
def _setter_hook_stats_actor_output_filename(self, output_filename):
1212
# By default, write_to_disk is False.
1313
# However, if user actively sets the output_filename
1414
# s/he most likely wants to write to disk also
@@ -40,6 +40,7 @@ class ActorOutputStatisticsActor(ActorOutputBase):
4040
"Relative paths and filenames are taken "
4141
"relative to the global simulation output folder "
4242
"set via the Simulation.output_dir option. ",
43+
"setter_hook": _setter_hook_stats_actor_output_filename,
4344
},
4445
),
4546
"write_to_disk": (
@@ -57,10 +58,10 @@ def __init__(self, *args, **kwargs):
5758

5859
# predefine the merged_data
5960
self.merged_data = Box()
60-
self.merged_data.run_count = 0
61-
self.merged_data.event_count = 0
62-
self.merged_data.track_count = 0
63-
self.merged_data.step_count = 0
61+
self.merged_data.runs = 0
62+
self.merged_data.events = 0
63+
self.merged_data.tracks = 0
64+
self.merged_data.steps = 0
6465
self.merged_data.duration = 0
6566
self.merged_data.start_time = 0
6667
self.merged_data.stop_time = 0
@@ -74,7 +75,7 @@ def __init__(self, *args, **kwargs):
7475
def pps(self):
7576
if self.merged_data.duration != 0:
7677
return int(
77-
self.merged_data.event_count / (self.merged_data.duration / g4_units.s)
78+
self.merged_data.events / (self.merged_data.duration / g4_units.s)
7879
)
7980
else:
8081
return 0
@@ -83,7 +84,7 @@ def pps(self):
8384
def tps(self):
8485
if self.merged_data.duration != 0:
8586
return int(
86-
self.merged_data.track_count / (self.merged_data.duration / g4_units.s)
87+
self.merged_data.tracks / (self.merged_data.duration / g4_units.s)
8788
)
8889
else:
8990
return 0
@@ -92,7 +93,7 @@ def tps(self):
9293
def sps(self):
9394
if self.merged_data.duration != 0:
9495
return int(
95-
self.merged_data.step_count / (self.merged_data.duration / g4_units.s)
96+
self.merged_data.steps / (self.merged_data.duration / g4_units.s)
9697
)
9798
else:
9899
return 0
@@ -112,10 +113,10 @@ def get_data(self, **kwargs):
112113

113114
def get_processed_output(self):
114115
d = {}
115-
d["runs"] = {"value": self.merged_data.run_count, "unit": None}
116-
d["events"] = {"value": self.merged_data.event_count, "unit": None}
117-
d["tracks"] = {"value": self.merged_data.track_count, "unit": None}
118-
d["steps"] = {"value": self.merged_data.step_count, "unit": None}
116+
d["runs"] = {"value": self.merged_data.runs, "unit": None}
117+
d["events"] = {"value": self.merged_data.events, "unit": None}
118+
d["tracks"] = {"value": self.merged_data.tracks, "unit": None}
119+
d["steps"] = {"value": self.merged_data.steps, "unit": None}
119120
val, unit = g4_best_unit_tuple(self.merged_data.init, "Time")
120121
d["init"] = {
121122
"value": val,
@@ -209,12 +210,6 @@ def __initcpp__(self):
209210
g4.GateSimulationStatisticsActor.__init__(self, self.user_info)
210211
self.AddActions({"StartSimulationAction", "EndSimulationAction"})
211212

212-
# def __finalize_init__(self):
213-
# super().__finalize_init__()
214-
# # this attribute is considered sometimes in the read_stat_file
215-
# # we declare it here to avoid warning
216-
# self.known_attributes.add("date")
217-
218213
def __str__(self):
219214
s = self.user_output["stats"].__str__()
220215
return s
@@ -233,7 +228,9 @@ def initialize(self):
233228

234229
def StartSimulationAction(self):
235230
g4.GateSimulationStatisticsActor.StartSimulationAction(self)
236-
self.user_output.stats.nb_threads = self.simulation.number_of_threads
231+
self.user_output.stats.merged_data.nb_threads = (
232+
self.simulation.number_of_threads
233+
)
237234

238235
def EndSimulationAction(self):
239236
g4.GateSimulationStatisticsActor.EndSimulationAction(self)

opengate/contrib/linacs/elektasynergy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def add_linac(sim, name="linac", sad=1000):
3737
# sim.volume_manager.add_material_database('../contrib/elekta_synergy_materials.db')
3838

3939
# check overlap
40-
sim.g4_check_overlap_flag = True
40+
sim.check_volumes_overlap = True
4141

4242
# global box
4343
linac = add_empty_linac_box(sim, name, sad)

opengate/contrib/phantoms/nemaiec.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def add_iec_phantom(
3939
create_material(simulation)
4040

4141
# check overlap only for debug
42-
simulation.g4_check_overlap_flag = check_overlap
42+
simulation.check_volumes_overlap = check_overlap
4343

4444
# Outside structure
4545
iec, _, _ = add_iec_body(simulation, name)

opengate/contrib/spect/ge_discovery_nm670.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def add_spect_head(sim, name="spect", collimator_type="lehr", debug=False):
5858
sim.volume_manager.add_material_database(fdb)
5959

6060
# check overlap
61-
sim.g4_check_overlap_flag = False # set to True for debug
61+
sim.check_volumes_overlap = False # set to True for debug
6262

6363
# spect head
6464
head, lead_cover = add_spect_box(sim, name)

opengate/contrib/spect/siemens_intevo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def add_spect_head(sim, name="spect", collimator_type="lehr", debug=False):
3535
sim.volume_manager.add_material_database(fdb)
3636

3737
# check overlap
38-
sim.g4_check_overlap_flag = debug
38+
sim.check_volumes_overlap = debug
3939

4040
# main box
4141
head = add_head_box(sim, name)

opengate/geometry/volumes.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,9 +239,10 @@ def __getstate__(self):
239239

240240
def __finalize_init__(self):
241241
super().__finalize_init__()
242-
# need to add this explciitly because anytree does not properly declare
242+
# need to add this explicitly because anytree does not properly declare
243243
# the attribute __parent in the NodeMixin.__init__ which leads to falls warnings
244244
self.known_attributes.add("_NodeMixin__parent")
245+
self.known_attributes.add("_NodeMixin__children")
245246

246247
def _update_node(self):
247248
"""Internal method which retrieves the volume object

opengate/tests/src/test004_simple_mt.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@
7777
# gate_test4_simulation_stats_actor
7878
# Gate mac/main.mac
7979
stats_ref = utility.read_stat_file(paths.gate_output / "stat.txt")
80-
stats_ref.counts.run_count = sim.number_of_threads
80+
stats_ref.counts.runs = sim.number_of_threads
8181
is_ok = utility.assert_stats(stats, stats_ref, tolerance=0.01)
8282

8383
utility.test_ok(is_ok)

opengate/tests/src/test004_simple_visu_gdml.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@
5959
sim.run()
6060

6161
stats = sim.get_actor("Stats")
62-
stats.counts.run_count = 1
62+
stats.counts.runs = 1
6363

6464
# gate_test4_simulation_stats_actor
6565
# Gate mac/main.mac

opengate/tests/src/test006_runs.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,10 @@
8787

8888
stats_ref = gate.actors.miscactors.SimulationStatisticsActor(name="stat_ref")
8989
c = stats_ref.counts
90-
c.run_count = 3
91-
c.event_count = 7800
92-
c.track_count = 37584 # 56394
93-
c.step_count = 266582 # 217234
90+
c.runs = 3
91+
c.events = 7800
92+
c.tracks = 37584 # 56394
93+
c.steps = 266582 # 217234
9494
# stats_ref.pps = 4059.6 3 3112.2
9595
c.duration = 1 / 4059.6 * 7800 * sec
9696
print("-" * 80)

opengate/tests/src/test007_volumes.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -215,10 +215,10 @@ def check_mat(se):
215215
# check
216216
stats_ref = gate.actors.miscactors.SimulationStatisticsActor(name="ref")
217217
c = stats_ref.counts
218-
c.run_count = 1
219-
c.event_count = 1280
220-
c.track_count = 17034 # 25668
221-
c.step_count = 78096 # 99465
218+
c.runs = 1
219+
c.events = 1280
220+
c.tracks = 17034 # 25668
221+
c.steps = 78096 # 99465
222222
# stats_ref.pps = 506.6
223223
sec = gate.g4_units.second
224224
c.duration = 2.5267 * sec

0 commit comments

Comments
 (0)