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 #
30 ###############################################################################
31 */
32 
33 
34 /*
35 ===============================================================================
36 egs_octree: an octree implementation to the egs++ geometry collection.
37 ===============================================================================
38 
39 An octree is a way to partition space in a tree-like fashion, and it is a direct
40 extension of binary trees to 3D. The idea is to start with a cuboid and divide
41 it in half along each of the three cartesian axes to yield 8 children cuboids
42 (hence the name "octree"), and so on recursively until some desired limit. One
43 can restrict this subdivision scheme for any branch of the octree to obtain
44 leaf cells of varying size: the resolution of the octree can vary spatially.
45 
46 The main motivation for coding an egs++ octree geometry is to increase the
47 performance of large voxelized geometries such as a the human phantom. If we
48 imagine individual voxels in the phantom to be the leaves of the octree, then we
49 can merge voxels of same medium into larger cells and significantly reduce the
50 overall number of regions, thus reducing the number of howfar calls.
51 
52 The geometry can handle multiple bounding boxes in order to model different part
53 of the geometry at different resolutions (see example below). This can be used
54 to model micromatrices, for example.
55 
56 Any egs++ geometry can be "octreefied". One defines a child geometry, a bounding
57 box and the desired resolution, and the geometry will grow and prune the octree
58 accordingly; the child geometry is simply used as an oracle that returns the
59 local medium index for a given point. This approach allows one to use any
60 existing input file and wrap an octree around it, so in effect in can also serve
61 as a generic voxelizer (there is an option to disable pruning).
62 
63 More documentation will follow in the code, but you should be able to try it
64 right away with your own geometries, following the examples I will commit to
65 the egs++/geometry/examples. Please take it out for a spin on your own favorite
66 geometry and let me know if it works well.
67 
68 Enjoy.
69 
70 
71 ===============================================================================
72 EXAMPLE:
73 ===============================================================================
74 
75 Here is a simple example that defines a sphere and then models it with an octree
76 in which the positive octant features a higher resolution:
77 
78 :start geometry definition:
79 
80  :start geometry:
81  library = egs_spheres
82  name = my_sphere
83  :start media input:
84  media = my_medium
85  :stop media input:
86  midpoint = 0 0 0
87  radii = 0.9
88  :stop geometry:
89 
90  :start geometry:
91  library = egs_octree
92  name = my_octree
93  :start octree box:
94  box min = -1 -1 -1
95  box max = 1 1 1
96  resolution = 32 32 32
97  :stop octree box:
98  :start octree box:
99  box min = 0 0 0
100  box max = 1 1 1
101  resolution = 128 128 128
102  :stop octree box:
103  child geometry = my_sphere
104  :stop geometry:
105 
106  simulation geometry = my_octree
107 
108 :stop geometry definition:
109 
110 
111 ===============================================================================
112 PERFORMANCE:
113 ===============================================================================
114 
115 I tested the octree geometry on the fax06 human phantom to check what kind of
116 gains can be achieved. Using the howfar time test from the geometry tester, I
117 found the following:
118 
119 fax06 human phantom:
120 -------------------------------------------------------------------------------
121 model # cells avg cell size avg # steps time / sample (on tux)
122 -------------------------------------------------------------------------------
123 voxels 141716520 0.12 284 14 us
124 octree 9045359 0.30 46 20 us
125 -------------------------------------------------------------------------------
126 GAIN 94 % 84 % 32 %
127 -------------------------------------------------------------------------------
128 
129 There is a major gain in the number of cells and the number of howfar calls,
130 but the performance increase is mitigated by the more intricate logic for
131 navigating in the octree (finding neighbors etc.). So the performance increase
132 is only 32%, as opposed to the 84% that could be expected solely from the
133 decrease of the number of steps. Of course, all these numbers are model
134 specific, and the gain will be better for geometries in which there are large
135 volumes of uniform medium.
136 
137 
138 ===============================================================================
139 MEMORY:
140 ===============================================================================
141 
142 Each node in the final octree (the number of reported regions) requires 46
143 bytes of memory (on 64-bit machines), that is, 38 bytes for each instance of
144 the node EGS_OCtree_node class and 8 bytes for a pointer to the node in the
145 region list. So for example the fax06 phantom requires about 400 MB of memory
146 (but it shows up as using about 600 MB, so maybe there is a leak?).
147 
148 
149 ===============================================================================
150 TODO:
151 ===============================================================================
152 
153 1. Add an option in the VHP geometry to represent the model with an octree, and
154 perhaps implement a bottom-up octree growth algorithm if it can be faster than
155 the current recursive method.
156 
157 2. Add the option to merge voxels when a certain proportion of the children
158 nodes have the same medium.
159 
160 3. Add the option to set the resolution depending on the medium or other
161 criteria than bounding boxes?
162 
163 =============================================================================
164 */
165 
166 
172 #include "egs_octree.h"
173 #include "egs_input.h"
174 
175 void EGS_Octree::printInfo() const {
177  egsInformation(" bounding box minimum = %g %g %g\n", bbxmin, bbymin, bbzmin);
178  egsInformation(" bounding box maximum = %g %g %g\n", bbxmax, bbymax, bbzmax);
179  egsInformation(" bounding box resolution = %d %d %d\n", nx, ny, nz);
180  egsInformation(" octree leaf size = %g %g %g\n", dx, dy, dz);
181  egsInformation(" octree cells (no medium) = %d\n", nreg-nLeaf);
182  egsInformation(" octree cells (medium) = %d\n", nLeaf);
183  egsInformation(" octree average cell size = %.2f\n", nLeafMax/(float)nLeaf);
184  char percent = '%';
185  egsInformation(" octree cell savings = %.1f%c\n", 100*(1-(float)nLeaf/nLeafMax),percent);
186  egsInformation("=======================================================\n");
187 }
188 
189 string EGS_Octree::type("EGS_Octree");
190 
191 static char EGS_OCTREE_LOCAL eoctree_message1[] = "createGeometry(octree): %s\n";
192 static char EGS_OCTREE_LOCAL eoctree_message2[] = "null input?";
193 //static char EGS_OCTREE_LOCAL eoctree_message3[] = "wrong/missing 'octree size' input?";
194 //static char EGS_OCTREE_LOCAL eoctree_message4[] = "expecting 1 or 3 float inputs for 'octree size'";
195 static char EGS_OCTREE_LOCAL eoctree_message5[] = "wrong/missing 'min' or input?";
196 static char EGS_OCTREE_LOCAL eoctree_message6[] = "expecting 3 float inputs for 'box min' input";
197 static char EGS_OCTREE_LOCAL eoctree_message7[] = "wrong/missing 'max' or input?";
198 static char EGS_OCTREE_LOCAL eoctree_message8[] = "expecting 3 float inputs for 'max max' input";
199 static char EGS_OCTREE_LOCAL eoctree_message9[] = "wrong or missing 'resolution' input?";
200 static char EGS_OCTREE_LOCAL eoctree_message10[] = "expecting 3 integer inputs for 'resolution' input";
201 static char EGS_OCTREE_LOCAL eoctree_message11[] = "wrong or missing 'discard child' input?";
202 static char EGS_OCTREE_LOCAL eoctree_message12[] = "expecting 'yes' or 'no' for 'discard child' input";
203 static char EGS_OCTREE_LOCAL eoctree_message13[] = "missing or wrong 'child geometry' input";
204 static char EGS_OCTREE_LOCAL eoctree_message14[] = "undefined child geometry";
205 static char EGS_OCTREE_LOCAL eoctree_message15[] = "you need to define at least one octree box";
206 static char EGS_OCTREE_LOCAL eoctree_message16[] = "wrong 'prune tree' input?";
207 static char EGS_OCTREE_LOCAL eoctree_message17[] = "expecting 'yes' or 'no' for 'prune tree' input?";
208 static char EGS_OCTREE_LOCAL eoctree_key0[] = "octree box";
209 static char EGS_OCTREE_LOCAL eoctree_key1[] = "box min";
210 static char EGS_OCTREE_LOCAL eoctree_key2[] = "box max";
211 static char EGS_OCTREE_LOCAL eoctree_key3[] = "resolution";
212 static char EGS_OCTREE_LOCAL eoctree_key4[] = "child geometry";
213 static char EGS_OCTREE_LOCAL eoctree_key5[] = "discard child";
214 static char EGS_OCTREE_LOCAL eoctree_key6[] = "prune tree";
215 
216 extern "C" {
217 
218  EGS_OCTREE_EXPORT EGS_BaseGeometry *createGeometry(EGS_Input *input) {
219 
220  EGS_Input *i;
221 
222  // check that we have an input
223  if (!input) {
224  egsWarning(eoctree_message1,eoctree_message2);
225  return 0;
226  }
227 
228  // read bounding boxes
229  vector<EGS_Octree_bbox> vBox;
230  while ((i = input->takeInputItem(eoctree_key0))) {
231 
232  // read the bounding box minimum
233  vector<EGS_Float> v;
234  int err = i->getInput(eoctree_key1, v);
235  if (err) {
236  egsWarning(eoctree_message1, eoctree_message5);
237  return 0;
238  }
239  if (v.size() != 3) {
240  egsWarning(eoctree_message1, eoctree_message6);
241  return 0;
242  }
243  EGS_Vector bboxMin(v[0],v[1],v[2]);
244 
245  // read the bounding box maximum
246  err = i->getInput(eoctree_key2, v);
247  if (err) {
248  egsWarning(eoctree_message1, eoctree_message7);
249  return 0;
250  }
251  if (v.size() != 3) {
252  egsWarning(eoctree_message1, eoctree_message8);
253  return 0;
254  }
255  EGS_Vector bboxMax(v[0],v[1],v[2]);
256 
257  // read the bounding box resolution
258  vector<int> bboxRes;
259  err = i->getInput(eoctree_key3, bboxRes);
260  if (err) {
261  egsWarning(eoctree_message1, eoctree_message9);
262  return 0;
263  }
264  if (bboxRes.size() != 3) {
265  egsWarning(eoctree_message1, eoctree_message10);
266  return 0;
267  }
268 
269  EGS_Octree_bbox box = EGS_Octree_bbox(bboxMin, bboxMax, bboxRes);
270  vBox.push_back(box);
271  }
272  if (vBox.size() < 1) {
273  egsWarning(eoctree_message1, eoctree_message15);
274  return 0;
275  }
276 
277  // read discard child option
278  bool discardChild = true;
279  string discard;
280  if (input->getInputItem(eoctree_key5)) {
281  int err = input->getInput(eoctree_key5, discard);
282  if (err) {
283  egsWarning(eoctree_message1, eoctree_message11);
284  return 0;
285  }
286  if (discard.find("yes")==string::npos && discard.find("no")==string::npos) {
287  egsWarning(eoctree_message1, eoctree_message12);
288  return 0;
289  }
290  if (discard.find("no")!=string::npos) {
291  discardChild = false;
292  }
293  }
294 
295  // read prune tree option
296  bool pruneTree = true;
297  string prune;
298  if (input->getInputItem(eoctree_key6)) {
299  int err = input->getInput(eoctree_key6, prune);
300  if (err) {
301  egsWarning(eoctree_message1, eoctree_message16);
302  return 0;
303  }
304  if (prune.find("yes")==string::npos && prune.find("no")==string::npos) {
305  egsWarning(eoctree_message1, eoctree_message17);
306  return 0;
307  }
308  if (prune.find("no")!=string::npos) {
309  pruneTree = false;
310  }
311  }
312 
313  // read and load the child geometry
314  string gname;
315  {
316  int err = input->getInput(eoctree_key4, gname);
317  if (err) {
318  egsWarning(eoctree_message1, eoctree_message13);
319  return 0;
320  }
321  }
323  if (!g) {
324  egsWarning(eoctree_message1, eoctree_message14);
325  return 0;
326  }
327 
328  // create the octree geometry
329  EGS_Octree *octree = new EGS_Octree(vBox, pruneTree, g);
330  octree->setName(input);
331  octree->setBoundaryTolerance(input);
332  octree->setLabels(input);
333  octree->printInfo();
334 
335  if (discardChild) {
336  delete g;
337  }
338  return octree;
339  }
340 }
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:226
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:245
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:338
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:57
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.