EGSnrc C++ class library  Report PIRS-898 (2021)
Iwan Kawrakow, Ernesto Mainegra-Hing, Frederic Tessier, Reid Townson and Blake Walters
egs_octree.cpp
Go to the documentation of this file.
1 /*
2 ###############################################################################
3 #
4 # EGSnrc egs++ octree geometry
5 # Copyright (C) 2015 National Research Council Canada
6 #
7 # This file is part of EGSnrc.
8 #
9 # EGSnrc is free software: you can redistribute it and/or modify it under
10 # the terms of the GNU Affero General Public License as published by the
11 # Free Software Foundation, either version 3 of the License, or (at your
12 # option) any later version.
13 #
14 # EGSnrc is distributed in the hope that it will be useful, but WITHOUT ANY
15 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16 # FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for
17 # more details.
18 #
19 # You should have received a copy of the GNU Affero General Public License
20 # along with EGSnrc. If not, see <http://www.gnu.org/licenses/>.
21 #
22 ###############################################################################
23 #
24 # Author: Frederic Tessier, 2008
25 #
26 # Contributors: Iwan Kawrakow
27 # Hubert Ho
28 # Marc Chamberland
29 # Hannah Gallop
30 #
31 ###############################################################################
32 */
33 
34 
35 /*
36 ===============================================================================
37 egs_octree: an octree implementation to the egs++ geometry collection.
38 ===============================================================================
39 
40 An octree is a way to partition space in a tree-like fashion, and it is a direct
41 extension of binary trees to 3D. The idea is to start with a cuboid and divide
42 it in half along each of the three cartesian axes to yield 8 children cuboids
43 (hence the name "octree"), and so on recursively until some desired limit. One
44 can restrict this subdivision scheme for any branch of the octree to obtain
45 leaf cells of varying size: the resolution of the octree can vary spatially.
46 
47 The main motivation for coding an egs++ octree geometry is to increase the
48 performance of large voxelized geometries such as a the human phantom. If we
49 imagine individual voxels in the phantom to be the leaves of the octree, then we
50 can merge voxels of same medium into larger cells and significantly reduce the
51 overall number of regions, thus reducing the number of howfar calls.
52 
53 The geometry can handle multiple bounding boxes in order to model different part
54 of the geometry at different resolutions (see example below). This can be used
55 to model micromatrices, for example.
56 
57 Any egs++ geometry can be "octreefied". One defines a child geometry, a bounding
58 box and the desired resolution, and the geometry will grow and prune the octree
59 accordingly; the child geometry is simply used as an oracle that returns the
60 local medium index for a given point. This approach allows one to use any
61 existing input file and wrap an octree around it, so in effect in can also serve
62 as a generic voxelizer (there is an option to disable pruning).
63 
64 More documentation will follow in the code, but you should be able to try it
65 right away with your own geometries, following the examples I will commit to
66 the egs++/geometry/examples. Please take it out for a spin on your own favorite
67 geometry and let me know if it works well.
68 
69 Enjoy.
70 
71 
72 ===============================================================================
73 EXAMPLE:
74 ===============================================================================
75 
76 Here is a simple example that defines a sphere and then models it with an octree
77 in which the positive octant features a higher resolution:
78 
79 :start geometry definition:
80 
81  :start geometry:
82  library = egs_spheres
83  name = my_sphere
84  :start media input:
85  media = my_medium
86  :stop media input:
87  midpoint = 0 0 0
88  radii = 0.9
89  :stop geometry:
90 
91  :start geometry:
92  library = egs_octree
93  name = my_octree
94  :start octree box:
95  box min = -1 -1 -1
96  box max = 1 1 1
97  resolution = 32 32 32
98  :stop octree box:
99  :start octree box:
100  box min = 0 0 0
101  box max = 1 1 1
102  resolution = 128 128 128
103  :stop octree box:
104  child geometry = my_sphere
105  :stop geometry:
106 
107  simulation geometry = my_octree
108 
109 :stop geometry definition:
110 
111 
112 ===============================================================================
113 PERFORMANCE:
114 ===============================================================================
115 
116 I tested the octree geometry on the fax06 human phantom to check what kind of
117 gains can be achieved. Using the howfar time test from the geometry tester, I
118 found the following:
119 
120 fax06 human phantom:
121 -------------------------------------------------------------------------------
122 model # cells avg cell size avg # steps time / sample (on tux)
123 -------------------------------------------------------------------------------
124 voxels 141716520 0.12 284 14 us
125 octree 9045359 0.30 46 20 us
126 -------------------------------------------------------------------------------
127 GAIN 94 % 84 % 32 %
128 -------------------------------------------------------------------------------
129 
130 There is a major gain in the number of cells and the number of howfar calls,
131 but the performance increase is mitigated by the more intricate logic for
132 navigating in the octree (finding neighbors etc.). So the performance increase
133 is only 32%, as opposed to the 84% that could be expected solely from the
134 decrease of the number of steps. Of course, all these numbers are model
135 specific, and the gain will be better for geometries in which there are large
136 volumes of uniform medium.
137 
138 
139 ===============================================================================
140 MEMORY:
141 ===============================================================================
142 
143 Each node in the final octree (the number of reported regions) requires 46
144 bytes of memory (on 64-bit machines), that is, 38 bytes for each instance of
145 the node EGS_OCtree_node class and 8 bytes for a pointer to the node in the
146 region list. So for example the fax06 phantom requires about 400 MB of memory
147 (but it shows up as using about 600 MB, so maybe there is a leak?).
148 
149 
150 ===============================================================================
151 TODO:
152 ===============================================================================
153 
154 1. Add an option in the VHP geometry to represent the model with an octree, and
155 perhaps implement a bottom-up octree growth algorithm if it can be faster than
156 the current recursive method.
157 
158 2. Add the option to merge voxels when a certain proportion of the children
159 nodes have the same medium.
160 
161 3. Add the option to set the resolution depending on the medium or other
162 criteria than bounding boxes?
163 
164 =============================================================================
165 */
166 
167 
173 #include "egs_octree.h"
174 #include "egs_input.h"
175 
176 void EGS_Octree::printInfo() const {
178  egsInformation(" bounding box minimum = %g %g %g\n", bbxmin, bbymin, bbzmin);
179  egsInformation(" bounding box maximum = %g %g %g\n", bbxmax, bbymax, bbzmax);
180  egsInformation(" bounding box resolution = %d %d %d\n", nx, ny, nz);
181  egsInformation(" octree leaf size = %g %g %g\n", dx, dy, dz);
182  egsInformation(" octree cells (no medium) = %d\n", nreg-nLeaf);
183  egsInformation(" octree cells (medium) = %d\n", nLeaf);
184  egsInformation(" octree average cell size = %.2f\n", nLeafMax/(float)nLeaf);
185  char percent = '%';
186  egsInformation(" octree cell savings = %.1f%c\n", 100*(1-(float)nLeaf/nLeafMax),percent);
187  egsInformation("=======================================================\n");
188 }
189 
190 string EGS_Octree::type("EGS_Octree");
191 
192 static char EGS_OCTREE_LOCAL eoctree_message1[] = "createGeometry(octree): %s\n";
193 static char EGS_OCTREE_LOCAL eoctree_message2[] = "null input?";
194 //static char EGS_OCTREE_LOCAL eoctree_message3[] = "wrong/missing 'octree size' input?";
195 //static char EGS_OCTREE_LOCAL eoctree_message4[] = "expecting 1 or 3 float inputs for 'octree size'";
196 static char EGS_OCTREE_LOCAL eoctree_message5[] = "wrong/missing 'min' or input?";
197 static char EGS_OCTREE_LOCAL eoctree_message6[] = "expecting 3 float inputs for 'box min' input";
198 static char EGS_OCTREE_LOCAL eoctree_message7[] = "wrong/missing 'max' or input?";
199 static char EGS_OCTREE_LOCAL eoctree_message8[] = "expecting 3 float inputs for 'box max' input";
200 static char EGS_OCTREE_LOCAL eoctree_message9[] = "wrong or missing 'resolution' input?";
201 static char EGS_OCTREE_LOCAL eoctree_message10[] = "expecting 3 integer inputs for 'resolution' input";
202 static char EGS_OCTREE_LOCAL eoctree_message11[] = "wrong or missing 'discard child' input?";
203 static char EGS_OCTREE_LOCAL eoctree_message12[] = "expecting 'yes' or 'no' for 'discard child' input";
204 static char EGS_OCTREE_LOCAL eoctree_message13[] = "missing or wrong 'child geometry' input";
205 static char EGS_OCTREE_LOCAL eoctree_message14[] = "undefined child geometry";
206 static char EGS_OCTREE_LOCAL eoctree_message15[] = "you need to define at least one octree box";
207 static char EGS_OCTREE_LOCAL eoctree_message16[] = "wrong 'prune tree' input?";
208 static char EGS_OCTREE_LOCAL eoctree_message17[] = "expecting 'yes' or 'no' for 'prune tree' input?";
209 static char EGS_OCTREE_LOCAL eoctree_key0[] = "octree box";
210 static char EGS_OCTREE_LOCAL eoctree_key1[] = "box min";
211 static char EGS_OCTREE_LOCAL eoctree_key2[] = "box max";
212 static char EGS_OCTREE_LOCAL eoctree_key3[] = "resolution";
213 static char EGS_OCTREE_LOCAL eoctree_key4[] = "child geometry";
214 static char EGS_OCTREE_LOCAL eoctree_key5[] = "discard child";
215 static char EGS_OCTREE_LOCAL eoctree_key6[] = "prune tree";
216 
217 static bool EGS_OCTREE_LOCAL inputSet = false;
218 
219 extern "C" {
220 
221  static void setInputs() {
222  inputSet = true;
223 
224  setBaseGeometryInputs(false);
225 
226  geomBlockInput->getSingleInput("library")->setValues({"egs_octree"});
227 
228  // Format: name, isRequired, description, vector string of allowed values
229  geomBlockInput->addSingleInput("child geometry", true, "The name of child geometry");
230  geomBlockInput->addSingleInput("discard child", true, "yes or no");
231  geomBlockInput->addSingleInput("prune tree", false, "yes or no");
232 
233  auto blockPtr = geomBlockInput->addBlockInput("octree box");
234  blockPtr->addSingleInput("box min", true, "(x, y, z)");
235  blockPtr->addSingleInput("box max", true, "(x, y, z)");
236  blockPtr->addSingleInput("resolution", true, "A specified resolution (x, y, z)");
237  }
238 
239  EGS_OCTREE_EXPORT shared_ptr<EGS_BlockInput> getInputs() {
240  if (!inputSet) {
241  setInputs();
242  }
243  return geomBlockInput;
244  }
245 
246  EGS_OCTREE_EXPORT EGS_BaseGeometry *createGeometry(EGS_Input *input) {
247 
248  EGS_Input *i;
249 
250  // check that we have an input
251  if (!input) {
252  egsWarning(eoctree_message1,eoctree_message2);
253  return 0;
254  }
255 
256  // read bounding boxes
257  vector<EGS_Octree_bbox> vBox;
258  while ((i = input->takeInputItem(eoctree_key0))) {
259 
260  // read the bounding box minimum
261  vector<EGS_Float> v;
262  int err = i->getInput(eoctree_key1, v);
263  if (err) {
264  egsWarning(eoctree_message1, eoctree_message5);
265  return 0;
266  }
267  if (v.size() != 3) {
268  egsWarning(eoctree_message1, eoctree_message6);
269  return 0;
270  }
271  EGS_Vector bboxMin(v[0],v[1],v[2]);
272 
273  // read the bounding box maximum
274  err = i->getInput(eoctree_key2, v);
275  if (err) {
276  egsWarning(eoctree_message1, eoctree_message7);
277  return 0;
278  }
279  if (v.size() != 3) {
280  egsWarning(eoctree_message1, eoctree_message8);
281  return 0;
282  }
283  EGS_Vector bboxMax(v[0],v[1],v[2]);
284 
285  // read the bounding box resolution
286  vector<int> bboxRes;
287  err = i->getInput(eoctree_key3, bboxRes);
288  if (err) {
289  egsWarning(eoctree_message1, eoctree_message9);
290  return 0;
291  }
292  if (bboxRes.size() != 3) {
293  egsWarning(eoctree_message1, eoctree_message10);
294  return 0;
295  }
296 
297  EGS_Octree_bbox box = EGS_Octree_bbox(bboxMin, bboxMax, bboxRes);
298  vBox.push_back(box);
299  }
300  if (vBox.size() < 1) {
301  egsWarning(eoctree_message1, eoctree_message15);
302  return 0;
303  }
304 
305  // read discard child option
306  bool discardChild = true;
307  string discard;
308  if (input->getInputItem(eoctree_key5)) {
309  int err = input->getInput(eoctree_key5, discard);
310  if (err) {
311  egsWarning(eoctree_message1, eoctree_message11);
312  return 0;
313  }
314  if (discard.find("yes")==string::npos && discard.find("no")==string::npos) {
315  egsWarning(eoctree_message1, eoctree_message12);
316  return 0;
317  }
318  if (discard.find("no")!=string::npos) {
319  discardChild = false;
320  }
321  }
322 
323  // read prune tree option
324  bool pruneTree = true;
325  string prune;
326  if (input->getInputItem(eoctree_key6)) {
327  int err = input->getInput(eoctree_key6, prune);
328  if (err) {
329  egsWarning(eoctree_message1, eoctree_message16);
330  return 0;
331  }
332  if (prune.find("yes")==string::npos && prune.find("no")==string::npos) {
333  egsWarning(eoctree_message1, eoctree_message17);
334  return 0;
335  }
336  if (prune.find("no")!=string::npos) {
337  pruneTree = false;
338  }
339  }
340 
341  // read and load the child geometry
342  string gname;
343  {
344  int err = input->getInput(eoctree_key4, gname);
345  if (err) {
346  egsWarning(eoctree_message1, eoctree_message13);
347  return 0;
348  }
349  }
351  if (!g) {
352  egsWarning(eoctree_message1, eoctree_message14);
353  return 0;
354  }
355 
356  // create the octree geometry
357  EGS_Octree *octree = new EGS_Octree(vBox, pruneTree, g);
358  octree->setName(input);
359  octree->setBoundaryTolerance(input);
360  octree->setLabels(input);
361  octree->printInfo();
362 
363  if (discardChild) {
364  delete g;
365  }
366  return octree;
367  }
368 }
Base geometry class. Every geometry class must be derived from EGS_BaseGeometry.
int nreg
Number of local regions in this geometry.
void setName(EGS_Input *inp)
Set the name of the geometry from the input inp.
int setLabels(EGS_Input *input)
Set the labels from an input block.
virtual void printInfo() const
Print information about this geometry.
static EGS_BaseGeometry * getGeometry(const string &Name)
Get a pointer to the geometry named Name.
void setBoundaryTolerance(EGS_Input *inp)
Set the value of the boundary tolerance from the input inp.
A class for storing information in a tree-like structure of key-value pairs. This class is used throu...
Definition: egs_input.h:182
EGS_Input * takeInputItem(const string &key, bool self=true)
Get the property named key.
Definition: egs_input.cpp:229
EGS_Input * getInputItem(const string &key) const
Same as the previous function but now ownership remains with the EGS_Input object.
Definition: egs_input.cpp:248
int getInput(const string &key, vector< string > &values) const
Assign values to an array of strings from an input identified by key.
Definition: egs_input.cpp:341
An octree geometry.
Definition: egs_octree.h:519
A class representing 3D vectors.
Definition: egs_vector.h:57
EGS_GLIB_EXPORT EGS_BaseGeometry * createGeometry(EGS_Input *input)
Definition: egs_glib.cpp:84
EGS_Input class header file.
An octree geometry: header.
EGS_InfoFunction EGS_EXPORT egsInformation
Always use this function for reporting the progress of a simulation and any other type of information...
EGS_InfoFunction EGS_EXPORT egsWarning
Always use this function for reporting warnings.