EGSnrc C++ class library  Report PIRS-898 (2021)
Iwan Kawrakow, Ernesto Mainegra-Hing, Frederic Tessier, Reid Townson and Blake Walters
tetgen_parser.h
1 /*
2 ###############################################################################
3 #
4 # EGS_Mesh TetGen node and ele file parser
5 # Copyright (C) 2022 Max Orok
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: Max Orok, 2022
25 #
26 # Contributors:
27 #
28 ###############################################################################
29 */
30 
31 // exclude from doxygen
33 
34 #ifndef EGS_MESH_TETGEN_PARSER_
35 #define EGS_MESH_TETGEN_PARSER_
36 
37 #include "egs_mesh.h" // for EGS_MeshSpec
38 
39 #include <algorithm>
40 #include <fstream>
41 #include <iostream>
42 #include <set>
43 #include <sstream>
44 #include <stdexcept>
45 #include <string>
46 #include <vector>
47 
48 namespace tetgen_parser {
49 
56 EGS_MeshSpec parse_tetgen_files(const std::string &filename,
57  EGS_InfoFunction info = nullptr);
58 
59 enum class TetGenFile { Node, Ele };
60 
63 namespace internal {
64 
68 static inline void ltrim(std::string &s) {
69  s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
70  return !std::isspace(ch);
71  }));
72 }
73 
87 std::vector<EGS_MeshSpec::Node> parse_tetgen_node_file(std::istream &input,
88  EGS_InfoFunction info) {
89  std::vector<EGS_MeshSpec::Node> nodes;
90  // Parse the header:
91  // ```
92  // num_nodes, num_coordinates (=3), num_attributes, num_boundary_markers
93  // ```
94  // Only num_nodes and num_coordinates (which must equal 3 or an exception is
95  // thrown) are used.
96  int num_nodes = -1;
97  std::string line;
98  {
99  int num_coords = -1;
100  int num_attr = -1;
101  int num_boundary = -1;
102  std::getline(input, line);
103  std::istringstream line_stream(line);
104  line_stream >> num_nodes >> num_coords >> num_attr >> num_boundary;
105  if (line_stream.fail() || num_nodes == -1) {
106  throw std::runtime_error("failed to parse TetGen node file header");
107  }
108  if (num_coords != 3) {
109  throw std::runtime_error("TetGen node file parsing failed, expected"
110  " num_coords = 3");
111  }
112  }
113 
114  if (num_nodes < 50000) {
115  info = nullptr; // don't log for small meshes
116  }
117 
118  egs_mesh::internal::PercentCounter progress(info, "EGS_Mesh: reading " +
119  std::to_string(num_nodes) + " nodes");
120  progress.start(num_nodes);
121 
122  nodes.reserve(num_nodes);
123 
124  // Parse node file body
125  while (nodes.size() < static_cast<std::size_t>(num_nodes)) {
126  std::getline(input, line);
127  // Skip lines starting with #
128  ltrim(line);
129  if (line.rfind('#', 0) == 0) {
130  continue;
131  }
132  std::istringstream line_stream(line);
133  int tag = -1;
134  double x = 0.0;
135  double y = 0.0;
136  double z = 0.0;
137  line_stream >> tag >> x >> y >> z;
138  if (line_stream.fail() || tag == -1) {
139  throw std::runtime_error("TetGen node file parsing failed");
140  }
141  nodes.push_back(EGS_MeshSpec::Node(tag, x, y, z));
142  progress.step(1);
143  }
144  progress.finish("EGS_Mesh: read " + std::to_string(num_nodes) + " nodes");
145  return nodes;
146 }
147 
161 std::vector<EGS_MeshSpec::Tetrahedron> parse_tetgen_ele_file(
162  std::istream &input, EGS_InfoFunction info) {
163  std::vector<EGS_MeshSpec::Tetrahedron> elts;
164  // Parse the header:
165  // ```
166  // num_elts, num_nodes (=4), num_attributes (=1)
167  // ```
168  // num_nodes must be 4 and num_attributes must be 1, or an exception is thrown.
169  int num_elts = -1;
170  std::string line;
171  {
172  int num_nodes = -1;
173  int num_attr = -1;
174  std::getline(input, line);
175  std::istringstream line_stream(line);
176  line_stream >> num_elts >> num_nodes >> num_attr;
177  if (line_stream.fail() || num_elts == -1) {
178  throw std::runtime_error("failed to parse TetGen ele file header");
179  }
180  if (num_nodes != 4) {
181  throw std::runtime_error("TetGen ele file parsing failed, expected"
182  " 4 nodes per tetrahedron");
183  }
184  if (num_attr != 1) {
185  throw std::runtime_error("TetGen ele file parsing failed, expected"
186  " each element to only have one attribute (EGSnrc medium)");
187  }
188  }
189 
190  if (num_elts < 50000) {
191  info = nullptr; // don't log for small meshes
192  }
193 
194  egs_mesh::internal::PercentCounter progress(info, "EGS_Mesh: reading " +
195  std::to_string(num_elts) + " tetrahedrons");
196  progress.start(num_elts);
197 
198  elts.reserve(num_elts);
199  // Parse ele file body
200  while (elts.size() < static_cast<std::size_t>(num_elts)) {
201  std::getline(input, line);
202  // Skip lines starting with #
203  ltrim(line);
204  if (line.rfind('#', 0) == 0) {
205  continue;
206  }
207  std::istringstream line_stream(line);
208  int tag = -1;
209  int n0 = -1;
210  int n1 = -1;
211  int n2 = -1;
212  int n3 = -1;
213  int media = -1;
214  line_stream >> tag >> n0 >> n1 >> n2 >> n3 >> media;
215  if (line_stream.fail() || tag == -1) {
216  throw std::runtime_error("Tetgen ele file parsing failed");
217  }
218  elts.push_back(EGS_MeshSpec::Tetrahedron(tag, media, n0, n1, n2, n3));
219  progress.step(1);
220  }
221  progress.finish("EGS_Mesh: read " + std::to_string(num_elts)
222  + " tetrahedrons");
223 
224  return elts;
225 }
226 
227 // Extract the unique media from the list of all tetrahedrons.
228 std::vector<EGS_MeshSpec::Medium> find_tetgen_elt_media(
229  const std::vector<EGS_MeshSpec::Tetrahedron> &elts) {
230  // Find set of unique media tags
231  std::set<int> media_tags;
232  for (const auto &e: elts) {
233  media_tags.insert(e.medium_tag);
234  }
235  std::vector<EGS_MeshSpec::Medium> media;
236  media.reserve(media_tags.size());
237  for (const auto &m : media_tags) {
238  // TetGen files only store media tag numbers, so use a string version of
239  // the media tag for the `medium_name` field instead.
240  media.push_back(EGS_MeshSpec::Medium(m, std::to_string(m)));
241  }
242  return media;
243 }
244 } // namespace tetgen_parser::internal
245 
246 EGS_MeshSpec parse_tetgen_files(const std::string &filename,
247  TetGenFile tetgen_file_kind, EGS_InfoFunction info /*default=nullptr*/) {
248  std::string node_file;
249  std::string ele_file;
250  if (tetgen_file_kind == TetGenFile::Ele) {
251  ele_file = filename;
252  node_file = filename.substr(0, filename.size() - 4) + ".node";
253  }
254  else if (tetgen_file_kind == TetGenFile::Node) {
255  ele_file = filename.substr(0, filename.size() - 5) + ".ele";
256  node_file = filename;
257  }
258  else {
259  throw std::runtime_error("Unhandled TetGen file type");
260  }
261 
262  std::ifstream node_stream(node_file);
263  if (!node_stream) {
264  throw std::runtime_error(std::string("Tetgen node file `") + node_file
265  + "` does not exist or is not readable");
266  }
267  std::ifstream ele_stream(ele_file);
268  if (!ele_stream) {
269  throw std::runtime_error(std::string("Tetgen ele file `") + ele_file
270  + "` does not exist or is not readable");
271  }
272 
273  auto nodes = internal::parse_tetgen_node_file(node_stream, info);
274  auto elts = internal::parse_tetgen_ele_file(ele_stream, info);
275  auto media = internal::find_tetgen_elt_media(elts);
276  return EGS_MeshSpec(std::move(elts), std::move(nodes), std::move(media));
277 }
278 
279 } // namespace tetgen_parser
280 
281 #endif // EGS_MESH_TETGEN_PARSER_
282 
A 3D point. Units are cm.
Definition: egs_mesh.h:111
A tetrahedral mesh element.
Definition: egs_mesh.h:98
A medium. The medium name must match an EGSnrc medium name.
Definition: egs_mesh.h:121
A container for raw unstructured tetrahedral mesh data.
Definition: egs_mesh.h:94
Tetrahedral mesh geometry: header.
void(* EGS_InfoFunction)(const char *,...)
Defines a function printf-like prototype for functions to be used to report info, warnings...