1use std::fmt::Debug;
6use std::fs::File;
7use std::io::{BufRead, BufReader, BufWriter, Write};
8use std::str::FromStr;
9
10use structopt::StructOpt;
11
12#[derive(StructOpt, Debug)]
14#[structopt(name = "dose2gmsh", author = "Max Orok <maxwellorok@gmail.com>", about = "Convert dosxyznrc 3ddose files to Gmsh msh files")]
15pub struct Cli {
16 #[structopt(parse(from_os_str))]
18 pub input_file: std::path::PathBuf,
19 #[structopt(parse(from_os_str), short, long)]
21 pub output_file: Option<std::path::PathBuf>,
22 #[structopt(short, long, default_value = "msh2")]
24 pub format: Fmt
25}
26
27#[derive(Debug, Copy, Clone)]
29pub enum Fmt {
30 Csv,
31 Msh2,
32}
33
34impl std::str::FromStr for Fmt {
35 type Err = String;
36 fn from_str(fmt: &str) -> Result<Self, Self::Err> {
37 match fmt {
38 "csv" => Ok(Fmt::Csv),
39 "msh2" => Ok(Fmt::Msh2),
40 _ => Err("Could not parse the format".to_string()),
41 }
42 }
43}
44
45#[derive(Debug, Clone)]
90pub struct DoseBlock {
91 pub xs: Vec<f64>,
93 pub ys: Vec<f64>,
95 pub zs: Vec<f64>,
97 pub doses: Vec<f64>,
99 pub uncerts: Vec<f64>,
101}
102
103impl DoseBlock {
104 pub fn from_3d_dose<P: AsRef<std::path::Path>>(input_file: P) -> Result<DoseBlock, std::io::Error> {
106 let dose_input = BufReader::new(File::open(input_file)?);
107
108 let mut lines = dose_input.lines().map(|l| l.unwrap());
109 let (num_x, num_y, num_z) = {
111 let voxel_nums = lines.next().expect("voxel numbers");
112 let voxel_nums = parse_simple_line::<usize>(voxel_nums, "voxel number", 3);
113 (voxel_nums[0], voxel_nums[1], voxel_nums[2])
114 };
115
116 let xs = parse_simple_line::<f64>(
118 lines.next().expect("x-coordinates"),
119 "x-coordinate",
120 num_x + 1,
121 );
122
123 let ys = parse_simple_line::<f64>(
125 lines.next().expect("y-coordinates"),
126 "y-coordinate",
127 num_y + 1,
128 );
129
130 let zs = parse_simple_line::<f64>(
132 lines.next().expect("z-coordinates"),
133 "z-coordinate",
134 num_z + 1,
135 );
136
137 let num_voxels = num_x * num_y * num_z;
138
139 let doses = parse_simple_line::<f64>(lines.next().expect("doses"), "dose value", num_voxels);
141
142 let uncerts = parse_simple_line::<f64>(
144 lines.next().expect("uncerts"),
145 "uncertainty value",
146 num_voxels,
147 );
148
149 Ok(DoseBlock {
150 xs,
151 ys,
152 zs,
153 doses,
154 uncerts,
155 })
156
157 }
158
159 pub fn num_x(&self) -> usize {
161 self.xs.len() - 1
162 }
163
164 pub fn num_y(&self) -> usize {
166 self.ys.len() - 1
167 }
168
169 pub fn num_z(&self) -> usize {
171 self.zs.len() - 1
172 }
173
174 pub fn num_voxels(&self) -> usize {
176 self.num_x() * self.num_y() * self.num_z()
177 }
178
179 pub fn num_nodes(&self) -> usize {
181 self.xs.len() * self.ys.len() * self.zs.len()
182 }
183
184 pub fn grid_index(&self, i: usize, j: usize, k: usize) -> usize {
186 i + self.xs.len() * j + self.xs.len() * self.ys.len() * k
187 }
188
189 pub fn write_msh2<P: AsRef<std::path::Path>>(&self, output: P) -> Result<(), std::io::Error> {
191 use itertools::Itertools;
192
193 let mut filestream = BufWriter::new(File::create(output)?);
194
195 writeln!(&mut filestream, "$MeshFormat\n2.2 0 8\n$EndMeshFormat")?;
197 write!(&mut filestream, "$Nodes\n{}\n", self.num_nodes())?;
199 for (k, z) in self.zs.iter().enumerate() {
200 for (j, y) in self.ys.iter().enumerate() {
201 for (i, x) in self.xs.iter().enumerate() {
202 writeln!(
204 &mut filestream,
205 "{} {} {} {}",
206 self.grid_index(i, j, k) + 1,
207 x,
208 y,
209 z
210 )?;
211 }
212 }
213 }
214 writeln!(&mut filestream, "$EndNodes")?;
215
216 let mut x_nodes = 1..=self.num_nodes();
218 let gmsh_y_index = |x_index| x_index + self.xs.len();
219 let gmsh_z_index = |x_index| x_index + self.xs.len() * self.ys.len();
220
221 writeln!(&mut filestream, "$Elements\n{}", self.num_voxels())?;
222 for index in 0..self.num_voxels() {
223 if index != 0 && index % self.num_x() == 0 {
241 x_nodes = x_nodes.dropping(1);
242 }
243
244 if index != 0 && index % (self.num_x() * self.num_y()) == 0 {
246 x_nodes = x_nodes.dropping(self.xs.len());
247 }
248
249 let xl = x_nodes.next().unwrap(); let xr = xl + 1; let yl = gmsh_y_index(xl); let yr = yl + 1; let zl = gmsh_z_index(xl); let zr = zl + 1; let yzl = gmsh_z_index(yl); let yzr = yzl + 1; writeln!(
262 &mut filestream,
263 "{} 5 2 0 0 {} {} {} {} {} {} {} {}",
267 index + 1,
268 xl, xr, yr, yl, zl, zr, yzr, yzl, )?;
277 }
278 writeln!(&mut filestream, "$EndElements")?;
279
280 let mut write_elt_data = |name: &str, data: &Vec<f64>| -> Result<(), std::io::Error> {
281 writeln!(&mut filestream, "$ElementData")?;
282 writeln!(&mut filestream, "1\n{}", name)?;
284 writeln!(&mut filestream, "1\n0.0")?;
286 writeln!(&mut filestream, "3\n0\n1\n{}", data.len())?;
291 for (index, val) in data.iter().enumerate() {
292 writeln!(&mut filestream, "{} {}", index + 1, val)?;
293 }
294 writeln!(&mut filestream, "$EndElementData")?;
295 Ok(())
296 };
297
298 write_elt_data(r#""Dose [Gy·cm2]""#, &self.doses)?;
299 write_elt_data(r#""Uncertainty fraction""#, &self.uncerts)
300 }
301
302 pub fn write_csv<P: AsRef<std::path::Path>>(&self, output: P) -> Result<(), std::io::Error> {
304 let calc_centroids = |pts: &Vec<f64>| -> Vec<f64> {
305 let num_centroids = pts.len() - 1;
306 let mut cs = Vec::with_capacity(num_centroids);
307 for i in 0..num_centroids {
308 cs.push((pts[i] + pts[i+1]) / 2.0);
309 }
310 cs
311 };
312
313 let voxel_idx = |i: usize, j: usize, k: usize| -> usize {
314 i + self.num_x() * j + self.num_x() * self.num_y() * k
315 };
316
317 let mut file = BufWriter::new(File::create(output)?);
318 writeln!(&mut file, "xc [cm],yc [cm],zc [cm],Dose [Gy cm2],Uncertainty fraction")?;
319 for (k, z) in calc_centroids(&self.zs).into_iter().enumerate() {
320 for (j, y) in calc_centroids(&self.ys).into_iter().enumerate() {
321 for (i, x) in calc_centroids(&self.xs).into_iter().enumerate() {
322 writeln!(&mut file, "{},{},{},{},{}", x, y, z,
323 self.doses[voxel_idx(i, j, k)],
324 self.uncerts[voxel_idx(i, j, k)])?;
325 }
326 }
327 }
328 Ok(())
329 }
330}
331
332fn parse_simple_line<T>(line: String, title: &'static str, expect_len: usize) -> Vec<T>
333where
334 T: FromStr,
335 <T as std::str::FromStr>::Err: Debug,
336{
337 let entries: Vec<T> = line
338 .trim()
339 .split_whitespace()
340 .map(|num| num.parse::<T>().expect(title))
341 .collect();
342 assert!(entries.len() == expect_len);
343 entries
344}
345
346#[cfg(test)]
347mod tests {
348
349 use super::*;
350
351 #[test]
352 fn read_3ddose() {
353 let mut path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
354 path.push("props");
355 path.push("water_block.3ddose");
356
357 let data = DoseBlock::from_3d_dose(&path).expect("couldn't parse 3ddose file");
358
359 assert_eq!(data.num_voxels(), 40 * 40 * 40);
360 assert_eq!(data.num_nodes(), 41 * 41 * 41);
361 assert_eq!(data.grid_index(0, 0, 0), 0);
363 assert_eq!(data.grid_index(0, 1, 0), 41);
364 assert_eq!(data.grid_index(0, 0, 1), 1681);
365 assert_eq!(data.grid_index(0, 0, 1), 1681);
366 assert_eq!(data.uncerts[21503], 0.37652693977336593);
368 }
369
370 #[test]
371 fn write_csv() {
372 let data = DoseBlock {
373 xs: vec![0.0, 2.0],
374 ys: vec![0.0, 2.0, 4.0],
375 zs: vec![0.0, 2.0, 4.0, 8.0],
376 doses: vec![10.0, 20.0, 30.0, 40.0, 50.0, 60.0],
377 uncerts: vec![0.1, 0.2, 0.3, 0.4, 0.5, 0.6],
378 };
379
380 let file = "tmp.csv";
381 data.write_csv(file).unwrap();
382 let mut rdr = csv::Reader::from_path(file).unwrap();
383 assert_eq!(
384 rdr.headers().unwrap(),
385 vec!["xc [cm]", "yc [cm]", "zc [cm]", "Dose [Gy cm2]", "Uncertainty fraction"]
386 );
387
388 let mut records = rdr.records();
389 let record = records.next().unwrap().unwrap();
390 approx::relative_eq!(record[0].parse::<f64>().unwrap(), 1.0);
391 approx::relative_eq!(record[1].parse::<f64>().unwrap(), 1.0);
392 approx::relative_eq!(record[2].parse::<f64>().unwrap(), 1.0);
393 approx::relative_eq!(record[3].parse::<f64>().unwrap(), 10.0);
394 approx::relative_eq!(record[4].parse::<f64>().unwrap(), 0.1);
395
396 let record = records.next().unwrap().unwrap();
397 approx::relative_eq!(record[0].parse::<f64>().unwrap(), 1.0);
398 approx::relative_eq!(record[1].parse::<f64>().unwrap(), 3.0);
399 approx::relative_eq!(record[2].parse::<f64>().unwrap(), 1.0);
400 approx::relative_eq!(record[3].parse::<f64>().unwrap(), 20.0);
401 approx::relative_eq!(record[4].parse::<f64>().unwrap(), 0.2);
402
403 let record = records.next().unwrap().unwrap();
404 approx::relative_eq!(record[0].parse::<f64>().unwrap(), 1.0);
405 approx::relative_eq!(record[1].parse::<f64>().unwrap(), 1.0);
406 approx::relative_eq!(record[2].parse::<f64>().unwrap(), 3.0);
407 approx::relative_eq!(record[3].parse::<f64>().unwrap(), 30.0);
408 approx::relative_eq!(record[4].parse::<f64>().unwrap(), 0.3);
409
410 std::fs::remove_file(file).unwrap();
411 }
412}