libfs
Header-only C++11 library for accessing FreeSurfer neuroimaging data
libfs.h
Go to the documentation of this file.
1 #pragma once
2 
3 #include <iostream>
4 #include <climits>
5 #include <stdio.h>
6 #include <vector>
7 #include <fstream>
8 #include <cassert>
9 #include <sstream>
10 #include <stdexcept>
11 #include <map>
12 #include <unordered_set>
13 #include <unordered_map>
14 #include <cmath>
15 #include <algorithm>
16 #include <chrono>
17 #include <cstdint>
18 #include <cstring>
19 
20 #define LIBFS_VERSION "0.4.1"
21 #define LIBFS_VERISION_MAJOR 0 # deprecated, use LIBFS_VERSION_MAJOR instead
22 #define LIBFS_VERISION_MINOR 4 # deprecated, use LIBFS_VERSION_MINOR instead
23 #define LIBFS_VERISION_PATCH 1 # deprecated, use LIBFS_VERSION_PATCH instead
24 #define LIBFS_VERSION_MAJOR 0
25 #define LIBFS_VERSION_MINOR 4
26 #define LIBFS_VERSION_PATCH 1
27 
28 // -- Security / defensive hardening configuration -------------------------------------
29 // Users can #define any of these BEFORE including libfs.h to override the defaults.
30 
33 #define LIBFS_MAX_ALLOC_BYTES_DEFAULT (2ULL * 1024ULL * 1024ULL * 1024ULL)
34 
36 #ifndef LIBFS_MAX_ALLOC_BYTES
37 #define LIBFS_MAX_ALLOC_BYTES LIBFS_MAX_ALLOC_BYTES_DEFAULT
38 #endif
39 
41 #ifndef LIBFS_MAX_STRING_LENGTH
42 #define LIBFS_MAX_STRING_LENGTH 4096
43 #endif
44 
46 #ifndef LIBFS_MAX_COLORTABLE_ENTRIES
47 #define LIBFS_MAX_COLORTABLE_ENTRIES 10000
48 #endif
49 // -- End security configuration --------------------------------------------------------
50 
53 
113 // Set apptag (printed as prefix of debug messages) for debug messages.
114 // Users can overwrite this by defining LIBFS_APPTAG before including 'libfs.h'.
115 #ifndef LIBFS_APPTAG
116 #define LIBFS_APPTAG "[libfs] "
117 #endif
118 
119 // Set default.
120 #define LIBFS_DBG_WARNING
121 
122 // If the user wants something below our default, remove our default.
123 #ifdef LIBFS_DBG_NONE
124 #undef LIBFS_DBG_WARNING
125 #endif
126 
127 #ifdef LIBFS_DBG_CRITICAL
128 #undef LIBFS_DBG_WARNING
129 #endif
130 
131 #ifdef LIBFS_DBG_ERROR
132 #undef LIBFS_DBG_WARNING
133 #endif
134 
135 // Ensure that the user does not have to define all debug levels
136 // up to the one they actually want, by defining all lower ones for them.
137 #ifdef LIBFS_DBG_EXCESSIVE
138 #define LIBFS_DBG_VERBOSE
139 #endif
140 
141 #ifdef LIBFS_DBG_VERBOSE
142 #define LIBFS_DBG_INFO
143 #endif
144 
145 #ifdef LIBFS_DBG_INFO
146 #define LIBFS_DBG_WARNING
147 #endif
148 
149 #ifdef LIBFS_DBG_WARNING
150 #define LIBFS_DBG_ERROR
151 #endif
152 
153 #ifdef LIBFS_DBG_ERROR
154 #define LIBFS_DBG_CRITICAL
155 #endif
156 
157 // End of debug handling.
158 
159 namespace fs
160 {
161 
162  namespace util
163  {
164 
168  tm _localtime(const std::time_t &time)
169  {
170  std::tm tm_snapshot;
171 #if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__))
172  ::localtime_s(&tm_snapshot, &time);
173 #else
174  ::localtime_r(&time, &tm_snapshot); // POSIX
175 #endif
176  return tm_snapshot;
177  }
178 
187  std::string time_tag(std::chrono::system_clock::time_point t)
188  {
189  auto as_time_t = std::chrono::system_clock::to_time_t(t);
190  struct tm tm;
191  char time_buffer[64];
192  // if (::gmtime_r(&as_time_t, &tm)) {
193  tm = _localtime(as_time_t);
194  if (std::strftime(time_buffer, sizeof(time_buffer), "%F %T", &tm))
195  {
196  return std::string{time_buffer};
197  }
198  throw std::runtime_error("Failed to get current date as string");
199  }
200 
202  const std::string LOGTAG_CRITICAL = "CRITICAL";
203 
205  const std::string LOGTAG_ERROR = "ERROR";
206 
208  const std::string LOGTAG_WARNING = "WARNING";
209 
211  const std::string LOGTAG_INFO = "INFO";
212 
214  const std::string LOGTAG_VERBOSE = "VERBOSE";
215 
217  const std::string LOGTAG_EXCESSIVE = "EXCESSIVE";
218 
222  inline void log(std::string const &message, std::string const loglevel = "INFO")
223  {
224  std::cout << LIBFS_APPTAG << "[" << loglevel << "] [" << fs::util::time_tag(std::chrono::system_clock::now()) << "] " << message << "\n";
225  }
226 
227  // -- Security / defensive hardening helpers -----------------------------------------
228 
231  inline bool safe_multiply(size_t a, size_t b, size_t &result)
232  {
233  if (a == 0 || b == 0)
234  {
235  result = 0;
236  return true;
237  }
238  if (a > std::numeric_limits<size_t>::max() / b)
239  {
240  return false;
241  }
242  result = a * b;
243  return true;
244  }
245 
250  inline bool check_alloc(size_t num_elements, size_t bytes_per_element)
251  {
252  size_t total_bytes = 0;
253  if (!safe_multiply(num_elements, bytes_per_element, total_bytes))
254  {
255  return false;
256  }
257  if (total_bytes > LIBFS_MAX_ALLOC_BYTES)
258  {
259  return false;
260  }
261  return true;
262  }
263 
266  inline size_t get_file_size(const std::string &filename)
267  {
268  std::ifstream ifs(filename, std::ios::binary | std::ios::ate);
269  if (!ifs.is_open())
270  {
271  return 0;
272  }
273  std::streampos end = ifs.tellg();
274  if (end < 0)
275  {
276  return 0;
277  }
278  return static_cast<size_t>(end);
279  }
280 
283  inline bool is_finite_float(float value)
284  {
285  return !std::isnan(value) && !std::isinf(value);
286  }
287 
288  // -- End security helpers ---------------------------------------------------------
289 
298  inline bool ends_with(std::string const &value, std::string const &suffix)
299  {
300  if (suffix.size() > value.size())
301  return false;
302  return std::equal(suffix.rbegin(), suffix.rend(), value.rbegin());
303  }
304 
313  inline bool ends_with(std::string const &value, std::initializer_list<std::string> suffixes)
314  {
315  for (auto suffix : suffixes)
316  {
317  if (ends_with(value, suffix))
318  {
319  return true;
320  }
321  }
322  return false;
323  }
324 
337  template <typename T>
338  std::vector<std::vector<T>> v2d(std::vector<T> values, size_t num_cols)
339  {
340  std::vector<std::vector<T>> result;
341  for (std::size_t i = 0; i < values.size(); ++i)
342  {
343  if (i % num_cols == 0)
344  {
345  result.resize(result.size() + 1);
346  }
347  result[i / num_cols].push_back(values[i]);
348  }
349  return result;
350  }
351 
362  template <typename T>
363  std::vector<T> vflatten(std::vector<std::vector<T>> values)
364  {
365  size_t total_size = 0;
366  for (std::size_t i = 0; i < values.size(); i++)
367  {
368  total_size += values[i].size();
369  }
370 
371  std::vector<T> result = std::vector<T>(total_size);
372  size_t cur_idx = 0;
373  for (std::size_t i = 0; i < values.size(); i++)
374  {
375  for (std::size_t j = 0; j < values[i].size(); j++)
376  {
377  result[cur_idx] = values[i][j];
378  cur_idx++;
379  }
380  }
381  return result;
382  }
383 
394  inline bool starts_with(std::string const &value, std::string const &prefix)
395  {
396  if (prefix.length() > value.length())
397  return false;
398  return value.rfind(prefix, 0) == 0;
399  }
400 
411  inline bool starts_with(std::string const &value, std::initializer_list<std::string> prefixes)
412  {
413  for (auto prefix : prefixes)
414  {
415  if (starts_with(value, prefix))
416  {
417  return true;
418  }
419  }
420  return false;
421  }
422 
434  inline bool file_exists(const std::string &name)
435  {
436  if (FILE *file = fopen(name.c_str(), "r"))
437  {
438  fclose(file);
439  return true;
440  }
441  else
442  {
443  return false;
444  }
445  }
446 
462  std::string fullpath(std::initializer_list<std::string> path_components, std::string path_sep = std::string("/"))
463  {
464  std::string fp;
465  if (path_components.size() == 0)
466  {
467  throw std::invalid_argument("The 'path_components' must not be empty.");
468  }
469 
470  std::string comp;
471  std::string comp_mod;
472  size_t idx = 0;
473  for (auto comp : path_components)
474  {
475  comp_mod = comp;
476  if (idx != 0)
477  { // We keep a leading slash intact for the first element (absolute path).
478  if (starts_with(comp, path_sep))
479  {
480  comp_mod = comp.substr(1, comp.size() - 1);
481  }
482  }
483 
484  if (ends_with(comp_mod, path_sep))
485  {
486  comp_mod = comp_mod.substr(0, comp_mod.size() - 1);
487  }
488 
489  fp += comp_mod;
490  if (idx < path_components.size() - 1)
491  {
492  fp += path_sep;
493  }
494  idx++;
495  }
496  return fp;
497  }
498 
509  void str_to_file(const std::string &filename, const std::string rep)
510  {
511  std::ofstream ofs;
512  ofs.open(filename, std::ofstream::out);
513 #ifdef LIBFS_DBG_VERBOSE
514  std::cout << LIBFS_APPTAG << "Opening file '" << filename << "' for writing.\n";
515 #endif
516  if (ofs.is_open())
517  {
518  ofs << rep;
519  ofs.close();
520  }
521  else
522  {
523  throw std::runtime_error("Unable to open file '" + filename + "' for writing.\n");
524  }
525  }
526 
565  std::vector<uint8_t> viridis(const std::vector<float> &data, float vmin = NAN, float vmax = NAN, uint8_t nan_r = 255, uint8_t nan_g = 255, uint8_t nan_b = 255)
566  {
567  std::vector<uint8_t> colors;
568  if (data.empty())
569  {
570  return colors;
571  }
572  colors.reserve(data.size() * 3);
573 
574  // The official 256-entry Viridis colormap (RGB, floats in [0, 1]), identical to the
575  // matplotlib viridis lookup table. Linearly interpolated between samples below.
576  static const float lut[768] = {
577  0.267004, 0.004874, 0.329415, 0.26851, 0.009605, 0.335427, 0.269944, 0.014625,
578  0.341379, 0.271305, 0.019942, 0.347269, 0.272594, 0.025563, 0.353093, 0.273809,
579  0.031497, 0.358853, 0.274952, 0.037752, 0.364543, 0.276022, 0.044167, 0.370164,
580  0.277018, 0.050344, 0.375715, 0.277941, 0.056324, 0.381191, 0.278791, 0.062145,
581  0.386592, 0.279566, 0.067836, 0.391917, 0.280267, 0.073417, 0.397163, 0.280894,
582  0.078907, 0.402329, 0.281446, 0.08432, 0.407414, 0.281924, 0.089666, 0.412415,
583  0.282327, 0.094955, 0.417331, 0.282656, 0.100196, 0.42216, 0.28291, 0.105393,
584  0.426902, 0.283091, 0.110553, 0.431554, 0.283197, 0.11568, 0.436115, 0.283229,
585  0.120777, 0.440584, 0.283187, 0.125848, 0.44496, 0.283072, 0.130895, 0.449241,
586  0.282884, 0.13592, 0.453427, 0.282623, 0.140926, 0.457517, 0.28229, 0.145912,
587  0.46151, 0.281887, 0.150881, 0.465405, 0.281412, 0.155834, 0.469201, 0.280868,
588  0.160771, 0.472899, 0.280255, 0.165693, 0.476498, 0.279574, 0.170599, 0.479997,
589  0.278826, 0.17549, 0.483397, 0.278012, 0.180367, 0.486697, 0.277134, 0.185228,
590  0.489898, 0.276194, 0.190074, 0.493001, 0.275191, 0.194905, 0.496005, 0.274128,
591  0.199721, 0.498911, 0.273006, 0.20452, 0.501721, 0.271828, 0.209303, 0.504434,
592  0.270595, 0.214069, 0.507052, 0.269308, 0.218818, 0.509577, 0.267968, 0.223549,
593  0.512008, 0.26658, 0.228262, 0.514349, 0.265145, 0.232956, 0.516599, 0.263663,
594  0.237631, 0.518762, 0.262138, 0.242286, 0.520837, 0.260571, 0.246922, 0.522828,
595  0.258965, 0.251537, 0.524736, 0.257322, 0.25613, 0.526563, 0.255645, 0.260703,
596  0.528312, 0.253935, 0.265254, 0.529983, 0.252194, 0.269783, 0.531579, 0.250425,
597  0.27429, 0.533103, 0.248629, 0.278775, 0.534556, 0.246811, 0.283237, 0.535941,
598  0.244972, 0.287675, 0.53726, 0.243113, 0.292092, 0.538516, 0.241237, 0.296485,
599  0.539709, 0.239346, 0.300855, 0.540844, 0.237441, 0.305202, 0.541921, 0.235526,
600  0.309527, 0.542944, 0.233603, 0.313828, 0.543914, 0.231674, 0.318106, 0.544834,
601  0.229739, 0.322361, 0.545706, 0.227802, 0.326594, 0.546532, 0.225863, 0.330805,
602  0.547314, 0.223925, 0.334994, 0.548053, 0.221989, 0.339161, 0.548752, 0.220057,
603  0.343307, 0.549413, 0.21813, 0.347432, 0.550038, 0.21621, 0.351535, 0.550627,
604  0.214298, 0.355619, 0.551184, 0.212395, 0.359683, 0.55171, 0.210503, 0.363727,
605  0.552206, 0.208623, 0.367752, 0.552675, 0.206756, 0.371758, 0.553117, 0.204903,
606  0.375746, 0.553533, 0.203063, 0.379716, 0.553925, 0.201239, 0.38367, 0.554294,
607  0.19943, 0.387607, 0.554642, 0.197636, 0.391528, 0.554969, 0.19586, 0.395433,
608  0.555276, 0.1941, 0.399323, 0.555565, 0.192357, 0.403199, 0.555836, 0.190631,
609  0.407061, 0.556089, 0.188923, 0.41091, 0.556326, 0.187231, 0.414746, 0.556547,
610  0.185556, 0.41857, 0.556753, 0.183898, 0.422383, 0.556944, 0.182256, 0.426184,
611  0.55712, 0.180629, 0.429975, 0.557282, 0.179019, 0.433756, 0.55743, 0.177423,
612  0.437527, 0.557565, 0.175841, 0.44129, 0.557685, 0.174274, 0.445044, 0.557792,
613  0.172719, 0.448791, 0.557885, 0.171176, 0.45253, 0.557965, 0.169646, 0.456262,
614  0.55803, 0.168126, 0.459988, 0.558082, 0.166617, 0.463708, 0.558119, 0.165117,
615  0.467423, 0.558141, 0.163625, 0.471133, 0.558148, 0.162142, 0.474838, 0.55814,
616  0.160665, 0.47854, 0.558115, 0.159194, 0.482237, 0.558073, 0.157729, 0.485932,
617  0.558013, 0.15627, 0.489624, 0.557936, 0.154815, 0.493313, 0.55784, 0.153364,
618  0.497, 0.557724, 0.151918, 0.500685, 0.557587, 0.150476, 0.504369, 0.55743,
619  0.149039, 0.508051, 0.55725, 0.147607, 0.511733, 0.557049, 0.14618, 0.515413,
620  0.556823, 0.144759, 0.519093, 0.556572, 0.143343, 0.522773, 0.556295, 0.141935,
621  0.526453, 0.555991, 0.140536, 0.530132, 0.555659, 0.139147, 0.533812, 0.555298,
622  0.13777, 0.537492, 0.554906, 0.136408, 0.541173, 0.554483, 0.135066, 0.544853,
623  0.554029, 0.133743, 0.548535, 0.553541, 0.132444, 0.552216, 0.553018, 0.131172,
624  0.555899, 0.552459, 0.129933, 0.559582, 0.551864, 0.128729, 0.563265, 0.551229,
625  0.127568, 0.566949, 0.550556, 0.126453, 0.570633, 0.549841, 0.125394, 0.574318,
626  0.549086, 0.124395, 0.578002, 0.548287, 0.123463, 0.581687, 0.547445, 0.122606,
627  0.585371, 0.546557, 0.121831, 0.589055, 0.545623, 0.121148, 0.592739, 0.544641,
628  0.120565, 0.596422, 0.543611, 0.120092, 0.600104, 0.54253, 0.119738, 0.603785,
629  0.5414, 0.119512, 0.607464, 0.540218, 0.119423, 0.611141, 0.538982, 0.119483,
630  0.614817, 0.537692, 0.119699, 0.61849, 0.536347, 0.120081, 0.622161, 0.534946,
631  0.120638, 0.625828, 0.533488, 0.12138, 0.629492, 0.531973, 0.122312, 0.633153,
632  0.530398, 0.123444, 0.636809, 0.528763, 0.12478, 0.640461, 0.527068, 0.126326,
633  0.644107, 0.525311, 0.128087, 0.647749, 0.523491, 0.130067, 0.651384, 0.521608,
634  0.132268, 0.655014, 0.519661, 0.134692, 0.658636, 0.517649, 0.137339, 0.662252,
635  0.515571, 0.14021, 0.665859, 0.513427, 0.143303, 0.669459, 0.511215, 0.146616,
636  0.67305, 0.508936, 0.150148, 0.676631, 0.506589, 0.153894, 0.680203, 0.504172,
637  0.157851, 0.683765, 0.501686, 0.162016, 0.687316, 0.499129, 0.166383, 0.690856,
638  0.496502, 0.170948, 0.694384, 0.493803, 0.175707, 0.6979, 0.491033, 0.180653,
639  0.701402, 0.488189, 0.185783, 0.704891, 0.485273, 0.19109, 0.708366, 0.482284,
640  0.196571, 0.711827, 0.479221, 0.202219, 0.715272, 0.476084, 0.20803, 0.718701,
641  0.472873, 0.214, 0.722114, 0.469588, 0.220124, 0.725509, 0.466226, 0.226397,
642  0.728888, 0.462789, 0.232815, 0.732247, 0.459277, 0.239374, 0.735588, 0.455688,
643  0.24607, 0.73891, 0.452024, 0.252899, 0.742211, 0.448284, 0.259857, 0.745492,
644  0.444467, 0.266941, 0.748751, 0.440573, 0.274149, 0.751988, 0.436601, 0.281477,
645  0.755203, 0.432552, 0.288921, 0.758394, 0.428426, 0.296479, 0.761561, 0.424223,
646  0.304148, 0.764704, 0.419943, 0.311925, 0.767822, 0.415586, 0.319809, 0.770914,
647  0.411152, 0.327796, 0.77398, 0.40664, 0.335885, 0.777018, 0.402049, 0.344074,
648  0.780029, 0.397381, 0.35236, 0.783011, 0.392636, 0.360741, 0.785964, 0.387814,
649  0.369214, 0.788888, 0.382914, 0.377779, 0.791781, 0.377939, 0.386433, 0.794644,
650  0.372886, 0.395174, 0.797475, 0.367757, 0.404001, 0.800275, 0.362552, 0.412913,
651  0.803041, 0.357269, 0.421908, 0.805774, 0.35191, 0.430983, 0.808473, 0.346476,
652  0.440137, 0.811138, 0.340967, 0.449368, 0.813768, 0.335384, 0.458674, 0.816363,
653  0.329727, 0.468053, 0.818921, 0.323998, 0.477504, 0.821444, 0.318195, 0.487026,
654  0.823929, 0.312321, 0.496615, 0.826376, 0.306377, 0.506271, 0.828786, 0.300362,
655  0.515992, 0.831158, 0.294279, 0.525776, 0.833491, 0.288127, 0.535621, 0.835785,
656  0.281908, 0.545524, 0.838039, 0.275626, 0.555484, 0.840254, 0.269281, 0.565498,
657  0.84243, 0.262877, 0.575563, 0.844566, 0.256415, 0.585678, 0.846661, 0.249897,
658  0.595839, 0.848717, 0.243329, 0.606045, 0.850733, 0.236712, 0.616293, 0.852709,
659  0.230052, 0.626579, 0.854645, 0.223353, 0.636902, 0.856542, 0.21662, 0.647257,
660  0.8584, 0.209861, 0.657642, 0.860219, 0.203082, 0.668054, 0.861999, 0.196293,
661  0.678489, 0.863742, 0.189503, 0.688944, 0.865448, 0.182725, 0.699415, 0.867117,
662  0.175971, 0.709898, 0.868751, 0.169257, 0.720391, 0.87035, 0.162603, 0.730889,
663  0.871916, 0.156029, 0.741388, 0.873449, 0.149561, 0.751884, 0.874951, 0.143228,
664  0.762373, 0.876424, 0.137064, 0.772852, 0.877868, 0.131109, 0.783315, 0.879285,
665  0.125405, 0.79376, 0.880678, 0.120005, 0.804182, 0.882046, 0.114965, 0.814576,
666  0.883393, 0.110347, 0.82494, 0.88472, 0.106217, 0.83527, 0.886029, 0.102646,
667  0.845561, 0.887322, 0.099702, 0.85581, 0.888601, 0.097452, 0.866013, 0.889868,
668  0.095953, 0.876168, 0.891125, 0.09525, 0.886271, 0.892374, 0.095374, 0.89632,
669  0.893616, 0.096335, 0.906311, 0.894855, 0.098125, 0.916242, 0.896091, 0.100717,
670  0.926106, 0.89733, 0.104071, 0.935904, 0.89857, 0.108131, 0.945636, 0.899815,
671  0.112838, 0.9553, 0.901065, 0.118128, 0.964894, 0.902323, 0.123941, 0.974417,
672  0.90359, 0.130215, 0.983868, 0.904867, 0.136897, 0.993248, 0.906157, 0.143936,
673  };
674 
675  const int n = 256;
676 
677  bool auto_min = std::isnan(vmin);
678  bool auto_max = std::isnan(vmax);
679 
680  // Determine the finite (non-NaN) min/max of the data, used for auto range.
681  float data_min = NAN;
682  float data_max = NAN;
683  bool have_finite = false;
684  for (size_t i = 0; i < data.size(); i++)
685  {
686  if (std::isnan(data[i]))
687  {
688  continue;
689  }
690  if (!have_finite)
691  {
692  data_min = data[i];
693  data_max = data[i];
694  have_finite = true;
695  }
696  else
697  {
698  if (data[i] < data_min)
699  {
700  data_min = data[i];
701  }
702  if (data[i] > data_max)
703  {
704  data_max = data[i];
705  }
706  }
707  }
708 
709  float lo = auto_min ? data_min : vmin;
710  float hi = auto_max ? data_max : vmax;
711 
712  if (!auto_min && !auto_max)
713  {
714  if (vmin > vmax)
715  {
716  throw std::invalid_argument("In viridis(): 'vmin' must not be greater than 'vmax'.");
717  }
718  }
719 
720  if (!have_finite)
721  {
722  // All input values are NaN: map the whole vector to the configured NaN color.
723  for (size_t i = 0; i < data.size(); i++)
724  {
725  colors.push_back(nan_r);
726  colors.push_back(nan_g);
727  colors.push_back(nan_b);
728  }
729  return colors;
730  }
731 
732  bool constant = (hi <= lo);
733 
734  for (size_t i = 0; i < data.size(); i++)
735  {
736  if (std::isnan(data[i]))
737  {
738  colors.push_back(nan_r);
739  colors.push_back(nan_g);
740  colors.push_back(nan_b);
741  continue;
742  }
743 
744  float t;
745  if (constant)
746  {
747  t = 0.5f;
748  }
749  else
750  {
751  t = (data[i] - lo) / (hi - lo);
752  if (t < 0.0f) { t = 0.0f; }
753  if (t > 1.0f) { t = 1.0f; }
754  }
755 
756  float pos = t * (n - 1);
757  int idx0 = static_cast<int>(pos);
758  if (idx0 < 0) { idx0 = 0; }
759  if (idx0 > n - 2) { idx0 = n - 2; }
760  int idx1 = idx0 + 1;
761  float frac = pos - static_cast<float>(idx0);
762 
763  for (int c = 0; c < 3; c++)
764  {
765  float val = lut[idx0 * 3 + c] * (1.0f - frac) + lut[idx1 * 3 + c] * frac;
766  int iv = static_cast<int>(val * 255.0f + 0.5f);
767  if (iv < 0) { iv = 0; }
768  if (iv > 255) { iv = 255; }
769  colors.push_back(static_cast<uint8_t>(iv));
770  }
771  }
772  return colors;
773  }
774  } // End namespace util.
775 
776  // MRI data types, used by the MGH functions.
777 
779  const int MRI_UCHAR = 0;
780 
782  const int MRI_INT = 1;
783 
785  const int MRI_FLOAT = 3;
786 
788  const int MRI_SHORT = 4;
789 
790  // Forward declarations.
791  int _fread3(std::istream &);
792  template <typename T>
793  T _freadt(std::istream &);
794  std::string _freadstringnewline(std::istream &);
795  std::string _freadfixedlengthstring(std::istream &, size_t, bool, size_t);
796  bool _ends_with(std::string const &fullString, std::string const &ending);
797  size_t _vidx_2d(size_t, size_t, size_t);
798  struct MghHeader;
799 
817  struct Mesh
818  {
819 
821  Mesh(std::vector<float> cvertices, std::vector<int32_t> cfaces)
822  {
823  vertices = cvertices;
824  faces = cfaces;
825  }
826 
827  // Construct from 2D vectors (Nx3).
828  Mesh(std::vector<std::vector<float>> cvertices, std::vector<std::vector<int32_t>> cfaces)
829  {
830  vertices = util::vflatten(cvertices);
831  faces = util::vflatten(cfaces);
832  }
833 
835  Mesh() {}
836 
837  std::vector<float> vertices;
838  std::vector<int32_t> faces;
839 
851  {
852  fs::Mesh mesh;
853  mesh.vertices = {1.0, 1.0, 1.0,
854  1.0, 1.0, -1.0,
855  1.0, -1.0, 1.0,
856  1.0, -1.0, -1.0,
857  -1.0, 1.0, 1.0,
858  -1.0, 1.0, -1.0,
859  -1.0, -1.0, 1.0,
860  -1.0, -1.0, -1.0};
861  mesh.faces = {0, 2, 3,
862  3, 1, 0,
863  4, 6, 7,
864  7, 5, 4,
865  0, 4, 5,
866  5, 1, 0,
867  2, 6, 7,
868  7, 3, 2,
869  0, 4, 6,
870  6, 2, 0,
871  1, 5, 7,
872  7, 3, 1};
873  return mesh;
874  }
875 
888  {
889  fs::Mesh mesh;
890  mesh.vertices = {0.0, 0.0, 0.0, // start with 4x base
891  0.0, 1.0, 0.0,
892  1.0, 1.0, 0.0,
893  1.0, 0.0, 0.0,
894  0.5, 0.5, 1.0}; // apex
895  mesh.faces = {0, 2, 1, // start with 2 base faces
896  0, 3, 2,
897  0, 4, 1, // now the 4 wall faces
898  1, 4, 2,
899  3, 2, 4,
900  0, 3, 4};
901  return mesh;
902  }
903 
920  static fs::Mesh construct_grid(const size_t nx = 4, const size_t ny = 5, const float distx = 1.0, const float disty = 1.0)
921  {
922  if (nx < 2 || ny < 2)
923  {
924  throw std::runtime_error("Parameters nx and ny must be at least 2.");
925  }
926  fs::Mesh mesh;
927  size_t num_vertices = nx * ny;
928  size_t num_faces = ((nx - 1) * (ny - 1)) * 2;
929  std::vector<float> vertices;
930  vertices.reserve(num_vertices * 3);
931  std::vector<int> faces;
932  faces.reserve(num_faces * 3);
933 
934  // Create vertices.
935  float cur_x, cur_y, cur_z;
936  cur_x = cur_y = cur_z = 0.0;
937  for (size_t i = 0; i < nx; i++)
938  {
939  for (size_t j = 0; j < ny; j++)
940  {
941  vertices.push_back(cur_x);
942  vertices.push_back(cur_y);
943  vertices.push_back(cur_z);
944  cur_y += disty;
945  }
946  cur_x += distx;
947  }
948 
949  // Create faces.
950  for (size_t i = 0; i < num_vertices; i++)
951  {
952  if ((i + 1) % ny == 0 || i >= num_vertices - ny)
953  {
954  // Do not use the last ones in row or column as source.
955  continue;
956  }
957  // Add the upper left triangle of this grid cell.
958  faces.push_back(int(i));
959  faces.push_back(int(i + ny + 1));
960  faces.push_back(int(i + 1));
961  // Add the lower right triangle of this grid cell.
962  faces.push_back(int(i));
963  faces.push_back(int(i + ny + 1));
964  faces.push_back(int(i + ny));
965  }
966 
967  mesh.vertices = vertices;
968  mesh.faces = faces;
969  return mesh;
970  }
971 
982  std::string to_obj() const
983  {
984  std::stringstream objs;
985  for (size_t vidx = 0; vidx < this->vertices.size(); vidx += 3)
986  { // vertex coords
987  objs << "v " << vertices[vidx] << " " << vertices[vidx + 1] << " " << vertices[vidx + 2] << "\n";
988  }
989  for (size_t fidx = 0; fidx < this->faces.size(); fidx += 3)
990  { // faces: vertex indices, 1-based
991  objs << "f " << faces[fidx] + 1 << " " << faces[fidx + 1] + 1 << " " << faces[fidx + 2] + 1 << "\n";
992  }
993  return (objs.str());
994  }
995 
1007  std::vector<std::vector<bool>> as_adjmatrix() const
1008  {
1009  std::vector<std::vector<bool>> adjm = std::vector<std::vector<bool>>(this->num_vertices(), std::vector<bool>(this->num_vertices(), false));
1010  for (size_t fidx = 0; fidx < this->faces.size(); fidx += 3)
1011  { // faces: vertex indices
1012  adjm[faces[fidx]][faces[fidx + 1]] = true;
1013  adjm[faces[fidx + 1]][faces[fidx]] = true;
1014  adjm[faces[fidx + 1]][faces[fidx + 2]] = true;
1015  adjm[faces[fidx + 2]][faces[fidx + 1]] = true;
1016  adjm[faces[fidx + 2]][faces[fidx]] = true;
1017  adjm[faces[fidx]][faces[fidx + 2]] = true;
1018  }
1019  return adjm;
1020  }
1021 
1023  struct _tupleHashFunction
1024  {
1025  size_t operator()(const std::tuple<size_t, size_t> &x) const
1026  {
1027  return std::get<0>(x) ^ std::get<1>(x);
1028  }
1029  };
1030 
1033  typedef std::unordered_set<std::tuple<size_t, size_t>, _tupleHashFunction> edge_set;
1034 
1046  edge_set as_edgelist() const
1047  {
1048  edge_set edges;
1049  for (size_t fidx = 0; fidx < this->faces.size(); fidx += 3)
1050  { // faces: vertex indices
1051  edges.insert(std::make_tuple(faces[fidx], faces[fidx + 1]));
1052  edges.insert(std::make_tuple(faces[fidx + 1], faces[fidx]));
1053 
1054  edges.insert(std::make_tuple(faces[fidx + 1], faces[fidx + 2]));
1055  edges.insert(std::make_tuple(faces[fidx + 2], faces[fidx + 1]));
1056 
1057  edges.insert(std::make_tuple(faces[fidx], faces[fidx + 2]));
1058  edges.insert(std::make_tuple(faces[fidx + 2], faces[fidx]));
1059  }
1060  return edges;
1061  }
1062 
1075  std::vector<std::vector<size_t>> as_adjlist(const bool via_matrix = true) const
1076  {
1077  if (!via_matrix)
1078  {
1079  return (this->_as_adjlist_via_edgeset());
1080  }
1081  std::vector<std::vector<bool>> adjm = this->as_adjmatrix();
1082  std::vector<std::vector<size_t>> adjl = std::vector<std::vector<size_t>>(this->num_vertices(), std::vector<size_t>());
1083  size_t nv = adjm.size();
1084  for (size_t i = 0; i < nv; i++)
1085  {
1086  for (size_t j = i + 1; j < nv; j++)
1087  {
1088  if (adjm[i][j] == true)
1089  {
1090  adjl[i].push_back(j);
1091  adjl[j].push_back(i);
1092  }
1093  }
1094  }
1095  return adjl;
1096  }
1097 
1108  std::vector<std::vector<size_t>> _as_adjlist_via_edgeset() const
1109  {
1110  edge_set edges = this->as_edgelist();
1111  std::vector<std::vector<size_t>> adjl = std::vector<std::vector<size_t>>(this->num_vertices(), std::vector<size_t>());
1112  for (const std::tuple<size_t, size_t> &e : edges)
1113  {
1114  adjl[std::get<0>(e)].push_back(std::get<1>(e));
1115  }
1116  return adjl;
1117  }
1118 
1134  std::vector<float> smooth_pvd_nn(const std::vector<float> pvd, const size_t num_iter = 1, const bool via_matrix = true, const bool with_nan = true, const bool detect_nan = true) const
1135  {
1136 
1137  const std::vector<std::vector<size_t>> adjlist = this->as_adjlist(via_matrix);
1138  return fs::Mesh::smooth_pvd_nn(adjlist, pvd, num_iter, with_nan, detect_nan);
1139  }
1140 
1157  static std::vector<float> smooth_pvd_nn(const std::vector<std::vector<size_t>> mesh_adj, const std::vector<float> pvd, const size_t num_iter = 1, const bool with_nan = true, const bool detect_nan = true)
1158  {
1159  assert(pvd.size() == mesh_adj.size());
1160  bool final_with_nan = with_nan;
1161  if (detect_nan)
1162  {
1163  final_with_nan = false;
1164  for (size_t i = 0; i < pvd.size(); i++)
1165  {
1166  if (std::isnan(pvd[i]))
1167  {
1168  final_with_nan = true;
1169  break;
1170  }
1171  }
1172  }
1173  if (final_with_nan)
1174  {
1175  return fs::Mesh::_smooth_pvd_nn_nan(mesh_adj, pvd, num_iter);
1176  }
1177  std::vector<float> current_pvd_source;
1178  std::vector<float> current_pvd_smoothed = std::vector<float>(pvd.size());
1179 
1180  float val_sum;
1181  size_t num_neigh;
1182  for (size_t i = 0; i < num_iter; i++)
1183  {
1184  if (i == 0)
1185  {
1186  current_pvd_source = pvd;
1187  }
1188  else
1189  {
1190  current_pvd_source = current_pvd_smoothed;
1191  }
1192  for (size_t v_idx = 0; v_idx < mesh_adj.size(); v_idx++)
1193  {
1194  num_neigh = mesh_adj[v_idx].size();
1195  val_sum = current_pvd_source[v_idx] / (num_neigh + 1);
1196  for (size_t neigh_rel_idx = 0; neigh_rel_idx < num_neigh; neigh_rel_idx++)
1197  {
1198  val_sum += current_pvd_source[mesh_adj[v_idx][neigh_rel_idx]] / (num_neigh + 1);
1199  }
1200  current_pvd_smoothed[v_idx] = val_sum;
1201  }
1202  }
1203  return current_pvd_smoothed;
1204  }
1205 
1222  static std::vector<float> _smooth_pvd_nn_nan(const std::vector<std::vector<size_t>> mesh_adj, const std::vector<float> pvd, const size_t num_iter = 1)
1223  {
1224  std::vector<float> current_pvd_source;
1225  std::vector<float> current_pvd_smoothed = std::vector<float>(pvd.size());
1226 
1227  float val_sum;
1228  size_t num_neigh;
1229  size_t num_non_nan_values;
1230  float neigh_val;
1231  for (size_t i = 0; i < num_iter; i++)
1232  {
1233 
1234  if (i == 0)
1235  {
1236  current_pvd_source = pvd;
1237  }
1238  else
1239  {
1240  current_pvd_source = current_pvd_smoothed;
1241  }
1242 
1243  for (size_t v_idx = 0; v_idx < mesh_adj.size(); v_idx++)
1244  {
1245  if (std::isnan(current_pvd_source[v_idx]))
1246  {
1247  current_pvd_smoothed[v_idx] = NAN;
1248  continue;
1249  }
1250  val_sum = current_pvd_source[v_idx];
1251  num_non_nan_values = 1; // If we get here, the source vertex value is not NAN.
1252  num_neigh = mesh_adj[v_idx].size();
1253  for (size_t neigh_rel_idx = 0; neigh_rel_idx < num_neigh; neigh_rel_idx++)
1254  {
1255  neigh_val = current_pvd_source[mesh_adj[v_idx][neigh_rel_idx]];
1256  if (std::isnan(neigh_val))
1257  {
1258  continue;
1259  }
1260  else
1261  {
1262  val_sum += neigh_val;
1263  num_non_nan_values++;
1264  }
1265  }
1266  current_pvd_smoothed[v_idx] = val_sum / (float)num_non_nan_values;
1267  }
1268  }
1269  return current_pvd_smoothed;
1270  }
1271 
1278  static std::vector<std::vector<size_t>> extend_adj(const std::vector<std::vector<size_t>> mesh_adj, const size_t extend_by = 1, std::vector<std::vector<size_t>> mesh_adj_ext = std::vector<std::vector<size_t>>())
1279  {
1280  size_t num_vertices = mesh_adj.size();
1281  if (mesh_adj_ext.size() == 0)
1282  {
1283  mesh_adj_ext = mesh_adj;
1284  }
1285  std::vector<size_t> neighborhood;
1286  std::vector<size_t> ext_neighborhood;
1287  for (size_t ext_idx = 0; ext_idx < extend_by; ext_idx++)
1288  {
1289  for (size_t source_vert_idx = 0; source_vert_idx < num_vertices; source_vert_idx++)
1290  {
1291  neighborhood = mesh_adj_ext[source_vert_idx]; // copy needed so we do not modify during iteration.
1292  // Extension: add all neighbors in distance one for all vertices in the neighborhood.
1293  for (size_t neigh_vert_rel_idx = 0; neigh_vert_rel_idx < neighborhood.size(); neigh_vert_rel_idx++)
1294  {
1295  for (size_t canidate_rel_idx = 0; canidate_rel_idx < mesh_adj[neighborhood[neigh_vert_rel_idx]].size(); canidate_rel_idx++)
1296  {
1297  if (mesh_adj[neighborhood[neigh_vert_rel_idx]][canidate_rel_idx] != source_vert_idx)
1298  {
1299  mesh_adj_ext[source_vert_idx].push_back(mesh_adj[neighborhood[neigh_vert_rel_idx]][canidate_rel_idx]);
1300  }
1301  }
1302  }
1303  // We need to remove duplicates.
1304  std::sort(mesh_adj_ext[source_vert_idx].begin(), mesh_adj_ext[source_vert_idx].end());
1305  mesh_adj_ext[source_vert_idx].erase(std::unique(mesh_adj_ext[source_vert_idx].begin(), mesh_adj_ext[source_vert_idx].end()), mesh_adj_ext[source_vert_idx].end());
1306  }
1307  }
1308  return mesh_adj_ext;
1309  }
1310 
1323  void to_obj_file(const std::string &filename) const
1324  {
1325  fs::util::str_to_file(filename, this->to_obj());
1326  }
1327 
1344  std::pair<std::unordered_map<int32_t, int32_t>, fs::Mesh> submesh_vertex(const std::vector<int32_t> &old_vertex_indices, const bool mapdir_fulltosubmesh = false) const
1345  {
1346  fs::Mesh submesh;
1347  std::vector<float> new_vertices;
1348  std::vector<int> new_faces;
1349  std::unordered_map<int32_t, int32_t> vertex_index_map_full2submesh;
1350  int32_t new_vertex_idx = 0;
1351  for (size_t i = 0; i < old_vertex_indices.size(); i++)
1352  {
1353  vertex_index_map_full2submesh[old_vertex_indices[i]] = new_vertex_idx;
1354  new_vertices.push_back(this->vertices[size_t(old_vertex_indices[i]) * 3]);
1355  new_vertices.push_back(this->vertices[size_t(old_vertex_indices[i]) * 3 + 1]);
1356  new_vertices.push_back(this->vertices[size_t(old_vertex_indices[i]) * 3 + 2]);
1357  new_vertex_idx++;
1358  }
1359  int face_v0;
1360  int face_v1;
1361  int face_v2;
1362  for (size_t i = 0; i < this->num_faces(); i++)
1363  {
1364  face_v0 = this->faces[i * 3];
1365  face_v1 = this->faces[i * 3 + 1];
1366  face_v2 = this->faces[i * 3 + 2];
1367  if ((vertex_index_map_full2submesh.find(face_v0) != vertex_index_map_full2submesh.end()) && (vertex_index_map_full2submesh.find(face_v1) != vertex_index_map_full2submesh.end()) && (vertex_index_map_full2submesh.find(face_v2) != vertex_index_map_full2submesh.end()))
1368  {
1369  new_faces.push_back(vertex_index_map_full2submesh[face_v0]);
1370  new_faces.push_back(vertex_index_map_full2submesh[face_v1]);
1371  new_faces.push_back(vertex_index_map_full2submesh[face_v2]);
1372  }
1373  }
1374  submesh.vertices = new_vertices;
1375  submesh.faces = new_faces;
1376 
1377  std::pair<std::unordered_map<int32_t, int32_t>, fs::Mesh> result;
1378  if (!mapdir_fulltosubmesh)
1379  { // Compute the new2old (reverse) vertex index map:
1380  std::unordered_map<int32_t, int32_t> vertex_index_map_submesh2full;
1381  for (auto const &pair : vertex_index_map_full2submesh)
1382  {
1383  vertex_index_map_submesh2full[pair.second] = pair.first;
1384  }
1385  result = std::pair<std::unordered_map<int32_t, int32_t>, fs::Mesh>(vertex_index_map_submesh2full, submesh);
1386  }
1387  else
1388  {
1389  result = std::pair<std::unordered_map<int32_t, int32_t>, fs::Mesh>(vertex_index_map_full2submesh, submesh);
1390  }
1391 
1392  return result;
1393  }
1394 
1401  static std::vector<float> curv_data_for_orig_mesh(const std::vector<float> data_submesh, const std::unordered_map<int32_t, int32_t> submesh_to_orig_mapping, const int32_t orig_mesh_num_vertices, const float fill_value = std::numeric_limits<float>::quiet_NaN())
1402  {
1403 
1404  if (submesh_to_orig_mapping.size() != data_submesh.size())
1405  {
1406  throw std::domain_error("The number of vertices of the submesh and the number of values in the submesh_to_orig_mapping do not match: got " + std::to_string(data_submesh.size()) + " and " + std::to_string(submesh_to_orig_mapping.size()) + ".");
1407  }
1408 
1409  std::vector<float> data_orig_mesh(orig_mesh_num_vertices, fill_value);
1410  for (size_t i = 0; i < data_submesh.size(); i++)
1411  {
1412  auto got = submesh_to_orig_mapping.find(int(i));
1413  if (got != submesh_to_orig_mapping.end())
1414  {
1415  data_orig_mesh[got->second] = data_submesh[i];
1416  }
1417  }
1418  return (data_orig_mesh);
1419  }
1420 
1435  static void from_obj(Mesh *mesh, std::istream *is)
1436  {
1437  std::string line;
1438  int line_idx = -1;
1439 
1440  std::vector<float> vertices;
1441  std::vector<int> faces;
1442 
1443 #ifdef LIBFS_DBG_INFO
1444  size_t num_lines_ignored = 0; // Not comments, but custom extensions or material data lines which are ignored by libfs.
1445 #endif
1446 
1447  while (std::getline(*is, line))
1448  {
1449  line_idx += 1;
1450  std::istringstream iss(line);
1451  if (fs::util::starts_with(line, "#"))
1452  {
1453  continue; // skip comment.
1454  }
1455  else
1456  {
1457  if (fs::util::starts_with(line, "v "))
1458  {
1459  std::string elem_type_identifier;
1460  float x, y, z;
1461  if (!(iss >> elem_type_identifier >> x >> y >> z))
1462  {
1463  throw std::domain_error("Could not parse vertex line " + std::to_string(line_idx + 1) + " of OBJ data, invalid format.\n");
1464  }
1465  assert(elem_type_identifier == "v");
1466  vertices.push_back(x);
1467  vertices.push_back(y);
1468  vertices.push_back(z);
1469  }
1470  else if (fs::util::starts_with(line, "f "))
1471  {
1472  std::string elem_type_identifier, v0raw, v1raw, v2raw;
1473  int v0, v1, v2;
1474  if (!(iss >> elem_type_identifier >> v0raw >> v1raw >> v2raw))
1475  {
1476  throw std::domain_error("Could not parse face line " + std::to_string(line_idx + 1) + " of OBJ data, invalid format.\n");
1477  }
1478  assert(elem_type_identifier == "f");
1479 
1480  // The OBJ format allows to specifiy face indices with slashes to also set normal and material indices.
1481  // So instead of a line like 'f 22 34 45', we could get 'f 3/1 4/2 5/3' or 'f 6/4/1 3/5/3 7/6/5' or 'f 7//1 8//2 9//3'.
1482  // We need to extract the stuff before the first slash and interprete it as int to get the vertex index we are looking for.
1483  std::size_t found_v0 = v0raw.find("/");
1484  std::size_t found_v1 = v1raw.find("/");
1485  std::size_t found_v2 = v2raw.find("/");
1486  if (found_v0 != std::string::npos)
1487  {
1488  v0raw = v0raw.substr(0, found_v0);
1489  }
1490  if (found_v1 != std::string::npos)
1491  {
1492  v1raw = v1raw.substr(0, found_v1);
1493  }
1494  if (found_v2 != std::string::npos)
1495  {
1496  v2raw = v0raw.substr(0, found_v2);
1497  }
1498  v0 = std::stoi(v0raw);
1499  v1 = std::stoi(v1raw);
1500  v2 = std::stoi(v2raw);
1501 
1502  // The vertex indices in Wavefront OBJ files are 1-based, so we have to substract 1 here.
1503  faces.push_back(v0 - 1);
1504  faces.push_back(v1 - 1);
1505  faces.push_back(v2 - 1);
1506  }
1507  else
1508  {
1509 #ifdef LIBFS_DBG_INFO
1510  num_lines_ignored++;
1511 #endif
1512 
1513  continue;
1514  }
1515  }
1516  }
1517 #ifdef LIBFS_DBG_INFO
1518  if (num_lines_ignored > 0)
1519  {
1520  std::cout << LIBFS_APPTAG << "Ignored " << num_lines_ignored << " lines in Wavefront OBJ format mesh file.\n";
1521  }
1522 #endif
1523  mesh->vertices = vertices;
1524  mesh->faces = faces;
1525  }
1526 
1541  static void from_obj(Mesh *mesh, const std::string &filename)
1542  {
1543 #ifdef LIBFS_DBG_INFO
1544  std::cout << LIBFS_APPTAG << "Reading brain mesh from Wavefront object format file " << filename << ".\n";
1545 #endif
1546  std::ifstream input(filename, std::fstream::in);
1547  if (input.is_open())
1548  {
1549  Mesh::from_obj(mesh, &input);
1550  input.close();
1551  }
1552  else
1553  {
1554  throw std::runtime_error("Could not open Wavefront object format mesh file '" + filename + "' for reading.\n");
1555  }
1556  }
1557 
1564  static void from_off(Mesh *mesh, std::istream *is, const std::string &source_filename = "")
1565  {
1566 
1567  std::string msg_source_file_part = source_filename.empty() ? "" : "'" + source_filename + "'";
1568 
1569  std::string line;
1570  int line_idx = -1;
1571  int noncomment_line_idx = -1;
1572 
1573  std::vector<float> vertices;
1574  std::vector<int> faces;
1575  size_t num_vertices = 0;
1576  size_t num_faces = 0;
1577  size_t num_edges = 0;
1578  size_t num_verts_parsed = 0;
1579  size_t num_faces_parsed = 0;
1580  float x, y, z; // vertex xyz coords
1581  // bool has_color;
1582  // int r, g, b, a; // vertex colors
1583  int num_verts_this_face, v0, v1, v2; // face, defined by number of vertices and vertex indices.
1584 
1585  while (std::getline(*is, line))
1586  {
1587  line_idx++;
1588  std::istringstream iss(line);
1589  if (fs::util::starts_with(line, "#"))
1590  {
1591  continue; // skip comment.
1592  }
1593  else
1594  {
1595  noncomment_line_idx++;
1596  if (noncomment_line_idx == 0)
1597  {
1598  std::string off_header_magic;
1599  if (!(iss >> off_header_magic))
1600  {
1601  throw std::domain_error("Could not parse first header line " + std::to_string(line_idx + 1) + " of OFF data, invalid format.\n");
1602  }
1603  if (!(off_header_magic == "OFF" || off_header_magic == "COFF"))
1604  {
1605  throw std::domain_error("OFF magic string invalid, file " + msg_source_file_part + " not in OFF format.\n");
1606  }
1607  // has_color = off_header_magic == "COFF";
1608  }
1609  else if (noncomment_line_idx == 1)
1610  {
1611  if (!(iss >> num_vertices >> num_faces >> num_edges))
1612  {
1613  throw std::domain_error("Could not parse element count header line " + std::to_string(line_idx + 1) + " of OFF data " + msg_source_file_part + ", invalid format.\n");
1614  }
1615  }
1616  else
1617  {
1618 
1619  if (num_verts_parsed < num_vertices)
1620  {
1621  if (!(iss >> x >> y >> z))
1622  {
1623  throw std::domain_error("Could not parse vertex coordinate line " + std::to_string(line_idx + 1) + " of OFF data " + msg_source_file_part + ", invalid format.\n");
1624  }
1625  vertices.push_back(x);
1626  vertices.push_back(y);
1627  vertices.push_back(z);
1628  num_verts_parsed++;
1629  }
1630  else
1631  {
1632  if (num_faces_parsed < num_faces)
1633  {
1634  if (!(iss >> num_verts_this_face >> v0 >> v1 >> v2))
1635  {
1636  throw std::domain_error("Could not parse face line " + std::to_string(line_idx + 1) + " of OFF data " + msg_source_file_part + ", invalid format.\n");
1637  }
1638  if (num_verts_this_face != 3)
1639  {
1640  throw std::domain_error("At OFF data " + msg_source_file_part + " line " + std::to_string(line_idx + 1) + ": only triangular meshes supported.\n");
1641  }
1642  faces.push_back(v0);
1643  faces.push_back(v1);
1644  faces.push_back(v2);
1645  num_faces_parsed++;
1646  }
1647  }
1648  }
1649  }
1650  }
1651  if (num_verts_parsed < num_vertices)
1652  {
1653  throw std::domain_error("Vertex count mismatch between OFF data " + msg_source_file_part + " header (" + std::to_string(num_vertices) + ") and data (" + std::to_string(num_verts_parsed) + ").\n");
1654  }
1655  if (num_faces_parsed < num_faces)
1656  {
1657  throw std::domain_error("Face count mismatch between OFF data " + msg_source_file_part + " header (" + std::to_string(num_faces) + ") and data (" + std::to_string(num_faces_parsed) + ").\n");
1658  }
1659  mesh->vertices = vertices;
1660  mesh->faces = faces;
1661  }
1662 
1677  static void from_off(Mesh *mesh, const std::string &filename)
1678  {
1679 #ifdef LIBFS_DBG_INFO
1680  std::cout << LIBFS_APPTAG << "Reading brain mesh from OFF format file " << filename << ".\n";
1681 #endif
1682  std::ifstream input(filename, std::fstream::in);
1683  if (input.is_open())
1684  {
1685  Mesh::from_off(mesh, &input);
1686  input.close();
1687  }
1688  else
1689  {
1690  throw std::runtime_error("Could not open Object file format (OFF) mesh file '" + filename + "' for reading.\n");
1691  }
1692  }
1693 
1699  static void from_ply(Mesh *mesh, std::istream *is)
1700  {
1701  std::string line;
1702  int line_idx = -1;
1703  int noncomment_line_idx = -1;
1704 
1705  std::vector<float> vertices;
1706  std::vector<int> faces;
1707 
1708  bool in_header = true; // current status
1709  int num_verts = -1;
1710  int num_faces = -1;
1711  while (std::getline(*is, line))
1712  {
1713  line_idx += 1;
1714  std::istringstream iss(line);
1715  if (fs::util::starts_with(line, "comment"))
1716  {
1717  continue; // skip comment.
1718  }
1719  else
1720  {
1721  noncomment_line_idx++;
1722  if (in_header)
1723  {
1724  if (noncomment_line_idx == 0)
1725  {
1726  if (line != "ply")
1727  throw std::domain_error("Invalid PLY file");
1728  }
1729  else if (noncomment_line_idx == 1)
1730  {
1731  if (line != "format ascii 1.0")
1732  throw std::domain_error("Unsupported PLY file format, only format 'format ascii 1.0' is supported.");
1733  }
1734 
1735  if (line == "end_header")
1736  {
1737  in_header = false;
1738  }
1739  else if (fs::util::starts_with(line, "element vertex"))
1740  {
1741  std::string elem, elem_type_identifier;
1742  if (!(iss >> elem >> elem_type_identifier >> num_verts))
1743  {
1744  throw std::domain_error("Could not parse element vertex line of PLY header, invalid format.\n");
1745  }
1746  }
1747  else if (fs::util::starts_with(line, "element face"))
1748  {
1749  std::string elem, elem_type_identifier;
1750  if (!(iss >> elem >> elem_type_identifier >> num_faces))
1751  {
1752  throw std::domain_error("Could not parse element face line of PLY header, invalid format.\n");
1753  }
1754  } // Other properties like vertex colors and normals are ignored for now.
1755  }
1756  else
1757  { // in data part.
1758  if (num_verts < 1 || num_faces < 1)
1759  {
1760  throw std::domain_error("Invalid PLY file: missing element count lines of header.");
1761  }
1762  // Read vertices
1763  if (vertices.size() < (size_t)num_verts * 3)
1764  {
1765  float x, y, z;
1766  if (!(iss >> x >> y >> z))
1767  {
1768  throw std::domain_error("Could not parse vertex line " + std::to_string(line_idx) + " of PLY data, invalid format.\n");
1769  }
1770  vertices.push_back(x);
1771  vertices.push_back(y);
1772  vertices.push_back(z);
1773  }
1774  else
1775  {
1776  if (faces.size() < (size_t)num_faces * 3)
1777  {
1778  int verts_per_face, v0, v1, v2;
1779  if (!(iss >> verts_per_face >> v0 >> v1 >> v2))
1780  {
1781  throw std::domain_error("Could not parse face line " + std::to_string(line_idx) + " of PLY data, invalid format.\n");
1782  }
1783  if (verts_per_face != 3)
1784  {
1785  throw std::domain_error("Only triangular meshes are supported: PLY faces lines must contain exactly 3 vertex indices.\n");
1786  }
1787  faces.push_back(v0);
1788  faces.push_back(v1);
1789  faces.push_back(v2);
1790  }
1791  }
1792  }
1793  }
1794  }
1795  if (vertices.size() != (size_t)num_verts * 3)
1796  {
1797  std::cerr << "PLY header mentions " << num_verts << " vertices, but found " << vertices.size() / 3 << ".\n";
1798  }
1799  if (faces.size() != (size_t)num_faces * 3)
1800  {
1801  std::cerr << "PLY header mentions " << num_faces << " faces, but found " << faces.size() / 3 << ".\n";
1802  }
1803  mesh->vertices = vertices;
1804  mesh->faces = faces;
1805  }
1806 
1820  static void from_ply(Mesh *mesh, const std::string &filename)
1821  {
1822 #ifdef LIBFS_DBG_INFO
1823  std::cout << LIBFS_APPTAG << "Reading brain mesh from PLY format file " << filename << ".\n";
1824 #endif
1825  std::ifstream input(filename, std::fstream::in);
1826  if (input.is_open())
1827  {
1828  Mesh::from_ply(mesh, &input);
1829  input.close();
1830  }
1831  else
1832  {
1833  throw std::runtime_error("Could not open Stanford PLY format mesh file '" + filename + "' for reading.\n");
1834  }
1835  }
1836 
1846  size_t num_vertices() const
1847  {
1848  return (this->vertices.size() / 3);
1849  }
1850 
1860  size_t num_faces() const
1861  {
1862  return (this->faces.size() / 3);
1863  }
1864 
1877  const int32_t &fm_at(const size_t i, const size_t j) const
1878  {
1879  size_t idx = _vidx_2d(i, j, 3);
1880  if (idx > this->faces.size() - 1)
1881  {
1882  throw std::range_error("Indices (" + std::to_string(i) + "," + std::to_string(j) + ") into Mesh.faces out of bounds. Hit " + std::to_string(idx) + " with max valid index " + std::to_string(this->faces.size() - 1) + ".\n");
1883  }
1884  return (this->faces[idx]);
1885  }
1886 
1898  std::vector<int32_t> face_vertices(const size_t face) const
1899  {
1900  if (face > this->num_faces() - 1)
1901  {
1902  throw std::range_error("Index " + std::to_string(face) + " into Mesh.faces out of bounds, max valid index is " + std::to_string(this->num_faces() - 1) + ".\n");
1903  }
1904  std::vector<int32_t> fv(3);
1905  fv[0] = this->fm_at(face, 0);
1906  fv[1] = this->fm_at(face, 1);
1907  fv[2] = this->fm_at(face, 2);
1908  return (fv);
1909  }
1910 
1922  std::vector<float> vertex_coords(const size_t vertex) const
1923  {
1924  if (vertex > this->num_vertices() - 1)
1925  {
1926  throw std::range_error("Index " + std::to_string(vertex) + " into Mesh.vertices out of bounds, max valid index is " + std::to_string(this->num_vertices() - 1) + ".\n");
1927  }
1928  std::vector<float> vc(3);
1929  vc[0] = this->vm_at(vertex, 0);
1930  vc[1] = this->vm_at(vertex, 1);
1931  vc[2] = this->vm_at(vertex, 2);
1932  return (vc);
1933  }
1934 
1948  const float &vm_at(const size_t i, const size_t j) const
1949  {
1950  size_t idx = _vidx_2d(i, j, 3);
1951  if (idx > this->vertices.size() - 1)
1952  {
1953  throw std::range_error("Indices (" + std::to_string(i) + "," + std::to_string(j) + ") into Mesh.vertices out of bounds. Hit " + std::to_string(idx) + " with max valid index " + std::to_string(this->vertices.size() - 1) + ".\n");
1954  }
1955  return (this->vertices[idx]);
1956  }
1957 
1966  std::string to_ply() const
1967  {
1968  std::vector<uint8_t> empty_col;
1969  return (this->to_ply(empty_col));
1970  }
1971 
1982  std::string to_ply(const std::vector<uint8_t> col) const
1983  {
1984  bool use_vertex_colors = col.size() != 0;
1985  std::stringstream plys;
1986  plys << "ply\nformat ascii 1.0\n";
1987  plys << "element vertex " << this->num_vertices() << "\n";
1988  plys << "property float x\nproperty float y\nproperty float z\n";
1989  if (use_vertex_colors)
1990  {
1991  if (col.size() != this->vertices.size())
1992  {
1993  throw std::invalid_argument("Number of vertex coordinates and vertex colors must match when writing PLY file, but got " + std::to_string(this->vertices.size()) + " and " + std::to_string(col.size()) + ".");
1994  }
1995  plys << "property uchar red\nproperty uchar green\nproperty uchar blue\n";
1996  }
1997  plys << "element face " << this->num_faces() << "\n";
1998  plys << "property list uchar int vertex_index\n";
1999  plys << "end_header\n";
2000 
2001 #ifdef LIBFS_DBG_DEBUG
2002  fs::util::log("Writing " + std::to_string(this->vertices.size() / 3) + " PLY format vertices.", "INFO");
2003 #endif
2004 
2005  for (size_t vidx = 0; vidx < this->vertices.size(); vidx += 3)
2006  { // vertex coords
2007  plys << vertices[vidx] << " " << vertices[vidx + 1] << " " << vertices[vidx + 2];
2008  if (use_vertex_colors)
2009  {
2010  plys << " " << (int)col[vidx] << " " << (int)col[vidx + 1] << " " << (int)col[vidx + 2];
2011  }
2012  plys << "\n";
2013  }
2014 
2015 #ifdef LIBFS_DBG_DEBUG
2016  fs::util::log("Writing " + std::to_string(this->faces.size() / 3) + " PLY format faces.", "INFO");
2017 #endif
2018 
2019  const int num_vertices_per_face = 3;
2020  for (size_t fidx = 0; fidx < this->faces.size(); fidx += 3)
2021  { // faces: vertex indices, 0-based
2022  plys << num_vertices_per_face << " " << faces[fidx] << " " << faces[fidx + 1] << " " << faces[fidx + 2] << "\n";
2023  }
2024  return (plys.str());
2025  }
2026 
2036  void to_ply_file(const std::string &filename) const
2037  {
2038 #ifdef LIBFS_DBG_INFO
2039  fs::util::log("Writing mesh to PLY file '" + filename + "'.", "INFO");
2040 #endif
2041  fs::util::str_to_file(filename, this->to_ply());
2042  }
2043 
2046  void to_ply_file(const std::string &filename, const std::vector<uint8_t> col) const
2047  {
2048  fs::util::str_to_file(filename, this->to_ply(col));
2049  }
2050 
2059  std::string to_off() const
2060  {
2061  std::vector<uint8_t> empty_col;
2062  return (this->to_off(empty_col));
2063  }
2064 
2068  std::string to_off(const std::vector<uint8_t> col) const
2069  {
2070  bool use_vertex_colors = col.size() != 0;
2071  std::stringstream offs;
2072  if (use_vertex_colors)
2073  {
2074 #ifdef LIBFS_DBG_INFO
2075  fs::util::log("Writing OFF representation of mesh with vertex colors.", "INFO");
2076 #endif
2077  if (col.size() != this->vertices.size())
2078  {
2079  throw std::invalid_argument("Number of vertex coordinates and vertex colors must match when writing OFF file but got " + std::to_string(this->vertices.size()) + " and " + std::to_string(col.size()) + ".");
2080  }
2081  offs << "COFF\n";
2082  }
2083  else
2084  {
2085 #ifdef LIBFS_DBG_INFO
2086  fs::util::log("Writing OFF representation of mesh without vertex colors.", "INFO");
2087 #endif
2088  offs << "OFF\n";
2089  }
2090  offs << this->num_vertices() << " " << this->num_faces() << " 0\n";
2091 
2092  for (size_t vidx = 0; vidx < this->vertices.size(); vidx += 3)
2093  { // vertex coords
2094  offs << vertices[vidx] << " " << vertices[vidx + 1] << " " << vertices[vidx + 2];
2095  if (use_vertex_colors)
2096  {
2097  offs << " " << (int)col[vidx] << " " << (int)col[vidx + 1] << " " << (int)col[vidx + 2] << " 255";
2098  }
2099  offs << "\n";
2100  }
2101 
2102  const int num_vertices_per_face = 3;
2103  for (size_t fidx = 0; fidx < this->faces.size(); fidx += 3)
2104  { // faces: vertex indices, 0-based
2105  offs << num_vertices_per_face << " " << faces[fidx] << " " << faces[fidx + 1] << " " << faces[fidx + 2] << "\n";
2106  }
2107  return (offs.str());
2108  }
2109 
2119  void to_off_file(const std::string &filename) const
2120  {
2121  fs::util::str_to_file(filename, this->to_off());
2122  }
2123 
2126  void to_off_file(const std::string &filename, const std::vector<uint8_t> col) const
2127  {
2128  fs::util::str_to_file(filename, this->to_off(col));
2129  }
2130  };
2131 
2133  struct Curv
2134  {
2135 
2137  Curv(std::vector<float> curv_data) : num_faces(100000), num_vertices(0), num_values_per_vertex(1)
2138  {
2139  data = curv_data;
2140  num_vertices = int(data.size());
2141  }
2142 
2144  Curv() : num_faces(100000), num_vertices(0), num_values_per_vertex(1) {}
2145 
2147  int32_t num_faces;
2148 
2150  std::vector<float> data;
2151 
2153  int32_t num_vertices;
2154 
2157  };
2158 
2160  struct Colortable
2161  {
2162  std::vector<int32_t> id;
2163  std::vector<std::string> name;
2164  std::vector<int32_t> r;
2165  std::vector<int32_t> g;
2166  std::vector<int32_t> b;
2167  std::vector<int32_t> a;
2168  std::vector<int32_t> label;
2169 
2171  size_t num_entries() const
2172  {
2173  size_t num_ids = this->id.size();
2174  if (this->name.size() != num_ids || this->r.size() != num_ids || this->g.size() != num_ids || this->b.size() != num_ids || this->a.size() != num_ids || this->label.size() != num_ids)
2175  {
2176  std::cerr << "Inconsistent Colortable, vector sizes do not match.\n";
2177  }
2178  return num_ids;
2179  }
2180 
2182  int32_t get_region_idx(const std::string &query_name) const
2183  {
2184  for (size_t i = 0; i < this->num_entries(); i++)
2185  {
2186  if (this->name[i] == query_name)
2187  {
2188  return (int32_t)i;
2189  }
2190  }
2191  return (-1);
2192  }
2193 
2195  int32_t get_region_idx(int32_t query_label) const
2196  {
2197  for (size_t i = 0; i < this->num_entries(); i++)
2198  {
2199  if (this->label[i] == query_label)
2200  {
2201  return (int32_t)i;
2202  }
2203  }
2204  return (-1);
2205  }
2206  };
2207 
2209  struct Annot
2210  {
2211  std::vector<int32_t> vertex_indices;
2212  std::vector<int32_t> vertex_labels;
2214 
2216  std::vector<int32_t> region_vertices(const std::string &region_name) const
2217  {
2218  int32_t region_idx = this->colortable.get_region_idx(region_name);
2219  if (region_idx >= 0)
2220  {
2221  return (this->region_vertices(this->colortable.label[region_idx]));
2222  }
2223  else
2224  {
2225  std::cerr << "No such region in annot, returning empty vector.\n";
2226  std::vector<int32_t> empty;
2227  return (empty);
2228  }
2229  }
2230 
2232  std::vector<int32_t> region_vertices(int32_t region_label) const
2233  {
2234  std::vector<int32_t> reg_verts;
2235  for (size_t i = 0; i < this->vertex_labels.size(); i++)
2236  {
2237  if (this->vertex_labels[i] == region_label)
2238  {
2239  reg_verts.push_back(int(i));
2240  }
2241  }
2242  return (reg_verts);
2243  }
2244 
2247  std::vector<uint8_t> vertex_colors(bool alpha = false) const
2248  {
2249  int num_channels = alpha ? 4 : 3;
2250  std::vector<uint8_t> col;
2251  col.reserve(this->num_vertices() * num_channels);
2252  std::vector<size_t> vertex_region_indices = this->vertex_regions();
2253  for (size_t i = 0; i < this->num_vertices(); i++)
2254  {
2255  col.push_back(this->colortable.r[vertex_region_indices[i]]);
2256  col.push_back(this->colortable.g[vertex_region_indices[i]]);
2257  col.push_back(this->colortable.b[vertex_region_indices[i]]);
2258  if (alpha)
2259  {
2260  col.push_back(this->colortable.a[vertex_region_indices[i]]);
2261  }
2262  }
2263  return (col);
2264  }
2265 
2268  size_t num_vertices() const
2269  {
2270  size_t nv = this->vertex_indices.size();
2271  if (this->vertex_labels.size() != nv)
2272  {
2273  throw std::runtime_error("Inconsistent annot, number of vertex indices and labels does not match.\n");
2274  }
2275  return nv;
2276  }
2277 
2280  std::vector<size_t> vertex_regions() const
2281  {
2282  std::vector<size_t> vert_reg;
2283  for (size_t i = 0; i < this->num_vertices(); i++)
2284  {
2285  vert_reg.push_back(0); // init with zeros.
2286  }
2287  for (size_t region_idx = 0; region_idx < this->colortable.num_entries(); region_idx++)
2288  {
2289  std::vector<int32_t> reg_vertices = this->region_vertices(this->colortable.label[region_idx]);
2290  for (size_t region_vert_local_idx = 0; region_vert_local_idx < reg_vertices.size(); region_vert_local_idx++)
2291  {
2292  int32_t region_vert_idx = reg_vertices[region_vert_local_idx];
2293  vert_reg[region_vert_idx] = region_idx;
2294  }
2295  }
2296  return vert_reg;
2297  }
2298 
2300  std::vector<std::string> vertex_region_names() const
2301  {
2302  std::vector<std::string> region_names;
2303  std::vector<size_t> vertex_region_indices = this->vertex_regions();
2304  for (size_t i = 0; i < this->num_vertices(); i++)
2305  {
2306  region_names.push_back(this->colortable.name[vertex_region_indices[i]]);
2307  }
2308  return (region_names);
2309  }
2310  };
2311 
2313  struct MghHeader
2314  {
2317  {
2318  dim1length = curv.data.size();
2319  dim2length = 1;
2320  dim3length = 1;
2321  dim4length = 1;
2322  dtype = fs::MRI_FLOAT;
2323  }
2324  MghHeader(std::vector<float> curv_data)
2325  {
2326  dim1length = curv_data.size();
2327  dim2length = 1;
2328  dim3length = 1;
2329  dim4length = 1;
2330  dtype = fs::MRI_FLOAT;
2331  }
2332  int32_t dim1length = 0;
2333  int32_t dim2length = 0;
2334  int32_t dim3length = 0;
2335  int32_t dim4length = 0;
2336 
2337  int32_t dtype = 0;
2338  int32_t dof = 0;
2339  int16_t ras_good_flag = 0;
2340 
2342  size_t num_values() const
2343  {
2344  return ((size_t)dim1length * dim2length * dim3length * dim4length);
2345  }
2346 
2347  float xsize = 0.0;
2348  float ysize = 0.0;
2349  float zsize = 0.0;
2350  std::vector<float> Mdc;
2351  std::vector<float> Pxyz_c;
2352  };
2353 
2355  struct MghData
2356  {
2357  MghData() {}
2358  MghData(std::vector<int32_t> curv_data) { data_mri_int = curv_data; }
2359  explicit MghData(std::vector<uint8_t> curv_data) { data_mri_uchar = curv_data; }
2360  explicit MghData(std::vector<short> curv_data) { data_mri_short = curv_data; }
2361  MghData(std::vector<float> curv_data) { data_mri_float = curv_data; }
2362  MghData(Curv curv) { data_mri_float = curv.data; }
2363  std::vector<int32_t> data_mri_int;
2364  std::vector<uint8_t> data_mri_uchar;
2365  std::vector<float> data_mri_float;
2366  std::vector<short> data_mri_short;
2367  };
2368 
2370  struct Mgh
2371  {
2374  Mgh() {}
2375  Mgh(Curv curv)
2376  {
2377  header = MghHeader(curv);
2378  data = MghData(curv);
2379  }
2380  Mgh(std::vector<float> curv_data)
2381  {
2382  header = MghHeader(curv_data);
2383  data = MghData(curv_data);
2384  }
2385  };
2386 
2389  template <class T>
2390  struct Array4D
2391  {
2396  Array4D(unsigned int d1, unsigned int d2, unsigned int d3, unsigned int d4) : d1(d1), d2(d2), d3(d3), d4(d4), data(_compute_4d_size(d1, d2, d3, d4)) {}
2397 
2403  Array4D(MghHeader *mgh_header) : d1(_validate_mgh_dim(mgh_header->dim1length)), d2(_validate_mgh_dim(mgh_header->dim2length)), d3(_validate_mgh_dim(mgh_header->dim3length)), d4(_validate_mgh_dim(mgh_header->dim4length)), data(_compute_4d_size(d1, d2, d3, d4)) {}
2404 
2409  Array4D(Mgh *mgh) : // This does NOT init the data atm.
2410  d1(_validate_mgh_dim(mgh->header.dim1length)), d2(_validate_mgh_dim(mgh->header.dim2length)), d3(_validate_mgh_dim(mgh->header.dim3length)), d4(_validate_mgh_dim(mgh->header.dim4length)), data(_compute_4d_size(d1, d2, d3, d4))
2411  {
2412  }
2413 
2415  const T &at(const unsigned int i1, const unsigned int i2, const unsigned int i3, const unsigned int i4) const
2416  {
2417  return data[get_index(i1, i2, i3, i4)];
2418  }
2419 
2421  unsigned int get_index(const unsigned int i1, const unsigned int i2, const unsigned int i3, const unsigned int i4) const
2422  {
2423  assert(i1 >= 0 && i1 < d1);
2424  assert(i2 >= 0 && i2 < d2);
2425  assert(i3 >= 0 && i3 < d3);
2426  assert(i4 >= 0 && i4 < d4);
2427  return (((i1 * d2 + i2) * d3 + i3) * d4 + i4);
2428  }
2429 
2431  unsigned int num_values() const
2432  {
2433  return (d1 * d2 * d3 * d4);
2434  }
2435 
2436  unsigned int d1;
2437  unsigned int d2;
2438  unsigned int d3;
2439  unsigned int d4;
2440  std::vector<T> data;
2441 
2442  private:
2445  static unsigned int _validate_mgh_dim(int32_t dim)
2446  {
2447  if (dim <= 0)
2448  {
2449  throw std::domain_error("MGH dimension " + std::to_string(dim) + " is not positive.\n");
2450  }
2451  return static_cast<unsigned int>(dim);
2452  }
2453 
2455  static size_t _compute_4d_size(unsigned int d1, unsigned int d2, unsigned int d3, unsigned int d4)
2456  {
2457  if (d1 == 0 || d2 == 0 || d3 == 0 || d4 == 0)
2458  {
2459  throw std::domain_error("Array4D dimensions must be positive.\n");
2460  }
2461  size_t s1, s2, s3;
2462  if (!fs::util::safe_multiply(d1, d2, s1) ||
2463  !fs::util::safe_multiply(s1, d3, s2) ||
2464  !fs::util::safe_multiply(s2, d4, s3))
2465  {
2466  throw std::overflow_error("Array4D dimensions cause size_t overflow.\n");
2467  }
2468  if (s3 > LIBFS_MAX_ALLOC_BYTES / sizeof(T))
2469  {
2470  throw std::runtime_error("Array4D size " + std::to_string(s3) +
2471  " elements exceeds maximum allowed allocation (" +
2472  std::to_string(LIBFS_MAX_ALLOC_BYTES) + " bytes).\n");
2473  }
2474  return s3;
2475  }
2476  };
2477 
2478  // More declarations, should also go to separate header.
2479  void read_mgh_header(MghHeader *, const std::string &);
2480  void read_mgh_header(MghHeader *, std::istream *);
2481  template <typename T>
2482  std::vector<T> _read_mgh_data(MghHeader *, const std::string &);
2483  template <typename T>
2484  std::vector<T> _read_mgh_data(MghHeader *, std::istream *);
2485  std::vector<int32_t> _read_mgh_data_int(MghHeader *, const std::string &);
2486  std::vector<int32_t> _read_mgh_data_int(MghHeader *, std::istream *);
2487  std::vector<uint8_t> _read_mgh_data_uchar(MghHeader *, const std::string &);
2488  std::vector<uint8_t> _read_mgh_data_uchar(MghHeader *, std::istream *);
2489  std::vector<short> _read_mgh_data_short(MghHeader *, const std::string &);
2490  std::vector<short> _read_mgh_data_short(MghHeader *, std::istream *);
2491  std::vector<float> _read_mgh_data_float(MghHeader *, const std::string &);
2492  std::vector<float> _read_mgh_data_float(MghHeader *, std::istream *);
2493 
2506  void read_mgh(Mgh *mgh, const std::string &filename)
2507  {
2508  MghHeader mgh_header;
2509  read_mgh_header(&mgh_header, filename);
2510  mgh->header = mgh_header;
2511  if (mgh->header.dtype == MRI_INT)
2512  {
2513  std::vector<int32_t> data = _read_mgh_data_int(&mgh_header, filename);
2514  mgh->data.data_mri_int = data;
2515  }
2516  else if (mgh->header.dtype == MRI_UCHAR)
2517  {
2518  std::vector<uint8_t> data = _read_mgh_data_uchar(&mgh_header, filename);
2519  mgh->data.data_mri_uchar = data;
2520  }
2521  else if (mgh->header.dtype == MRI_FLOAT)
2522  {
2523  std::vector<float> data = _read_mgh_data_float(&mgh_header, filename);
2524  mgh->data.data_mri_float = data;
2525  }
2526  else if (mgh->header.dtype == MRI_SHORT)
2527  {
2528  std::vector<short> data = _read_mgh_data_short(&mgh_header, filename);
2529  mgh->data.data_mri_short = data;
2530  }
2531  else
2532  {
2533 #ifdef LIBFS_DBG_INFO
2534  if (fs::util::ends_with(filename, ".mgz"))
2535  {
2536  std::cout << LIBFS_APPTAG << "Note: your MGH filename ends with '.mgz'. Keep in mind that MGZ format is not supported directly. You can ignore this message if you wrapped a gz stream.\n";
2537  }
2538 #endif
2539  throw std::runtime_error("Not reading MGH data from file '" + filename + "', data type " + std::to_string(mgh->header.dtype) + " not supported yet.\n");
2540  }
2541  }
2542 
2552  std::vector<std::string> read_subjectsfile(const std::string &filename)
2553  {
2554  std::vector<std::string> subjects;
2555  std::ifstream input(filename, std::fstream::in);
2556  std::string line;
2557 
2558  if (!input.is_open())
2559  {
2560  throw std::runtime_error("Could not open subjects file '" + filename + "'.\n");
2561  }
2562 
2563  while (std::getline(input, line))
2564  {
2565  subjects.push_back(line);
2566  }
2567  return (subjects);
2568  }
2569 
2575  void read_mgh(Mgh *mgh, std::istream *is)
2576  {
2577  MghHeader mgh_header;
2578  read_mgh_header(&mgh_header, is);
2579  mgh->header = mgh_header;
2580  if (mgh->header.dtype == MRI_INT)
2581  {
2582  std::vector<int32_t> data = _read_mgh_data_int(&mgh_header, is);
2583  mgh->data.data_mri_int = data;
2584  }
2585  else if (mgh->header.dtype == MRI_UCHAR)
2586  {
2587  std::vector<uint8_t> data = _read_mgh_data_uchar(&mgh_header, is);
2588  mgh->data.data_mri_uchar = data;
2589  }
2590  else if (mgh->header.dtype == MRI_FLOAT)
2591  {
2592  std::vector<float> data = _read_mgh_data_float(&mgh_header, is);
2593  mgh->data.data_mri_float = data;
2594  }
2595  else if (mgh->header.dtype == MRI_SHORT)
2596  {
2597  std::vector<short> data = _read_mgh_data_short(&mgh_header, is);
2598  mgh->data.data_mri_short = data;
2599  }
2600  else
2601  {
2602  throw std::runtime_error("Not reading data from MGH stream, data type " + std::to_string(mgh->header.dtype) + " not supported yet.\n");
2603  }
2604  }
2605 
2611  void read_mgh_header(MghHeader *mgh_header, std::istream *is)
2612  {
2613  const int MGH_VERSION = 1;
2614 
2615  int format_version = _freadt<int32_t>(*is);
2616  if (format_version != MGH_VERSION)
2617  {
2618  throw std::runtime_error("Invalid MGH file or unsupported file format version: expected version " + std::to_string(MGH_VERSION) + ", found " + std::to_string(format_version) + ".\n");
2619  }
2620  mgh_header->dim1length = _freadt<int32_t>(*is);
2621  mgh_header->dim2length = _freadt<int32_t>(*is);
2622  mgh_header->dim3length = _freadt<int32_t>(*is);
2623  mgh_header->dim4length = _freadt<int32_t>(*is);
2624 
2625  // Validate dimensions: must be positive (negative would wrap to huge size_t).
2626  if (mgh_header->dim1length <= 0 || mgh_header->dim2length <= 0 ||
2627  mgh_header->dim3length <= 0 || mgh_header->dim4length <= 0)
2628  {
2629  throw std::domain_error("MGH header contains non-positive dimension(s): dims=(" +
2630  std::to_string(mgh_header->dim1length) + "," +
2631  std::to_string(mgh_header->dim2length) + "," +
2632  std::to_string(mgh_header->dim3length) + "," +
2633  std::to_string(mgh_header->dim4length) + ").\n");
2634  }
2635 
2636  // Validate total number of values against allocation limit.
2637  if (!fs::util::check_alloc(static_cast<size_t>(mgh_header->dim1length) *
2638  static_cast<size_t>(mgh_header->dim2length) *
2639  static_cast<size_t>(mgh_header->dim3length),
2640  static_cast<size_t>(mgh_header->dim4length)))
2641  {
2642  throw std::runtime_error("MGH header volume size exceeds maximum allowed allocation (" +
2643  std::to_string(LIBFS_MAX_ALLOC_BYTES) + " bytes).\n");
2644  }
2645 
2646  mgh_header->dtype = _freadt<int32_t>(*is);
2647  mgh_header->dof = _freadt<int32_t>(*is);
2648 
2649  int unused_header_space_size_left = 256; // in bytes
2650  mgh_header->ras_good_flag = _freadt<int16_t>(*is);
2651  unused_header_space_size_left -= 2; // for the ras_good_flag
2652 
2653  // Read the RAS part of the header.
2654  if (mgh_header->ras_good_flag == 1)
2655  {
2656  mgh_header->xsize = _freadt<float>(*is);
2657  mgh_header->ysize = _freadt<float>(*is);
2658  mgh_header->zsize = _freadt<float>(*is);
2659 
2660  // Validate voxel sizes: must be finite and non-zero to prevent division-by-zero
2661  // and NaN/Inf propagation in spatial transform calculations.
2662  if (!fs::util::is_finite_float(mgh_header->xsize) ||
2663  !fs::util::is_finite_float(mgh_header->ysize) ||
2664  !fs::util::is_finite_float(mgh_header->zsize))
2665  {
2666  throw std::domain_error("MGH header contains NaN or Inf voxel size(s): x=" +
2667  std::to_string(mgh_header->xsize) + " y=" +
2668  std::to_string(mgh_header->ysize) + " z=" +
2669  std::to_string(mgh_header->zsize) + ".\n");
2670  }
2671  if (mgh_header->xsize == 0.0f || mgh_header->ysize == 0.0f || mgh_header->zsize == 0.0f)
2672  {
2673  throw std::domain_error("MGH header contains zero voxel size(s): x=" +
2674  std::to_string(mgh_header->xsize) + " y=" +
2675  std::to_string(mgh_header->ysize) + " z=" +
2676  std::to_string(mgh_header->zsize) + ".\n");
2677  }
2678 
2679  for (int i = 0; i < 9; i++)
2680  {
2681  mgh_header->Mdc.push_back(_freadt<float>(*is));
2682  }
2683  for (int i = 0; i < 3; i++)
2684  {
2685  mgh_header->Pxyz_c.push_back(_freadt<float>(*is));
2686  }
2687 
2688  // Validate the direction cosine matrix (Mdc) and center coordinates (Pxyz_c).
2689  for (size_t i = 0; i < mgh_header->Mdc.size(); i++)
2690  {
2691  if (!fs::util::is_finite_float(mgh_header->Mdc[i]))
2692  {
2693  throw std::domain_error("MGH header Mdc matrix contains NaN or Inf at index " +
2694  std::to_string(i) + ".\n");
2695  }
2696  }
2697  for (size_t i = 0; i < mgh_header->Pxyz_c.size(); i++)
2698  {
2699  if (!fs::util::is_finite_float(mgh_header->Pxyz_c[i]))
2700  {
2701  throw std::domain_error("MGH header Pxyz_c contains NaN or Inf at index " +
2702  std::to_string(i) + ".\n");
2703  }
2704  }
2705 
2706  unused_header_space_size_left -= 60;
2707  }
2708 
2709  // Advance to data part. We do not seek here because that is not
2710  // possible if the stream is gzip-wrapped with zstr, as in the read_mgz example.
2711  uint8_t discarded;
2712  while (unused_header_space_size_left > 0)
2713  {
2714  discarded = _freadt<uint8_t>(*is);
2715  unused_header_space_size_left -= 1;
2716  }
2717  (void)discarded; // Suppress warnings about unused variable.
2718  }
2719 
2724  std::vector<int32_t> _read_mgh_data_int(MghHeader *mgh_header, const std::string &filename)
2725  {
2726  if (mgh_header->dtype != MRI_INT)
2727  {
2728  std::cerr << "Expected MRI data type " << MRI_INT << ", but found " << mgh_header->dtype << ".\n";
2729  }
2730  return (_read_mgh_data<int32_t>(mgh_header, filename));
2731  }
2732 
2737  std::vector<int32_t> _read_mgh_data_int(MghHeader *mgh_header, std::istream *is)
2738  {
2739  if (mgh_header->dtype != MRI_INT)
2740  {
2741  std::cerr << "Expected MRI data type " << MRI_INT << ", but found " << mgh_header->dtype << ".\n";
2742  }
2743  return (_read_mgh_data<int32_t>(mgh_header, is));
2744  }
2745 
2750  std::vector<short> _read_mgh_data_short(MghHeader *mgh_header, const std::string &filename)
2751  {
2752  if (mgh_header->dtype != MRI_SHORT)
2753  {
2754  std::cerr << "Expected MRI data type " << MRI_SHORT << ", but found " << mgh_header->dtype << ".\n";
2755  }
2756  return (_read_mgh_data<short>(mgh_header, filename));
2757  }
2758 
2763  std::vector<short> _read_mgh_data_short(MghHeader *mgh_header, std::istream *is)
2764  {
2765  if (mgh_header->dtype != MRI_SHORT)
2766  {
2767  std::cerr << "Expected MRI data type " << MRI_SHORT << ", but found " << mgh_header->dtype << ".\n";
2768  }
2769  return (_read_mgh_data<short>(mgh_header, is));
2770  }
2771 
2778  void read_mgh_header(MghHeader *mgh_header, const std::string &filename)
2779  {
2780  std::ifstream ifs;
2781  ifs.open(filename, std::ios_base::in | std::ios::binary);
2782  if (ifs.is_open())
2783  {
2784  read_mgh_header(mgh_header, &ifs);
2785  ifs.close();
2786  }
2787  else
2788  {
2789  throw std::runtime_error("Unable to open MGH file '" + filename + "'.\n");
2790  }
2791  }
2792 
2798  template <typename T>
2799  std::vector<T> _read_mgh_data(MghHeader *mgh_header, const std::string &filename)
2800  {
2801  std::ifstream ifs;
2802  ifs.open(filename, std::ios_base::in | std::ios::binary);
2803  if (ifs.is_open())
2804  {
2805  size_t num_values = mgh_header->num_values();
2806 
2807  // Cross-check: ensure the file has enough data after the 284-byte header.
2808  size_t file_size = fs::util::get_file_size(filename);
2809  if (file_size > 0)
2810  {
2811  const size_t HEADER_SIZE = 284;
2812  size_t expected_data_bytes = 0;
2813  if (!fs::util::safe_multiply(num_values, sizeof(T), expected_data_bytes))
2814  {
2815  throw std::overflow_error("MGH data size computation overflowed.\n");
2816  }
2817  if (file_size < HEADER_SIZE || (file_size - HEADER_SIZE) < expected_data_bytes)
2818  {
2819  throw std::runtime_error("MGH file '" + filename + "' is too small (" +
2820  std::to_string(file_size) + " bytes) for the data claimed in its header (" +
2821  std::to_string(HEADER_SIZE + expected_data_bytes) + " bytes).\n");
2822  }
2823  }
2824 
2825  if (!fs::util::check_alloc(num_values, sizeof(T)))
2826  {
2827  throw std::runtime_error("MGH file data size exceeds maximum allowed allocation.\n");
2828  }
2829 
2830  ifs.seekg(284, ifs.beg); // skip to end of header and beginning of data
2831 
2832  std::vector<T> data;
2833  data.reserve(num_values);
2834  for (size_t i = 0; i < num_values; i++)
2835  {
2836  data.push_back(_freadt<T>(ifs));
2837  }
2838  ifs.close();
2839  return (data);
2840  }
2841  else
2842  {
2843  throw std::runtime_error("Unable to open MGH file '" + filename + "'.\n");
2844  }
2845  }
2846 
2851  template <typename T>
2852  std::vector<T> _read_mgh_data(MghHeader *mgh_header, std::istream *is)
2853  {
2854  size_t num_values = mgh_header->num_values();
2855  if (!fs::util::check_alloc(num_values, sizeof(T)))
2856  {
2857  throw std::runtime_error("MGH stream data size exceeds maximum allowed allocation.\n");
2858  }
2859  std::vector<T> data;
2860  data.reserve(num_values);
2861  for (size_t i = 0; i < num_values; i++)
2862  {
2863  data.push_back(_freadt<T>(*is));
2864  }
2865  return (data);
2866  }
2867 
2872  std::vector<float> _read_mgh_data_float(MghHeader *mgh_header, const std::string &filename)
2873  {
2874  if (mgh_header->dtype != MRI_FLOAT)
2875  {
2876  std::cerr << "Expected MRI data type " << MRI_FLOAT << ", but found " << mgh_header->dtype << ".\n";
2877  }
2878  return (_read_mgh_data<float>(mgh_header, filename));
2879  }
2880 
2885  std::vector<float> _read_mgh_data_float(MghHeader *mgh_header, std::istream *is)
2886  {
2887  if (mgh_header->dtype != MRI_FLOAT)
2888  {
2889  std::cerr << "Expected MRI data type " << MRI_FLOAT << ", but found " << mgh_header->dtype << ".\n";
2890  }
2891  return (_read_mgh_data<float>(mgh_header, is));
2892  }
2893 
2898  std::vector<uint8_t> _read_mgh_data_uchar(MghHeader *mgh_header, const std::string &filename)
2899  {
2900  if (mgh_header->dtype != MRI_UCHAR)
2901  {
2902  std::cerr << "Expected MRI data type " << MRI_UCHAR << ", but found " << mgh_header->dtype << ".\n";
2903  }
2904  return (_read_mgh_data<uint8_t>(mgh_header, filename));
2905  }
2906 
2911  std::vector<uint8_t> _read_mgh_data_uchar(MghHeader *mgh_header, std::istream *is)
2912  {
2913  if (mgh_header->dtype != MRI_UCHAR)
2914  {
2915  std::cerr << "Expected MRI data type " << MRI_UCHAR << ", but found " << mgh_header->dtype << ".\n";
2916  }
2917  return (_read_mgh_data<uint8_t>(mgh_header, is));
2918  }
2919 
2933  void read_surf(Mesh *surface, const std::string &filename)
2934  {
2935  const int SURF_TRIS_MAGIC = 16777214;
2936  std::ifstream is;
2937  is.open(filename, std::ios_base::in | std::ios::binary);
2938  if (is.is_open())
2939  {
2940  int magic = _fread3(is);
2941  if (magic != SURF_TRIS_MAGIC)
2942  {
2943  throw std::domain_error("Surf file '" + filename + "' magic code in header did not match: expected " + std::to_string(SURF_TRIS_MAGIC) + ", found " + std::to_string(magic) + ".\n");
2944  }
2945  std::string created_line = _freadstringnewline(is);
2946  std::string comment_line = _freadstringnewline(is);
2947  int num_verts = _freadt<int32_t>(is);
2948  int num_faces = _freadt<int32_t>(is);
2949 
2950  // Validate header fields.
2951  if (num_verts <= 0)
2952  {
2953  throw std::domain_error("Surf file '" + filename + "' has invalid num_verts: " + std::to_string(num_verts) + ".\n");
2954  }
2955  if (num_faces < 0)
2956  {
2957  throw std::domain_error("Surf file '" + filename + "' has invalid num_faces: " + std::to_string(num_faces) + ".\n");
2958  }
2959 
2960  // Safe multiplication: num_verts * 3 (x,y,z per vertex).
2961  size_t num_vert_coords = 0;
2962  if (!fs::util::safe_multiply(static_cast<size_t>(num_verts), 3, num_vert_coords))
2963  {
2964  throw std::overflow_error("Surf file '" + filename + "': num_verts * 3 overflowed.\n");
2965  }
2966  size_t num_face_indices = 0;
2967  if (!fs::util::safe_multiply(static_cast<size_t>(num_faces), 3, num_face_indices))
2968  {
2969  throw std::overflow_error("Surf file '" + filename + "': num_faces * 3 overflowed.\n");
2970  }
2971 
2972  // Cross-check against file size.
2973  size_t file_size = fs::util::get_file_size(filename);
2974  if (file_size > 0)
2975  {
2976  size_t vert_bytes = 0, face_bytes = 0;
2977  if (!fs::util::safe_multiply(num_vert_coords, sizeof(float), vert_bytes) ||
2978  !fs::util::safe_multiply(num_face_indices, sizeof(int32_t), face_bytes))
2979  {
2980  throw std::overflow_error("Surf file '" + filename + "': expected data size overflowed.\n");
2981  }
2982  // Guard against addition overflow (paranoid, since each is already <= LIBFS_MAX_ALLOC_BYTES).
2983  if (vert_bytes > std::numeric_limits<size_t>::max() - face_bytes)
2984  {
2985  throw std::overflow_error("Surf file '" + filename + "': total data size overflowed.\n");
2986  }
2987  size_t expected_total = vert_bytes + face_bytes;
2988  // Header takes some space, so file_size > raw data size for any valid file.
2989  if (file_size < expected_total)
2990  {
2991  throw std::runtime_error("Surf file '" + filename + "' is too small (" +
2992  std::to_string(file_size) + " bytes) for the data claimed in its header.\n");
2993  }
2994  }
2995 
2996  if (!fs::util::check_alloc(num_vert_coords, sizeof(float)) ||
2997  !fs::util::check_alloc(num_face_indices, sizeof(int32_t)))
2998  {
2999  throw std::runtime_error("Surf file '" + filename + "' data size exceeds maximum allowed allocation.\n");
3000  }
3001 
3002 #ifdef LIBFS_DBG_INFO
3003  std::cout << LIBFS_APPTAG << "Read surface file with " << num_verts << " vertices, " << num_faces << " faces.\n";
3004 #endif
3005  std::vector<float> vdata;
3006  vdata.reserve(num_vert_coords);
3007  for (size_t i = 0; i < num_vert_coords; i++)
3008  {
3009  vdata.push_back(_freadt<float>(is));
3010  }
3011  std::vector<int> fdata;
3012  fdata.reserve(num_face_indices);
3013  for (size_t i = 0; i < num_face_indices; i++)
3014  {
3015  fdata.push_back(_freadt<int32_t>(is));
3016  }
3017  is.close();
3018  surface->vertices = vdata;
3019  surface->faces = fdata;
3020  }
3021  else
3022  {
3023  throw std::runtime_error("Unable to open surface file '" + filename + "'.\n");
3024  }
3025  }
3026 
3039  void read_mesh(Mesh *surface, const std::string &filename)
3040  {
3041  if (fs::util::ends_with(filename, ".obj"))
3042  {
3043  fs::Mesh::from_obj(surface, filename);
3044  }
3045  else if (fs::util::ends_with(filename, ".ply"))
3046  {
3047  fs::Mesh::from_ply(surface, filename);
3048  }
3049  else if (fs::util::ends_with(filename, ".off"))
3050  {
3051  fs::Mesh::from_off(surface, filename);
3052  }
3053  else
3054  {
3055  read_surf(surface, filename);
3056  }
3057  }
3058 
3064  bool _is_bigendian()
3065  {
3066  const short int number = 0x1;
3067  const char *numPtr = reinterpret_cast<const char *>(&number);
3068  return (numPtr[0] != 1);
3069  }
3070 
3076  void read_curv(Curv *curv, std::istream *is, const std::string &source_filename = "")
3077  {
3078  const std::string msg_source_file_part = source_filename.empty() ? "" : "'" + source_filename + "' ";
3079  const int CURV_MAGIC = 16777215;
3080  int magic = _fread3(*is);
3081  if (magic != CURV_MAGIC)
3082  {
3083  throw std::domain_error("Curv file " + msg_source_file_part + "header magic did not match: expected " + std::to_string(CURV_MAGIC) + ", found " + std::to_string(magic) + ".\n");
3084  }
3085  curv->num_vertices = _freadt<int32_t>(*is);
3086  curv->num_faces = _freadt<int32_t>(*is);
3087  curv->num_values_per_vertex = _freadt<int32_t>(*is);
3088 
3089  // Validate header fields.
3090  if (curv->num_vertices <= 0)
3091  {
3092  throw std::domain_error("Curv file " + msg_source_file_part + "has invalid num_vertices: " + std::to_string(curv->num_vertices) + ".\n");
3093  }
3094  if (curv->num_faces < 0)
3095  {
3096  throw std::domain_error("Curv file " + msg_source_file_part + "has invalid num_faces: " + std::to_string(curv->num_faces) + ".\n");
3097  }
3098 
3099 #ifdef LIBFS_DBG_INFO
3100  std::cout << LIBFS_APPTAG << "Read curv file with " << curv->num_vertices << " vertices, " << curv->num_faces << " faces and " << curv->num_values_per_vertex << " values per vertex.\n";
3101 #endif
3102  if (curv->num_values_per_vertex != 1)
3103  { // Not supported, I know no case where this is used. Please submit a PR with a demo file if you have one, and let me know where it came from.
3104  throw std::domain_error("Curv file " + msg_source_file_part + "must contain exactly 1 value per vertex, found " + std::to_string(curv->num_values_per_vertex) + ".\n");
3105  }
3106 
3107  // File-size cross-check (only when reading from a file, not a generic stream).
3108  if (!source_filename.empty())
3109  {
3110  size_t file_size = fs::util::get_file_size(source_filename);
3111  if (file_size > 0)
3112  {
3113  // Curv header: 3 (magic) + 12 (three int32) = 15 bytes.
3114  const size_t CURV_HEADER_SIZE = 15;
3115  size_t expected_data_bytes = 0;
3116  if (!fs::util::safe_multiply(static_cast<size_t>(curv->num_vertices), sizeof(float), expected_data_bytes))
3117  {
3118  throw std::overflow_error("Curv file " + msg_source_file_part + "data size computation overflowed.\n");
3119  }
3120  if (file_size < CURV_HEADER_SIZE || (file_size - CURV_HEADER_SIZE) < expected_data_bytes)
3121  {
3122  throw std::runtime_error("Curv file " + msg_source_file_part + "is too small (" +
3123  std::to_string(file_size) + " bytes) for the data claimed in its header (" +
3124  std::to_string(CURV_HEADER_SIZE + expected_data_bytes) + " bytes expected).\n");
3125  }
3126  }
3127  }
3128 
3129  std::vector<float> data;
3130  if (!fs::util::check_alloc(static_cast<size_t>(curv->num_vertices), sizeof(float)))
3131  {
3132  throw std::runtime_error("Curv file " + msg_source_file_part + "data size exceeds maximum allowed allocation.\n");
3133  }
3134  data.reserve(static_cast<size_t>(curv->num_vertices));
3135  for (size_t i = 0; i < static_cast<size_t>(curv->num_vertices); i++)
3136  {
3137  data.push_back(_freadt<float>(*is));
3138  }
3139  curv->data = data;
3140  }
3141 
3154  void read_curv(Curv *curv, const std::string &filename)
3155  {
3156  std::ifstream is(filename, std::fstream::in | std::fstream::binary);
3157  if (is.is_open())
3158  {
3159  read_curv(curv, &is, filename);
3160  is.close();
3161  }
3162  else
3163  {
3164  throw std::runtime_error("Could not open curv file '" + filename + "' for reading.\n");
3165  }
3166  }
3167 
3170  void _read_annot_colortable(Colortable *colortable, std::istream *is, int32_t num_entries)
3171  {
3172  // Validate num_entries against a reasonable cap.
3173  if (num_entries < 0 || static_cast<size_t>(num_entries) > LIBFS_MAX_COLORTABLE_ENTRIES)
3174  {
3175  throw std::domain_error("Annot colortable num_entries " + std::to_string(num_entries) +
3176  " is invalid or exceeds maximum (" + std::to_string(LIBFS_MAX_COLORTABLE_ENTRIES) + ").\n");
3177  }
3178 
3179  int32_t num_chars_orig_filename = _freadt<int32_t>(*is); // The number of characters of the file this annot was built from.
3180 
3181  // Validate and cap the original filename length.
3182  if (num_chars_orig_filename < 0 || static_cast<size_t>(num_chars_orig_filename) > LIBFS_MAX_STRING_LENGTH)
3183  {
3184  throw std::domain_error("Annot colortable original filename length " + std::to_string(num_chars_orig_filename) +
3185  " exceeds maximum (" + std::to_string(LIBFS_MAX_STRING_LENGTH) + ").\n");
3186  }
3187 
3188  // It follows the name of the file this annot was built from. This is development metadata and irrelevant afaik. We skip it.
3189  uint8_t discarded;
3190  for (int32_t i = 0; i < num_chars_orig_filename; i++)
3191  {
3192  discarded = _freadt<uint8_t>(*is);
3193  }
3194  (void)discarded; // Suppress warnings about unused variable.
3195 
3196  int32_t num_entries_duplicated = _freadt<int32_t>(*is); // Yes, once more.
3197  if (num_entries != num_entries_duplicated)
3198  {
3199  std::cerr << "Warning: the two num_entries header fields of this annotation do not match. Use with care.\n";
3200  }
3201 
3202  colortable->id.reserve(static_cast<size_t>(num_entries));
3203  colortable->name.reserve(static_cast<size_t>(num_entries));
3204  colortable->r.reserve(static_cast<size_t>(num_entries));
3205  colortable->g.reserve(static_cast<size_t>(num_entries));
3206  colortable->b.reserve(static_cast<size_t>(num_entries));
3207  colortable->a.reserve(static_cast<size_t>(num_entries));
3208  colortable->label.reserve(static_cast<size_t>(num_entries));
3209 
3210  int32_t entry_num_chars;
3211  for (int32_t i = 0; i < num_entries; i++)
3212  {
3213  colortable->id.push_back(_freadt<int32_t>(*is));
3214  entry_num_chars = _freadt<int32_t>(*is);
3215  // Pass a tighter max_length for region names (256 chars should be plenty).
3216  colortable->name.push_back(_freadfixedlengthstring(*is, entry_num_chars, true, 256));
3217  colortable->r.push_back(_freadt<int32_t>(*is));
3218  colortable->g.push_back(_freadt<int32_t>(*is));
3219  colortable->b.push_back(_freadt<int32_t>(*is));
3220  colortable->a.push_back(_freadt<int32_t>(*is));
3221  colortable->label.push_back(colortable->r[i] + colortable->g[i] * 256 + colortable->b[i] * 65536 + colortable->a[i] * 16777216);
3222  }
3223  }
3224 
3227  size_t _vidx_2d(size_t row, size_t column, size_t row_length = 3)
3228  {
3229  return (row + 1) * row_length - row_length + column;
3230  }
3231 
3237  void read_annot(Annot *annot, std::istream *is)
3238  {
3239 
3240  int32_t num_vertices = _freadt<int32_t>(*is);
3241 
3242  // Validate num_vertices.
3243  if (num_vertices <= 0)
3244  {
3245  throw std::domain_error("Annot file has invalid num_vertices: " + std::to_string(num_vertices) + ".\n");
3246  }
3247 
3248  // Safe multiplication: num_vertices * 2 (vertex index + label per vertex).
3249  size_t num_entries = 0;
3250  if (!fs::util::safe_multiply(static_cast<size_t>(num_vertices), 2, num_entries))
3251  {
3252  throw std::overflow_error("Annot: num_vertices * 2 overflowed.\n");
3253  }
3254  if (!fs::util::check_alloc(num_entries, sizeof(int32_t)))
3255  {
3256  throw std::runtime_error("Annot vertex/label data size exceeds maximum allowed allocation.\n");
3257  }
3258 
3259  std::vector<int32_t> vertices;
3260  std::vector<int32_t> labels;
3261  vertices.reserve(num_vertices);
3262  labels.reserve(num_vertices);
3263  for (size_t i = 0; i < num_entries; i++)
3264  { // The vertices and their labels are stored directly after one another: v1,v1_label,v2,v2_label,...
3265  if (i % 2 == 0)
3266  {
3267  vertices.push_back(_freadt<int32_t>(*is));
3268  }
3269  else
3270  {
3271  labels.push_back(_freadt<int32_t>(*is));
3272  }
3273  }
3274  annot->vertex_indices = vertices;
3275  annot->vertex_labels = labels;
3276  int32_t has_colortable = _freadt<int32_t>(*is);
3277  if (has_colortable == 1)
3278  {
3279  int32_t num_colortable_entries_old_format = _freadt<int32_t>(*is);
3280  if (num_colortable_entries_old_format > 0)
3281  {
3282  throw std::domain_error("Reading annotation in old format not supported. Please open an issue and supply an example file if you need this.\n");
3283  }
3284  else
3285  {
3286  int32_t colortable_format_version = -num_colortable_entries_old_format; // If the value is negative, we are in new format and its absolute value is the format version.
3287  if (colortable_format_version == 2)
3288  {
3289  int32_t num_colortable_entries = _freadt<int32_t>(*is); // This time for real.
3290  _read_annot_colortable(&annot->colortable, is, num_colortable_entries);
3291  }
3292  else
3293  {
3294  throw std::domain_error("Reading annotation in new format version !=2 not supported. Please open an issue and supply an example file if you need this.\n");
3295  }
3296  }
3297  }
3298  else
3299  {
3300  throw std::domain_error("Reading annotation without colortable not supported. Maybe invalid annotation file?\n");
3301  }
3302  }
3303 
3317  void read_annot(Annot *annot, const std::string &filename)
3318  {
3319  std::ifstream is(filename, std::fstream::in | std::fstream::binary);
3320  if (is.is_open())
3321  {
3322  read_annot(annot, &is);
3323  is.close();
3324  }
3325  else
3326  {
3327  throw std::runtime_error("Could not open annot file '" + filename + "' for reading.\n");
3328  }
3329  }
3330 
3343  std::vector<float> read_curv_data(const std::string &filename)
3344  {
3345  Curv curv;
3346  read_curv(&curv, filename);
3347  return (curv.data);
3348  }
3349 
3363  std::vector<float> read_desc_data(const std::string &filename)
3364  {
3365  if (fs::util::ends_with(filename, {".MGH", ".mgh"}))
3366  {
3367  fs::Mgh mgh;
3368  fs::read_mgh(&mgh, filename);
3369  assert(mgh.header.dtype == fs::MRI_FLOAT);
3370  int num_gt_1 = 0;
3371  std::vector<int> dims = {mgh.header.dim1length, mgh.header.dim2length, mgh.header.dim3length, mgh.header.dim4length};
3372  for (size_t i = 0; i < dims.size(); i++)
3373  {
3374  if (dims[i] > 1)
3375  {
3376  num_gt_1++;
3377  }
3378  }
3379  if (num_gt_1 > 1)
3380  {
3381  std::cerr << "MGH file '" << filename << "' contains more than one non-empty dimension. Returning concatinated data.\n";
3382  }
3383  return mgh.data.data_mri_float;
3384  }
3385  else
3386  {
3387  Curv curv;
3388  read_curv(&curv, filename);
3389  return (curv.data);
3390  }
3391  }
3392 
3400  template <typename T>
3401  T _swap_endian(T u)
3402  {
3403  static_assert(CHAR_BIT == 8, "CHAR_BIT != 8");
3404 
3405  unsigned char src[sizeof(T)];
3406  unsigned char dst[sizeof(T)];
3407  std::memcpy(src, &u, sizeof(T));
3408 
3409  for (size_t k = 0; k < sizeof(T); k++)
3410  {
3411  dst[k] = src[sizeof(T) - k - 1];
3412  }
3413 
3414  T result;
3415  std::memcpy(&result, dst, sizeof(T));
3416  return result;
3417  }
3418 
3423  template <typename T>
3424  T _freadt(std::istream &is)
3425  {
3426  T t;
3427  is.read(reinterpret_cast<char *>(&t), sizeof(t));
3428  if (static_cast<size_t>(is.gcount()) != sizeof(T))
3429  {
3430  if (is.gcount() == 0)
3431  {
3432  throw std::runtime_error("Unexpected end of binary stream: expected " + std::to_string(sizeof(T)) + " bytes, got EOF.\n");
3433  }
3434  throw std::runtime_error("Short read in binary stream: expected " + std::to_string(sizeof(T)) + " bytes, got " + std::to_string(is.gcount()) + ".\n");
3435  }
3436  if (!_is_bigendian())
3437  {
3438  t = _swap_endian<T>(t);
3439  }
3440  return (t);
3441  }
3442 
3447  int _fread3(std::istream &is)
3448  {
3449  uint32_t i;
3450  is.read(reinterpret_cast<char *>(&i), 3);
3451  if (static_cast<size_t>(is.gcount()) != 3)
3452  {
3453  if (is.gcount() == 0)
3454  {
3455  throw std::runtime_error("Unexpected end of binary stream: expected 3 bytes, got EOF.\n");
3456  }
3457  throw std::runtime_error("Short read in binary stream: expected 3 bytes, got " + std::to_string(is.gcount()) + ".\n");
3458  }
3459  if (!_is_bigendian())
3460  {
3461  i = _swap_endian<std::uint32_t>(i);
3462  }
3463  i = ((i >> 8) & 0xffffff);
3464  return (i);
3465  }
3466 
3471  template <typename T>
3472  void _fwritet(std::ostream &os, T t)
3473  {
3474  if (!_is_bigendian())
3475  {
3476  t = _swap_endian<T>(t);
3477  }
3478  os.write(reinterpret_cast<const char *>(&t), sizeof(t));
3479  }
3480 
3481  // Write big endian 24 bit integer to a stream, extracted from the first 3 bytes of an unsigned 32 bit integer.
3482  //
3483  // THIS FUNCTION IS INTERNAL AND SHOULD NOT BE CALLED BY API CLIENTS.
3485  void _fwritei3(std::ostream &os, uint32_t i)
3486  {
3487  unsigned char b1 = (i >> 16) & 255;
3488  unsigned char b2 = (i >> 8) & 255;
3489  unsigned char b3 = i & 255;
3490 
3491  if (!_is_bigendian())
3492  {
3493  b1 = _swap_endian<unsigned char>(b1);
3494  b2 = _swap_endian<unsigned char>(b2);
3495  b3 = _swap_endian<unsigned char>(b3);
3496  }
3497 
3498  os.write(reinterpret_cast<const char *>(&b1), sizeof(b1));
3499  os.write(reinterpret_cast<const char *>(&b2), sizeof(b2));
3500  os.write(reinterpret_cast<const char *>(&b3), sizeof(b3));
3501  }
3502 
3507  std::string _freadstringnewline(std::istream &is)
3508  {
3509  std::string s;
3510  std::getline(is, s, '\n');
3511  return s;
3512  }
3513 
3518  std::string _freadfixedlengthstring(std::istream &is, size_t length, bool strip_last_char = true, size_t max_length = LIBFS_MAX_STRING_LENGTH)
3519  {
3520  if (length == 0)
3521  {
3522  throw std::domain_error("Fixed-length string read with zero length.\n");
3523  }
3524  if (length > max_length)
3525  {
3526  throw std::domain_error("Fixed-length string length " + std::to_string(length) + " exceeds maximum " + std::to_string(max_length) + ".\n");
3527  }
3528  std::string str;
3529  str.resize(length);
3530  is.read(&str[0], length);
3531  if (static_cast<size_t>(is.gcount()) != length)
3532  {
3533  if (is.gcount() == 0)
3534  {
3535  throw std::runtime_error("Unexpected end of binary stream while reading fixed-length string: expected " + std::to_string(length) + " bytes, got EOF.\n");
3536  }
3537  throw std::runtime_error("Short read in binary stream while reading fixed-length string: expected " + std::to_string(length) + " bytes, got " + std::to_string(is.gcount()) + ".\n");
3538  }
3539  if (strip_last_char)
3540  {
3541  str = str.substr(0, length - 1);
3542  }
3543  return str;
3544  }
3545 
3551  void write_curv(std::ostream &os, std::vector<float> curv_data, int32_t num_faces = 100000)
3552  {
3553  const uint32_t CURV_MAGIC = 16777215;
3554  _fwritei3(os, CURV_MAGIC);
3555  _fwritet<int32_t>(os, int(curv_data.size()));
3556  _fwritet<int32_t>(os, num_faces);
3557  _fwritet<int32_t>(os, 1); // Number of values per vertex.
3558  for (size_t i = 0; i < curv_data.size(); i++)
3559  {
3560  _fwritet<float>(os, curv_data[i]);
3561  }
3562  }
3563 
3578  void write_curv(const std::string &filename, std::vector<float> curv_data, const int32_t num_faces = 100000)
3579  {
3580  std::ofstream ofs;
3581  ofs.open(filename, std::ofstream::out | std::ofstream::binary);
3582  if (ofs.is_open())
3583  {
3584  write_curv(ofs, curv_data, num_faces);
3585  ofs.close();
3586  }
3587  else
3588  {
3589  throw std::runtime_error("Unable to open curvature file '" + filename + "' for writing.\n");
3590  }
3591  }
3592 
3598  void write_mgh(const Mgh &mgh, std::ostream &os)
3599  {
3600  _fwritet<int32_t>(os, 1); // MGH file format version
3601  _fwritet<int32_t>(os, mgh.header.dim1length);
3602  _fwritet<int32_t>(os, mgh.header.dim2length);
3603  _fwritet<int32_t>(os, mgh.header.dim3length);
3604  _fwritet<int32_t>(os, mgh.header.dim4length);
3605 
3606  _fwritet<int32_t>(os, mgh.header.dtype);
3607  _fwritet<int32_t>(os, mgh.header.dof);
3608 
3609  size_t unused_header_space_size_left = 256; // in bytes
3610  _fwritet<int16_t>(os, mgh.header.ras_good_flag);
3611  unused_header_space_size_left -= 2; // for RAS flag
3612 
3613  // Write RAS part of of header if flag is 1.
3614  if (mgh.header.ras_good_flag == 1)
3615  {
3616  _fwritet<float>(os, mgh.header.xsize);
3617  _fwritet<float>(os, mgh.header.ysize);
3618  _fwritet<float>(os, mgh.header.zsize);
3619 
3620  for (int i = 0; i < 9; i++)
3621  {
3622  _fwritet<float>(os, mgh.header.Mdc[i]);
3623  }
3624  for (int i = 0; i < 3; i++)
3625  {
3626  _fwritet<float>(os, mgh.header.Pxyz_c[i]);
3627  }
3628 
3629  unused_header_space_size_left -= 60;
3630  }
3631 
3632  for (size_t i = 0; i < unused_header_space_size_left; i++)
3633  { // Fill rest of header space.
3634  _fwritet<uint8_t>(os, 0);
3635  }
3636 
3637  // Write data
3638  size_t num_values = mgh.header.num_values();
3639  if (mgh.header.dtype == MRI_INT)
3640  {
3641  if (mgh.data.data_mri_int.size() != num_values)
3642  {
3643  throw std::logic_error("Detected mismatch of MRI_INT data size and MGH header dim length values.\n");
3644  }
3645  for (size_t i = 0; i < num_values; i++)
3646  {
3647  _fwritet<int32_t>(os, mgh.data.data_mri_int[i]);
3648  }
3649  }
3650  else if (mgh.header.dtype == MRI_FLOAT)
3651  {
3652  if (mgh.data.data_mri_float.size() != num_values)
3653  {
3654  throw std::logic_error("Detected mismatch of MRI_FLOAT data size and MGH header dim length values.\n");
3655  }
3656  for (size_t i = 0; i < num_values; i++)
3657  {
3658  _fwritet<float>(os, mgh.data.data_mri_float[i]);
3659  }
3660  }
3661  else if (mgh.header.dtype == MRI_UCHAR)
3662  {
3663  if (mgh.data.data_mri_uchar.size() != num_values)
3664  {
3665  throw std::logic_error("Detected mismatch of MRI_UCHAR data size and MGH header dim length values.\n");
3666  }
3667  for (size_t i = 0; i < num_values; i++)
3668  {
3669  _fwritet<uint8_t>(os, mgh.data.data_mri_uchar[i]);
3670  }
3671  }
3672  else if (mgh.header.dtype == MRI_SHORT)
3673  {
3674  if (mgh.data.data_mri_short.size() != num_values)
3675  {
3676  throw std::logic_error("Detected mismatch of MRI_SHORT data size and MGH header dim length values.\n");
3677  }
3678  for (size_t i = 0; i < num_values; i++)
3679  {
3680  _fwritet<short>(os, mgh.data.data_mri_short[i]);
3681  }
3682  }
3683  else
3684  {
3685  throw std::domain_error("Unsupported MRI data type " + std::to_string(mgh.header.dtype) + ", cannot write MGH data.\n");
3686  }
3687  }
3688 
3704  void write_mgh(const Mgh &mgh, const std::string &filename)
3705  {
3706  std::ofstream ofs;
3707  ofs.open(filename, std::ofstream::out | std::ofstream::binary);
3708  if (ofs.is_open())
3709  {
3710  write_mgh(mgh, ofs);
3711  ofs.close();
3712  }
3713  else
3714  {
3715  throw std::runtime_error("Unable to open MGH file '" + filename + "' for writing.\n");
3716  }
3717  }
3718 
3725  struct Label
3726  {
3727 
3729  Label() {}
3730 
3732  Label(std::vector<int> vertices, std::vector<float> values)
3733  {
3734  assert(vertices.size() == values.size());
3735  vertex = vertices;
3736  value = values;
3737  coord_x = std::vector<float>(vertices.size(), 0.0f);
3738  coord_y = std::vector<float>(vertices.size(), 0.0f);
3739  coord_z = std::vector<float>(vertices.size(), 0.0f);
3740  }
3741 
3743  Label(std::vector<int> vertices)
3744  {
3745  vertex = vertices;
3746  value = std::vector<float>(vertices.size(), 0.0f);
3747  coord_x = std::vector<float>(vertices.size(), 0.0f);
3748  coord_y = std::vector<float>(vertices.size(), 0.0f);
3749  coord_z = std::vector<float>(vertices.size(), 0.0f);
3750  }
3751 
3752  std::vector<int> vertex;
3753  std::vector<float> coord_x;
3754  std::vector<float> coord_y;
3755  std::vector<float> coord_z;
3756  std::vector<float> value;
3757 
3759  std::vector<bool> vert_in_label(size_t surface_num_verts) const
3760  {
3761  if (surface_num_verts < this->vertex.size())
3762  { // nonsense, so we warn (but don't throw, maybe the user really wants this).
3763  std::cerr << "Invalid number of vertices for surface, must be at least " << this->vertex.size() << "\n";
3764  }
3765  std::vector<bool> is_in = std::vector<bool>(surface_num_verts, false);
3766 
3767  for (size_t i = 0; i < this->vertex.size(); i++)
3768  {
3769  is_in[this->vertex[i]] = true;
3770  }
3771  return (is_in);
3772  }
3773 
3775  size_t num_entries() const
3776  {
3777  size_t num_ent = this->vertex.size();
3778  if (this->coord_x.size() != num_ent || this->coord_y.size() != num_ent || this->coord_z.size() != num_ent || this->value.size() != num_ent)
3779  {
3780  std::cerr << "Inconsistent label: sizes of property vectors do not match.\n";
3781  }
3782  return (num_ent);
3783  }
3784  };
3785 
3792  void write_surf(std::vector<float> vertices, std::vector<int32_t> faces, std::ostream &os)
3793  {
3794  const uint32_t SURF_TRIS_MAGIC = 16777214;
3795  _fwritei3(os, SURF_TRIS_MAGIC);
3796  std::string created_and_comment_lines = "Created by fslib\n\n";
3797  os << created_and_comment_lines;
3798  _fwritet<int32_t>(os, int(vertices.size() / 3)); // number of vertices
3799  _fwritet<int32_t>(os, int(faces.size() / 3)); // number of faces
3800  for (size_t i = 0; i < vertices.size(); i++)
3801  {
3802  _fwritet<float>(os, vertices[i]);
3803  }
3804  for (size_t i = 0; i < faces.size(); i++)
3805  {
3806  _fwritet<int32_t>(os, faces[i]);
3807  }
3808  }
3809 
3823  void write_surf(std::vector<float> vertices, std::vector<int32_t> faces, const std::string &filename)
3824  {
3825  std::ofstream ofs;
3826  ofs.open(filename, std::ofstream::out | std::ofstream::binary);
3827  if (ofs.is_open())
3828  {
3829  write_surf(vertices, faces, ofs);
3830  ofs.close();
3831  }
3832  else
3833  {
3834  throw std::runtime_error("Unable to open surf file '" + filename + "' for writing.\n");
3835  }
3836  }
3837 
3850  void write_surf(const Mesh &mesh, const std::string &filename)
3851  {
3852  std::ofstream ofs;
3853  ofs.open(filename, std::ofstream::out | std::ofstream::binary);
3854  if (ofs.is_open())
3855  {
3856  write_surf(mesh.vertices, mesh.faces, ofs);
3857  ofs.close();
3858  }
3859  else
3860  {
3861  throw std::runtime_error("Unable to open surf file '" + filename + "' for writing.\n");
3862  }
3863  }
3864 
3871  void read_label(Label *label, std::istream *is)
3872  {
3873  std::string line;
3874  int line_idx = -1;
3875  size_t num_entries_header = 0; // number of vertices/voxels according to header
3876  size_t num_entries = 0; // number of vertices/voxels for which the file contains label entries.
3877  while (std::getline(*is, line))
3878  {
3879  line_idx += 1;
3880  std::istringstream iss(line);
3881  if (line_idx == 0)
3882  {
3883  continue; // skip comment.
3884  }
3885  else
3886  {
3887  if (line_idx == 1)
3888  {
3889  if (!(iss >> num_entries_header))
3890  {
3891  throw std::domain_error("Could not parse entry count from label file, invalid format.\n");
3892  }
3893  }
3894  else
3895  {
3896  int vertex;
3897  float x, y, z, value;
3898  if (!(iss >> vertex >> x >> y >> z >> value))
3899  {
3900  throw std::domain_error("Could not parse line " + std::to_string(line_idx + 1) + " of label file, invalid format.\n");
3901  }
3902  label->vertex.push_back(vertex);
3903  label->coord_x.push_back(x);
3904  label->coord_y.push_back(y);
3905  label->coord_z.push_back(z);
3906  label->value.push_back(value);
3907  num_entries++;
3908  }
3909  }
3910  }
3911  if (num_entries != num_entries_header)
3912  {
3913  throw std::domain_error("Expected " + std::to_string(num_entries_header) + " entries from label file header, but found " + std::to_string(num_entries) + " in file, invalid label file.\n");
3914  }
3915  if (label->vertex.size() != num_entries || label->coord_x.size() != num_entries || label->coord_y.size() != num_entries || label->coord_z.size() != num_entries || label->value.size() != num_entries)
3916  {
3917  throw std::domain_error("Expected " + std::to_string(num_entries) + " entries in all Label vectors, but some did not match.\n");
3918  }
3919  }
3920 
3934  void read_label(Label *label, const std::string &filename)
3935  {
3936  std::ifstream infile(filename, std::fstream::in);
3937  if (infile.is_open())
3938  {
3939  read_label(label, &infile);
3940  infile.close();
3941  }
3942  else
3943  {
3944  throw std::runtime_error("Could not open label file '" + filename + "' for reading.\n");
3945  }
3946  }
3947 
3952  void write_label(const Label &label, std::ostream &os)
3953  {
3954  const size_t num_entries = label.num_entries();
3955  os << "#!ascii label from subject anonymous\n"
3956  << num_entries << "\n";
3957  for (size_t i = 0; i < num_entries; i++)
3958  {
3959  os << label.vertex[i] << " " << label.coord_x[i] << " " << label.coord_y[i] << " " << label.coord_z[i] << " " << label.value[i] << "\n";
3960  }
3961  }
3962 
3976  void write_label(const Label &label, const std::string &filename)
3977  {
3978  std::ofstream ofs;
3979  ofs.open(filename, std::ofstream::out);
3980  if (ofs.is_open())
3981  {
3982  write_label(label, ofs);
3983  ofs.close();
3984  }
3985  else
3986  {
3987  throw std::runtime_error("Unable to open label file '" + filename + "' for writing.\n");
3988  }
3989  }
3990 
4006  void write_mesh(const Mesh &mesh, const std::string &filename)
4007  {
4008  if (fs::util::ends_with(filename, {".ply", ".PLY"}))
4009  {
4010  mesh.to_ply_file(filename);
4011  }
4012  else if (fs::util::ends_with(filename, {".obj", ".OBJ"}))
4013  {
4014  mesh.to_obj_file(filename);
4015  }
4016  else if (fs::util::ends_with(filename, {".off", ".OFF"}))
4017  {
4018  mesh.to_off_file(filename);
4019  }
4020  else
4021  {
4022  fs::write_surf(mesh, filename);
4023  }
4024  }
4025 
4026 } // End namespace fs
void write_curv(std::ostream &os, std::vector< float > curv_data, int32_t num_faces=100000)
Write curv data to a stream.
Definition: libfs.h:3551
std::vector< float > Mdc
matrix
Definition: libfs.h:2350
std::vector< float > vertices
n x 3 vector of the x,y,z coordinates for the n vertices. The x,y,z coordinates for a single vertex f...
Definition: libfs.h:837
std::vector< int32_t > vertex_indices
Indices of the vertices, these always go from 0 to N-1 (where N is the number of vertices in the resp...
Definition: libfs.h:2211
size_t num_entries() const
Get the number of enties (regions) in this Colortable.
Definition: libfs.h:2171
Mgh(Curv curv)
Definition: libfs.h:2375
std::vector< float > coord_x
x coordinates of the vertices in case of a surface label, or voxels coordinates for a volume label...
Definition: libfs.h:3753
std::string to_off(const std::vector< uint8_t > col) const
Return string representing the mesh in PLY format.
Definition: libfs.h:2068
Models a FreeSurfer curv file that contains per-vertex float data.
Definition: libfs.h:2133
const int MRI_SHORT
MRI data type representing a 16 bit signed integer.
Definition: libfs.h:788
std::vector< float > Pxyz_c
x,y,z coordinates of central vertex
Definition: libfs.h:2351
std::vector< float > coord_y
y coordinates of the vertices in case of a surface label, or voxels coordinates for a volume label...
Definition: libfs.h:3754
int32_t dof
typically ignored
Definition: libfs.h:2338
unsigned int d2
size of data along 2nd dimension
Definition: libfs.h:2437
const std::string LOGTAG_EXCESSIVE
Logging threshold for warning messages.
Definition: libfs.h:217
An annotation, also known as a brain surface parcellation. Assigns to each vertex a region...
Definition: libfs.h:2209
const std::string LOGTAG_VERBOSE
Logging threshold for warning messages.
Definition: libfs.h:214
void read_curv(Curv *curv, std::istream *is, const std::string &source_filename="")
Read per-vertex brain morphometry data from a FreeSurfer curv stream.
Definition: libfs.h:3076
edge_set as_edgelist() const
Return edge list representation of this mesh.
Definition: libfs.h:1046
Definition: libfs.h:3725
MghHeader()
Empty default constuctor.
Definition: libfs.h:2315
std::vector< int32_t > label
label integer computed from rgba values. Maps to the Annot.vertex_label field.
Definition: libfs.h:2168
Array4D(MghHeader *mgh_header)
Definition: libfs.h:2403
std::string to_obj() const
Return string representing the mesh in Wavefront Object (.obj) format.
Definition: libfs.h:982
std::string time_tag(std::chrono::system_clock::time_point t)
Get current time as string, e.g. for log messages.
Definition: libfs.h:187
static void from_obj(Mesh *mesh, const std::string &filename)
Read a brainmesh from a Wavefront object format mesh file.
Definition: libfs.h:1541
const std::string LOGTAG_WARNING
Logging threshold for warning messages.
Definition: libfs.h:208
static std::vector< float > smooth_pvd_nn(const std::vector< std::vector< size_t >> mesh_adj, const std::vector< float > pvd, const size_t num_iter=1, const bool with_nan=true, const bool detect_nan=true)
Smooth given per-vertex data using nearest neighbor smoothing based on adjacency list mesh represenat...
Definition: libfs.h:1157
std::vector< uint8_t > viridis(const std::vector< float > &data, float vmin=NAN, float vmax=NAN, uint8_t nan_r=255, uint8_t nan_g=255, uint8_t nan_b=255)
Map per-vertex numeric data to RGB colors using the Viridis perceptually-uniform colormap.
Definition: libfs.h:565
bool file_exists(const std::string &name)
Check whether a file exists (can be read) at given path.
Definition: libfs.h:434
const std::string LOGTAG_INFO
Logging threshold for warning messages.
Definition: libfs.h:211
#define LIBFS_MAX_ALLOC_BYTES
Maximum memory allocation limit.
Definition: libfs.h:37
A simple 4D array datastructure, useful for representing volume data.
Definition: libfs.h:2390
std::vector< float > data
The curvature data, one value per vertex. Something like the cortical thickness at each vertex...
Definition: libfs.h:2150
static void from_ply(Mesh *mesh, std::istream *is)
Read a brainmesh from a Stanford PLY format stream.
Definition: libfs.h:1699
const int32_t & fm_at(const size_t i, const size_t j) const
Retrieve a vertex index of a face, treating the faces vector as an nx3 matrix.
Definition: libfs.h:1877
std::vector< T > data
the data, as a 1D vector. Use fs::Array4D::at for easy access in 4D.
Definition: libfs.h:2440
std::vector< int32_t > b
green channel of RGBA color
Definition: libfs.h:2166
static void from_ply(Mesh *mesh, const std::string &filename)
Read a brainmesh from a Stanford PLY format mesh file.
Definition: libfs.h:1820
std::vector< int32_t > g
blue channel of RGBA color
Definition: libfs.h:2165
std::vector< float > read_curv_data(const std::string &filename)
Read per-vertex brain morphometry data from a FreeSurfer curv format file.
Definition: libfs.h:3343
void write_mgh(const Mgh &mgh, std::ostream &os)
Write MGH data to a stream.
Definition: libfs.h:3598
static void from_off(Mesh *mesh, const std::string &filename)
Read a brainmesh from an OFF format mesh file.
Definition: libfs.h:1677
std::vector< bool > vert_in_label(size_t surface_num_verts) const
Compute for each vertex of the surface whether it is inside the label.
Definition: libfs.h:3759
static std::vector< float > curv_data_for_orig_mesh(const std::vector< float > data_submesh, const std::unordered_map< int32_t, int32_t > submesh_to_orig_mapping, const int32_t orig_mesh_num_vertices, const float fill_value=std::numeric_limits< float >::quiet_NaN())
Given per-vertex data for a submesh, add NAN values inbetween to restore the original mesh size...
Definition: libfs.h:1401
MghHeader(Curv curv)
Definition: libfs.h:2316
MghHeader(std::vector< float > curv_data)
Definition: libfs.h:2324
int32_t dim1length
size of data along 1st dimension
Definition: libfs.h:2332
Models a triangular mesh, used for brain surface meshes.
Definition: libfs.h:817
const int MRI_UCHAR
MRI data type representing an 8 bit unsigned integer.
Definition: libfs.h:779
float xsize
size of voxels along 1st axis (x or r)
Definition: libfs.h:2347
size_t num_entries() const
Return the number of entries (vertices/voxels) in this label.
Definition: libfs.h:3775
unsigned int get_index(const unsigned int i1, const unsigned int i2, const unsigned int i3, const unsigned int i4) const
Get the index in the vector for the given 4D position.
Definition: libfs.h:2421
Models the data of an MGH file. Currently these are 1D vectors, but one can compute the 4D array usin...
Definition: libfs.h:2355
void read_label(Label *label, std::istream *is)
Read a FreeSurfer ASCII label from a stream.
Definition: libfs.h:3871
void to_ply_file(const std::string &filename) const
Export this mesh to a file in Stanford PLY format.
Definition: libfs.h:2036
MghData(std::vector< uint8_t > curv_data)
constructor to create MghData from MRI_UCHAR (uint8_t) data.
Definition: libfs.h:2359
Mesh(std::vector< float > cvertices, std::vector< int32_t > cfaces)
Construct a Mesh from the given vertices and faces.
Definition: libfs.h:821
std::vector< size_t > vertex_regions() const
Compute the region indices in the Colortable for all vertices in this brain surface parcellation...
Definition: libfs.h:2280
std::vector< std::vector< bool > > as_adjmatrix() const
Return adjacency matrix representation of this mesh.
Definition: libfs.h:1007
const T & at(const unsigned int i1, const unsigned int i2, const unsigned int i3, const unsigned int i4) const
Get the value at the given 4D position.
Definition: libfs.h:2415
size_t num_values() const
Compute the number of values based on the dim*length header fields.
Definition: libfs.h:2342
Array4D(Mgh *mgh)
Definition: libfs.h:2409
unsigned int d1
size of data along 1st dimension
Definition: libfs.h:2436
Models the header of an MGH file.
Definition: libfs.h:2313
#define LIBFS_MAX_STRING_LENGTH
Maximum length for fixed-length strings read from binary headers (e.g., filenames in annot colortable...
Definition: libfs.h:42
Definition: libfs.h:159
std::string to_ply() const
Return string representing the mesh in PLY format. Overload that works without passing a color vector...
Definition: libfs.h:1966
The colortable from an Annot file, can be used for parcellations and integer labels. Typically each index (in all fields) describes a brain region.
Definition: libfs.h:2160
const int MRI_FLOAT
MRI data type representing a 32 bit float.
Definition: libfs.h:785
void write_surf(std::vector< float > vertices, std::vector< int32_t > faces, std::ostream &os)
Write a mesh to a stream in FreeSurfer surf format.
Definition: libfs.h:3792
std::vector< std::vector< size_t > > as_adjlist(const bool via_matrix=true) const
Return adjacency list representation of this mesh.
Definition: libfs.h:1075
static fs::Mesh construct_pyramid()
Construct and return a simple pyramidal mesh.
Definition: libfs.h:887
std::vector< float > smooth_pvd_nn(const std::vector< float > pvd, const size_t num_iter=1, const bool via_matrix=true, const bool with_nan=true, const bool detect_nan=true) const
Smooth given per-vertex data using nearest neighbor smoothing.
Definition: libfs.h:1134
std::vector< int32_t > id
internal region index
Definition: libfs.h:2162
void write_mesh(const Mesh &mesh, const std::string &filename)
Write a mesh to a file in different formats.
Definition: libfs.h:4006
std::vector< int32_t > region_vertices(int32_t region_label) const
Get all vertices of a region given by label in the brain surface parcellation. Returns an integer vec...
Definition: libfs.h:2232
std::vector< short > data_mri_short
data of type MRI_SHORT, check the dtype to see whether this is relevant for this instance.
Definition: libfs.h:2366
const int MRI_INT
MRI data type representing a 32 bit signed integer.
Definition: libfs.h:782
float zsize
size of voxels along 3rd axis (z or s)
Definition: libfs.h:2349
static void from_obj(Mesh *mesh, std::istream *is)
Read a brainmesh from a Wavefront object format stream.
Definition: libfs.h:1435
Curv(std::vector< float > curv_data)
Construct a Curv instance from the given per-vertex data.
Definition: libfs.h:2137
unsigned int d3
size of data along 3rd dimension
Definition: libfs.h:2438
Models a whole MGH file.
Definition: libfs.h:2370
size_t num_vertices() const
Get the number of vertices of this parcellation (or the associated surface).
Definition: libfs.h:2268
int32_t num_values_per_vertex
The number of values per vertex, stored in this file. Almost all apps (including FreeSurfer itself) o...
Definition: libfs.h:2156
void write_surf(const Mesh &mesh, const std::string &filename)
Write a mesh to a binary file in FreeSurfer surf format.
Definition: libfs.h:3850
void str_to_file(const std::string &filename, const std::string rep)
Write the given text representation (any string) to a file.
Definition: libfs.h:509
void read_annot(Annot *annot, std::istream *is)
Read a FreeSurfer annotation or brain surface parcellation from an annot stream.
Definition: libfs.h:3237
int32_t num_vertices
The number of vertices of the mesh to which this belongs. Can be deduced from length of &#39;data&#39;...
Definition: libfs.h:2153
std::vector< float > vertex_coords(const size_t vertex) const
Get all coordinates of the vertex, given by its index.
Definition: libfs.h:1922
static void from_off(Mesh *mesh, std::istream *is, const std::string &source_filename="")
Read a brainmesh from an Object File format (OFF) stream.
Definition: libfs.h:1564
std::vector< int32_t > face_vertices(const size_t face) const
Get all vertex indices of the face, given by its index.
Definition: libfs.h:1898
Mgh(std::vector< float > curv_data)
Definition: libfs.h:2380
std::vector< std::string > read_subjectsfile(const std::string &filename)
Read a vector of subject identifiers from a FreeSurfer subjects file.
Definition: libfs.h:2552
std::vector< float > value
the value of the label, can represent continuous data like a p-value, or sometimes simply 1...
Definition: libfs.h:3756
std::vector< int32_t > r
red channel of RGBA color
Definition: libfs.h:2164
void read_mgh(Mgh *mgh, const std::string &filename)
Read a FreeSurfer volume file in MGH format into the given Mgh struct.
Definition: libfs.h:2506
void to_ply_file(const std::string &filename, const std::vector< uint8_t > col) const
Export this mesh to a file in Stanford PLY format with vertex colors.
Definition: libfs.h:2046
std::unordered_set< std::tuple< size_t, size_t >, _tupleHashFunction > edge_set
Datastructure for storing, and quickly querying the existence of, mesh edges.
Definition: libfs.h:1033
int32_t dtype
the MRI data type
Definition: libfs.h:2337
size_t num_faces() const
Return the number of faces in this mesh.
Definition: libfs.h:1860
void write_label(const Label &label, std::ostream &os)
Write label data to a stream.
Definition: libfs.h:3952
std::vector< int > vertex
vertex indices for the data in this label if it is a surface label. These are indices into the vertic...
Definition: libfs.h:3752
std::string to_ply(const std::vector< uint8_t > col) const
Return string representing the mesh in PLY format.
Definition: libfs.h:1982
std::vector< std::string > vertex_region_names() const
Compute the region names in the Colortable for all vertices in this brain surface parcellation...
Definition: libfs.h:2300
std::vector< uint8_t > vertex_colors(bool alpha=false) const
Get the vertex colors as an array of uchar values, 3 consecutive values are the red, green and blue channel values for a single vertex.
Definition: libfs.h:2247
static fs::Mesh construct_cube()
Construct and return a simple cube mesh.
Definition: libfs.h:850
int32_t dim4length
size of data along 4th dimension
Definition: libfs.h:2335
std::vector< int32_t > data_mri_int
data of type MRI_INT, check the dtype to see whether this is relevant for this instance.
Definition: libfs.h:2363
void read_mgh(Mgh *mgh, std::istream *is)
Read MGH data from a stream.
Definition: libfs.h:2575
MghData(std::vector< float > curv_data)
constructor to create MghData from MRI_FLOAT (float) data.
Definition: libfs.h:2361
MghData(std::vector< short > curv_data)
constructor to create MghData from MRI_SHORT (short) data.
Definition: libfs.h:2360
int32_t dim2length
size of data along 2nd dimension
Definition: libfs.h:2333
const std::string LOGTAG_ERROR
Logging threshold for error messages.
Definition: libfs.h:205
Mesh()
Construct an empty Mesh.
Definition: libfs.h:835
std::pair< std::unordered_map< int32_t, int32_t >, fs::Mesh > submesh_vertex(const std::vector< int32_t > &old_vertex_indices, const bool mapdir_fulltosubmesh=false) const
Compute a new mesh that is a submesh of this mesh, based on a subset of the vertices of this mesh...
Definition: libfs.h:1344
int32_t dim3length
size of data along 3rd dimension
Definition: libfs.h:2334
std::vector< uint8_t > data_mri_uchar
data of type MRI_UCHAR, check the dtype to see whether this is relevant for this instance.
Definition: libfs.h:2364
Label(std::vector< int > vertices)
Construct a Label from the given vertices / voxel numbers.
Definition: libfs.h:3743
static std::vector< std::vector< size_t > > extend_adj(const std::vector< std::vector< size_t >> mesh_adj, const size_t extend_by=1, std::vector< std::vector< size_t >> mesh_adj_ext=std::vector< std::vector< size_t >>())
Extend mesh neighborhoods based on mesh adjacency representation.
Definition: libfs.h:1278
void to_obj_file(const std::string &filename) const
Export this mesh to a file in Wavefront OBJ format.
Definition: libfs.h:1323
void read_surf(Mesh *surface, const std::string &filename)
Read a brain mesh from a file in binary FreeSurfer &#39;surf&#39; format into the given Mesh instance...
Definition: libfs.h:2933
Curv()
Construct an empty Curv instance.
Definition: libfs.h:2144
std::string fullpath(std::initializer_list< std::string > path_components, std::string path_sep=std::string("/"))
Construct a UNIX file system path from the given path_components.
Definition: libfs.h:462
std::vector< int32_t > region_vertices(const std::string &region_name) const
Get all vertices of a region given by name in the brain surface parcellation. Returns an integer vect...
Definition: libfs.h:2216
void to_off_file(const std::string &filename, const std::vector< uint8_t > col) const
Export this mesh to a file in OFF format with vertex colors (COFF).
Definition: libfs.h:2126
void read_mesh(Mesh *surface, const std::string &filename)
Read a triangular mesh from a surf, obj, or ply file into the given Mesh instance.
Definition: libfs.h:3039
int32_t get_region_idx(const std::string &query_name) const
Get the index of a region in the Colortable by region name. Returns a negative value if the region is...
Definition: libfs.h:2182
int32_t num_faces
The number of faces of the mesh to which this belongs, typically irrelevant and ignored.
Definition: libfs.h:2147
float ysize
size of voxels along 2nd axis (y or a)
Definition: libfs.h:2348
std::vector< float > data_mri_float
data of type MRI_FLOAT, check the dtype to see whether this is relevant for this instance.
Definition: libfs.h:2365
#define LIBFS_MAX_COLORTABLE_ENTRIES
Maximum number of entries in an annotation colortable.
Definition: libfs.h:47
unsigned int num_values() const
Get number of values/voxels.
Definition: libfs.h:2431
Colortable colortable
A Colortable defining the regions (most importantly, the region name and visualization color)...
Definition: libfs.h:2213
MghHeader header
Header for this MGH instance.
Definition: libfs.h:2372
int16_t ras_good_flag
flag indicating whether the data in the RAS fields (Mdc, Pxyz_c) are valid. 1 means valid...
Definition: libfs.h:2339
void log(std::string const &message, std::string const loglevel="INFO")
Log a message, goes to stdout.
Definition: libfs.h:222
const float & vm_at(const size_t i, const size_t j) const
Retrieve a single (x, y, or z) coordinate of a vertex, treating the vertices vector as an nx3 matrix...
Definition: libfs.h:1948
std::vector< T > vflatten(std::vector< std::vector< T >> values)
Flatten 2D vector.
Definition: libfs.h:363
Label(std::vector< int > vertices, std::vector< float > values)
Construct a Label from the given vertices / voxel numbers and values.
Definition: libfs.h:3732
std::vector< int32_t > a
alpha channel of RGBA color
Definition: libfs.h:2167
const std::string LOGTAG_CRITICAL
Logging threshold for critical messages.
Definition: libfs.h:202
int32_t get_region_idx(int32_t query_label) const
Get the index of a region in the Colortable by label. Returns a negative value if the region is not f...
Definition: libfs.h:2195
Label()
Default constructor for a label.
Definition: libfs.h:3729
static fs::Mesh construct_grid(const size_t nx=4, const size_t ny=5, const float distx=1.0, const float disty=1.0)
Construct and return a simple planar grid mesh.
Definition: libfs.h:920
std::vector< int32_t > faces
n x 3 vector of the 3 vertex indices for the n triangles or faces. The 3 vertices of a single face fo...
Definition: libfs.h:838
void to_off_file(const std::string &filename) const
Export this mesh to a file in OFF format.
Definition: libfs.h:2119
std::vector< int32_t > vertex_labels
The label code for each vertex, defining the region it belongs to. Check in the Colortable for a regi...
Definition: libfs.h:2212
MghData(Curv curv)
constructor to create MghData from a Curv instance
Definition: libfs.h:2362
std::vector< float > coord_z
z coordinates of the vertices in case of a surface label, or voxels coordinates for a volume label...
Definition: libfs.h:3755
Array4D(unsigned int d1, unsigned int d2, unsigned int d3, unsigned int d4)
Definition: libfs.h:2396
MghData(std::vector< int32_t > curv_data)
constructor to create MghData from MRI_INT (int32_t) data.
Definition: libfs.h:2358
std::vector< float > read_desc_data(const std::string &filename)
Read per-vertex brain morphometry data from a FreeSurfer curv format or MGH format file...
Definition: libfs.h:3363
Mgh()
Empty default constuctor.
Definition: libfs.h:2374
std::string to_off() const
Return string representing the mesh in OFF format. Overload that works without passing a color vector...
Definition: libfs.h:2059
std::vector< std::string > name
region name
Definition: libfs.h:2163
MghData data
4D data for this MGH instance.
Definition: libfs.h:2373
unsigned int d4
size of data along 4th dimension
Definition: libfs.h:2439
size_t num_vertices() const
Return the number of vertices in this mesh.
Definition: libfs.h:1846
void read_mgh_header(MghHeader *, const std::string &)
Read the header of a FreeSurfer volume file in MGH format into the given MghHeader struct...
Definition: libfs.h:2778