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 // -- Optional MGZ / NIfTI-gz support via zlib -------------------------------------
21 // When LIBFS_HAS_ZLIB is #defined before including this header, the following
22 // become available:
23 // - read_mgz() / write_mgz()
24 // - read_nifti_gz() / write_nifti_gz() (and .nii.gz support in read_nifti() /
25 // write_nifti() / read_desc_data())
26 // Just link with -lz.
27 //
28 // There is NO auto-detection — you must explicitly opt in:
29 // #define LIBFS_HAS_ZLIB
30 // #include "libfs.h"
31 //
32 // If LIBFS_HAS_ZLIB is not defined, the MGZ / NIfTI-gz functions are simply
33 // absent. Attempting to use them results in a compile error, and attempting
34 // to read/write a .gz file via the generic read_nifti() / write_nifti() /
35 // read_desc_data() functions throws a runtime_error at runtime.
36 //
37 // This is all compile-time; there is zero runtime overhead when zlib support
38 // is not enabled.
39 #ifdef LIBFS_HAS_ZLIB
40 #include <zlib.h>
41 #endif
42 // -- End optional MGZ support -----------------------------------------------------
43 
50 #define LIBFS_VERSION "0.5.0"
51 
58 #define LIBFS_VERSION_MAJOR 0
59 
64 #define LIBFS_VERSION_MINOR 5
65 
70 #define LIBFS_VERSION_PATCH 0
71 
72 // -- Security / defensive hardening configuration -------------------------------------
73 // Users can #define any of these BEFORE including libfs.h to override the defaults.
74 
77 #define LIBFS_MAX_ALLOC_BYTES_DEFAULT (2ULL * 1024ULL * 1024ULL * 1024ULL)
78 
80 #ifndef LIBFS_MAX_ALLOC_BYTES
81 #define LIBFS_MAX_ALLOC_BYTES LIBFS_MAX_ALLOC_BYTES_DEFAULT
82 #endif
83 
85 #ifndef LIBFS_MAX_STRING_LENGTH
86 #define LIBFS_MAX_STRING_LENGTH 4096
87 #endif
88 
90 #ifndef LIBFS_MAX_COLORTABLE_ENTRIES
91 #define LIBFS_MAX_COLORTABLE_ENTRIES 10000
92 #endif
93 // -- End security configuration --------------------------------------------------------
94 
97 
157 #ifndef LIBFS_APPTAG
168 #define LIBFS_APPTAG "[libfs] "
169 #endif
170 
178 #define LIBFS_DBG_WARNING
179 
180 // If the user wants something below our default, remove our default.
181 #ifdef LIBFS_DBG_NONE
182 #undef LIBFS_DBG_WARNING
183 #endif
184 
185 #ifdef LIBFS_DBG_CRITICAL
186 #undef LIBFS_DBG_WARNING
187 #endif
188 
189 #ifdef LIBFS_DBG_ERROR
190 #undef LIBFS_DBG_WARNING
191 #endif
192 
193 // Ensure that the user does not have to define all debug levels
194 // up to the one they actually want, by defining all lower ones for them.
195 #ifdef LIBFS_DBG_EXCESSIVE
196 #define LIBFS_DBG_VERBOSE
197 #endif
198 
199 #ifdef LIBFS_DBG_VERBOSE
200 #define LIBFS_DBG_INFO
201 #endif
202 
203 #ifdef LIBFS_DBG_INFO
204 #define LIBFS_DBG_WARNING
205 #endif
206 
214 #ifdef LIBFS_DBG_WARNING
215 #define LIBFS_DBG_ERROR
216 #endif
217 
224 #ifdef LIBFS_DBG_ERROR
225 #define LIBFS_DBG_CRITICAL
226 #endif
227 
228 // End of debug handling.
229 
230 namespace fs
231 {
232 
233  namespace util
234  {
235 
239  tm _localtime(const std::time_t &time)
240  {
241  std::tm tm_snapshot;
242 #if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__))
243  ::localtime_s(&tm_snapshot, &time);
244 #else
245  ::localtime_r(&time, &tm_snapshot); // POSIX
246 #endif
247  return tm_snapshot;
248  }
249 
258  std::string time_tag(std::chrono::system_clock::time_point t)
259  {
260  auto as_time_t = std::chrono::system_clock::to_time_t(t);
261  struct tm tm;
262  char time_buffer[64];
263  // if (::gmtime_r(&as_time_t, &tm)) {
264  tm = _localtime(as_time_t);
265  if (std::strftime(time_buffer, sizeof(time_buffer), "%F %T", &tm))
266  {
267  return std::string{time_buffer};
268  }
269  throw std::runtime_error("Failed to get current date as string");
270  }
271 
273  const std::string LOGTAG_CRITICAL = "CRITICAL";
274 
276  const std::string LOGTAG_ERROR = "ERROR";
277 
279  const std::string LOGTAG_WARNING = "WARNING";
280 
282  const std::string LOGTAG_INFO = "INFO";
283 
285  const std::string LOGTAG_VERBOSE = "VERBOSE";
286 
288  const std::string LOGTAG_EXCESSIVE = "EXCESSIVE";
289 
293  inline void log(std::string const &message, std::string const loglevel = "INFO")
294  {
295  std::cout << LIBFS_APPTAG << "[" << loglevel << "] [" << fs::util::time_tag(std::chrono::system_clock::now()) << "] " << message << "\n";
296  }
297 
298  // -- Security / defensive hardening helpers -----------------------------------------
299 
302  inline bool safe_multiply(size_t a, size_t b, size_t &result)
303  {
304  if (a == 0 || b == 0)
305  {
306  result = 0;
307  return true;
308  }
309  if (a > std::numeric_limits<size_t>::max() / b)
310  {
311  return false;
312  }
313  result = a * b;
314  return true;
315  }
316 
321  inline bool check_alloc(size_t num_elements, size_t bytes_per_element)
322  {
323  size_t total_bytes = 0;
324  if (!safe_multiply(num_elements, bytes_per_element, total_bytes))
325  {
326  return false;
327  }
328  if (total_bytes > LIBFS_MAX_ALLOC_BYTES)
329  {
330  return false;
331  }
332  return true;
333  }
334 
337  inline size_t get_file_size(const std::string &filename)
338  {
339  std::ifstream ifs(filename, std::ios::binary | std::ios::ate);
340  if (!ifs.is_open())
341  {
342  return 0;
343  }
344  std::streampos end = ifs.tellg();
345  if (end < 0)
346  {
347  return 0;
348  }
349  return static_cast<size_t>(end);
350  }
351 
354  inline bool is_finite_float(float value)
355  {
356  return !std::isnan(value) && !std::isinf(value);
357  }
358 
359  // -- End security helpers ---------------------------------------------------------
360 
369  inline bool ends_with(std::string const &value, std::string const &suffix)
370  {
371  if (suffix.size() > value.size())
372  return false;
373  return std::equal(suffix.rbegin(), suffix.rend(), value.rbegin());
374  }
375 
384  inline bool ends_with(std::string const &value, std::initializer_list<std::string> suffixes)
385  {
386  for (auto suffix : suffixes)
387  {
388  if (ends_with(value, suffix))
389  {
390  return true;
391  }
392  }
393  return false;
394  }
395 
408  template <typename T>
409  std::vector<std::vector<T>> v2d(std::vector<T> values, size_t num_cols)
410  {
411  std::vector<std::vector<T>> result;
412  for (std::size_t i = 0; i < values.size(); ++i)
413  {
414  if (i % num_cols == 0)
415  {
416  result.resize(result.size() + 1);
417  }
418  result[i / num_cols].push_back(values[i]);
419  }
420  return result;
421  }
422 
433  template <typename T>
434  std::vector<T> vflatten(std::vector<std::vector<T>> values)
435  {
436  size_t total_size = 0;
437  for (std::size_t i = 0; i < values.size(); i++)
438  {
439  total_size += values[i].size();
440  }
441 
442  std::vector<T> result = std::vector<T>(total_size);
443  size_t cur_idx = 0;
444  for (std::size_t i = 0; i < values.size(); i++)
445  {
446  for (std::size_t j = 0; j < values[i].size(); j++)
447  {
448  result[cur_idx] = values[i][j];
449  cur_idx++;
450  }
451  }
452  return result;
453  }
454 
465  inline bool starts_with(std::string const &value, std::string const &prefix)
466  {
467  if (prefix.length() > value.length())
468  return false;
469  return value.rfind(prefix, 0) == 0;
470  }
471 
482  inline bool starts_with(std::string const &value, std::initializer_list<std::string> prefixes)
483  {
484  for (auto prefix : prefixes)
485  {
486  if (starts_with(value, prefix))
487  {
488  return true;
489  }
490  }
491  return false;
492  }
493 
505  inline bool file_exists(const std::string &name)
506  {
507  if (FILE *file = fopen(name.c_str(), "r"))
508  {
509  fclose(file);
510  return true;
511  }
512  else
513  {
514  return false;
515  }
516  }
517 
533  std::string fullpath(std::initializer_list<std::string> path_components, std::string path_sep = std::string("/"))
534  {
535  std::string fp;
536  if (path_components.size() == 0)
537  {
538  throw std::invalid_argument("The 'path_components' must not be empty.");
539  }
540 
541  std::string comp;
542  std::string comp_mod;
543  size_t idx = 0;
544  for (auto comp : path_components)
545  {
546  comp_mod = comp;
547  if (idx != 0)
548  { // We keep a leading slash intact for the first element (absolute path).
549  if (starts_with(comp, path_sep))
550  {
551  comp_mod = comp.substr(1, comp.size() - 1);
552  }
553  }
554 
555  if (ends_with(comp_mod, path_sep))
556  {
557  comp_mod = comp_mod.substr(0, comp_mod.size() - 1);
558  }
559 
560  fp += comp_mod;
561  if (idx < path_components.size() - 1)
562  {
563  fp += path_sep;
564  }
565  idx++;
566  }
567  return fp;
568  }
569 
580  void str_to_file(const std::string &filename, const std::string rep)
581  {
582  std::ofstream ofs;
583  ofs.open(filename, std::ofstream::out);
584 #ifdef LIBFS_DBG_VERBOSE
585  std::cout << LIBFS_APPTAG << "Opening file '" << filename << "' for writing.\n";
586 #endif
587  if (ofs.is_open())
588  {
589  ofs << rep;
590  ofs.close();
591  }
592  else
593  {
594  throw std::runtime_error("Unable to open file '" + filename + "' for writing.\n");
595  }
596  }
597 
636  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)
637  {
638  std::vector<uint8_t> colors;
639  if (data.empty())
640  {
641  return colors;
642  }
643  colors.reserve(data.size() * 3);
644 
645  // The official 256-entry Viridis colormap (RGB, floats in [0, 1]), identical to the
646  // matplotlib viridis lookup table. Linearly interpolated between samples below.
647  static const float lut[768] = {
648  0.267004, 0.004874, 0.329415, 0.26851, 0.009605, 0.335427, 0.269944, 0.014625,
649  0.341379, 0.271305, 0.019942, 0.347269, 0.272594, 0.025563, 0.353093, 0.273809,
650  0.031497, 0.358853, 0.274952, 0.037752, 0.364543, 0.276022, 0.044167, 0.370164,
651  0.277018, 0.050344, 0.375715, 0.277941, 0.056324, 0.381191, 0.278791, 0.062145,
652  0.386592, 0.279566, 0.067836, 0.391917, 0.280267, 0.073417, 0.397163, 0.280894,
653  0.078907, 0.402329, 0.281446, 0.08432, 0.407414, 0.281924, 0.089666, 0.412415,
654  0.282327, 0.094955, 0.417331, 0.282656, 0.100196, 0.42216, 0.28291, 0.105393,
655  0.426902, 0.283091, 0.110553, 0.431554, 0.283197, 0.11568, 0.436115, 0.283229,
656  0.120777, 0.440584, 0.283187, 0.125848, 0.44496, 0.283072, 0.130895, 0.449241,
657  0.282884, 0.13592, 0.453427, 0.282623, 0.140926, 0.457517, 0.28229, 0.145912,
658  0.46151, 0.281887, 0.150881, 0.465405, 0.281412, 0.155834, 0.469201, 0.280868,
659  0.160771, 0.472899, 0.280255, 0.165693, 0.476498, 0.279574, 0.170599, 0.479997,
660  0.278826, 0.17549, 0.483397, 0.278012, 0.180367, 0.486697, 0.277134, 0.185228,
661  0.489898, 0.276194, 0.190074, 0.493001, 0.275191, 0.194905, 0.496005, 0.274128,
662  0.199721, 0.498911, 0.273006, 0.20452, 0.501721, 0.271828, 0.209303, 0.504434,
663  0.270595, 0.214069, 0.507052, 0.269308, 0.218818, 0.509577, 0.267968, 0.223549,
664  0.512008, 0.26658, 0.228262, 0.514349, 0.265145, 0.232956, 0.516599, 0.263663,
665  0.237631, 0.518762, 0.262138, 0.242286, 0.520837, 0.260571, 0.246922, 0.522828,
666  0.258965, 0.251537, 0.524736, 0.257322, 0.25613, 0.526563, 0.255645, 0.260703,
667  0.528312, 0.253935, 0.265254, 0.529983, 0.252194, 0.269783, 0.531579, 0.250425,
668  0.27429, 0.533103, 0.248629, 0.278775, 0.534556, 0.246811, 0.283237, 0.535941,
669  0.244972, 0.287675, 0.53726, 0.243113, 0.292092, 0.538516, 0.241237, 0.296485,
670  0.539709, 0.239346, 0.300855, 0.540844, 0.237441, 0.305202, 0.541921, 0.235526,
671  0.309527, 0.542944, 0.233603, 0.313828, 0.543914, 0.231674, 0.318106, 0.544834,
672  0.229739, 0.322361, 0.545706, 0.227802, 0.326594, 0.546532, 0.225863, 0.330805,
673  0.547314, 0.223925, 0.334994, 0.548053, 0.221989, 0.339161, 0.548752, 0.220057,
674  0.343307, 0.549413, 0.21813, 0.347432, 0.550038, 0.21621, 0.351535, 0.550627,
675  0.214298, 0.355619, 0.551184, 0.212395, 0.359683, 0.55171, 0.210503, 0.363727,
676  0.552206, 0.208623, 0.367752, 0.552675, 0.206756, 0.371758, 0.553117, 0.204903,
677  0.375746, 0.553533, 0.203063, 0.379716, 0.553925, 0.201239, 0.38367, 0.554294,
678  0.19943, 0.387607, 0.554642, 0.197636, 0.391528, 0.554969, 0.19586, 0.395433,
679  0.555276, 0.1941, 0.399323, 0.555565, 0.192357, 0.403199, 0.555836, 0.190631,
680  0.407061, 0.556089, 0.188923, 0.41091, 0.556326, 0.187231, 0.414746, 0.556547,
681  0.185556, 0.41857, 0.556753, 0.183898, 0.422383, 0.556944, 0.182256, 0.426184,
682  0.55712, 0.180629, 0.429975, 0.557282, 0.179019, 0.433756, 0.55743, 0.177423,
683  0.437527, 0.557565, 0.175841, 0.44129, 0.557685, 0.174274, 0.445044, 0.557792,
684  0.172719, 0.448791, 0.557885, 0.171176, 0.45253, 0.557965, 0.169646, 0.456262,
685  0.55803, 0.168126, 0.459988, 0.558082, 0.166617, 0.463708, 0.558119, 0.165117,
686  0.467423, 0.558141, 0.163625, 0.471133, 0.558148, 0.162142, 0.474838, 0.55814,
687  0.160665, 0.47854, 0.558115, 0.159194, 0.482237, 0.558073, 0.157729, 0.485932,
688  0.558013, 0.15627, 0.489624, 0.557936, 0.154815, 0.493313, 0.55784, 0.153364,
689  0.497, 0.557724, 0.151918, 0.500685, 0.557587, 0.150476, 0.504369, 0.55743,
690  0.149039, 0.508051, 0.55725, 0.147607, 0.511733, 0.557049, 0.14618, 0.515413,
691  0.556823, 0.144759, 0.519093, 0.556572, 0.143343, 0.522773, 0.556295, 0.141935,
692  0.526453, 0.555991, 0.140536, 0.530132, 0.555659, 0.139147, 0.533812, 0.555298,
693  0.13777, 0.537492, 0.554906, 0.136408, 0.541173, 0.554483, 0.135066, 0.544853,
694  0.554029, 0.133743, 0.548535, 0.553541, 0.132444, 0.552216, 0.553018, 0.131172,
695  0.555899, 0.552459, 0.129933, 0.559582, 0.551864, 0.128729, 0.563265, 0.551229,
696  0.127568, 0.566949, 0.550556, 0.126453, 0.570633, 0.549841, 0.125394, 0.574318,
697  0.549086, 0.124395, 0.578002, 0.548287, 0.123463, 0.581687, 0.547445, 0.122606,
698  0.585371, 0.546557, 0.121831, 0.589055, 0.545623, 0.121148, 0.592739, 0.544641,
699  0.120565, 0.596422, 0.543611, 0.120092, 0.600104, 0.54253, 0.119738, 0.603785,
700  0.5414, 0.119512, 0.607464, 0.540218, 0.119423, 0.611141, 0.538982, 0.119483,
701  0.614817, 0.537692, 0.119699, 0.61849, 0.536347, 0.120081, 0.622161, 0.534946,
702  0.120638, 0.625828, 0.533488, 0.12138, 0.629492, 0.531973, 0.122312, 0.633153,
703  0.530398, 0.123444, 0.636809, 0.528763, 0.12478, 0.640461, 0.527068, 0.126326,
704  0.644107, 0.525311, 0.128087, 0.647749, 0.523491, 0.130067, 0.651384, 0.521608,
705  0.132268, 0.655014, 0.519661, 0.134692, 0.658636, 0.517649, 0.137339, 0.662252,
706  0.515571, 0.14021, 0.665859, 0.513427, 0.143303, 0.669459, 0.511215, 0.146616,
707  0.67305, 0.508936, 0.150148, 0.676631, 0.506589, 0.153894, 0.680203, 0.504172,
708  0.157851, 0.683765, 0.501686, 0.162016, 0.687316, 0.499129, 0.166383, 0.690856,
709  0.496502, 0.170948, 0.694384, 0.493803, 0.175707, 0.6979, 0.491033, 0.180653,
710  0.701402, 0.488189, 0.185783, 0.704891, 0.485273, 0.19109, 0.708366, 0.482284,
711  0.196571, 0.711827, 0.479221, 0.202219, 0.715272, 0.476084, 0.20803, 0.718701,
712  0.472873, 0.214, 0.722114, 0.469588, 0.220124, 0.725509, 0.466226, 0.226397,
713  0.728888, 0.462789, 0.232815, 0.732247, 0.459277, 0.239374, 0.735588, 0.455688,
714  0.24607, 0.73891, 0.452024, 0.252899, 0.742211, 0.448284, 0.259857, 0.745492,
715  0.444467, 0.266941, 0.748751, 0.440573, 0.274149, 0.751988, 0.436601, 0.281477,
716  0.755203, 0.432552, 0.288921, 0.758394, 0.428426, 0.296479, 0.761561, 0.424223,
717  0.304148, 0.764704, 0.419943, 0.311925, 0.767822, 0.415586, 0.319809, 0.770914,
718  0.411152, 0.327796, 0.77398, 0.40664, 0.335885, 0.777018, 0.402049, 0.344074,
719  0.780029, 0.397381, 0.35236, 0.783011, 0.392636, 0.360741, 0.785964, 0.387814,
720  0.369214, 0.788888, 0.382914, 0.377779, 0.791781, 0.377939, 0.386433, 0.794644,
721  0.372886, 0.395174, 0.797475, 0.367757, 0.404001, 0.800275, 0.362552, 0.412913,
722  0.803041, 0.357269, 0.421908, 0.805774, 0.35191, 0.430983, 0.808473, 0.346476,
723  0.440137, 0.811138, 0.340967, 0.449368, 0.813768, 0.335384, 0.458674, 0.816363,
724  0.329727, 0.468053, 0.818921, 0.323998, 0.477504, 0.821444, 0.318195, 0.487026,
725  0.823929, 0.312321, 0.496615, 0.826376, 0.306377, 0.506271, 0.828786, 0.300362,
726  0.515992, 0.831158, 0.294279, 0.525776, 0.833491, 0.288127, 0.535621, 0.835785,
727  0.281908, 0.545524, 0.838039, 0.275626, 0.555484, 0.840254, 0.269281, 0.565498,
728  0.84243, 0.262877, 0.575563, 0.844566, 0.256415, 0.585678, 0.846661, 0.249897,
729  0.595839, 0.848717, 0.243329, 0.606045, 0.850733, 0.236712, 0.616293, 0.852709,
730  0.230052, 0.626579, 0.854645, 0.223353, 0.636902, 0.856542, 0.21662, 0.647257,
731  0.8584, 0.209861, 0.657642, 0.860219, 0.203082, 0.668054, 0.861999, 0.196293,
732  0.678489, 0.863742, 0.189503, 0.688944, 0.865448, 0.182725, 0.699415, 0.867117,
733  0.175971, 0.709898, 0.868751, 0.169257, 0.720391, 0.87035, 0.162603, 0.730889,
734  0.871916, 0.156029, 0.741388, 0.873449, 0.149561, 0.751884, 0.874951, 0.143228,
735  0.762373, 0.876424, 0.137064, 0.772852, 0.877868, 0.131109, 0.783315, 0.879285,
736  0.125405, 0.79376, 0.880678, 0.120005, 0.804182, 0.882046, 0.114965, 0.814576,
737  0.883393, 0.110347, 0.82494, 0.88472, 0.106217, 0.83527, 0.886029, 0.102646,
738  0.845561, 0.887322, 0.099702, 0.85581, 0.888601, 0.097452, 0.866013, 0.889868,
739  0.095953, 0.876168, 0.891125, 0.09525, 0.886271, 0.892374, 0.095374, 0.89632,
740  0.893616, 0.096335, 0.906311, 0.894855, 0.098125, 0.916242, 0.896091, 0.100717,
741  0.926106, 0.89733, 0.104071, 0.935904, 0.89857, 0.108131, 0.945636, 0.899815,
742  0.112838, 0.9553, 0.901065, 0.118128, 0.964894, 0.902323, 0.123941, 0.974417,
743  0.90359, 0.130215, 0.983868, 0.904867, 0.136897, 0.993248, 0.906157, 0.143936,
744  };
745 
746  const int n = 256;
747 
748  bool auto_min = std::isnan(vmin);
749  bool auto_max = std::isnan(vmax);
750 
751  // Determine the finite (non-NaN) min/max of the data, used for auto range.
752  float data_min = NAN;
753  float data_max = NAN;
754  bool have_finite = false;
755  for (size_t i = 0; i < data.size(); i++)
756  {
757  if (std::isnan(data[i]))
758  {
759  continue;
760  }
761  if (!have_finite)
762  {
763  data_min = data[i];
764  data_max = data[i];
765  have_finite = true;
766  }
767  else
768  {
769  if (data[i] < data_min)
770  {
771  data_min = data[i];
772  }
773  if (data[i] > data_max)
774  {
775  data_max = data[i];
776  }
777  }
778  }
779 
780  float lo = auto_min ? data_min : vmin;
781  float hi = auto_max ? data_max : vmax;
782 
783  if (!auto_min && !auto_max)
784  {
785  if (vmin > vmax)
786  {
787  throw std::invalid_argument("In viridis(): 'vmin' must not be greater than 'vmax'.");
788  }
789  }
790 
791  if (!have_finite)
792  {
793  // All input values are NaN: map the whole vector to the configured NaN color.
794  for (size_t i = 0; i < data.size(); i++)
795  {
796  colors.push_back(nan_r);
797  colors.push_back(nan_g);
798  colors.push_back(nan_b);
799  }
800  return colors;
801  }
802 
803  bool constant = (hi <= lo);
804 
805  for (size_t i = 0; i < data.size(); i++)
806  {
807  if (std::isnan(data[i]))
808  {
809  colors.push_back(nan_r);
810  colors.push_back(nan_g);
811  colors.push_back(nan_b);
812  continue;
813  }
814 
815  float t;
816  if (constant)
817  {
818  t = 0.5f;
819  }
820  else
821  {
822  t = (data[i] - lo) / (hi - lo);
823  if (t < 0.0f) { t = 0.0f; }
824  if (t > 1.0f) { t = 1.0f; }
825  }
826 
827  float pos = t * (n - 1);
828  int idx0 = static_cast<int>(pos);
829  if (idx0 < 0) { idx0 = 0; }
830  if (idx0 > n - 2) { idx0 = n - 2; }
831  int idx1 = idx0 + 1;
832  float frac = pos - static_cast<float>(idx0);
833 
834  for (int c = 0; c < 3; c++)
835  {
836  float val = lut[idx0 * 3 + c] * (1.0f - frac) + lut[idx1 * 3 + c] * frac;
837  int iv = static_cast<int>(val * 255.0f + 0.5f);
838  if (iv < 0) { iv = 0; }
839  if (iv > 255) { iv = 255; }
840  colors.push_back(static_cast<uint8_t>(iv));
841  }
842  }
843  return colors;
844  }
845  } // End namespace util.
846 
847  // MRI data types, used by the MGH functions.
848 
850  const int MRI_UCHAR = 0;
851 
853  const int MRI_INT = 1;
854 
856  const int MRI_FLOAT = 3;
857 
859  const int MRI_SHORT = 4;
860 
861  // Forward declarations.
862  int _fread3(std::istream &);
863  template <typename T>
864  T _freadt(std::istream &);
865  std::string _freadstringnewline(std::istream &);
866  std::string _freadfixedlengthstring(std::istream &, size_t, bool, size_t);
867  bool _ends_with(std::string const &fullString, std::string const &ending);
868  size_t _vidx_2d(size_t, size_t, size_t);
869  struct MghHeader;
870  struct Mgh;
871 
872  // NIfTI-1 forward declarations (needed by read_desc_data).
873  void read_nifti(Mgh *, std::istream *, bool force_standard = false);
874  void read_nifti(Mgh *, const std::string &, bool force_standard = false);
875 #ifdef LIBFS_HAS_ZLIB
876  inline void read_nifti_gz(Mgh *, const std::string &, bool force_standard = false);
877  inline void write_nifti_gz(const Mgh &, const std::string &);
878 #endif
879 
897  struct Mesh
898  {
899 
901  Mesh(std::vector<float> cvertices, std::vector<int32_t> cfaces)
902  {
903  vertices = cvertices;
904  faces = cfaces;
905  }
906 
916  Mesh(std::vector<std::vector<float>> cvertices, std::vector<std::vector<int32_t>> cfaces)
917  {
918  vertices = util::vflatten(cvertices);
919  faces = util::vflatten(cfaces);
920  }
921 
923  Mesh() {}
924 
925  std::vector<float> vertices;
926  std::vector<int32_t> faces;
927  std::vector<uint8_t> vertex_colors;
928 
940  {
941  fs::Mesh mesh;
942  mesh.vertices = {1.0, 1.0, 1.0,
943  1.0, 1.0, -1.0,
944  1.0, -1.0, 1.0,
945  1.0, -1.0, -1.0,
946  -1.0, 1.0, 1.0,
947  -1.0, 1.0, -1.0,
948  -1.0, -1.0, 1.0,
949  -1.0, -1.0, -1.0};
950  mesh.faces = {0, 2, 3,
951  3, 1, 0,
952  4, 7, 6,
953  7, 4, 5,
954  0, 5, 4,
955  5, 0, 1,
956  2, 6, 7,
957  7, 3, 2,
958  0, 4, 6,
959  6, 2, 0,
960  1, 7, 5,
961  7, 1, 3};
962  return mesh;
963  }
964 
977  {
978  fs::Mesh mesh;
979  mesh.vertices = {0.0, 0.0, 0.0, // start with 4x base
980  0.0, 1.0, 0.0,
981  1.0, 1.0, 0.0,
982  1.0, 0.0, 0.0,
983  0.5, 0.5, 1.0}; // apex
984  mesh.faces = {0, 1, 2, // start with 2 base faces
985  0, 2, 3,
986  0, 4, 1, // now the 4 wall faces
987  1, 4, 2,
988  3, 2, 4,
989  0, 3, 4};
990  return mesh;
991  }
992 
1009  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)
1010  {
1011  if (nx < 2 || ny < 2)
1012  {
1013  throw std::runtime_error("Parameters nx and ny must be at least 2.");
1014  }
1015  fs::Mesh mesh;
1016  size_t num_vertices = nx * ny;
1017  size_t num_faces = ((nx - 1) * (ny - 1)) * 2;
1018  std::vector<float> vertices;
1019  vertices.reserve(num_vertices * 3);
1020  std::vector<int> faces;
1021  faces.reserve(num_faces * 3);
1022 
1023  // Create vertices.
1024  float cur_x, cur_y, cur_z;
1025  cur_x = cur_y = cur_z = 0.0;
1026  for (size_t i = 0; i < nx; i++)
1027  {
1028  cur_y = 0.0;
1029  for (size_t j = 0; j < ny; j++)
1030  {
1031  vertices.push_back(cur_x);
1032  vertices.push_back(cur_y);
1033  vertices.push_back(cur_z);
1034  cur_y += disty;
1035  }
1036  cur_x += distx;
1037  }
1038 
1039  // Create faces.
1040  for (size_t i = 0; i < num_vertices; i++)
1041  {
1042  if ((i + 1) % ny == 0 || i >= num_vertices - ny)
1043  {
1044  // Do not use the last ones in row or column as source.
1045  continue;
1046  }
1047  // Add the upper left triangle of this grid cell.
1048  faces.push_back(int(i));
1049  faces.push_back(int(i + ny + 1));
1050  faces.push_back(int(i + 1));
1051  // Add the lower right triangle of this grid cell.
1052  faces.push_back(int(i));
1053  faces.push_back(int(i + ny + 1));
1054  faces.push_back(int(i + ny));
1055  }
1056 
1057  mesh.vertices = vertices;
1058  mesh.faces = faces;
1059  return mesh;
1060  }
1061 
1072  std::string to_obj() const
1073  {
1074  std::vector<uint8_t> empty_col;
1075  return (this->to_obj(empty_col));
1076  }
1077 
1090  std::string to_obj(const std::vector<uint8_t> col) const
1091  {
1092  bool use_vertex_colors = col.size() != 0;
1093  std::stringstream objs;
1094  for (size_t vidx = 0; vidx < this->vertices.size(); vidx += 3)
1095  { // vertex coords
1096  objs << "v " << vertices[vidx] << " " << vertices[vidx + 1] << " " << vertices[vidx + 2];
1097  if (use_vertex_colors)
1098  {
1099  if (col.size() != this->vertices.size())
1100  {
1101  throw std::invalid_argument("Number of vertex coordinates and vertex colors must match when writing OBJ file, but got " + std::to_string(this->vertices.size()) + " and " + std::to_string(col.size()) + ".");
1102  }
1103  objs << " " << (col[vidx] / 255.0f) << " " << (col[vidx + 1] / 255.0f) << " " << (col[vidx + 2] / 255.0f);
1104  }
1105  objs << "\n";
1106  }
1107  for (size_t fidx = 0; fidx < this->faces.size(); fidx += 3)
1108  { // faces: vertex indices, 1-based
1109  objs << "f " << faces[fidx] + 1 << " " << faces[fidx + 1] + 1 << " " << faces[fidx + 2] + 1 << "\n";
1110  }
1111  return (objs.str());
1112  }
1113 
1125  std::vector<std::vector<bool>> as_adjmatrix() const
1126  {
1127  std::vector<std::vector<bool>> adjm = std::vector<std::vector<bool>>(this->num_vertices(), std::vector<bool>(this->num_vertices(), false));
1128  for (size_t fidx = 0; fidx < this->faces.size(); fidx += 3)
1129  { // faces: vertex indices
1130  adjm[faces[fidx]][faces[fidx + 1]] = true;
1131  adjm[faces[fidx + 1]][faces[fidx]] = true;
1132  adjm[faces[fidx + 1]][faces[fidx + 2]] = true;
1133  adjm[faces[fidx + 2]][faces[fidx + 1]] = true;
1134  adjm[faces[fidx + 2]][faces[fidx]] = true;
1135  adjm[faces[fidx]][faces[fidx + 2]] = true;
1136  }
1137  return adjm;
1138  }
1139 
1141  struct _tupleHashFunction
1142  {
1143  size_t operator()(const std::tuple<size_t, size_t> &x) const
1144  {
1145  size_t a = std::get<0>(x);
1146  size_t b = std::get<1>(x);
1147  return a ^ (b << 1) ^ (b >> (sizeof(size_t) * 8 - 1));
1148  }
1149  };
1150 
1153  typedef std::unordered_set<std::tuple<size_t, size_t>, _tupleHashFunction> edge_set;
1154 
1166  edge_set as_edgelist() const
1167  {
1168  edge_set edges;
1169  for (size_t fidx = 0; fidx < this->faces.size(); fidx += 3)
1170  { // faces: vertex indices
1171  edges.insert(std::make_tuple(faces[fidx], faces[fidx + 1]));
1172  edges.insert(std::make_tuple(faces[fidx + 1], faces[fidx]));
1173 
1174  edges.insert(std::make_tuple(faces[fidx + 1], faces[fidx + 2]));
1175  edges.insert(std::make_tuple(faces[fidx + 2], faces[fidx + 1]));
1176 
1177  edges.insert(std::make_tuple(faces[fidx], faces[fidx + 2]));
1178  edges.insert(std::make_tuple(faces[fidx + 2], faces[fidx]));
1179  }
1180  return edges;
1181  }
1182 
1195  std::vector<std::vector<size_t>> as_adjlist(const bool via_matrix = true) const
1196  {
1197  if (!via_matrix)
1198  {
1199  return (this->_as_adjlist_via_edgeset());
1200  }
1201  std::vector<std::vector<bool>> adjm = this->as_adjmatrix();
1202  std::vector<std::vector<size_t>> adjl = std::vector<std::vector<size_t>>(this->num_vertices(), std::vector<size_t>());
1203  size_t nv = adjm.size();
1204  for (size_t i = 0; i < nv; i++)
1205  {
1206  for (size_t j = i + 1; j < nv; j++)
1207  {
1208  if (adjm[i][j] == true)
1209  {
1210  adjl[i].push_back(j);
1211  adjl[j].push_back(i);
1212  }
1213  }
1214  }
1215  return adjl;
1216  }
1217 
1228  std::vector<std::vector<size_t>> _as_adjlist_via_edgeset() const
1229  {
1230  edge_set edges = this->as_edgelist();
1231  std::vector<std::vector<size_t>> adjl = std::vector<std::vector<size_t>>(this->num_vertices(), std::vector<size_t>());
1232  for (const std::tuple<size_t, size_t> &e : edges)
1233  {
1234  adjl[std::get<0>(e)].push_back(std::get<1>(e));
1235  }
1236  return adjl;
1237  }
1238 
1254  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
1255  {
1256 
1257  const std::vector<std::vector<size_t>> adjlist = this->as_adjlist(via_matrix);
1258  return fs::Mesh::smooth_pvd_nn(adjlist, pvd, num_iter, with_nan, detect_nan);
1259  }
1260 
1277  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)
1278  {
1279  assert(pvd.size() == mesh_adj.size());
1280  bool final_with_nan = with_nan;
1281  if (detect_nan)
1282  {
1283  final_with_nan = false;
1284  for (size_t i = 0; i < pvd.size(); i++)
1285  {
1286  if (std::isnan(pvd[i]))
1287  {
1288  final_with_nan = true;
1289  break;
1290  }
1291  }
1292  }
1293  if (final_with_nan)
1294  {
1295  return fs::Mesh::_smooth_pvd_nn_nan(mesh_adj, pvd, num_iter);
1296  }
1297  std::vector<float> current_pvd_source;
1298  std::vector<float> current_pvd_smoothed = std::vector<float>(pvd.size());
1299 
1300  float val_sum;
1301  size_t num_neigh;
1302  for (size_t i = 0; i < num_iter; i++)
1303  {
1304  if (i == 0)
1305  {
1306  current_pvd_source = pvd;
1307  }
1308  else
1309  {
1310  current_pvd_source = current_pvd_smoothed;
1311  }
1312  for (size_t v_idx = 0; v_idx < mesh_adj.size(); v_idx++)
1313  {
1314  num_neigh = mesh_adj[v_idx].size();
1315  val_sum = current_pvd_source[v_idx] / (num_neigh + 1);
1316  for (size_t neigh_rel_idx = 0; neigh_rel_idx < num_neigh; neigh_rel_idx++)
1317  {
1318  val_sum += current_pvd_source[mesh_adj[v_idx][neigh_rel_idx]] / (num_neigh + 1);
1319  }
1320  current_pvd_smoothed[v_idx] = val_sum;
1321  }
1322  }
1323  return current_pvd_smoothed;
1324  }
1325 
1342  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)
1343  {
1344  std::vector<float> current_pvd_source;
1345  std::vector<float> current_pvd_smoothed = std::vector<float>(pvd.size());
1346 
1347  float val_sum;
1348  size_t num_neigh;
1349  size_t num_non_nan_values;
1350  float neigh_val;
1351  for (size_t i = 0; i < num_iter; i++)
1352  {
1353 
1354  if (i == 0)
1355  {
1356  current_pvd_source = pvd;
1357  }
1358  else
1359  {
1360  current_pvd_source = current_pvd_smoothed;
1361  }
1362 
1363  for (size_t v_idx = 0; v_idx < mesh_adj.size(); v_idx++)
1364  {
1365  if (std::isnan(current_pvd_source[v_idx]))
1366  {
1367  current_pvd_smoothed[v_idx] = NAN;
1368  continue;
1369  }
1370  val_sum = current_pvd_source[v_idx];
1371  num_non_nan_values = 1; // If we get here, the source vertex value is not NAN.
1372  num_neigh = mesh_adj[v_idx].size();
1373  for (size_t neigh_rel_idx = 0; neigh_rel_idx < num_neigh; neigh_rel_idx++)
1374  {
1375  neigh_val = current_pvd_source[mesh_adj[v_idx][neigh_rel_idx]];
1376  if (std::isnan(neigh_val))
1377  {
1378  continue;
1379  }
1380  else
1381  {
1382  val_sum += neigh_val;
1383  num_non_nan_values++;
1384  }
1385  }
1386  current_pvd_smoothed[v_idx] = val_sum / (float)num_non_nan_values;
1387  }
1388  }
1389  return current_pvd_smoothed;
1390  }
1391 
1398  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>>())
1399  {
1400  size_t num_vertices = mesh_adj.size();
1401  if (mesh_adj_ext.size() == 0)
1402  {
1403  mesh_adj_ext = mesh_adj;
1404  }
1405  std::vector<size_t> neighborhood;
1406  std::vector<size_t> ext_neighborhood;
1407  for (size_t ext_idx = 0; ext_idx < extend_by; ext_idx++)
1408  {
1409  for (size_t source_vert_idx = 0; source_vert_idx < num_vertices; source_vert_idx++)
1410  {
1411  neighborhood = mesh_adj_ext[source_vert_idx]; // copy needed so we do not modify during iteration.
1412  // Extension: add all neighbors in distance one for all vertices in the neighborhood.
1413  for (size_t neigh_vert_rel_idx = 0; neigh_vert_rel_idx < neighborhood.size(); neigh_vert_rel_idx++)
1414  {
1415  for (size_t canidate_rel_idx = 0; canidate_rel_idx < mesh_adj[neighborhood[neigh_vert_rel_idx]].size(); canidate_rel_idx++)
1416  {
1417  if (mesh_adj[neighborhood[neigh_vert_rel_idx]][canidate_rel_idx] != source_vert_idx)
1418  {
1419  mesh_adj_ext[source_vert_idx].push_back(mesh_adj[neighborhood[neigh_vert_rel_idx]][canidate_rel_idx]);
1420  }
1421  }
1422  }
1423  // We need to remove duplicates.
1424  std::sort(mesh_adj_ext[source_vert_idx].begin(), mesh_adj_ext[source_vert_idx].end());
1425  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());
1426  }
1427  }
1428  return mesh_adj_ext;
1429  }
1430 
1443  void to_obj_file(const std::string &filename) const
1444  {
1445  fs::util::str_to_file(filename, this->to_obj());
1446  }
1447 
1450  void to_obj_file(const std::string &filename, const std::vector<uint8_t> col) const
1451  {
1452  fs::util::str_to_file(filename, this->to_obj(col));
1453  }
1454 
1471  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
1472  {
1473  fs::Mesh submesh;
1474  std::vector<float> new_vertices;
1475  std::vector<int> new_faces;
1476  std::unordered_map<int32_t, int32_t> vertex_index_map_full2submesh;
1477  int32_t new_vertex_idx = 0;
1478  for (size_t i = 0; i < old_vertex_indices.size(); i++)
1479  {
1480  vertex_index_map_full2submesh[old_vertex_indices[i]] = new_vertex_idx;
1481  new_vertices.push_back(this->vertices[size_t(old_vertex_indices[i]) * 3]);
1482  new_vertices.push_back(this->vertices[size_t(old_vertex_indices[i]) * 3 + 1]);
1483  new_vertices.push_back(this->vertices[size_t(old_vertex_indices[i]) * 3 + 2]);
1484  new_vertex_idx++;
1485  }
1486  int face_v0;
1487  int face_v1;
1488  int face_v2;
1489  for (size_t i = 0; i < this->num_faces(); i++)
1490  {
1491  face_v0 = this->faces[i * 3];
1492  face_v1 = this->faces[i * 3 + 1];
1493  face_v2 = this->faces[i * 3 + 2];
1494  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()))
1495  {
1496  new_faces.push_back(vertex_index_map_full2submesh[face_v0]);
1497  new_faces.push_back(vertex_index_map_full2submesh[face_v1]);
1498  new_faces.push_back(vertex_index_map_full2submesh[face_v2]);
1499  }
1500  }
1501  submesh.vertices = new_vertices;
1502  submesh.faces = new_faces;
1503 
1504  std::pair<std::unordered_map<int32_t, int32_t>, fs::Mesh> result;
1505  if (!mapdir_fulltosubmesh)
1506  { // Compute the new2old (reverse) vertex index map:
1507  std::unordered_map<int32_t, int32_t> vertex_index_map_submesh2full;
1508  for (auto const &pair : vertex_index_map_full2submesh)
1509  {
1510  vertex_index_map_submesh2full[pair.second] = pair.first;
1511  }
1512  result = std::pair<std::unordered_map<int32_t, int32_t>, fs::Mesh>(vertex_index_map_submesh2full, submesh);
1513  }
1514  else
1515  {
1516  result = std::pair<std::unordered_map<int32_t, int32_t>, fs::Mesh>(vertex_index_map_full2submesh, submesh);
1517  }
1518 
1519  return result;
1520  }
1521 
1538  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())
1539  {
1540 
1541  if (submesh_to_orig_mapping.size() != data_submesh.size())
1542  {
1543  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()) + ".");
1544  }
1545 
1546  std::vector<float> data_orig_mesh(orig_mesh_num_vertices, fill_value);
1547  for (size_t i = 0; i < data_submesh.size(); i++)
1548  {
1549  auto got = submesh_to_orig_mapping.find(int(i));
1550  if (got != submesh_to_orig_mapping.end())
1551  {
1552  data_orig_mesh[got->second] = data_submesh[i];
1553  }
1554  }
1555  return (data_orig_mesh);
1556  }
1557 
1572  static void from_obj(Mesh *mesh, std::istream *is)
1573  {
1574  std::string line;
1575  int line_idx = -1;
1576 
1577  std::vector<float> vertices;
1578  std::vector<int> faces;
1579  std::vector<uint8_t> vertex_colors;
1580  int detected_format = -1; // -1 = unknown, 0 = no vertex colors, 1 = has vertex colors (r g b after x y z)
1581 
1582 #ifdef LIBFS_DBG_INFO
1583  size_t num_lines_ignored = 0; // Not comments, but custom extensions or material data lines which are ignored by libfs.
1584 #endif
1585 
1586  while (std::getline(*is, line))
1587  {
1588  line_idx += 1;
1589  std::istringstream iss(line);
1590  if (fs::util::starts_with(line, "#"))
1591  {
1592  continue; // skip comment.
1593  }
1594  else
1595  {
1596  if (fs::util::starts_with(line, "v "))
1597  {
1598  std::string elem_type_identifier;
1599  float x, y, z;
1600  if (!(iss >> elem_type_identifier >> x >> y >> z))
1601  {
1602  throw std::domain_error("Could not parse vertex line " + std::to_string(line_idx + 1) + " of OBJ data, invalid format.\n");
1603  }
1604  assert(elem_type_identifier == "v");
1605  vertices.push_back(x);
1606  vertices.push_back(y);
1607  vertices.push_back(z);
1608 
1609  // Check for optional per-vertex colors: 6-value lines (x y z r g b) have colors,
1610  // 3-value lines (x y z) and 4-value lines (x y z w) do not.
1611  // Detect the format from the first vertex line.
1612  if (detected_format == -1)
1613  {
1614  float vr, vg, vb;
1615  if ((iss >> vr >> vg >> vb))
1616  {
1617  // We read 3 more floats successfully. Check if there is even more data
1618  // (e.g., x y z w nx ny nz) — if so, treat as no-colors format.
1619  float extra;
1620  if (iss >> extra)
1621  {
1622  detected_format = 0;
1623  }
1624  else
1625  {
1626  detected_format = 1;
1627  // Store colors for the first vertex (already consumed from stream).
1628  int ri = static_cast<int>(vr * 255.0f + 0.5f);
1629  int gi = static_cast<int>(vg * 255.0f + 0.5f);
1630  int bi = static_cast<int>(vb * 255.0f + 0.5f);
1631  if (ri < 0) { ri = 0; }
1632  if (ri > 255) { ri = 255; }
1633  if (gi < 0) { gi = 0; }
1634  if (gi > 255) { gi = 255; }
1635  if (bi < 0) { bi = 0; }
1636  if (bi > 255) { bi = 255; }
1637  vertex_colors.push_back(static_cast<uint8_t>(ri));
1638  vertex_colors.push_back(static_cast<uint8_t>(gi));
1639  vertex_colors.push_back(static_cast<uint8_t>(bi));
1640  }
1641  }
1642  else
1643  {
1644  detected_format = 0;
1645  }
1646  }
1647  else if (detected_format == 1)
1648  {
1649  // Read colors for subsequent vertices.
1650  float vr, vg, vb;
1651  if (!(iss >> vr >> vg >> vb))
1652  {
1653  throw std::domain_error("Expected vertex colors (r g b) on line " + std::to_string(line_idx + 1) + " of OBJ data, but could not parse them.\n");
1654  }
1655  int ri = static_cast<int>(vr * 255.0f + 0.5f);
1656  int gi = static_cast<int>(vg * 255.0f + 0.5f);
1657  int bi = static_cast<int>(vb * 255.0f + 0.5f);
1658  if (ri < 0) { ri = 0; }
1659  if (ri > 255) { ri = 255; }
1660  if (gi < 0) { gi = 0; }
1661  if (gi > 255) { gi = 255; }
1662  if (bi < 0) { bi = 0; }
1663  if (bi > 255) { bi = 255; }
1664  vertex_colors.push_back(static_cast<uint8_t>(ri));
1665  vertex_colors.push_back(static_cast<uint8_t>(gi));
1666  vertex_colors.push_back(static_cast<uint8_t>(bi));
1667  }
1668  }
1669  else if (fs::util::starts_with(line, "f "))
1670  {
1671  std::string elem_type_identifier, v0raw, v1raw, v2raw;
1672  int v0, v1, v2;
1673  if (!(iss >> elem_type_identifier >> v0raw >> v1raw >> v2raw))
1674  {
1675  throw std::domain_error("Could not parse face line " + std::to_string(line_idx + 1) + " of OBJ data, invalid format.\n");
1676  }
1677  assert(elem_type_identifier == "f");
1678 
1679  // The OBJ format allows to specifiy face indices with slashes to also set normal and material indices.
1680  // 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'.
1681  // We need to extract the stuff before the first slash and interprete it as int to get the vertex index we are looking for.
1682  std::size_t found_v0 = v0raw.find("/");
1683  std::size_t found_v1 = v1raw.find("/");
1684  std::size_t found_v2 = v2raw.find("/");
1685  if (found_v0 != std::string::npos)
1686  {
1687  v0raw = v0raw.substr(0, found_v0);
1688  }
1689  if (found_v1 != std::string::npos)
1690  {
1691  v1raw = v1raw.substr(0, found_v1);
1692  }
1693  if (found_v2 != std::string::npos)
1694  {
1695  v2raw = v2raw.substr(0, found_v2);
1696  }
1697  v0 = std::stoi(v0raw);
1698  v1 = std::stoi(v1raw);
1699  v2 = std::stoi(v2raw);
1700 
1701  // The vertex indices in Wavefront OBJ files are 1-based, so we have to substract 1 here.
1702  faces.push_back(v0 - 1);
1703  faces.push_back(v1 - 1);
1704  faces.push_back(v2 - 1);
1705  }
1706  else
1707  {
1708 #ifdef LIBFS_DBG_INFO
1709  num_lines_ignored++;
1710 #endif
1711 
1712  continue;
1713  }
1714  }
1715  }
1716 #ifdef LIBFS_DBG_INFO
1717  if (num_lines_ignored > 0)
1718  {
1719  std::cout << LIBFS_APPTAG << "Ignored " << num_lines_ignored << " lines in Wavefront OBJ format mesh file.\n";
1720  }
1721 #endif
1722  mesh->vertices = vertices;
1723  mesh->faces = faces;
1724  mesh->vertex_colors = vertex_colors;
1725  }
1726 
1741  static void from_obj(Mesh *mesh, const std::string &filename)
1742  {
1743 #ifdef LIBFS_DBG_INFO
1744  std::cout << LIBFS_APPTAG << "Reading brain mesh from Wavefront object format file " << filename << ".\n";
1745 #endif
1746  std::ifstream input(filename, std::fstream::in);
1747  if (input.is_open())
1748  {
1749  Mesh::from_obj(mesh, &input);
1750  input.close();
1751  }
1752  else
1753  {
1754  throw std::runtime_error("Could not open Wavefront object format mesh file '" + filename + "' for reading.\n");
1755  }
1756  }
1757 
1764  static void from_off(Mesh *mesh, std::istream *is, const std::string &source_filename = "")
1765  {
1766 
1767  std::string msg_source_file_part = source_filename.empty() ? "" : "'" + source_filename + "'";
1768 
1769  std::string line;
1770  int line_idx = -1;
1771  int noncomment_line_idx = -1;
1772 
1773  std::vector<float> vertices;
1774  std::vector<int> faces;
1775  size_t num_vertices = 0;
1776  size_t num_faces = 0;
1777  size_t num_edges = 0;
1778  size_t num_verts_parsed = 0;
1779  size_t num_faces_parsed = 0;
1780  bool has_vertex_colors = false;
1781  float x, y, z; // vertex xyz coords
1782  int r, g, b, a; // vertex colors
1783  int num_verts_this_face, v0, v1, v2; // face, defined by number of vertices and vertex indices.
1784  std::vector<uint8_t> vertex_colors;
1785 
1786  while (std::getline(*is, line))
1787  {
1788  line_idx++;
1789  std::istringstream iss(line);
1790  if (fs::util::starts_with(line, "#"))
1791  {
1792  continue; // skip comment.
1793  }
1794  else
1795  {
1796  noncomment_line_idx++;
1797  if (noncomment_line_idx == 0)
1798  {
1799  std::string off_header_magic;
1800  if (!(iss >> off_header_magic))
1801  {
1802  throw std::domain_error("Could not parse first header line " + std::to_string(line_idx + 1) + " of OFF data, invalid format.\n");
1803  }
1804  if (!(off_header_magic == "OFF" || off_header_magic == "COFF"))
1805  {
1806  throw std::domain_error("OFF magic string invalid, file " + msg_source_file_part + " not in OFF format.\n");
1807  }
1808  has_vertex_colors = (off_header_magic == "COFF");
1809  }
1810  else if (noncomment_line_idx == 1)
1811  {
1812  if (!(iss >> num_vertices >> num_faces >> num_edges))
1813  {
1814  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");
1815  }
1816  }
1817  else
1818  {
1819 
1820  if (num_verts_parsed < num_vertices)
1821  {
1822  if (has_vertex_colors)
1823  {
1824  if (!(iss >> x >> y >> z >> r >> g >> b >> a))
1825  {
1826  throw std::domain_error("Could not parse vertex coordinate and color line " + std::to_string(line_idx + 1) + " of COFF data " + msg_source_file_part + ", invalid format.\n");
1827  }
1828  vertex_colors.push_back(static_cast<uint8_t>(r));
1829  vertex_colors.push_back(static_cast<uint8_t>(g));
1830  vertex_colors.push_back(static_cast<uint8_t>(b));
1831  }
1832  else
1833  {
1834  if (!(iss >> x >> y >> z))
1835  {
1836  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");
1837  }
1838  }
1839  vertices.push_back(x);
1840  vertices.push_back(y);
1841  vertices.push_back(z);
1842  num_verts_parsed++;
1843  }
1844  else
1845  {
1846  if (num_faces_parsed < num_faces)
1847  {
1848  if (!(iss >> num_verts_this_face >> v0 >> v1 >> v2))
1849  {
1850  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");
1851  }
1852  if (num_verts_this_face != 3)
1853  {
1854  throw std::domain_error("At OFF data " + msg_source_file_part + " line " + std::to_string(line_idx + 1) + ": only triangular meshes supported.\n");
1855  }
1856  faces.push_back(v0);
1857  faces.push_back(v1);
1858  faces.push_back(v2);
1859  num_faces_parsed++;
1860  }
1861  }
1862  }
1863  }
1864  }
1865  if (num_verts_parsed < num_vertices)
1866  {
1867  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");
1868  }
1869  if (num_faces_parsed < num_faces)
1870  {
1871  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");
1872  }
1873  mesh->vertices = vertices;
1874  mesh->faces = faces;
1875  mesh->vertex_colors = vertex_colors;
1876  }
1877 
1892  static void from_off(Mesh *mesh, const std::string &filename)
1893  {
1894 #ifdef LIBFS_DBG_INFO
1895  std::cout << LIBFS_APPTAG << "Reading brain mesh from OFF format file " << filename << ".\n";
1896 #endif
1897  std::ifstream input(filename, std::fstream::in);
1898  if (input.is_open())
1899  {
1900  Mesh::from_off(mesh, &input);
1901  input.close();
1902  }
1903  else
1904  {
1905  throw std::runtime_error("Could not open Object file format (OFF) mesh file '" + filename + "' for reading.\n");
1906  }
1907  }
1908 
1914  static void from_ply(Mesh *mesh, std::istream *is)
1915  {
1916  std::string line;
1917  int line_idx = -1;
1918  int noncomment_line_idx = -1;
1919 
1920  std::vector<float> vertices;
1921  std::vector<int> faces;
1922  std::vector<uint8_t> vertex_colors;
1923 
1924  bool in_header = true; // current status
1925  int num_verts = -1;
1926  int num_faces = -1;
1927  bool in_vertex_element = false; // track whether we are inside 'element vertex' in header
1928  std::vector<std::string> vertex_properties; // ordered list of property names under element vertex
1929  while (std::getline(*is, line))
1930  {
1931  line_idx += 1;
1932  std::istringstream iss(line);
1933  if (fs::util::starts_with(line, "comment"))
1934  {
1935  continue; // skip comment.
1936  }
1937  else
1938  {
1939  noncomment_line_idx++;
1940  if (in_header)
1941  {
1942  if (noncomment_line_idx == 0)
1943  {
1944  if (line != "ply")
1945  throw std::domain_error("Invalid PLY file");
1946  }
1947  else if (noncomment_line_idx == 1)
1948  {
1949  if (line != "format ascii 1.0")
1950  throw std::domain_error("Unsupported PLY file format, only format 'format ascii 1.0' is supported.");
1951  }
1952 
1953  if (line == "end_header")
1954  {
1955  in_header = false;
1956  }
1957  else if (fs::util::starts_with(line, "element vertex"))
1958  {
1959  std::string elem, elem_type_identifier;
1960  if (!(iss >> elem >> elem_type_identifier >> num_verts))
1961  {
1962  throw std::domain_error("Could not parse element vertex line of PLY header, invalid format.\n");
1963  }
1964  in_vertex_element = true;
1965  }
1966  else if (fs::util::starts_with(line, "element face"))
1967  {
1968  std::string elem, elem_type_identifier;
1969  if (!(iss >> elem >> elem_type_identifier >> num_faces))
1970  {
1971  throw std::domain_error("Could not parse element face line of PLY header, invalid format.\n");
1972  }
1973  in_vertex_element = false;
1974  }
1975  else if (fs::util::starts_with(line, "element "))
1976  {
1977  // Some other element (e.g., edges): stop tracking vertex properties.
1978  in_vertex_element = false;
1979  }
1980  else if (fs::util::starts_with(line, "property ") && in_vertex_element)
1981  {
1982  // Record property order for the vertex element so we can parse data lines correctly.
1983  std::string kw, type, name;
1984  if (iss >> kw >> type >> name)
1985  {
1986  vertex_properties.push_back(name);
1987  }
1988  }
1989  }
1990  else
1991  { // in data part.
1992  if (num_verts < 1 || num_faces < 1)
1993  {
1994  throw std::domain_error("Invalid PLY file: missing element count lines of header.");
1995  }
1996  // Read vertices
1997  if (vertices.size() < (size_t)num_verts * 3)
1998  {
1999  float x = 0.0f, y = 0.0f, z = 0.0f;
2000  int r = 0, g = 0, b = 0;
2001  if (vertex_properties.empty())
2002  {
2003  // No property declarations tracked: fall back to default x y z order.
2004  if (!(iss >> x >> y >> z))
2005  {
2006  throw std::domain_error("Could not parse vertex line " + std::to_string(line_idx) + " of PLY data, invalid format.\n");
2007  }
2008  vertices.push_back(x);
2009  vertices.push_back(y);
2010  vertices.push_back(z);
2011  }
2012  else
2013  {
2014  for (size_t pi = 0; pi < vertex_properties.size(); pi++)
2015  {
2016  const std::string &pname = vertex_properties[pi];
2017  if (pname == "x") { iss >> x; }
2018  else if (pname == "y") { iss >> y; }
2019  else if (pname == "z") { iss >> z; }
2020  else if (pname == "red") { iss >> r; }
2021  else if (pname == "green") { iss >> g; }
2022  else if (pname == "blue") { iss >> b; }
2023  else if (pname == "nx" || pname == "ny" || pname == "nz")
2024  {
2025  // Skip normals.
2026  float dummy; iss >> dummy;
2027  }
2028  else
2029  {
2030  // Skip unknown property.
2031  std::string dummy; iss >> dummy;
2032  }
2033  if (iss.fail())
2034  {
2035  throw std::domain_error("Could not parse vertex property '" + pname + "' at line " + std::to_string(line_idx) + " of PLY data.\n");
2036  }
2037  }
2038  if (iss.fail())
2039  {
2040  throw std::domain_error("Could not parse vertex line " + std::to_string(line_idx) + " of PLY data, invalid format.\n");
2041  }
2042  vertices.push_back(x);
2043  vertices.push_back(y);
2044  vertices.push_back(z);
2045  // Only store colors if red/green/blue were declared in the header.
2046  bool has_r = false, has_g = false, has_b = false;
2047  for (size_t pi = 0; pi < vertex_properties.size(); pi++)
2048  {
2049  if (vertex_properties[pi] == "red") has_r = true;
2050  if (vertex_properties[pi] == "green") has_g = true;
2051  if (vertex_properties[pi] == "blue") has_b = true;
2052  }
2053  if (has_r && has_g && has_b)
2054  {
2055  vertex_colors.push_back(static_cast<uint8_t>(r));
2056  vertex_colors.push_back(static_cast<uint8_t>(g));
2057  vertex_colors.push_back(static_cast<uint8_t>(b));
2058  }
2059  }
2060  }
2061  else
2062  {
2063  if (faces.size() < (size_t)num_faces * 3)
2064  {
2065  int verts_per_face, v0, v1, v2;
2066  if (!(iss >> verts_per_face >> v0 >> v1 >> v2))
2067  {
2068  throw std::domain_error("Could not parse face line " + std::to_string(line_idx) + " of PLY data, invalid format.\n");
2069  }
2070  if (verts_per_face != 3)
2071  {
2072  throw std::domain_error("Only triangular meshes are supported: PLY faces lines must contain exactly 3 vertex indices.\n");
2073  }
2074  faces.push_back(v0);
2075  faces.push_back(v1);
2076  faces.push_back(v2);
2077  }
2078  }
2079  }
2080  }
2081  }
2082  if (vertices.size() != (size_t)num_verts * 3)
2083  {
2084  std::cerr << "PLY header mentions " << num_verts << " vertices, but found " << vertices.size() / 3 << ".\n";
2085  }
2086  if (faces.size() != (size_t)num_faces * 3)
2087  {
2088  std::cerr << "PLY header mentions " << num_faces << " faces, but found " << faces.size() / 3 << ".\n";
2089  }
2090  mesh->vertices = vertices;
2091  mesh->faces = faces;
2092  mesh->vertex_colors = vertex_colors;
2093  }
2094 
2108  static void from_ply(Mesh *mesh, const std::string &filename)
2109  {
2110 #ifdef LIBFS_DBG_INFO
2111  std::cout << LIBFS_APPTAG << "Reading brain mesh from PLY format file " << filename << ".\n";
2112 #endif
2113  std::ifstream input(filename, std::fstream::in);
2114  if (input.is_open())
2115  {
2116  Mesh::from_ply(mesh, &input);
2117  input.close();
2118  }
2119  else
2120  {
2121  throw std::runtime_error("Could not open Stanford PLY format mesh file '" + filename + "' for reading.\n");
2122  }
2123  }
2124 
2134  size_t num_vertices() const
2135  {
2136  return (this->vertices.size() / 3);
2137  }
2138 
2148  size_t num_faces() const
2149  {
2150  return (this->faces.size() / 3);
2151  }
2152 
2165  const int32_t &fm_at(const size_t i, const size_t j) const
2166  {
2167  size_t idx = _vidx_2d(i, j, 3);
2168  if (idx > this->faces.size() - 1)
2169  {
2170  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");
2171  }
2172  return (this->faces[idx]);
2173  }
2174 
2186  std::vector<int32_t> face_vertices(const size_t face) const
2187  {
2188  if (face > this->num_faces() - 1)
2189  {
2190  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");
2191  }
2192  std::vector<int32_t> fv(3);
2193  fv[0] = this->fm_at(face, 0);
2194  fv[1] = this->fm_at(face, 1);
2195  fv[2] = this->fm_at(face, 2);
2196  return (fv);
2197  }
2198 
2210  std::vector<float> vertex_coords(const size_t vertex) const
2211  {
2212  if (vertex > this->num_vertices() - 1)
2213  {
2214  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");
2215  }
2216  std::vector<float> vc(3);
2217  vc[0] = this->vm_at(vertex, 0);
2218  vc[1] = this->vm_at(vertex, 1);
2219  vc[2] = this->vm_at(vertex, 2);
2220  return (vc);
2221  }
2222 
2236  const float &vm_at(const size_t i, const size_t j) const
2237  {
2238  size_t idx = _vidx_2d(i, j, 3);
2239  if (idx > this->vertices.size() - 1)
2240  {
2241  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");
2242  }
2243  return (this->vertices[idx]);
2244  }
2245 
2254  std::string to_ply() const
2255  {
2256  std::vector<uint8_t> empty_col;
2257  return (this->to_ply(empty_col));
2258  }
2259 
2270  std::string to_ply(const std::vector<uint8_t> col) const
2271  {
2272  bool use_vertex_colors = col.size() != 0;
2273  std::stringstream plys;
2274  plys << "ply\nformat ascii 1.0\n";
2275  plys << "element vertex " << this->num_vertices() << "\n";
2276  plys << "property float x\nproperty float y\nproperty float z\n";
2277  if (use_vertex_colors)
2278  {
2279  if (col.size() != this->vertices.size())
2280  {
2281  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()) + ".");
2282  }
2283  plys << "property uchar red\nproperty uchar green\nproperty uchar blue\n";
2284  }
2285  plys << "element face " << this->num_faces() << "\n";
2286  plys << "property list uchar int vertex_index\n";
2287  plys << "end_header\n";
2288 
2289 #ifdef LIBFS_DBG_DEBUG
2290  fs::util::log("Writing " + std::to_string(this->vertices.size() / 3) + " PLY format vertices.", "INFO");
2291 #endif
2292 
2293  for (size_t vidx = 0; vidx < this->vertices.size(); vidx += 3)
2294  { // vertex coords
2295  plys << vertices[vidx] << " " << vertices[vidx + 1] << " " << vertices[vidx + 2];
2296  if (use_vertex_colors)
2297  {
2298  plys << " " << (int)col[vidx] << " " << (int)col[vidx + 1] << " " << (int)col[vidx + 2];
2299  }
2300  plys << "\n";
2301  }
2302 
2303 #ifdef LIBFS_DBG_DEBUG
2304  fs::util::log("Writing " + std::to_string(this->faces.size() / 3) + " PLY format faces.", "INFO");
2305 #endif
2306 
2307  const int num_vertices_per_face = 3;
2308  for (size_t fidx = 0; fidx < this->faces.size(); fidx += 3)
2309  { // faces: vertex indices, 0-based
2310  plys << num_vertices_per_face << " " << faces[fidx] << " " << faces[fidx + 1] << " " << faces[fidx + 2] << "\n";
2311  }
2312  return (plys.str());
2313  }
2314 
2324  void to_ply_file(const std::string &filename) const
2325  {
2326 #ifdef LIBFS_DBG_INFO
2327  fs::util::log("Writing mesh to PLY file '" + filename + "'.", "INFO");
2328 #endif
2329  fs::util::str_to_file(filename, this->to_ply());
2330  }
2331 
2334  void to_ply_file(const std::string &filename, const std::vector<uint8_t> col) const
2335  {
2336  fs::util::str_to_file(filename, this->to_ply(col));
2337  }
2338 
2347  std::string to_off() const
2348  {
2349  std::vector<uint8_t> empty_col;
2350  return (this->to_off(empty_col));
2351  }
2352 
2356  std::string to_off(const std::vector<uint8_t> col) const
2357  {
2358  bool use_vertex_colors = col.size() != 0;
2359  std::stringstream offs;
2360  if (use_vertex_colors)
2361  {
2362 #ifdef LIBFS_DBG_INFO
2363  fs::util::log("Writing OFF representation of mesh with vertex colors.", "INFO");
2364 #endif
2365  if (col.size() != this->vertices.size())
2366  {
2367  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()) + ".");
2368  }
2369  offs << "COFF\n";
2370  }
2371  else
2372  {
2373 #ifdef LIBFS_DBG_INFO
2374  fs::util::log("Writing OFF representation of mesh without vertex colors.", "INFO");
2375 #endif
2376  offs << "OFF\n";
2377  }
2378  offs << this->num_vertices() << " " << this->num_faces() << " 0\n";
2379 
2380  for (size_t vidx = 0; vidx < this->vertices.size(); vidx += 3)
2381  { // vertex coords
2382  offs << vertices[vidx] << " " << vertices[vidx + 1] << " " << vertices[vidx + 2];
2383  if (use_vertex_colors)
2384  {
2385  offs << " " << (int)col[vidx] << " " << (int)col[vidx + 1] << " " << (int)col[vidx + 2] << " 255";
2386  }
2387  offs << "\n";
2388  }
2389 
2390  const int num_vertices_per_face = 3;
2391  for (size_t fidx = 0; fidx < this->faces.size(); fidx += 3)
2392  { // faces: vertex indices, 0-based
2393  offs << num_vertices_per_face << " " << faces[fidx] << " " << faces[fidx + 1] << " " << faces[fidx + 2] << "\n";
2394  }
2395  return (offs.str());
2396  }
2397 
2407  void to_off_file(const std::string &filename) const
2408  {
2409  fs::util::str_to_file(filename, this->to_off());
2410  }
2411 
2414  void to_off_file(const std::string &filename, const std::vector<uint8_t> col) const
2415  {
2416  fs::util::str_to_file(filename, this->to_off(col));
2417  }
2418  };
2419 
2421  struct Curv
2422  {
2423 
2425  Curv(std::vector<float> curv_data) : num_faces(100000), num_vertices(0), num_values_per_vertex(1)
2426  {
2427  data = curv_data;
2428  num_vertices = int(data.size());
2429  }
2430 
2432  Curv() : num_faces(100000), num_vertices(0), num_values_per_vertex(1) {}
2433 
2435  int32_t num_faces;
2436 
2438  std::vector<float> data;
2439 
2441  int32_t num_vertices;
2442 
2445  };
2446 
2448  struct Colortable
2449  {
2450  std::vector<int32_t> id;
2451  std::vector<std::string> name;
2452  std::vector<int32_t> r;
2453  std::vector<int32_t> g;
2454  std::vector<int32_t> b;
2455  std::vector<int32_t> a;
2456  std::vector<int32_t> label;
2457 
2459  size_t num_entries() const
2460  {
2461  size_t num_ids = this->id.size();
2462  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)
2463  {
2464  std::cerr << "Inconsistent Colortable, vector sizes do not match.\n";
2465  }
2466  return num_ids;
2467  }
2468 
2470  int32_t get_region_idx(const std::string &query_name) const
2471  {
2472  for (size_t i = 0; i < this->num_entries(); i++)
2473  {
2474  if (this->name[i] == query_name)
2475  {
2476  return (int32_t)i;
2477  }
2478  }
2479  return (-1);
2480  }
2481 
2483  int32_t get_region_idx(int32_t query_label) const
2484  {
2485  for (size_t i = 0; i < this->num_entries(); i++)
2486  {
2487  if (this->label[i] == query_label)
2488  {
2489  return (int32_t)i;
2490  }
2491  }
2492  return (-1);
2493  }
2494  };
2495 
2497  struct Annot
2498  {
2499  std::vector<int32_t> vertex_indices;
2500  std::vector<int32_t> vertex_labels;
2502 
2504  std::vector<int32_t> region_vertices(const std::string &region_name) const
2505  {
2506  int32_t region_idx = this->colortable.get_region_idx(region_name);
2507  if (region_idx >= 0)
2508  {
2509  return (this->region_vertices(this->colortable.label[region_idx]));
2510  }
2511  else
2512  {
2513  std::cerr << "No such region in annot, returning empty vector.\n";
2514  std::vector<int32_t> empty;
2515  return (empty);
2516  }
2517  }
2518 
2520  std::vector<int32_t> region_vertices(int32_t region_label) const
2521  {
2522  std::vector<int32_t> reg_verts;
2523  for (size_t i = 0; i < this->vertex_labels.size(); i++)
2524  {
2525  if (this->vertex_labels[i] == region_label)
2526  {
2527  reg_verts.push_back(int(i));
2528  }
2529  }
2530  return (reg_verts);
2531  }
2532 
2535  std::vector<uint8_t> vertex_colors(bool alpha = false) const
2536  {
2537  int num_channels = alpha ? 4 : 3;
2538  std::vector<uint8_t> col;
2539  col.reserve(this->num_vertices() * num_channels);
2540  std::vector<size_t> vertex_region_indices = this->vertex_regions();
2541  for (size_t i = 0; i < this->num_vertices(); i++)
2542  {
2543  col.push_back(this->colortable.r[vertex_region_indices[i]]);
2544  col.push_back(this->colortable.g[vertex_region_indices[i]]);
2545  col.push_back(this->colortable.b[vertex_region_indices[i]]);
2546  if (alpha)
2547  {
2548  col.push_back(this->colortable.a[vertex_region_indices[i]]);
2549  }
2550  }
2551  return (col);
2552  }
2553 
2556  size_t num_vertices() const
2557  {
2558  size_t nv = this->vertex_indices.size();
2559  if (this->vertex_labels.size() != nv)
2560  {
2561  throw std::runtime_error("Inconsistent annot, number of vertex indices and labels does not match.\n");
2562  }
2563  return nv;
2564  }
2565 
2568  std::vector<size_t> vertex_regions() const
2569  {
2570  std::vector<size_t> vert_reg;
2571  for (size_t i = 0; i < this->num_vertices(); i++)
2572  {
2573  vert_reg.push_back(0); // init with zeros.
2574  }
2575  for (size_t region_idx = 0; region_idx < this->colortable.num_entries(); region_idx++)
2576  {
2577  std::vector<int32_t> reg_vertices = this->region_vertices(this->colortable.label[region_idx]);
2578  for (size_t region_vert_local_idx = 0; region_vert_local_idx < reg_vertices.size(); region_vert_local_idx++)
2579  {
2580  int32_t region_vert_idx = reg_vertices[region_vert_local_idx];
2581  vert_reg[region_vert_idx] = region_idx;
2582  }
2583  }
2584  return vert_reg;
2585  }
2586 
2588  std::vector<std::string> vertex_region_names() const
2589  {
2590  std::vector<std::string> region_names;
2591  std::vector<size_t> vertex_region_indices = this->vertex_regions();
2592  for (size_t i = 0; i < this->num_vertices(); i++)
2593  {
2594  region_names.push_back(this->colortable.name[vertex_region_indices[i]]);
2595  }
2596  return (region_names);
2597  }
2598  };
2599 
2601  struct MghHeader
2602  {
2605  {
2606  dim1length = curv.data.size();
2607  dim2length = 1;
2608  dim3length = 1;
2609  dim4length = 1;
2610  dtype = fs::MRI_FLOAT;
2611  }
2612  MghHeader(std::vector<float> curv_data)
2613  {
2614  dim1length = curv_data.size();
2615  dim2length = 1;
2616  dim3length = 1;
2617  dim4length = 1;
2618  dtype = fs::MRI_FLOAT;
2619  }
2620  int32_t dim1length = 0;
2621  int32_t dim2length = 0;
2622  int32_t dim3length = 0;
2623  int32_t dim4length = 0;
2624 
2625  int32_t dtype = 0;
2626  int32_t dof = 0;
2627  int16_t ras_good_flag = 0;
2628 
2630  size_t num_values() const
2631  {
2632  return ((size_t)dim1length * dim2length * dim3length * dim4length);
2633  }
2634 
2635  float xsize = 0.0;
2636  float ysize = 0.0;
2637  float zsize = 0.0;
2638  std::vector<float> Mdc;
2639  std::vector<float> Pxyz_c;
2640  };
2641 
2643  struct MghData
2644  {
2645  MghData() {}
2646  MghData(std::vector<int32_t> curv_data) { data_mri_int = curv_data; }
2647  explicit MghData(std::vector<uint8_t> curv_data) { data_mri_uchar = curv_data; }
2648  explicit MghData(std::vector<short> curv_data) { data_mri_short = curv_data; }
2649  MghData(std::vector<float> curv_data) { data_mri_float = curv_data; }
2650  MghData(Curv curv) { data_mri_float = curv.data; }
2651  std::vector<int32_t> data_mri_int;
2652  std::vector<uint8_t> data_mri_uchar;
2653  std::vector<float> data_mri_float;
2654  std::vector<short> data_mri_short;
2655  };
2656 
2658  struct Mgh
2659  {
2662  Mgh() {}
2663  Mgh(Curv curv)
2664  {
2665  header = MghHeader(curv);
2666  data = MghData(curv);
2667  }
2668  Mgh(std::vector<float> curv_data)
2669  {
2670  header = MghHeader(curv_data);
2671  data = MghData(curv_data);
2672  }
2673  };
2674 
2677  template <class T>
2678  struct Array4D
2679  {
2684  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)) {}
2685 
2691  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)) {}
2692 
2697  Array4D(Mgh *mgh) : // This does NOT init the data atm.
2698  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))
2699  {
2700  }
2701 
2703  const T &at(const unsigned int i1, const unsigned int i2, const unsigned int i3, const unsigned int i4) const
2704  {
2705  return data[get_index(i1, i2, i3, i4)];
2706  }
2707 
2709  unsigned int get_index(const unsigned int i1, const unsigned int i2, const unsigned int i3, const unsigned int i4) const
2710  {
2711  assert(i1 >= 0 && i1 < d1);
2712  assert(i2 >= 0 && i2 < d2);
2713  assert(i3 >= 0 && i3 < d3);
2714  assert(i4 >= 0 && i4 < d4);
2715  return (((i1 * d2 + i2) * d3 + i3) * d4 + i4);
2716  }
2717 
2719  unsigned int num_values() const
2720  {
2721  return (d1 * d2 * d3 * d4);
2722  }
2723 
2724  unsigned int d1;
2725  unsigned int d2;
2726  unsigned int d3;
2727  unsigned int d4;
2728  std::vector<T> data;
2729 
2730  private:
2733  static unsigned int _validate_mgh_dim(int32_t dim)
2734  {
2735  if (dim <= 0)
2736  {
2737  throw std::domain_error("MGH dimension " + std::to_string(dim) + " is not positive.\n");
2738  }
2739  return static_cast<unsigned int>(dim);
2740  }
2741 
2743  static size_t _compute_4d_size(unsigned int d1, unsigned int d2, unsigned int d3, unsigned int d4)
2744  {
2745  if (d1 == 0 || d2 == 0 || d3 == 0 || d4 == 0)
2746  {
2747  throw std::domain_error("Array4D dimensions must be positive.\n");
2748  }
2749  size_t s1, s2, s3;
2750  if (!fs::util::safe_multiply(d1, d2, s1) ||
2751  !fs::util::safe_multiply(s1, d3, s2) ||
2752  !fs::util::safe_multiply(s2, d4, s3))
2753  {
2754  throw std::overflow_error("Array4D dimensions cause size_t overflow.\n");
2755  }
2756  if (s3 > LIBFS_MAX_ALLOC_BYTES / sizeof(T))
2757  {
2758  throw std::runtime_error("Array4D size " + std::to_string(s3) +
2759  " elements exceeds maximum allowed allocation (" +
2760  std::to_string(LIBFS_MAX_ALLOC_BYTES) + " bytes).\n");
2761  }
2762  return s3;
2763  }
2764  };
2765 
2766  // More declarations, should also go to separate header.
2767  void read_mgh_header(MghHeader *, const std::string &);
2768  void read_mgh_header(MghHeader *, std::istream *);
2769  template <typename T>
2770  std::vector<T> _read_mgh_data(MghHeader *, const std::string &);
2771  template <typename T>
2772  std::vector<T> _read_mgh_data(MghHeader *, std::istream *);
2773  std::vector<int32_t> _read_mgh_data_int(MghHeader *, const std::string &);
2774  std::vector<int32_t> _read_mgh_data_int(MghHeader *, std::istream *);
2775  std::vector<uint8_t> _read_mgh_data_uchar(MghHeader *, const std::string &);
2776  std::vector<uint8_t> _read_mgh_data_uchar(MghHeader *, std::istream *);
2777  std::vector<short> _read_mgh_data_short(MghHeader *, const std::string &);
2778  std::vector<short> _read_mgh_data_short(MghHeader *, std::istream *);
2779  std::vector<float> _read_mgh_data_float(MghHeader *, const std::string &);
2780  std::vector<float> _read_mgh_data_float(MghHeader *, std::istream *);
2781 
2794  void read_mgh(Mgh *mgh, const std::string &filename)
2795  {
2796  MghHeader mgh_header;
2797  read_mgh_header(&mgh_header, filename);
2798  mgh->header = mgh_header;
2799  if (mgh->header.dtype == MRI_INT)
2800  {
2801  std::vector<int32_t> data = _read_mgh_data_int(&mgh_header, filename);
2802  mgh->data.data_mri_int = data;
2803  }
2804  else if (mgh->header.dtype == MRI_UCHAR)
2805  {
2806  std::vector<uint8_t> data = _read_mgh_data_uchar(&mgh_header, filename);
2807  mgh->data.data_mri_uchar = data;
2808  }
2809  else if (mgh->header.dtype == MRI_FLOAT)
2810  {
2811  std::vector<float> data = _read_mgh_data_float(&mgh_header, filename);
2812  mgh->data.data_mri_float = data;
2813  }
2814  else if (mgh->header.dtype == MRI_SHORT)
2815  {
2816  std::vector<short> data = _read_mgh_data_short(&mgh_header, filename);
2817  mgh->data.data_mri_short = data;
2818  }
2819  else
2820  {
2821 #ifdef LIBFS_DBG_INFO
2822  if (fs::util::ends_with(filename, ".mgz"))
2823  {
2824 #ifndef LIBFS_HAS_ZLIB
2825  std::cout << LIBFS_APPTAG << "Note: your MGH filename ends with '.mgz'. MGZ support requires zlib: link with -lz. If you already have zlib and see this, #define LIBFS_HAS_ZLIB before including libfs.h, or upgrade your compiler.\n";
2826 #else
2827  std::cout << LIBFS_APPTAG << "Note: your MGH filename ends with '.mgz'. Did you mean to call read_mgz() instead of read_mgh()?\n";
2828 #endif
2829  }
2830 #endif
2831  throw std::runtime_error("Not reading MGH data from file '" + filename + "', data type " + std::to_string(mgh->header.dtype) + " not supported yet.\n");
2832  }
2833  }
2834 
2844  std::vector<std::string> read_subjectsfile(const std::string &filename)
2845  {
2846  std::vector<std::string> subjects;
2847  std::ifstream input(filename, std::fstream::in);
2848  std::string line;
2849 
2850  if (!input.is_open())
2851  {
2852  throw std::runtime_error("Could not open subjects file '" + filename + "'.\n");
2853  }
2854 
2855  while (std::getline(input, line))
2856  {
2857  subjects.push_back(line);
2858  }
2859  return (subjects);
2860  }
2861 
2873  void write_subjectsfile(const std::string &filename, const std::vector<std::string> &subjects)
2874  {
2875  std::ofstream ofs;
2876  ofs.open(filename, std::ofstream::out);
2877  if (ofs.is_open())
2878  {
2879  for (size_t i = 0; i < subjects.size(); i++)
2880  {
2881  ofs << subjects[i] << "\n";
2882  }
2883  ofs.close();
2884  }
2885  else
2886  {
2887  throw std::runtime_error("Unable to open subjects file '" + filename + "' for writing.\n");
2888  }
2889  }
2890 
2896  void read_mgh(Mgh *mgh, std::istream *is)
2897  {
2898  MghHeader mgh_header;
2899  read_mgh_header(&mgh_header, is);
2900  mgh->header = mgh_header;
2901  if (mgh->header.dtype == MRI_INT)
2902  {
2903  std::vector<int32_t> data = _read_mgh_data_int(&mgh_header, is);
2904  mgh->data.data_mri_int = data;
2905  }
2906  else if (mgh->header.dtype == MRI_UCHAR)
2907  {
2908  std::vector<uint8_t> data = _read_mgh_data_uchar(&mgh_header, is);
2909  mgh->data.data_mri_uchar = data;
2910  }
2911  else if (mgh->header.dtype == MRI_FLOAT)
2912  {
2913  std::vector<float> data = _read_mgh_data_float(&mgh_header, is);
2914  mgh->data.data_mri_float = data;
2915  }
2916  else if (mgh->header.dtype == MRI_SHORT)
2917  {
2918  std::vector<short> data = _read_mgh_data_short(&mgh_header, is);
2919  mgh->data.data_mri_short = data;
2920  }
2921  else
2922  {
2923  throw std::runtime_error("Not reading data from MGH stream, data type " + std::to_string(mgh->header.dtype) + " not supported yet.\n");
2924  }
2925  }
2926 
2932  void read_mgh_header(MghHeader *mgh_header, std::istream *is)
2933  {
2934  const int MGH_VERSION = 1;
2935 
2936  int format_version = _freadt<int32_t>(*is);
2937  if (format_version != MGH_VERSION)
2938  {
2939  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");
2940  }
2941  mgh_header->dim1length = _freadt<int32_t>(*is);
2942  mgh_header->dim2length = _freadt<int32_t>(*is);
2943  mgh_header->dim3length = _freadt<int32_t>(*is);
2944  mgh_header->dim4length = _freadt<int32_t>(*is);
2945 
2946  // Validate dimensions: must be positive (negative would wrap to huge size_t).
2947  if (mgh_header->dim1length <= 0 || mgh_header->dim2length <= 0 ||
2948  mgh_header->dim3length <= 0 || mgh_header->dim4length <= 0)
2949  {
2950  throw std::domain_error("MGH header contains non-positive dimension(s): dims=(" +
2951  std::to_string(mgh_header->dim1length) + "," +
2952  std::to_string(mgh_header->dim2length) + "," +
2953  std::to_string(mgh_header->dim3length) + "," +
2954  std::to_string(mgh_header->dim4length) + ").\n");
2955  }
2956 
2957  // Validate total number of values against allocation limit.
2958  if (!fs::util::check_alloc(static_cast<size_t>(mgh_header->dim1length) *
2959  static_cast<size_t>(mgh_header->dim2length) *
2960  static_cast<size_t>(mgh_header->dim3length),
2961  static_cast<size_t>(mgh_header->dim4length)))
2962  {
2963  throw std::runtime_error("MGH header volume size exceeds maximum allowed allocation (" +
2964  std::to_string(LIBFS_MAX_ALLOC_BYTES) + " bytes).\n");
2965  }
2966 
2967  mgh_header->dtype = _freadt<int32_t>(*is);
2968  mgh_header->dof = _freadt<int32_t>(*is);
2969 
2970  int unused_header_space_size_left = 256; // in bytes
2971  mgh_header->ras_good_flag = _freadt<int16_t>(*is);
2972  unused_header_space_size_left -= 2; // for the ras_good_flag
2973 
2974  // Read the RAS part of the header.
2975  if (mgh_header->ras_good_flag == 1)
2976  {
2977  mgh_header->xsize = _freadt<float>(*is);
2978  mgh_header->ysize = _freadt<float>(*is);
2979  mgh_header->zsize = _freadt<float>(*is);
2980 
2981  // Validate voxel sizes: must be finite and non-zero to prevent division-by-zero
2982  // and NaN/Inf propagation in spatial transform calculations.
2983  if (!fs::util::is_finite_float(mgh_header->xsize) ||
2984  !fs::util::is_finite_float(mgh_header->ysize) ||
2985  !fs::util::is_finite_float(mgh_header->zsize))
2986  {
2987  throw std::domain_error("MGH header contains NaN or Inf voxel size(s): x=" +
2988  std::to_string(mgh_header->xsize) + " y=" +
2989  std::to_string(mgh_header->ysize) + " z=" +
2990  std::to_string(mgh_header->zsize) + ".\n");
2991  }
2992  if (mgh_header->xsize == 0.0f || mgh_header->ysize == 0.0f || mgh_header->zsize == 0.0f)
2993  {
2994  throw std::domain_error("MGH header contains zero voxel size(s): x=" +
2995  std::to_string(mgh_header->xsize) + " y=" +
2996  std::to_string(mgh_header->ysize) + " z=" +
2997  std::to_string(mgh_header->zsize) + ".\n");
2998  }
2999 
3000  for (int i = 0; i < 9; i++)
3001  {
3002  mgh_header->Mdc.push_back(_freadt<float>(*is));
3003  }
3004  for (int i = 0; i < 3; i++)
3005  {
3006  mgh_header->Pxyz_c.push_back(_freadt<float>(*is));
3007  }
3008 
3009  // Validate the direction cosine matrix (Mdc) and center coordinates (Pxyz_c).
3010  for (size_t i = 0; i < mgh_header->Mdc.size(); i++)
3011  {
3012  if (!fs::util::is_finite_float(mgh_header->Mdc[i]))
3013  {
3014  throw std::domain_error("MGH header Mdc matrix contains NaN or Inf at index " +
3015  std::to_string(i) + ".\n");
3016  }
3017  }
3018  for (size_t i = 0; i < mgh_header->Pxyz_c.size(); i++)
3019  {
3020  if (!fs::util::is_finite_float(mgh_header->Pxyz_c[i]))
3021  {
3022  throw std::domain_error("MGH header Pxyz_c contains NaN or Inf at index " +
3023  std::to_string(i) + ".\n");
3024  }
3025  }
3026 
3027  unused_header_space_size_left -= 60;
3028  }
3029 
3030  // Advance to data part. We do not seek here because that is not
3031  // possible if the stream is gzip-wrapped with zstr, as in the read_mgz example.
3032  uint8_t discarded;
3033  while (unused_header_space_size_left > 0)
3034  {
3035  discarded = _freadt<uint8_t>(*is);
3036  unused_header_space_size_left -= 1;
3037  }
3038  (void)discarded; // Suppress warnings about unused variable.
3039  }
3040 
3045  std::vector<int32_t> _read_mgh_data_int(MghHeader *mgh_header, const std::string &filename)
3046  {
3047  if (mgh_header->dtype != MRI_INT)
3048  {
3049  std::cerr << "Expected MRI data type " << MRI_INT << ", but found " << mgh_header->dtype << ".\n";
3050  }
3051  return (_read_mgh_data<int32_t>(mgh_header, filename));
3052  }
3053 
3058  std::vector<int32_t> _read_mgh_data_int(MghHeader *mgh_header, std::istream *is)
3059  {
3060  if (mgh_header->dtype != MRI_INT)
3061  {
3062  std::cerr << "Expected MRI data type " << MRI_INT << ", but found " << mgh_header->dtype << ".\n";
3063  }
3064  return (_read_mgh_data<int32_t>(mgh_header, is));
3065  }
3066 
3071  std::vector<short> _read_mgh_data_short(MghHeader *mgh_header, const std::string &filename)
3072  {
3073  if (mgh_header->dtype != MRI_SHORT)
3074  {
3075  std::cerr << "Expected MRI data type " << MRI_SHORT << ", but found " << mgh_header->dtype << ".\n";
3076  }
3077  return (_read_mgh_data<short>(mgh_header, filename));
3078  }
3079 
3084  std::vector<short> _read_mgh_data_short(MghHeader *mgh_header, std::istream *is)
3085  {
3086  if (mgh_header->dtype != MRI_SHORT)
3087  {
3088  std::cerr << "Expected MRI data type " << MRI_SHORT << ", but found " << mgh_header->dtype << ".\n";
3089  }
3090  return (_read_mgh_data<short>(mgh_header, is));
3091  }
3092 
3099  void read_mgh_header(MghHeader *mgh_header, const std::string &filename)
3100  {
3101  std::ifstream ifs;
3102  ifs.open(filename, std::ios_base::in | std::ios::binary);
3103  if (ifs.is_open())
3104  {
3105  read_mgh_header(mgh_header, &ifs);
3106  ifs.close();
3107  }
3108  else
3109  {
3110  throw std::runtime_error("Unable to open MGH file '" + filename + "'.\n");
3111  }
3112  }
3113 
3119  template <typename T>
3120  std::vector<T> _read_mgh_data(MghHeader *mgh_header, const std::string &filename)
3121  {
3122  std::ifstream ifs;
3123  ifs.open(filename, std::ios_base::in | std::ios::binary);
3124  if (ifs.is_open())
3125  {
3126  size_t num_values = mgh_header->num_values();
3127 
3128  // Cross-check: ensure the file has enough data after the 284-byte header.
3129  size_t file_size = fs::util::get_file_size(filename);
3130  if (file_size > 0)
3131  {
3132  const size_t HEADER_SIZE = 284;
3133  size_t expected_data_bytes = 0;
3134  if (!fs::util::safe_multiply(num_values, sizeof(T), expected_data_bytes))
3135  {
3136  throw std::overflow_error("MGH data size computation overflowed.\n");
3137  }
3138  if (file_size < HEADER_SIZE || (file_size - HEADER_SIZE) < expected_data_bytes)
3139  {
3140  throw std::runtime_error("MGH file '" + filename + "' is too small (" +
3141  std::to_string(file_size) + " bytes) for the data claimed in its header (" +
3142  std::to_string(HEADER_SIZE + expected_data_bytes) + " bytes).\n");
3143  }
3144  }
3145 
3146  if (!fs::util::check_alloc(num_values, sizeof(T)))
3147  {
3148  throw std::runtime_error("MGH file data size exceeds maximum allowed allocation.\n");
3149  }
3150 
3151  ifs.seekg(284, ifs.beg); // skip to end of header and beginning of data
3152 
3153  std::vector<T> data;
3154  data.reserve(num_values);
3155  for (size_t i = 0; i < num_values; i++)
3156  {
3157  data.push_back(_freadt<T>(ifs));
3158  }
3159  ifs.close();
3160  return (data);
3161  }
3162  else
3163  {
3164  throw std::runtime_error("Unable to open MGH file '" + filename + "'.\n");
3165  }
3166  }
3167 
3172  template <typename T>
3173  std::vector<T> _read_mgh_data(MghHeader *mgh_header, std::istream *is)
3174  {
3175  size_t num_values = mgh_header->num_values();
3176  if (!fs::util::check_alloc(num_values, sizeof(T)))
3177  {
3178  throw std::runtime_error("MGH stream data size exceeds maximum allowed allocation.\n");
3179  }
3180  std::vector<T> data;
3181  data.reserve(num_values);
3182  for (size_t i = 0; i < num_values; i++)
3183  {
3184  data.push_back(_freadt<T>(*is));
3185  }
3186  return (data);
3187  }
3188 
3193  std::vector<float> _read_mgh_data_float(MghHeader *mgh_header, const std::string &filename)
3194  {
3195  if (mgh_header->dtype != MRI_FLOAT)
3196  {
3197  std::cerr << "Expected MRI data type " << MRI_FLOAT << ", but found " << mgh_header->dtype << ".\n";
3198  }
3199  return (_read_mgh_data<float>(mgh_header, filename));
3200  }
3201 
3206  std::vector<float> _read_mgh_data_float(MghHeader *mgh_header, std::istream *is)
3207  {
3208  if (mgh_header->dtype != MRI_FLOAT)
3209  {
3210  std::cerr << "Expected MRI data type " << MRI_FLOAT << ", but found " << mgh_header->dtype << ".\n";
3211  }
3212  return (_read_mgh_data<float>(mgh_header, is));
3213  }
3214 
3219  std::vector<uint8_t> _read_mgh_data_uchar(MghHeader *mgh_header, const std::string &filename)
3220  {
3221  if (mgh_header->dtype != MRI_UCHAR)
3222  {
3223  std::cerr << "Expected MRI data type " << MRI_UCHAR << ", but found " << mgh_header->dtype << ".\n";
3224  }
3225  return (_read_mgh_data<uint8_t>(mgh_header, filename));
3226  }
3227 
3232  std::vector<uint8_t> _read_mgh_data_uchar(MghHeader *mgh_header, std::istream *is)
3233  {
3234  if (mgh_header->dtype != MRI_UCHAR)
3235  {
3236  std::cerr << "Expected MRI data type " << MRI_UCHAR << ", but found " << mgh_header->dtype << ".\n";
3237  }
3238  return (_read_mgh_data<uint8_t>(mgh_header, is));
3239  }
3240 
3254  void read_surf(Mesh *surface, const std::string &filename)
3255  {
3256  const int SURF_TRIS_MAGIC = 16777214;
3257  std::ifstream is;
3258  is.open(filename, std::ios_base::in | std::ios::binary);
3259  if (is.is_open())
3260  {
3261  int magic = _fread3(is);
3262  if (magic != SURF_TRIS_MAGIC)
3263  {
3264  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");
3265  }
3266  std::string created_line = _freadstringnewline(is);
3267  std::string comment_line = _freadstringnewline(is);
3268  int num_verts = _freadt<int32_t>(is);
3269  int num_faces = _freadt<int32_t>(is);
3270 
3271  // Validate header fields.
3272  if (num_verts <= 0)
3273  {
3274  throw std::domain_error("Surf file '" + filename + "' has invalid num_verts: " + std::to_string(num_verts) + ".\n");
3275  }
3276  if (num_faces < 0)
3277  {
3278  throw std::domain_error("Surf file '" + filename + "' has invalid num_faces: " + std::to_string(num_faces) + ".\n");
3279  }
3280 
3281  // Safe multiplication: num_verts * 3 (x,y,z per vertex).
3282  size_t num_vert_coords = 0;
3283  if (!fs::util::safe_multiply(static_cast<size_t>(num_verts), 3, num_vert_coords))
3284  {
3285  throw std::overflow_error("Surf file '" + filename + "': num_verts * 3 overflowed.\n");
3286  }
3287  size_t num_face_indices = 0;
3288  if (!fs::util::safe_multiply(static_cast<size_t>(num_faces), 3, num_face_indices))
3289  {
3290  throw std::overflow_error("Surf file '" + filename + "': num_faces * 3 overflowed.\n");
3291  }
3292 
3293  // Cross-check against file size.
3294  size_t file_size = fs::util::get_file_size(filename);
3295  if (file_size > 0)
3296  {
3297  size_t vert_bytes = 0, face_bytes = 0;
3298  if (!fs::util::safe_multiply(num_vert_coords, sizeof(float), vert_bytes) ||
3299  !fs::util::safe_multiply(num_face_indices, sizeof(int32_t), face_bytes))
3300  {
3301  throw std::overflow_error("Surf file '" + filename + "': expected data size overflowed.\n");
3302  }
3303  // Guard against addition overflow (paranoid, since each is already <= LIBFS_MAX_ALLOC_BYTES).
3304  if (vert_bytes > std::numeric_limits<size_t>::max() - face_bytes)
3305  {
3306  throw std::overflow_error("Surf file '" + filename + "': total data size overflowed.\n");
3307  }
3308  size_t expected_total = vert_bytes + face_bytes;
3309  // Header takes some space, so file_size > raw data size for any valid file.
3310  if (file_size < expected_total)
3311  {
3312  throw std::runtime_error("Surf file '" + filename + "' is too small (" +
3313  std::to_string(file_size) + " bytes) for the data claimed in its header.\n");
3314  }
3315  }
3316 
3317  if (!fs::util::check_alloc(num_vert_coords, sizeof(float)) ||
3318  !fs::util::check_alloc(num_face_indices, sizeof(int32_t)))
3319  {
3320  throw std::runtime_error("Surf file '" + filename + "' data size exceeds maximum allowed allocation.\n");
3321  }
3322 
3323 #ifdef LIBFS_DBG_INFO
3324  std::cout << LIBFS_APPTAG << "Read surface file with " << num_verts << " vertices, " << num_faces << " faces.\n";
3325 #endif
3326  std::vector<float> vdata;
3327  vdata.reserve(num_vert_coords);
3328  for (size_t i = 0; i < num_vert_coords; i++)
3329  {
3330  vdata.push_back(_freadt<float>(is));
3331  }
3332  std::vector<int> fdata;
3333  fdata.reserve(num_face_indices);
3334  for (size_t i = 0; i < num_face_indices; i++)
3335  {
3336  fdata.push_back(_freadt<int32_t>(is));
3337  }
3338  is.close();
3339  surface->vertices = vdata;
3340  surface->faces = fdata;
3341  }
3342  else
3343  {
3344  throw std::runtime_error("Unable to open surface file '" + filename + "'.\n");
3345  }
3346  }
3347 
3360  void read_mesh(Mesh *surface, const std::string &filename)
3361  {
3362  if (fs::util::ends_with(filename, ".obj"))
3363  {
3364  fs::Mesh::from_obj(surface, filename);
3365  }
3366  else if (fs::util::ends_with(filename, ".ply"))
3367  {
3368  fs::Mesh::from_ply(surface, filename);
3369  }
3370  else if (fs::util::ends_with(filename, ".off"))
3371  {
3372  fs::Mesh::from_off(surface, filename);
3373  }
3374  else
3375  {
3376  read_surf(surface, filename);
3377  }
3378  }
3379 
3385  bool _is_bigendian()
3386  {
3387  const short int number = 0x1;
3388  const char *numPtr = reinterpret_cast<const char *>(&number);
3389  return (numPtr[0] != 1);
3390  }
3391 
3401  void read_curv(Curv *curv, std::istream *is, const std::string &source_filename = "")
3402  {
3403  const std::string msg_source_file_part = source_filename.empty() ? "" : "'" + source_filename + "' ";
3404  const int CURV_MAGIC = 16777215;
3405  int magic = _fread3(*is);
3406  if (magic != CURV_MAGIC)
3407  {
3408  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");
3409  }
3410  curv->num_vertices = _freadt<int32_t>(*is);
3411  curv->num_faces = _freadt<int32_t>(*is);
3412  curv->num_values_per_vertex = _freadt<int32_t>(*is);
3413 
3414  // Validate header fields.
3415  if (curv->num_vertices <= 0)
3416  {
3417  throw std::domain_error("Curv file " + msg_source_file_part + "has invalid num_vertices: " + std::to_string(curv->num_vertices) + ".\n");
3418  }
3419  if (curv->num_faces < 0)
3420  {
3421  throw std::domain_error("Curv file " + msg_source_file_part + "has invalid num_faces: " + std::to_string(curv->num_faces) + ".\n");
3422  }
3423 
3424 #ifdef LIBFS_DBG_INFO
3425  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";
3426 #endif
3427  if (curv->num_values_per_vertex != 1)
3428  { // 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.
3429  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");
3430  }
3431 
3432  // File-size cross-check (only when reading from a file, not a generic stream).
3433  if (!source_filename.empty())
3434  {
3435  size_t file_size = fs::util::get_file_size(source_filename);
3436  if (file_size > 0)
3437  {
3438  // Curv header: 3 (magic) + 12 (three int32) = 15 bytes.
3439  const size_t CURV_HEADER_SIZE = 15;
3440  size_t expected_data_bytes = 0;
3441  if (!fs::util::safe_multiply(static_cast<size_t>(curv->num_vertices), sizeof(float), expected_data_bytes))
3442  {
3443  throw std::overflow_error("Curv file " + msg_source_file_part + "data size computation overflowed.\n");
3444  }
3445  if (file_size < CURV_HEADER_SIZE || (file_size - CURV_HEADER_SIZE) < expected_data_bytes)
3446  {
3447  throw std::runtime_error("Curv file " + msg_source_file_part + "is too small (" +
3448  std::to_string(file_size) + " bytes) for the data claimed in its header (" +
3449  std::to_string(CURV_HEADER_SIZE + expected_data_bytes) + " bytes expected).\n");
3450  }
3451  }
3452  }
3453 
3454  std::vector<float> data;
3455  if (!fs::util::check_alloc(static_cast<size_t>(curv->num_vertices), sizeof(float)))
3456  {
3457  throw std::runtime_error("Curv file " + msg_source_file_part + "data size exceeds maximum allowed allocation.\n");
3458  }
3459  data.reserve(static_cast<size_t>(curv->num_vertices));
3460  for (size_t i = 0; i < static_cast<size_t>(curv->num_vertices); i++)
3461  {
3462  data.push_back(_freadt<float>(*is));
3463  }
3464  curv->data = data;
3465  }
3466 
3479  void read_curv(Curv *curv, const std::string &filename)
3480  {
3481  std::ifstream is(filename, std::fstream::in | std::fstream::binary);
3482  if (is.is_open())
3483  {
3484  read_curv(curv, &is, filename);
3485  is.close();
3486  }
3487  else
3488  {
3489  throw std::runtime_error("Could not open curv file '" + filename + "' for reading.\n");
3490  }
3491  }
3492 
3495  void _read_annot_colortable(Colortable *colortable, std::istream *is, int32_t num_entries)
3496  {
3497  // Validate num_entries against a reasonable cap.
3498  if (num_entries < 0 || static_cast<size_t>(num_entries) > LIBFS_MAX_COLORTABLE_ENTRIES)
3499  {
3500  throw std::domain_error("Annot colortable num_entries " + std::to_string(num_entries) +
3501  " is invalid or exceeds maximum (" + std::to_string(LIBFS_MAX_COLORTABLE_ENTRIES) + ").\n");
3502  }
3503 
3504  int32_t num_chars_orig_filename = _freadt<int32_t>(*is); // The number of characters of the file this annot was built from.
3505 
3506  // Validate and cap the original filename length.
3507  if (num_chars_orig_filename < 0 || static_cast<size_t>(num_chars_orig_filename) > LIBFS_MAX_STRING_LENGTH)
3508  {
3509  throw std::domain_error("Annot colortable original filename length " + std::to_string(num_chars_orig_filename) +
3510  " exceeds maximum (" + std::to_string(LIBFS_MAX_STRING_LENGTH) + ").\n");
3511  }
3512 
3513  // It follows the name of the file this annot was built from. This is development metadata and irrelevant afaik. We skip it.
3514  uint8_t discarded;
3515  for (int32_t i = 0; i < num_chars_orig_filename; i++)
3516  {
3517  discarded = _freadt<uint8_t>(*is);
3518  }
3519  (void)discarded; // Suppress warnings about unused variable.
3520 
3521  int32_t num_entries_duplicated = _freadt<int32_t>(*is); // Yes, once more.
3522  if (num_entries != num_entries_duplicated)
3523  {
3524  std::cerr << "Warning: the two num_entries header fields of this annotation do not match. Use with care.\n";
3525  }
3526 
3527  colortable->id.reserve(static_cast<size_t>(num_entries));
3528  colortable->name.reserve(static_cast<size_t>(num_entries));
3529  colortable->r.reserve(static_cast<size_t>(num_entries));
3530  colortable->g.reserve(static_cast<size_t>(num_entries));
3531  colortable->b.reserve(static_cast<size_t>(num_entries));
3532  colortable->a.reserve(static_cast<size_t>(num_entries));
3533  colortable->label.reserve(static_cast<size_t>(num_entries));
3534 
3535  int32_t entry_num_chars;
3536  for (int32_t i = 0; i < num_entries; i++)
3537  {
3538  colortable->id.push_back(_freadt<int32_t>(*is));
3539  entry_num_chars = _freadt<int32_t>(*is);
3540  // Pass a tighter max_length for region names (256 chars should be plenty).
3541  colortable->name.push_back(_freadfixedlengthstring(*is, entry_num_chars, true, 256));
3542  colortable->r.push_back(_freadt<int32_t>(*is));
3543  colortable->g.push_back(_freadt<int32_t>(*is));
3544  colortable->b.push_back(_freadt<int32_t>(*is));
3545  colortable->a.push_back(_freadt<int32_t>(*is));
3546  colortable->label.push_back(static_cast<uint32_t>(colortable->r[i]) + static_cast<uint32_t>(colortable->g[i]) * 256u + static_cast<uint32_t>(colortable->b[i]) * 65536u + static_cast<uint32_t>(colortable->a[i]) * 16777216u);
3547  }
3548  }
3549 
3552  size_t _vidx_2d(size_t row, size_t column, size_t row_length = 3)
3553  {
3554  return (row + 1) * row_length - row_length + column;
3555  }
3556 
3562  void read_annot(Annot *annot, std::istream *is)
3563  {
3564 
3565  int32_t num_vertices = _freadt<int32_t>(*is);
3566 
3567  // Validate num_vertices.
3568  if (num_vertices <= 0)
3569  {
3570  throw std::domain_error("Annot file has invalid num_vertices: " + std::to_string(num_vertices) + ".\n");
3571  }
3572 
3573  // Safe multiplication: num_vertices * 2 (vertex index + label per vertex).
3574  size_t num_entries = 0;
3575  if (!fs::util::safe_multiply(static_cast<size_t>(num_vertices), 2, num_entries))
3576  {
3577  throw std::overflow_error("Annot: num_vertices * 2 overflowed.\n");
3578  }
3579  if (!fs::util::check_alloc(num_entries, sizeof(int32_t)))
3580  {
3581  throw std::runtime_error("Annot vertex/label data size exceeds maximum allowed allocation.\n");
3582  }
3583 
3584  std::vector<int32_t> vertices;
3585  std::vector<int32_t> labels;
3586  vertices.reserve(num_vertices);
3587  labels.reserve(num_vertices);
3588  for (size_t i = 0; i < num_entries; i++)
3589  { // The vertices and their labels are stored directly after one another: v1,v1_label,v2,v2_label,...
3590  if (i % 2 == 0)
3591  {
3592  vertices.push_back(_freadt<int32_t>(*is));
3593  }
3594  else
3595  {
3596  labels.push_back(_freadt<int32_t>(*is));
3597  }
3598  }
3599  annot->vertex_indices = vertices;
3600  annot->vertex_labels = labels;
3601  int32_t has_colortable = _freadt<int32_t>(*is);
3602  if (has_colortable == 1)
3603  {
3604  int32_t num_colortable_entries_old_format = _freadt<int32_t>(*is);
3605  if (num_colortable_entries_old_format > 0)
3606  {
3607  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");
3608  }
3609  else
3610  {
3611  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.
3612  if (colortable_format_version == 2)
3613  {
3614  int32_t num_colortable_entries = _freadt<int32_t>(*is); // This time for real.
3615  _read_annot_colortable(&annot->colortable, is, num_colortable_entries);
3616  }
3617  else
3618  {
3619  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");
3620  }
3621  }
3622  }
3623  else
3624  {
3625  throw std::domain_error("Reading annotation without colortable not supported. Maybe invalid annotation file?\n");
3626  }
3627  }
3628 
3642  void read_annot(Annot *annot, const std::string &filename)
3643  {
3644  std::ifstream is(filename, std::fstream::in | std::fstream::binary);
3645  if (is.is_open())
3646  {
3647  read_annot(annot, &is);
3648  is.close();
3649  }
3650  else
3651  {
3652  throw std::runtime_error("Could not open annot file '" + filename + "' for reading.\n");
3653  }
3654  }
3655 
3668  std::vector<float> read_curv_data(const std::string &filename)
3669  {
3670  Curv curv;
3671  read_curv(&curv, filename);
3672  return (curv.data);
3673  }
3674 
3690  inline std::vector<float> read_desc_data(const std::string &filename)
3691  {
3692  if (fs::util::ends_with(filename, {".MGH", ".mgh"}))
3693  {
3694  fs::Mgh mgh;
3695  fs::read_mgh(&mgh, filename);
3696  assert(mgh.header.dtype == fs::MRI_FLOAT);
3697  int num_gt_1 = 0;
3698  std::vector<int> dims = {mgh.header.dim1length, mgh.header.dim2length, mgh.header.dim3length, mgh.header.dim4length};
3699  for (size_t i = 0; i < dims.size(); i++)
3700  {
3701  if (dims[i] > 1)
3702  {
3703  num_gt_1++;
3704  }
3705  }
3706  if (num_gt_1 > 1)
3707  {
3708  std::cerr << "MGH file '" << filename << "' contains more than one non-empty dimension. Returning concatinated data.\n";
3709  }
3710  return mgh.data.data_mri_float;
3711  }
3712  else if (fs::util::ends_with(filename, {".NII", ".nii", ".NII.GZ", ".nii.gz"}))
3713  {
3714  fs::Mgh mgh;
3715  fs::read_nifti(&mgh, filename);
3716  if (mgh.header.dtype != fs::MRI_FLOAT)
3717  {
3718  throw std::runtime_error("read_desc_data currently only supports NIfTI files with FLOAT32 data.\n");
3719  }
3720  int num_gt_1 = 0;
3721  std::vector<int> dims = {mgh.header.dim1length, mgh.header.dim2length, mgh.header.dim3length, mgh.header.dim4length};
3722  for (size_t i = 0; i < dims.size(); i++)
3723  {
3724  if (dims[i] > 1)
3725  {
3726  num_gt_1++;
3727  }
3728  }
3729  if (num_gt_1 > 1)
3730  {
3731  std::cerr << "NIfTI file '" << filename << "' contains more than one non-empty dimension. Returning concatenated data.\n";
3732  }
3733  return mgh.data.data_mri_float;
3734  }
3735  else
3736  {
3737  Curv curv;
3738  read_curv(&curv, filename);
3739  return (curv.data);
3740  }
3741  }
3742 
3750  template <typename T>
3751  T _swap_endian(T u)
3752  {
3753  static_assert(CHAR_BIT == 8, "CHAR_BIT != 8");
3754 
3755  unsigned char src[sizeof(T)];
3756  unsigned char dst[sizeof(T)];
3757  std::memcpy(src, &u, sizeof(T));
3758 
3759  for (size_t k = 0; k < sizeof(T); k++)
3760  {
3761  dst[k] = src[sizeof(T) - k - 1];
3762  }
3763 
3764  T result;
3765  std::memcpy(&result, dst, sizeof(T));
3766  return result;
3767  }
3768 
3773  template <typename T>
3774  T _freadt(std::istream &is)
3775  {
3776  T t;
3777  is.read(reinterpret_cast<char *>(&t), sizeof(t));
3778  if (static_cast<size_t>(is.gcount()) != sizeof(T))
3779  {
3780  if (is.gcount() == 0)
3781  {
3782  throw std::runtime_error("Unexpected end of binary stream: expected " + std::to_string(sizeof(T)) + " bytes, got EOF.\n");
3783  }
3784  throw std::runtime_error("Short read in binary stream: expected " + std::to_string(sizeof(T)) + " bytes, got " + std::to_string(is.gcount()) + ".\n");
3785  }
3786  if (!_is_bigendian())
3787  {
3788  t = _swap_endian<T>(t);
3789  }
3790  return (t);
3791  }
3792 
3797  int _fread3(std::istream &is)
3798  {
3799  uint32_t i = 0;
3800  is.read(reinterpret_cast<char *>(&i), 3);
3801  if (static_cast<size_t>(is.gcount()) != 3)
3802  {
3803  if (is.gcount() == 0)
3804  {
3805  throw std::runtime_error("Unexpected end of binary stream: expected 3 bytes, got EOF.\n");
3806  }
3807  throw std::runtime_error("Short read in binary stream: expected 3 bytes, got " + std::to_string(is.gcount()) + ".\n");
3808  }
3809  if (!_is_bigendian())
3810  {
3811  i = _swap_endian<std::uint32_t>(i);
3812  }
3813  i = ((i >> 8) & 0xffffff);
3814  return (i);
3815  }
3816 
3821  template <typename T>
3822  void _fwritet(std::ostream &os, T t)
3823  {
3824  if (!_is_bigendian())
3825  {
3826  t = _swap_endian<T>(t);
3827  }
3828  os.write(reinterpret_cast<const char *>(&t), sizeof(t));
3829  }
3830 
3831  // Write big endian 24 bit integer to a stream, extracted from the first 3 bytes of an unsigned 32 bit integer.
3832  //
3833  // THIS FUNCTION IS INTERNAL AND SHOULD NOT BE CALLED BY API CLIENTS.
3835  void _fwritei3(std::ostream &os, uint32_t i)
3836  {
3837  unsigned char b1 = (i >> 16) & 255;
3838  unsigned char b2 = (i >> 8) & 255;
3839  unsigned char b3 = i & 255;
3840 
3841  os.write(reinterpret_cast<const char *>(&b1), sizeof(b1));
3842  os.write(reinterpret_cast<const char *>(&b2), sizeof(b2));
3843  os.write(reinterpret_cast<const char *>(&b3), sizeof(b3));
3844  }
3845 
3851  void _fwritefixedlengthstring(std::ostream &os, const std::string &str, size_t len)
3852  {
3853  std::string buf(len, '\0');
3854  size_t copy_len = str.size() < len ? str.size() : len;
3855  std::memcpy(&buf[0], str.data(), copy_len);
3856  os.write(buf.data(), static_cast<std::streamsize>(len));
3857  }
3858 
3863  std::string _freadstringnewline(std::istream &is)
3864  {
3865  std::string s;
3866  std::getline(is, s, '\n');
3867  return s;
3868  }
3869 
3874  std::string _freadfixedlengthstring(std::istream &is, size_t length, bool strip_last_char = true, size_t max_length = LIBFS_MAX_STRING_LENGTH)
3875  {
3876  if (length == 0)
3877  {
3878  throw std::domain_error("Fixed-length string read with zero length.\n");
3879  }
3880  if (length > max_length)
3881  {
3882  throw std::domain_error("Fixed-length string length " + std::to_string(length) + " exceeds maximum " + std::to_string(max_length) + ".\n");
3883  }
3884  std::string str;
3885  str.resize(length);
3886  is.read(&str[0], length);
3887  if (static_cast<size_t>(is.gcount()) != length)
3888  {
3889  if (is.gcount() == 0)
3890  {
3891  throw std::runtime_error("Unexpected end of binary stream while reading fixed-length string: expected " + std::to_string(length) + " bytes, got EOF.\n");
3892  }
3893  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");
3894  }
3895  if (strip_last_char)
3896  {
3897  str = str.substr(0, length - 1);
3898  }
3899  return str;
3900  }
3901 
3907  void write_annot(const Annot &annot, std::ostream &os)
3908  {
3909  int32_t num_vertices = static_cast<int32_t>(annot.num_vertices());
3910  _fwritet<int32_t>(os, num_vertices);
3911 
3912  // Interleaved vertex indices and labels.
3913  for (size_t i = 0; i < static_cast<size_t>(num_vertices); i++)
3914  {
3915  _fwritet<int32_t>(os, annot.vertex_indices[i]);
3916  _fwritet<int32_t>(os, annot.vertex_labels[i]);
3917  }
3918 
3919  // Colortable presence flag + version tag (version 2, no old-format entries).
3920  _fwritet<int32_t>(os, 1); // has_colortable
3921  _fwritet<int32_t>(os, -2); // version tag: negative means new format, abs value is version
3922 
3923  int32_t num_entries = static_cast<int32_t>(annot.colortable.num_entries());
3924  _fwritet<int32_t>(os, num_entries);
3925 
3926  // Original filename (not meaningful when writing, write "unknown" as placeholder).
3927  std::string orig_filename = "unknown";
3928  int32_t orig_filename_len = static_cast<int32_t>(orig_filename.size());
3929  _fwritet<int32_t>(os, orig_filename_len);
3930  _fwritefixedlengthstring(os, orig_filename, static_cast<size_t>(orig_filename_len));
3931 
3932  // Duplicate num_entries (yes, the format stores it twice).
3933  _fwritet<int32_t>(os, num_entries);
3934 
3935  for (int32_t i = 0; i < num_entries; i++)
3936  {
3937  _fwritet<int32_t>(os, annot.colortable.id[i]);
3938  // Name length: strlen + 1 for the trailing null byte, matching _freadfixedlengthstring(strip_last_char=true).
3939  int32_t name_len = static_cast<int32_t>(annot.colortable.name[i].size()) + 1;
3940  _fwritet<int32_t>(os, name_len);
3941  _fwritefixedlengthstring(os, annot.colortable.name[i] + '\0', static_cast<size_t>(name_len));
3942  _fwritet<int32_t>(os, annot.colortable.r[i]);
3943  _fwritet<int32_t>(os, annot.colortable.g[i]);
3944  _fwritet<int32_t>(os, annot.colortable.b[i]);
3945  _fwritet<int32_t>(os, annot.colortable.a[i]);
3946  }
3947  }
3948 
3963  void write_annot(const Annot &annot, const std::string &filename)
3964  {
3965  std::ofstream ofs;
3966  ofs.open(filename, std::ofstream::out | std::ofstream::binary);
3967  if (ofs.is_open())
3968  {
3969  write_annot(annot, ofs);
3970  ofs.close();
3971  }
3972  else
3973  {
3974  throw std::runtime_error("Unable to open annot file '" + filename + "' for writing.\n");
3975  }
3976  }
3977 
3983  void write_curv(std::ostream &os, std::vector<float> curv_data, int32_t num_faces = 100000)
3984  {
3985  const uint32_t CURV_MAGIC = 16777215;
3986  _fwritei3(os, CURV_MAGIC);
3987  _fwritet<int32_t>(os, int(curv_data.size()));
3988  _fwritet<int32_t>(os, num_faces);
3989  _fwritet<int32_t>(os, 1); // Number of values per vertex.
3990  for (size_t i = 0; i < curv_data.size(); i++)
3991  {
3992  _fwritet<float>(os, curv_data[i]);
3993  }
3994  }
3995 
4010  void write_curv(const std::string &filename, std::vector<float> curv_data, const int32_t num_faces = 100000)
4011  {
4012  std::ofstream ofs;
4013  ofs.open(filename, std::ofstream::out | std::ofstream::binary);
4014  if (ofs.is_open())
4015  {
4016  write_curv(ofs, curv_data, num_faces);
4017  ofs.close();
4018  }
4019  else
4020  {
4021  throw std::runtime_error("Unable to open curvature file '" + filename + "' for writing.\n");
4022  }
4023  }
4024 
4030  void write_mgh(const Mgh &mgh, std::ostream &os)
4031  {
4032  _fwritet<int32_t>(os, 1); // MGH file format version
4033  _fwritet<int32_t>(os, mgh.header.dim1length);
4034  _fwritet<int32_t>(os, mgh.header.dim2length);
4035  _fwritet<int32_t>(os, mgh.header.dim3length);
4036  _fwritet<int32_t>(os, mgh.header.dim4length);
4037 
4038  _fwritet<int32_t>(os, mgh.header.dtype);
4039  _fwritet<int32_t>(os, mgh.header.dof);
4040 
4041  size_t unused_header_space_size_left = 256; // in bytes
4042  _fwritet<int16_t>(os, mgh.header.ras_good_flag);
4043  unused_header_space_size_left -= 2; // for RAS flag
4044 
4045  // Write RAS part of of header if flag is 1.
4046  if (mgh.header.ras_good_flag == 1)
4047  {
4048  if (mgh.header.Mdc.size() < 9 || mgh.header.Pxyz_c.size() < 3)
4049  {
4050  throw std::logic_error("MGH header ras_good_flag set but Mdc and/or Pxyz_c vectors are undersized.\n");
4051  }
4052  _fwritet<float>(os, mgh.header.xsize);
4053  _fwritet<float>(os, mgh.header.ysize);
4054  _fwritet<float>(os, mgh.header.zsize);
4055 
4056  for (int i = 0; i < 9; i++)
4057  {
4058  _fwritet<float>(os, mgh.header.Mdc[i]);
4059  }
4060  for (int i = 0; i < 3; i++)
4061  {
4062  _fwritet<float>(os, mgh.header.Pxyz_c[i]);
4063  }
4064 
4065  unused_header_space_size_left -= 60;
4066  }
4067 
4068  for (size_t i = 0; i < unused_header_space_size_left; i++)
4069  { // Fill rest of header space.
4070  _fwritet<uint8_t>(os, 0);
4071  }
4072 
4073  // Write data
4074  size_t num_values = mgh.header.num_values();
4075  if (mgh.header.dtype == MRI_INT)
4076  {
4077  if (mgh.data.data_mri_int.size() != num_values)
4078  {
4079  throw std::logic_error("Detected mismatch of MRI_INT data size and MGH header dim length values.\n");
4080  }
4081  for (size_t i = 0; i < num_values; i++)
4082  {
4083  _fwritet<int32_t>(os, mgh.data.data_mri_int[i]);
4084  }
4085  }
4086  else if (mgh.header.dtype == MRI_FLOAT)
4087  {
4088  if (mgh.data.data_mri_float.size() != num_values)
4089  {
4090  throw std::logic_error("Detected mismatch of MRI_FLOAT data size and MGH header dim length values.\n");
4091  }
4092  for (size_t i = 0; i < num_values; i++)
4093  {
4094  _fwritet<float>(os, mgh.data.data_mri_float[i]);
4095  }
4096  }
4097  else if (mgh.header.dtype == MRI_UCHAR)
4098  {
4099  if (mgh.data.data_mri_uchar.size() != num_values)
4100  {
4101  throw std::logic_error("Detected mismatch of MRI_UCHAR data size and MGH header dim length values.\n");
4102  }
4103  for (size_t i = 0; i < num_values; i++)
4104  {
4105  _fwritet<uint8_t>(os, mgh.data.data_mri_uchar[i]);
4106  }
4107  }
4108  else if (mgh.header.dtype == MRI_SHORT)
4109  {
4110  if (mgh.data.data_mri_short.size() != num_values)
4111  {
4112  throw std::logic_error("Detected mismatch of MRI_SHORT data size and MGH header dim length values.\n");
4113  }
4114  for (size_t i = 0; i < num_values; i++)
4115  {
4116  _fwritet<short>(os, mgh.data.data_mri_short[i]);
4117  }
4118  }
4119  else
4120  {
4121  throw std::domain_error("Unsupported MRI data type " + std::to_string(mgh.header.dtype) + ", cannot write MGH data.\n");
4122  }
4123  }
4124 
4140  void write_mgh(const Mgh &mgh, const std::string &filename)
4141  {
4142  std::ofstream ofs;
4143  ofs.open(filename, std::ofstream::out | std::ofstream::binary);
4144  if (ofs.is_open())
4145  {
4146  write_mgh(mgh, ofs);
4147  ofs.close();
4148  }
4149  else
4150  {
4151  throw std::runtime_error("Unable to open MGH file '" + filename + "' for writing.\n");
4152  }
4153  }
4154 
4155 #ifdef LIBFS_HAS_ZLIB
4156 
4170  inline void read_mgz(Mgh *mgh, const std::string &filename)
4171  {
4172  gzFile gz = gzopen(filename.c_str(), "rb");
4173  if (!gz)
4174  {
4175  int errnum = 0;
4176  const char *errstr = gzerror(gz, &errnum);
4177  throw std::runtime_error("Could not open MGZ file '" + filename + "' for reading: " +
4178  (errstr ? std::string(errstr) : "unknown error") + "\n");
4179  }
4180  std::vector<char> buf;
4181  char chunk[131072];
4182  int n;
4183  while ((n = gzread(gz, chunk, sizeof(chunk))) > 0)
4184  {
4185  buf.insert(buf.end(), chunk, chunk + n);
4186  }
4187  if (n < 0)
4188  {
4189  int errnum = 0;
4190  const char *errstr = gzerror(gz, &errnum);
4191  gzclose(gz);
4192  throw std::runtime_error("Error decompressing MGZ file '" + filename + "': " +
4193  (errstr ? std::string(errstr) : "unknown error") + "\n");
4194  }
4195  gzclose(gz);
4196  std::istringstream iss(std::string(buf.data(), buf.size()));
4197  read_mgh(mgh, &iss);
4198  }
4199 
4215  inline void write_mgz(const Mgh &mgh, const std::string &filename)
4216  {
4217  std::ostringstream oss;
4218  write_mgh(mgh, oss);
4219  std::string data = oss.str();
4220 
4221  gzFile gz = gzopen(filename.c_str(), "wb");
4222  if (!gz)
4223  {
4224  int errnum = 0;
4225  const char *errstr = gzerror(gz, &errnum);
4226  throw std::runtime_error("Could not open MGZ file '" + filename + "' for writing: " +
4227  (errstr ? std::string(errstr) : "unknown error") + "\n");
4228  }
4229  z_size_t total_written = 0;
4230  while (total_written < data.size())
4231  {
4232  int written = gzwrite(gz, data.data() + total_written, static_cast<unsigned int>(data.size() - total_written));
4233  if (written <= 0)
4234  {
4235  int errnum = 0;
4236  const char *errstr = gzerror(gz, &errnum);
4237  gzclose(gz);
4238  throw std::runtime_error("Error writing MGZ file '" + filename + "': " +
4239  (errstr ? std::string(errstr) : "unknown error") + "\n");
4240  }
4241  total_written += static_cast<z_size_t>(written);
4242  }
4243  gzclose(gz);
4244  }
4245 
4246 #endif // LIBFS_HAS_ZLIB
4247 
4248  // ========================================================================
4249  // NIfTI-1 Support
4250  // ========================================================================
4251 
4262 
4264  const int16_t NIFTI_DT_NONE = 0;
4265 
4268  const int16_t NIFTI_DT_BINARY = 1;
4269 
4272  const int16_t NIFTI_DT_UINT8 = 2;
4273 
4277  const int16_t NIFTI_DT_INT16 = 4;
4278 
4282  const int16_t NIFTI_DT_INT32 = 8;
4283 
4287  const int16_t NIFTI_DT_FLOAT32 = 16;
4288 
4292  const int16_t NIFTI_DT_COMPLEX64 = 32;
4293 
4297  const int16_t NIFTI_DT_FLOAT64 = 64;
4298 
4302  const int16_t NIFTI_DT_RGB24 = 128;
4303 
4307  const int16_t NIFTI_DT_INT8 = 256;
4308 
4311  const int16_t NIFTI_DT_UINT16 = 512;
4312 
4315  const int16_t NIFTI_DT_UINT32 = 768;
4316 
4320  const int16_t NIFTI_DT_INT64 = 1024;
4321 
4324  const int16_t NIFTI_DT_UINT64 = 1280;
4325 
4329  const int16_t NIFTI_DT_FLOAT128 = 1536;
4330 
4333  const int16_t NIFTI_DT_COMPLEX128 = 1792;
4334 
4339  const int16_t NIFTI_DT_COMPLEX256 = 2048;
4340 
4342 
4344 #pragma pack(push, 1)
4346  {
4347  int32_t sizeof_hdr;
4348  char data_type[10];
4349  char db_name[18];
4350  int32_t extents;
4351  int16_t session_error;
4352  char regular;
4353  char dim_info;
4354  int16_t dim[8];
4355  float intent_p1;
4356  float intent_p2;
4357  float intent_p3;
4358  int16_t intent_code;
4359  int16_t datatype;
4360  int16_t bitpix;
4361  int16_t slice_start;
4362  float pixdim[8];
4363  float vox_offset;
4364  float scl_slope;
4365  float scl_inter;
4366  int16_t slice_end;
4367  char slice_code;
4368  char xyzt_units;
4369  float cal_max;
4370  float cal_min;
4372  float toffset;
4373  int32_t glmax;
4374  int32_t glmin;
4375  char descrip[80];
4376  char aux_file[24];
4377  int16_t qform_code;
4378  int16_t sform_code;
4379  float quatern_b;
4380  float quatern_c;
4381  float quatern_d;
4382  float qoffset_x;
4383  float qoffset_y;
4384  float qoffset_z;
4385  float srow_x[4];
4386  float srow_y[4];
4387  float srow_z[4];
4388  char intent_name[16];
4389  char magic[4];
4390  };
4391 #pragma pack(pop)
4392 
4393  // --- Internal NIfTI helpers ---
4394 
4398  inline int _nifti_dtype_to_mri(int16_t nifti_dtype)
4399  {
4400  switch (nifti_dtype)
4401  {
4402  case NIFTI_DT_UINT8: return MRI_UCHAR;
4403  case NIFTI_DT_INT16: return MRI_SHORT;
4404  case NIFTI_DT_INT32: return MRI_INT;
4405  case NIFTI_DT_FLOAT32: return MRI_FLOAT;
4406  default:
4407  throw std::runtime_error("Unsupported NIfTI data type " + std::to_string(nifti_dtype) +
4408  ". Supported types: UINT8 (2), INT16 (4), INT32 (8), FLOAT32 (16).\n");
4409  }
4410  }
4411 
4415  inline int16_t _mri_dtype_to_nifti(int32_t mri_dtype)
4416  {
4417  switch (mri_dtype)
4418  {
4419  case MRI_UCHAR: return NIFTI_DT_UINT8;
4420  case MRI_SHORT: return NIFTI_DT_INT16;
4421  case MRI_INT: return NIFTI_DT_INT32;
4422  case MRI_FLOAT: return NIFTI_DT_FLOAT32;
4423  default:
4424  throw std::runtime_error("Unsupported MGH data type " + std::to_string(mri_dtype) +
4425  " for NIfTI output.\n");
4426  }
4427  }
4428 
4435  inline Nifti1Header _read_nifti1_header(std::istream &is, bool &file_is_bigendian)
4436  {
4437  Nifti1Header hdr;
4438  is.read(reinterpret_cast<char *>(&hdr), sizeof(Nifti1Header));
4439  if (static_cast<size_t>(is.gcount()) != sizeof(Nifti1Header))
4440  {
4441  throw std::runtime_error("NIfTI file too small for header: expected " +
4442  std::to_string(sizeof(Nifti1Header)) + " bytes.\n");
4443  }
4444 
4445  // Detect endianness: sizeof_hdr must be 348.
4446  if (hdr.sizeof_hdr != 348)
4447  {
4448  int32_t swapped = _swap_endian(hdr.sizeof_hdr);
4449  if (swapped == 348)
4450  {
4451  file_is_bigendian = true;
4452  }
4453  else
4454  {
4455  throw std::runtime_error("Invalid NIfTI file: sizeof_hdr = " +
4456  std::to_string(hdr.sizeof_hdr) + " (expected 348).\n");
4457  }
4458  }
4459  else
4460  {
4461  file_is_bigendian = false;
4462  }
4463 
4464  // If file endianness differs from host, byte-swap the numeric fields.
4465  bool need_swap = (file_is_bigendian != _is_bigendian());
4466  if (need_swap)
4467  {
4468  hdr.sizeof_hdr = 348; // already correct, keep it
4469  hdr.extents = _swap_endian(hdr.extents);
4470  hdr.session_error = _swap_endian(hdr.session_error);
4471  // dim_info, regular are char — no swap
4472  for (int i = 0; i < 8; i++) hdr.dim[i] = _swap_endian(hdr.dim[i]);
4473  hdr.intent_p1 = _swap_endian(hdr.intent_p1);
4474  hdr.intent_p2 = _swap_endian(hdr.intent_p2);
4475  hdr.intent_p3 = _swap_endian(hdr.intent_p3);
4476  hdr.intent_code = _swap_endian(hdr.intent_code);
4477  hdr.datatype = _swap_endian(hdr.datatype);
4478  hdr.bitpix = _swap_endian(hdr.bitpix);
4479  hdr.slice_start = _swap_endian(hdr.slice_start);
4480  for (int i = 0; i < 8; i++) hdr.pixdim[i] = _swap_endian(hdr.pixdim[i]);
4481  hdr.vox_offset = _swap_endian(hdr.vox_offset);
4482  hdr.scl_slope = _swap_endian(hdr.scl_slope);
4483  hdr.scl_inter = _swap_endian(hdr.scl_inter);
4484  hdr.slice_end = _swap_endian(hdr.slice_end);
4485  // slice_code, xyzt_units are char — no swap
4486  hdr.cal_max = _swap_endian(hdr.cal_max);
4487  hdr.cal_min = _swap_endian(hdr.cal_min);
4488  hdr.slice_duration = _swap_endian(hdr.slice_duration);
4489  hdr.toffset = _swap_endian(hdr.toffset);
4490  hdr.glmax = _swap_endian(hdr.glmax);
4491  hdr.glmin = _swap_endian(hdr.glmin);
4492  hdr.qform_code = _swap_endian(hdr.qform_code);
4493  hdr.sform_code = _swap_endian(hdr.sform_code);
4494  hdr.quatern_b = _swap_endian(hdr.quatern_b);
4495  hdr.quatern_c = _swap_endian(hdr.quatern_c);
4496  hdr.quatern_d = _swap_endian(hdr.quatern_d);
4497  hdr.qoffset_x = _swap_endian(hdr.qoffset_x);
4498  hdr.qoffset_y = _swap_endian(hdr.qoffset_y);
4499  hdr.qoffset_z = _swap_endian(hdr.qoffset_z);
4500  for (int i = 0; i < 4; i++) hdr.srow_x[i] = _swap_endian(hdr.srow_x[i]);
4501  for (int i = 0; i < 4; i++) hdr.srow_y[i] = _swap_endian(hdr.srow_y[i]);
4502  for (int i = 0; i < 4; i++) hdr.srow_z[i] = _swap_endian(hdr.srow_z[i]);
4503  }
4504 
4505  // Validate magic.
4506  if (std::memcmp(hdr.magic, "n+1\0", 4) != 0 &&
4507  std::memcmp(hdr.magic, "ni1\0", 4) != 0)
4508  {
4509  // The magic may also need swapping.
4510  throw std::runtime_error("NIfTI file has invalid magic string. "
4511  "Only single-file .nii (n+1) is supported.\n");
4512  }
4513 
4514  return hdr;
4515  }
4516 
4519  template <typename T>
4520  inline T _nifti_read_data_element(std::istream &is, bool file_is_bigendian)
4521  {
4522  T val;
4523  is.read(reinterpret_cast<char *>(&val), sizeof(T));
4524  if (static_cast<size_t>(is.gcount()) != sizeof(T))
4525  {
4526  throw std::runtime_error("Unexpected end of NIfTI data stream.\n");
4527  }
4528  if (file_is_bigendian != _is_bigendian())
4529  {
4530  val = _swap_endian(val);
4531  }
4532  return val;
4533  }
4534 
4537  template <typename T>
4538  inline void _nifti_write_data_element(std::ostream &os, T val, bool file_is_bigendian)
4539  {
4540  if (file_is_bigendian != _is_bigendian())
4541  {
4542  val = _swap_endian(val);
4543  }
4544  os.write(reinterpret_cast<const char *>(&val), sizeof(T));
4545  }
4546 
4550  inline void _nifti_extract_ras(const Nifti1Header &hdr, MghHeader *mgh_header)
4551  {
4552  if (hdr.sform_code > 0)
4553  {
4554  // Use affine (sform) transform.
4555  mgh_header->ras_good_flag = 1;
4556  mgh_header->xsize = hdr.pixdim[1];
4557  mgh_header->ysize = hdr.pixdim[2];
4558  mgh_header->zsize = hdr.pixdim[3];
4559  mgh_header->Mdc.clear();
4560  mgh_header->Pxyz_c.clear();
4561  // Mdc: 3×3 rotation/scale part of srow (column-major to row-major, but
4562  // MGH stores 9 floats in row-major order: [r11,r12,r13, r21,r22,r23, r31,r32,r33]).
4563  // srow_x = [r11, r12, r13, tx], srow_y = [r21, r22, r23, ty], srow_z = [r31, r32, r33, tz].
4564  mgh_header->Mdc.push_back(hdr.srow_x[0]); mgh_header->Mdc.push_back(hdr.srow_x[1]); mgh_header->Mdc.push_back(hdr.srow_x[2]);
4565  mgh_header->Mdc.push_back(hdr.srow_y[0]); mgh_header->Mdc.push_back(hdr.srow_y[1]); mgh_header->Mdc.push_back(hdr.srow_y[2]);
4566  mgh_header->Mdc.push_back(hdr.srow_z[0]); mgh_header->Mdc.push_back(hdr.srow_z[1]); mgh_header->Mdc.push_back(hdr.srow_z[2]);
4567  mgh_header->Pxyz_c.push_back(hdr.srow_x[3]);
4568  mgh_header->Pxyz_c.push_back(hdr.srow_y[3]);
4569  mgh_header->Pxyz_c.push_back(hdr.srow_z[3]);
4570  }
4571  else if (hdr.qform_code > 0)
4572  {
4573  // Compute rotation from quaternion and store as affine.
4574  float b = hdr.quatern_b;
4575  float c = hdr.quatern_c;
4576  float d = hdr.quatern_d;
4577  float a = std::sqrt(std::max(0.0f, 1.0f - (b * b + c * c + d * d)));
4578  float qfac = (hdr.pixdim[0] < 0.0f) ? -1.0f : 1.0f;
4579 
4580  mgh_header->ras_good_flag = 1;
4581  mgh_header->xsize = hdr.pixdim[1];
4582  mgh_header->ysize = hdr.pixdim[2];
4583  mgh_header->zsize = hdr.pixdim[3];
4584  mgh_header->Mdc.clear();
4585  mgh_header->Pxyz_c.clear();
4586 
4587  // Rotation matrix from unit quaternion.
4588  float R11 = a * a + b * b - c * c - d * d;
4589  float R12 = 2.0f * (b * c - a * d);
4590  float R13 = 2.0f * (b * d + a * c);
4591  float R21 = 2.0f * (b * c + a * d);
4592  float R22 = a * a + c * c - b * b - d * d;
4593  float R23 = 2.0f * (c * d - a * b);
4594  float R31 = 2.0f * (b * d - a * c);
4595  float R32 = 2.0f * (c * d + a * b);
4596  float R33 = a * a + d * d - b * b - c * c;
4597 
4598  // Apply pixdim scaling and qfac.
4599  float sx = hdr.pixdim[1];
4600  float sy = hdr.pixdim[2];
4601  float sz = hdr.pixdim[3] * qfac;
4602 
4603  mgh_header->Mdc.push_back(R11 * sx); mgh_header->Mdc.push_back(R12 * sy); mgh_header->Mdc.push_back(R13 * sz);
4604  mgh_header->Mdc.push_back(R21 * sx); mgh_header->Mdc.push_back(R22 * sy); mgh_header->Mdc.push_back(R23 * sz);
4605  mgh_header->Mdc.push_back(R31 * sx); mgh_header->Mdc.push_back(R32 * sy); mgh_header->Mdc.push_back(R33 * sz);
4606 
4607  mgh_header->Pxyz_c.push_back(hdr.qoffset_x);
4608  mgh_header->Pxyz_c.push_back(hdr.qoffset_y);
4609  mgh_header->Pxyz_c.push_back(hdr.qoffset_z);
4610  }
4611  else
4612  {
4613  // No valid spatial transform — just store voxel sizes.
4614  mgh_header->ras_good_flag = 0;
4615  mgh_header->xsize = hdr.pixdim[1];
4616  mgh_header->ysize = hdr.pixdim[2];
4617  mgh_header->zsize = hdr.pixdim[3];
4618  }
4619  }
4620 
4621  // --- Public NIfTI read API ---
4622 
4628  inline void read_nifti(Mgh *mgh, std::istream *is, bool force_standard)
4629  {
4630  // 1. Determine stream size (for FS hack recovery and validation).
4631  std::streampos start_pos = is->tellg();
4632  is->seekg(0, std::ios::end);
4633  std::streamsize total_file_size = is->tellg();
4634  is->seekg(start_pos, std::ios::beg);
4635 
4636  // 2. Read and validate header.
4637  bool file_is_bigendian = false;
4638  Nifti1Header hdr = _read_nifti1_header(*is, file_is_bigendian);
4639 
4640  // 3. Detect FreeSurfer hack.
4641  int64_t true_dim1 = hdr.dim[1];
4642  bool hack_detected = false;
4643 
4644  // dim[1] is int16_t; values > 32767 wrap to negative via signed overflow.
4645  if (hdr.dim[1] < 0 && hdr.dim[2] == 1 && hdr.dim[3] == 1)
4646  {
4647  int bytes_per_element = hdr.bitpix / 8;
4648  // dim[4]: NIfTI convention says dim[i] for i>dim[0] should be 1,
4649  // but FreeSurfer files may set it to 0. Treat 0 and 1 both as 1 frame.
4650  int64_t frames = (hdr.dim[4] > 1) ? static_cast<int64_t>(hdr.dim[4]) : 1;
4651 
4652  int64_t payload_bytes = total_file_size - static_cast<int64_t>(hdr.vox_offset);
4653  int64_t computed_x = payload_bytes / (bytes_per_element * frames);
4654 
4655  // False-positive mitigation: only accept if the recovered vertex count
4656  // is in a plausible range for a FreeSurfer surface mesh (1K – 5M vertices).
4657  if (computed_x >= 1000 && computed_x <= 5000000)
4658  {
4659  hack_detected = true;
4660  true_dim1 = computed_x;
4661  }
4662  }
4663 
4664  // If the caller requested strict conformance, reject the hack.
4665  if (force_standard && hack_detected)
4666  {
4667  throw std::runtime_error(
4668  "NIfTI file does not conform to the NIfTI-1 standard: "
4669  "dim[1] overflow detected (likely FreeSurfer hack). "
4670  "Re-run with force_standard=false to recover surface data.\n");
4671  }
4672 
4673  // 4. Map dimensions (treat dim[i] <= 0 as 1).
4674  int32_t dim2 = (hdr.dim[2] > 0) ? hdr.dim[2] : 1;
4675  int32_t dim3 = (hdr.dim[3] > 0) ? hdr.dim[3] : 1;
4676  int32_t dim4 = (hdr.dim[4] > 0) ? hdr.dim[4] : 1;
4677  int bytes_per_element = hdr.bitpix / 8;
4678 
4679  // 5. Overflow-safe size validation.
4680  uint64_t total_elements = static_cast<uint64_t>(true_dim1) *
4681  static_cast<uint64_t>(dim2) *
4682  static_cast<uint64_t>(dim3) *
4683  static_cast<uint64_t>(dim4);
4684  uint64_t expected_payload = total_elements * static_cast<uint64_t>(bytes_per_element);
4685 
4686  if (!fs::util::check_alloc(static_cast<size_t>(total_elements), static_cast<size_t>(bytes_per_element)))
4687  {
4688  throw std::runtime_error("NIfTI dimensions exceed maximum allowed allocation (" +
4689  std::to_string(LIBFS_MAX_ALLOC_BYTES) + " bytes).\n");
4690  }
4691 
4692  uint64_t available_bytes = static_cast<uint64_t>(total_file_size) - static_cast<uint64_t>(hdr.vox_offset);
4693  if (expected_payload > available_bytes)
4694  {
4695  throw std::runtime_error("Corrupted NIfTI file: dimensions require " +
4696  std::to_string(expected_payload) + " bytes but only " +
4697  std::to_string(available_bytes) + " available.\n");
4698  }
4699 
4700  if (hdr.vox_offset < 348 || static_cast<uint64_t>(hdr.vox_offset) >= static_cast<uint64_t>(total_file_size))
4701  {
4702  throw std::runtime_error("Corrupted NIfTI file: invalid vox_offset " +
4703  std::to_string(hdr.vox_offset) + ".\n");
4704  }
4705 
4706  // 6. Map data type and prepare MGH header.
4707  int mri_dtype = _nifti_dtype_to_mri(hdr.datatype);
4708  mgh->header.dim1length = static_cast<int32_t>(true_dim1);
4709  mgh->header.dim2length = dim2;
4710  mgh->header.dim3length = dim3;
4711  mgh->header.dim4length = dim4;
4712  mgh->header.dtype = mri_dtype;
4713  mgh->header.dof = 0;
4714 
4715  // Extract spatial metadata.
4716  _nifti_extract_ras(hdr, &mgh->header);
4717 
4718  // 7. Skip any extensions and seek to voxel data.
4719  is->seekg(start_pos + std::streamoff(static_cast<int64_t>(hdr.vox_offset)), std::ios::beg);
4720 
4721  // 8. Read data and apply scaling.
4722  float slope = (hdr.scl_slope != 0.0f) ? hdr.scl_slope : 1.0f;
4723  float inter = hdr.scl_inter;
4724  size_t num_voxels = static_cast<size_t>(total_elements);
4725  // Suppress unused variable warning in builds without LIBFS_DBG_INFO
4726  (void)num_voxels;
4727 
4728 #ifdef LIBFS_DBG_INFO
4729  std::cout << LIBFS_APPTAG << "Reading NIfTI file: " << true_dim1 << "x" << dim2
4730  << "x" << dim3 << "x" << dim4 << " (" << num_voxels << " voxels), dtype="
4731  << hdr.datatype << (hack_detected ? " [FS hack]" : "") << "\n";
4732 #endif
4733 
4734  if (mri_dtype == MRI_INT)
4735  {
4736  mgh->data.data_mri_int.reserve(num_voxels);
4737  for (size_t i = 0; i < num_voxels; i++)
4738  {
4739  int32_t raw = _nifti_read_data_element<int32_t>(*is, file_is_bigendian);
4740  mgh->data.data_mri_int.push_back(static_cast<int32_t>(std::round(raw * slope + inter)));
4741  }
4742  }
4743  else if (mri_dtype == MRI_FLOAT)
4744  {
4745  mgh->data.data_mri_float.reserve(num_voxels);
4746  for (size_t i = 0; i < num_voxels; i++)
4747  {
4748  float raw = _nifti_read_data_element<float>(*is, file_is_bigendian);
4749  mgh->data.data_mri_float.push_back(raw * slope + inter);
4750  }
4751  }
4752  else if (mri_dtype == MRI_UCHAR)
4753  {
4754  mgh->data.data_mri_uchar.reserve(num_voxels);
4755  for (size_t i = 0; i < num_voxels; i++)
4756  {
4757  uint8_t raw = _nifti_read_data_element<uint8_t>(*is, file_is_bigendian);
4758  mgh->data.data_mri_uchar.push_back(static_cast<uint8_t>(std::max(0.0f, std::min(255.0f, std::round(raw * slope + inter)))));
4759  }
4760  }
4761  else if (mri_dtype == MRI_SHORT)
4762  {
4763  mgh->data.data_mri_short.reserve(num_voxels);
4764  for (size_t i = 0; i < num_voxels; i++)
4765  {
4766  int16_t raw = _nifti_read_data_element<int16_t>(*is, file_is_bigendian);
4767  mgh->data.data_mri_short.push_back(static_cast<short>(std::round(raw * slope + inter)));
4768  }
4769  }
4770  }
4771 
4778  inline void read_nifti(Mgh *mgh, const std::string &filename, bool force_standard)
4779  {
4780  if (fs::util::ends_with(filename, ".nii.gz") || fs::util::ends_with(filename, ".NII.GZ"))
4781  {
4782 #ifdef LIBFS_HAS_ZLIB
4783  read_nifti_gz(mgh, filename, force_standard);
4784  return;
4785 #else
4786  throw std::runtime_error("Cannot read .nii.gz file '" + filename +
4787  "': zlib support not enabled. "
4788  "Link with -lz or decompress the file first.\n");
4789 #endif
4790  }
4791 
4792  std::ifstream ifs(filename, std::ios::binary);
4793  if (!ifs.is_open())
4794  {
4795  throw std::runtime_error("Could not open NIfTI file '" + filename + "' for reading.\n");
4796  }
4797  read_nifti(mgh, &ifs, force_standard);
4798  ifs.close();
4799  }
4800 
4801  // --- NIfTI write API ---
4802 
4809  inline void write_nifti(const Mgh &mgh, std::ostream &os)
4810  {
4811  // Validate dimensions: NIfTI-1 uses int16_t for dim[].
4812  if (mgh.header.dim1length > 32767 || mgh.header.dim2length > 32767 ||
4813  mgh.header.dim3length > 32767 || mgh.header.dim4length > 32767)
4814  {
4815  throw std::runtime_error("MGH dimensions exceed NIfTI-1 int16 limit (32767). "
4816  "Cannot write as NIfTI.\n");
4817  }
4818 
4819  bool file_is_bigendian = true; // NIfTI standard is big-endian on disk.
4820 
4821  // Build header (all zeroed first).
4822  Nifti1Header hdr;
4823  std::memset(&hdr, 0, sizeof(hdr));
4824  hdr.sizeof_hdr = 348;
4825  hdr.dim[0] = 4; // always 4D for our purposes
4826  hdr.dim[1] = static_cast<int16_t>(mgh.header.dim1length);
4827  hdr.dim[2] = static_cast<int16_t>(mgh.header.dim2length);
4828  hdr.dim[3] = static_cast<int16_t>(mgh.header.dim3length);
4829  hdr.dim[4] = static_cast<int16_t>(mgh.header.dim4length);
4830  hdr.dim[5] = 1;
4831  hdr.dim[6] = 1;
4832  hdr.dim[7] = 1;
4833 
4834  hdr.datatype = _mri_dtype_to_nifti(mgh.header.dtype);
4835  hdr.bitpix = 0;
4836  switch (mgh.header.dtype)
4837  {
4838  case MRI_UCHAR: hdr.bitpix = 8; break;
4839  case MRI_SHORT: hdr.bitpix = 16; break;
4840  case MRI_INT: hdr.bitpix = 32; break;
4841  case MRI_FLOAT: hdr.bitpix = 32; break;
4842  }
4843 
4844  // Voxel sizes and spatial transform.
4845  hdr.pixdim[0] = 1.0f;
4846  hdr.pixdim[1] = mgh.header.xsize > 0.0f ? mgh.header.xsize : 1.0f;
4847  hdr.pixdim[2] = mgh.header.ysize > 0.0f ? mgh.header.ysize : 1.0f;
4848  hdr.pixdim[3] = mgh.header.zsize > 0.0f ? mgh.header.zsize : 1.0f;
4849  hdr.pixdim[4] = 1.0f;
4850  hdr.pixdim[5] = 1.0f;
4851  hdr.pixdim[6] = 1.0f;
4852  hdr.pixdim[7] = 1.0f;
4853 
4854  hdr.vox_offset = 352.0f; // 348-byte header + 4-byte extension indicator
4855  hdr.scl_slope = 1.0f;
4856  hdr.scl_inter = 0.0f;
4857 
4858  if (mgh.header.ras_good_flag == 1 && mgh.header.Mdc.size() >= 9 && mgh.header.Pxyz_c.size() >= 3)
4859  {
4860  hdr.sform_code = 1; // Scanner Anatomical
4861  hdr.qform_code = 1;
4862  hdr.srow_x[0] = mgh.header.Mdc[0]; hdr.srow_x[1] = mgh.header.Mdc[1]; hdr.srow_x[2] = mgh.header.Mdc[2]; hdr.srow_x[3] = mgh.header.Pxyz_c[0];
4863  hdr.srow_y[0] = mgh.header.Mdc[3]; hdr.srow_y[1] = mgh.header.Mdc[4]; hdr.srow_y[2] = mgh.header.Mdc[5]; hdr.srow_y[3] = mgh.header.Pxyz_c[1];
4864  hdr.srow_z[0] = mgh.header.Mdc[6]; hdr.srow_z[1] = mgh.header.Mdc[7]; hdr.srow_z[2] = mgh.header.Mdc[8]; hdr.srow_z[3] = mgh.header.Pxyz_c[2];
4865 
4866  // Set quaternion fields from sform for consistency (optional, but good practice).
4867  hdr.quatern_b = 0.0f;
4868  hdr.quatern_c = 0.0f;
4869  hdr.quatern_d = 0.0f;
4870  hdr.qoffset_x = hdr.srow_x[3];
4871  hdr.qoffset_y = hdr.srow_y[3];
4872  hdr.qoffset_z = hdr.srow_z[3];
4873  }
4874  else
4875  {
4876  hdr.sform_code = 0;
4877  hdr.qform_code = 0;
4878  }
4879 
4880  // Set magic for single-file NIfTI.
4881  std::memcpy(hdr.magic, "n+1\0", 4);
4882 
4883  // Write header.
4884  bool need_swap = (file_is_bigendian != _is_bigendian());
4885  if (need_swap)
4886  {
4887  Nifti1Header hdr_swapped = hdr;
4888  hdr_swapped.sizeof_hdr = _swap_endian(hdr.sizeof_hdr);
4889  hdr_swapped.extents = _swap_endian(hdr.extents);
4890  hdr_swapped.session_error = _swap_endian(hdr.session_error);
4891  for (int i = 0; i < 8; i++) hdr_swapped.dim[i] = _swap_endian(hdr.dim[i]);
4892  hdr_swapped.intent_p1 = _swap_endian(hdr.intent_p1);
4893  hdr_swapped.intent_p2 = _swap_endian(hdr.intent_p2);
4894  hdr_swapped.intent_p3 = _swap_endian(hdr.intent_p3);
4895  hdr_swapped.intent_code = _swap_endian(hdr.intent_code);
4896  hdr_swapped.datatype = _swap_endian(hdr.datatype);
4897  hdr_swapped.bitpix = _swap_endian(hdr.bitpix);
4898  hdr_swapped.slice_start = _swap_endian(hdr.slice_start);
4899  for (int i = 0; i < 8; i++) hdr_swapped.pixdim[i] = _swap_endian(hdr.pixdim[i]);
4900  hdr_swapped.vox_offset = _swap_endian(hdr.vox_offset);
4901  hdr_swapped.scl_slope = _swap_endian(hdr.scl_slope);
4902  hdr_swapped.scl_inter = _swap_endian(hdr.scl_inter);
4903  hdr_swapped.slice_end = _swap_endian(hdr.slice_end);
4904  hdr_swapped.cal_max = _swap_endian(hdr.cal_max);
4905  hdr_swapped.cal_min = _swap_endian(hdr.cal_min);
4906  hdr_swapped.slice_duration = _swap_endian(hdr.slice_duration);
4907  hdr_swapped.toffset = _swap_endian(hdr.toffset);
4908  hdr_swapped.glmax = _swap_endian(hdr.glmax);
4909  hdr_swapped.glmin = _swap_endian(hdr.glmin);
4910  hdr_swapped.qform_code = _swap_endian(hdr.qform_code);
4911  hdr_swapped.sform_code = _swap_endian(hdr.sform_code);
4912  hdr_swapped.quatern_b = _swap_endian(hdr.quatern_b);
4913  hdr_swapped.quatern_c = _swap_endian(hdr.quatern_c);
4914  hdr_swapped.quatern_d = _swap_endian(hdr.quatern_d);
4915  hdr_swapped.qoffset_x = _swap_endian(hdr.qoffset_x);
4916  hdr_swapped.qoffset_y = _swap_endian(hdr.qoffset_y);
4917  hdr_swapped.qoffset_z = _swap_endian(hdr.qoffset_z);
4918  for (int i = 0; i < 4; i++) hdr_swapped.srow_x[i] = _swap_endian(hdr.srow_x[i]);
4919  for (int i = 0; i < 4; i++) hdr_swapped.srow_y[i] = _swap_endian(hdr.srow_y[i]);
4920  for (int i = 0; i < 4; i++) hdr_swapped.srow_z[i] = _swap_endian(hdr.srow_z[i]);
4921  os.write(reinterpret_cast<const char *>(&hdr_swapped), sizeof(Nifti1Header));
4922  }
4923  else
4924  {
4925  os.write(reinterpret_cast<const char *>(&hdr), sizeof(Nifti1Header));
4926  }
4927 
4928  // Write 4-byte extension indicator (0 = no extensions).
4929  int32_t ext_indicator = 0;
4930  if (file_is_bigendian != _is_bigendian())
4931  {
4932  ext_indicator = _swap_endian(ext_indicator);
4933  }
4934  os.write(reinterpret_cast<const char *>(&ext_indicator), 4);
4935 
4936  // Write voxel data.
4937  size_t num_values = mgh.header.num_values();
4938  if (mgh.header.dtype == MRI_INT)
4939  {
4940  for (size_t i = 0; i < num_values; i++)
4941  {
4942  _nifti_write_data_element<int32_t>(os, mgh.data.data_mri_int[i], file_is_bigendian);
4943  }
4944  }
4945  else if (mgh.header.dtype == MRI_FLOAT)
4946  {
4947  for (size_t i = 0; i < num_values; i++)
4948  {
4949  _nifti_write_data_element<float>(os, mgh.data.data_mri_float[i], file_is_bigendian);
4950  }
4951  }
4952  else if (mgh.header.dtype == MRI_UCHAR)
4953  {
4954  for (size_t i = 0; i < num_values; i++)
4955  {
4956  _nifti_write_data_element<uint8_t>(os, mgh.data.data_mri_uchar[i], file_is_bigendian);
4957  }
4958  }
4959  else if (mgh.header.dtype == MRI_SHORT)
4960  {
4961  for (size_t i = 0; i < num_values; i++)
4962  {
4963  _nifti_write_data_element<short>(os, mgh.data.data_mri_short[i], file_is_bigendian);
4964  }
4965  }
4966  else
4967  {
4968  throw std::domain_error("Unsupported MRI data type " + std::to_string(mgh.header.dtype) +
4969  " for NIfTI output.\n");
4970  }
4971  }
4972 
4977  inline void write_nifti(const Mgh &mgh, const std::string &filename)
4978  {
4979  if (fs::util::ends_with(filename, ".nii.gz") || fs::util::ends_with(filename, ".NII.GZ"))
4980  {
4981 #ifdef LIBFS_HAS_ZLIB
4982  write_nifti_gz(mgh, filename);
4983  return;
4984 #else
4985  throw std::runtime_error("Cannot write .nii.gz file '" + filename +
4986  "': zlib support not enabled. Link with -lz.\n");
4987 #endif
4988  }
4989 
4990  std::ofstream ofs(filename, std::ofstream::out | std::ofstream::binary);
4991  if (!ofs.is_open())
4992  {
4993  throw std::runtime_error("Unable to open NIfTI file '" + filename + "' for writing.\n");
4994  }
4995  write_nifti(mgh, ofs);
4996  ofs.close();
4997  }
4998 
4999  // --- Gzip-compressed NIfTI (.nii.gz) ---
5000 
5001 #ifdef LIBFS_HAS_ZLIB
5002 
5008  inline void read_nifti_gz(Mgh *mgh, const std::string &filename, bool force_standard)
5009  {
5010  gzFile gz = gzopen(filename.c_str(), "rb");
5011  if (!gz)
5012  {
5013  int errnum = 0;
5014  const char *errstr = gzerror(gz, &errnum);
5015  throw std::runtime_error("Could not open NIfTI.GZ file '" + filename + "' for reading: " +
5016  (errstr ? std::string(errstr) : "unknown error") + "\n");
5017  }
5018  std::vector<char> buf;
5019  char chunk[131072];
5020  int n;
5021  while ((n = gzread(gz, chunk, sizeof(chunk))) > 0)
5022  {
5023  buf.insert(buf.end(), chunk, chunk + n);
5024  }
5025  if (n < 0)
5026  {
5027  int errnum = 0;
5028  const char *errstr = gzerror(gz, &errnum);
5029  gzclose(gz);
5030  throw std::runtime_error("Error decompressing NIfTI.GZ file '" + filename + "': " +
5031  (errstr ? std::string(errstr) : "unknown error") + "\n");
5032  }
5033  gzclose(gz);
5034  std::istringstream iss(std::string(buf.data(), buf.size()));
5035  read_nifti(mgh, &iss, force_standard);
5036  }
5037 
5042  inline void write_nifti_gz(const Mgh &mgh, const std::string &filename)
5043  {
5044  std::ostringstream oss;
5045  write_nifti(mgh, oss);
5046  std::string data = oss.str();
5047 
5048  gzFile gz = gzopen(filename.c_str(), "wb");
5049  if (!gz)
5050  {
5051  int errnum = 0;
5052  const char *errstr = gzerror(gz, &errnum);
5053  throw std::runtime_error("Could not open NIfTI.GZ file '" + filename + "' for writing: " +
5054  (errstr ? std::string(errstr) : "unknown error") + "\n");
5055  }
5056  z_size_t total_written = 0;
5057  while (total_written < data.size())
5058  {
5059  z_size_t remaining = data.size() - total_written;
5060  z_size_t chunk = (remaining > 131072) ? 131072 : remaining;
5061  int written = gzwrite(gz, data.data() + total_written, static_cast<unsigned int>(chunk));
5062  if (written <= 0)
5063  {
5064  int errnum = 0;
5065  const char *errstr = gzerror(gz, &errnum);
5066  gzclose(gz);
5067  throw std::runtime_error("Error writing NIfTI.GZ file '" + filename + "': " +
5068  (errstr ? std::string(errstr) : "unknown error") + "\n");
5069  }
5070  total_written += static_cast<z_size_t>(written);
5071  }
5072  gzclose(gz);
5073  }
5074 
5075 #endif // LIBFS_HAS_ZLIB (NIfTI GZ support)
5076 
5077  // --- NIfTI ↔ MGH conversion helpers ---
5078 
5086  inline Mgh nifti_to_mgh(const std::string &filename)
5087  {
5088  Mgh mgh;
5089  read_nifti(&mgh, filename);
5090  return mgh;
5091  }
5092 
5093  // ========================================================================
5094  // End NIfTI-1 Support
5095  // ========================================================================
5096 
5103  struct Label
5104  {
5105 
5107  Label() {}
5108 
5110  Label(std::vector<int> vertices, std::vector<float> values)
5111  {
5112  assert(vertices.size() == values.size());
5113  vertex = vertices;
5114  value = values;
5115  coord_x = std::vector<float>(vertices.size(), 0.0f);
5116  coord_y = std::vector<float>(vertices.size(), 0.0f);
5117  coord_z = std::vector<float>(vertices.size(), 0.0f);
5118  }
5119 
5121  Label(std::vector<int> vertices)
5122  {
5123  vertex = vertices;
5124  value = std::vector<float>(vertices.size(), 0.0f);
5125  coord_x = std::vector<float>(vertices.size(), 0.0f);
5126  coord_y = std::vector<float>(vertices.size(), 0.0f);
5127  coord_z = std::vector<float>(vertices.size(), 0.0f);
5128  }
5129 
5130  std::vector<int> vertex;
5131  std::vector<float> coord_x;
5132  std::vector<float> coord_y;
5133  std::vector<float> coord_z;
5134  std::vector<float> value;
5135 
5137  std::vector<bool> vert_in_label(size_t surface_num_verts) const
5138  {
5139  if (surface_num_verts < this->vertex.size())
5140  { // nonsense, so we warn (but don't throw, maybe the user really wants this).
5141  std::cerr << "Invalid number of vertices for surface, must be at least " << this->vertex.size() << "\n";
5142  }
5143  std::vector<bool> is_in = std::vector<bool>(surface_num_verts, false);
5144 
5145  for (size_t i = 0; i < this->vertex.size(); i++)
5146  {
5147  is_in[this->vertex[i]] = true;
5148  }
5149  return (is_in);
5150  }
5151 
5153  size_t num_entries() const
5154  {
5155  size_t num_ent = this->vertex.size();
5156  if (this->coord_x.size() != num_ent || this->coord_y.size() != num_ent || this->coord_z.size() != num_ent || this->value.size() != num_ent)
5157  {
5158  std::cerr << "Inconsistent label: sizes of property vectors do not match.\n";
5159  }
5160  return (num_ent);
5161  }
5162  };
5163 
5170  void write_surf(std::vector<float> vertices, std::vector<int32_t> faces, std::ostream &os)
5171  {
5172  const uint32_t SURF_TRIS_MAGIC = 16777214;
5173  _fwritei3(os, SURF_TRIS_MAGIC);
5174  std::string created_and_comment_lines = "Created by fslib\n\n";
5175  os << created_and_comment_lines;
5176  _fwritet<int32_t>(os, int(vertices.size() / 3)); // number of vertices
5177  _fwritet<int32_t>(os, int(faces.size() / 3)); // number of faces
5178  for (size_t i = 0; i < vertices.size(); i++)
5179  {
5180  _fwritet<float>(os, vertices[i]);
5181  }
5182  for (size_t i = 0; i < faces.size(); i++)
5183  {
5184  _fwritet<int32_t>(os, faces[i]);
5185  }
5186  }
5187 
5201  void write_surf(std::vector<float> vertices, std::vector<int32_t> faces, const std::string &filename)
5202  {
5203  std::ofstream ofs;
5204  ofs.open(filename, std::ofstream::out | std::ofstream::binary);
5205  if (ofs.is_open())
5206  {
5207  write_surf(vertices, faces, ofs);
5208  ofs.close();
5209  }
5210  else
5211  {
5212  throw std::runtime_error("Unable to open surf file '" + filename + "' for writing.\n");
5213  }
5214  }
5215 
5228  void write_surf(const Mesh &mesh, const std::string &filename)
5229  {
5230  std::ofstream ofs;
5231  ofs.open(filename, std::ofstream::out | std::ofstream::binary);
5232  if (ofs.is_open())
5233  {
5234  write_surf(mesh.vertices, mesh.faces, ofs);
5235  ofs.close();
5236  }
5237  else
5238  {
5239  throw std::runtime_error("Unable to open surf file '" + filename + "' for writing.\n");
5240  }
5241  }
5242 
5249  void read_label(Label *label, std::istream *is)
5250  {
5251  std::string line;
5252  int line_idx = -1;
5253  size_t num_entries_header = 0; // number of vertices/voxels according to header
5254  size_t num_entries = 0; // number of vertices/voxels for which the file contains label entries.
5255  while (std::getline(*is, line))
5256  {
5257  line_idx += 1;
5258  std::istringstream iss(line);
5259  if (line_idx == 0)
5260  {
5261  continue; // skip comment.
5262  }
5263  else
5264  {
5265  if (line_idx == 1)
5266  {
5267  if (!(iss >> num_entries_header))
5268  {
5269  throw std::domain_error("Could not parse entry count from label file, invalid format.\n");
5270  }
5271  }
5272  else
5273  {
5274  int vertex;
5275  float x, y, z, value;
5276  if (!(iss >> vertex >> x >> y >> z >> value))
5277  {
5278  throw std::domain_error("Could not parse line " + std::to_string(line_idx + 1) + " of label file, invalid format.\n");
5279  }
5280  label->vertex.push_back(vertex);
5281  label->coord_x.push_back(x);
5282  label->coord_y.push_back(y);
5283  label->coord_z.push_back(z);
5284  label->value.push_back(value);
5285  num_entries++;
5286  }
5287  }
5288  }
5289  if (num_entries != num_entries_header)
5290  {
5291  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");
5292  }
5293  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)
5294  {
5295  throw std::domain_error("Expected " + std::to_string(num_entries) + " entries in all Label vectors, but some did not match.\n");
5296  }
5297  }
5298 
5312  void read_label(Label *label, const std::string &filename)
5313  {
5314  std::ifstream infile(filename, std::fstream::in);
5315  if (infile.is_open())
5316  {
5317  read_label(label, &infile);
5318  infile.close();
5319  }
5320  else
5321  {
5322  throw std::runtime_error("Could not open label file '" + filename + "' for reading.\n");
5323  }
5324  }
5325 
5330  void write_label(const Label &label, std::ostream &os)
5331  {
5332  const size_t num_entries = label.num_entries();
5333  os << "#!ascii label from subject anonymous\n"
5334  << num_entries << "\n";
5335  for (size_t i = 0; i < num_entries; i++)
5336  {
5337  os << label.vertex[i] << " " << label.coord_x[i] << " " << label.coord_y[i] << " " << label.coord_z[i] << " " << label.value[i] << "\n";
5338  }
5339  }
5340 
5354  void write_label(const Label &label, const std::string &filename)
5355  {
5356  std::ofstream ofs;
5357  ofs.open(filename, std::ofstream::out);
5358  if (ofs.is_open())
5359  {
5360  write_label(label, ofs);
5361  ofs.close();
5362  }
5363  else
5364  {
5365  throw std::runtime_error("Unable to open label file '" + filename + "' for writing.\n");
5366  }
5367  }
5368 
5384  void write_mesh(const Mesh &mesh, const std::string &filename)
5385  {
5386  if (fs::util::ends_with(filename, {".ply", ".PLY"}))
5387  {
5388  mesh.to_ply_file(filename);
5389  }
5390  else if (fs::util::ends_with(filename, {".obj", ".OBJ"}))
5391  {
5392  mesh.to_obj_file(filename);
5393  }
5394  else if (fs::util::ends_with(filename, {".off", ".OFF"}))
5395  {
5396  mesh.to_off_file(filename);
5397  }
5398  else
5399  {
5400  fs::write_surf(mesh, filename);
5401  }
5402  }
5403 
5417  void write_mesh(const Mesh &mesh, const std::string &filename, const std::vector<uint8_t> col)
5418  {
5419  if (fs::util::ends_with(filename, {".ply", ".PLY"}))
5420  {
5421  mesh.to_ply_file(filename, col);
5422  }
5423  else if (fs::util::ends_with(filename, {".obj", ".OBJ"}))
5424  {
5425  mesh.to_obj_file(filename, col);
5426  }
5427  else if (fs::util::ends_with(filename, {".off", ".OFF"}))
5428  {
5429  mesh.to_off_file(filename, col);
5430  }
5431  else
5432  {
5433  fs::write_surf(mesh, filename);
5434  }
5435  }
5436 
5437 } // 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:3983
std::vector< float > Mdc
matrix
Definition: libfs.h:2638
const int16_t NIFTI_DT_UINT8
Definition: libfs.h:4272
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:925
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:2499
size_t num_entries() const
Get the number of enties (regions) in this Colortable.
Definition: libfs.h:2459
char dim_info
MRI slice ordering.
Definition: libfs.h:4353
Mgh(Curv curv)
Definition: libfs.h:2663
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:5131
float quatern_c
quaternion c param
Definition: libfs.h:4380
std::string to_off(const std::vector< uint8_t > col) const
Return string representing the mesh in PLY format.
Definition: libfs.h:2356
Models a FreeSurfer curv file that contains per-vertex float data.
Definition: libfs.h:2421
std::string to_obj(const std::vector< uint8_t > col) const
Return string representing the mesh in Wavefront Object (.obj) format with vertex colors...
Definition: libfs.h:1090
const int MRI_SHORT
MRI data type representing a 16 bit signed integer.
Definition: libfs.h:859
std::vector< float > Pxyz_c
x,y,z coordinates of central vertex
Definition: libfs.h:2639
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:5132
int32_t dof
typically ignored
Definition: libfs.h:2626
const int16_t NIFTI_DT_FLOAT128
Definition: libfs.h:4329
unsigned int d2
size of data along 2nd dimension
Definition: libfs.h:2725
const std::string LOGTAG_EXCESSIVE
Logging threshold for warning messages.
Definition: libfs.h:288
An annotation, also known as a brain surface parcellation. Assigns to each vertex a region...
Definition: libfs.h:2497
const int16_t NIFTI_DT_FLOAT32
Definition: libfs.h:4287
const std::string LOGTAG_VERBOSE
Logging threshold for warning messages.
Definition: libfs.h:285
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:3401
edge_set as_edgelist() const
Return edge list representation of this mesh.
Definition: libfs.h:1166
Definition: libfs.h:5103
MghHeader()
Empty default constuctor.
Definition: libfs.h:2603
int32_t sizeof_hdr
must be 348
Definition: libfs.h:4347
std::vector< int32_t > label
label integer computed from rgba values. Maps to the Annot.vertex_label field.
Definition: libfs.h:2456
Array4D(MghHeader *mgh_header)
Definition: libfs.h:2691
std::string to_obj() const
Return string representing the mesh in Wavefront Object (.obj) format.
Definition: libfs.h:1072
std::string time_tag(std::chrono::system_clock::time_point t)
Get current time as string, e.g. for log messages.
Definition: libfs.h:258
static void from_obj(Mesh *mesh, const std::string &filename)
Read a brainmesh from a Wavefront object format mesh file.
Definition: libfs.h:1741
float srow_y[4]
affine transform row y
Definition: libfs.h:4386
float cal_max
calibrated max
Definition: libfs.h:4369
const std::string LOGTAG_WARNING
Logging threshold for warning messages.
Definition: libfs.h:279
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:1277
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:636
bool file_exists(const std::string &name)
Check whether a file exists (can be read) at given path.
Definition: libfs.h:505
const std::string LOGTAG_INFO
Logging threshold for warning messages.
Definition: libfs.h:282
#define LIBFS_MAX_ALLOC_BYTES
Maximum memory allocation limit.
Definition: libfs.h:81
A simple 4D array datastructure, useful for representing volume data.
Definition: libfs.h:2678
int16_t sform_code
affine transform code (>0 = valid)
Definition: libfs.h:4378
std::vector< float > data
The curvature data, one value per vertex. Something like the cortical thickness at each vertex...
Definition: libfs.h:2438
static void from_ply(Mesh *mesh, std::istream *is)
Read a brainmesh from a Stanford PLY format stream.
Definition: libfs.h:1914
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:2165
std::vector< T > data
the data, as a 1D vector. Use fs::Array4D::at for easy access in 4D.
Definition: libfs.h:2728
std::vector< int32_t > b
green channel of RGBA color
Definition: libfs.h:2454
void write_nifti(const Mgh &mgh, std::ostream &os)
Write MGH data to a NIfTI-1 file (stream overload).
Definition: libfs.h:4809
static void from_ply(Mesh *mesh, const std::string &filename)
Read a brainmesh from a Stanford PLY format mesh file.
Definition: libfs.h:2108
int32_t extents
unused
Definition: libfs.h:4350
std::vector< int32_t > g
blue channel of RGBA color
Definition: libfs.h:2453
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:3668
void write_mgh(const Mgh &mgh, std::ostream &os)
Write MGH data to a stream.
Definition: libfs.h:4030
static void from_off(Mesh *mesh, const std::string &filename)
Read a brainmesh from an OFF format mesh file.
Definition: libfs.h:1892
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:5137
const int16_t NIFTI_DT_COMPLEX128
Definition: libfs.h:4333
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, expand it back to full mesh size.
Definition: libfs.h:1538
float quatern_d
quaternion d param
Definition: libfs.h:4381
int32_t glmax
global max (unused)
Definition: libfs.h:4373
MghHeader(Curv curv)
Definition: libfs.h:2604
MghHeader(std::vector< float > curv_data)
Definition: libfs.h:2612
int32_t dim1length
size of data along 1st dimension
Definition: libfs.h:2620
float quatern_b
quaternion b param
Definition: libfs.h:4379
Models a triangular mesh, used for brain surface meshes.
Definition: libfs.h:897
int16_t slice_end
last slice index
Definition: libfs.h:4366
const int MRI_UCHAR
MRI data type representing an 8 bit unsigned integer.
Definition: libfs.h:850
float xsize
size of voxels along 1st axis (x or r)
Definition: libfs.h:2635
size_t num_entries() const
Return the number of entries (vertices/voxels) in this label.
Definition: libfs.h:5153
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:2709
void to_obj_file(const std::string &filename, const std::vector< uint8_t > col) const
Export this mesh to a file in Wavefront OBJ format with vertex colors.
Definition: libfs.h:1450
Models the data of an MGH file. Currently these are 1D vectors, but one can compute the 4D array usin...
Definition: libfs.h:2643
void read_label(Label *label, std::istream *is)
Read a FreeSurfer ASCII label from a stream.
Definition: libfs.h:5249
void to_ply_file(const std::string &filename) const
Export this mesh to a file in Stanford PLY format.
Definition: libfs.h:2324
MghData(std::vector< uint8_t > curv_data)
constructor to create MghData from MRI_UCHAR (uint8_t) data.
Definition: libfs.h:2647
Mesh(std::vector< float > cvertices, std::vector< int32_t > cfaces)
Construct a Mesh from the given vertices and faces.
Definition: libfs.h:901
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:2568
const int16_t NIFTI_DT_COMPLEX64
Definition: libfs.h:4292
std::vector< std::vector< bool > > as_adjmatrix() const
Return adjacency matrix representation of this mesh.
Definition: libfs.h:1125
float vox_offset
byte offset to data from header start
Definition: libfs.h:4363
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:2703
size_t num_values() const
Compute the number of values based on the dim*length header fields.
Definition: libfs.h:2630
Array4D(Mgh *mgh)
Definition: libfs.h:2697
const int16_t NIFTI_DT_INT64
Definition: libfs.h:4320
float slice_duration
slice timing duration
Definition: libfs.h:4371
unsigned int d1
size of data along 1st dimension
Definition: libfs.h:2724
Models the header of an MGH file.
Definition: libfs.h:2601
#define LIBFS_MAX_STRING_LENGTH
Maximum length for fixed-length strings read from binary headers (e.g., filenames in annot colortable...
Definition: libfs.h:86
Definition: libfs.h:230
std::string to_ply() const
Return string representing the mesh in PLY format. Overload that works without passing a color vector...
Definition: libfs.h:2254
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:2448
const int MRI_FLOAT
MRI data type representing a 32 bit float.
Definition: libfs.h:856
const int16_t NIFTI_DT_FLOAT64
Definition: libfs.h:4297
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:5170
const int16_t NIFTI_DT_UINT64
Definition: libfs.h:4324
std::vector< std::vector< size_t > > as_adjlist(const bool via_matrix=true) const
Return adjacency list representation of this mesh.
Definition: libfs.h:1195
const int16_t NIFTI_DT_INT16
Definition: libfs.h:4277
static fs::Mesh construct_pyramid()
Construct and return a simple pyramidal mesh.
Definition: libfs.h:976
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:1254
std::vector< int32_t > id
internal region index
Definition: libfs.h:2450
const int16_t NIFTI_DT_UINT32
Definition: libfs.h:4315
char magic[4]
"n+1\0" (single file) or "ni1\0" (header/img pair)
Definition: libfs.h:4389
void write_mesh(const Mesh &mesh, const std::string &filename)
Write a mesh to a file in different formats.
Definition: libfs.h:5384
int16_t session_error
unused
Definition: libfs.h:4351
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:2520
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:2654
int16_t datatype
NIfTI data type code.
Definition: libfs.h:4359
const int MRI_INT
MRI data type representing a 32 bit signed integer.
Definition: libfs.h:853
float intent_p2
intent parameter 2
Definition: libfs.h:4356
float zsize
size of voxels along 3rd axis (z or s)
Definition: libfs.h:2637
static void from_obj(Mesh *mesh, std::istream *is)
Read a brainmesh from a Wavefront object format stream.
Definition: libfs.h:1572
Curv(std::vector< float > curv_data)
Construct a Curv instance from the given per-vertex data.
Definition: libfs.h:2425
const int16_t NIFTI_DT_NONE
No data / unknown type (value 0).
Definition: libfs.h:4264
unsigned int d3
size of data along 3rd dimension
Definition: libfs.h:2726
float qoffset_x
quaternion x shift
Definition: libfs.h:4382
Models a whole MGH file.
Definition: libfs.h:2658
size_t num_vertices() const
Get the number of vertices of this parcellation (or the associated surface).
Definition: libfs.h:2556
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:2444
void write_surf(const Mesh &mesh, const std::string &filename)
Write a mesh to a binary file in FreeSurfer surf format.
Definition: libfs.h:5228
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:580
void read_annot(Annot *annot, std::istream *is)
Read a FreeSurfer annotation or brain surface parcellation from an annot stream.
Definition: libfs.h:3562
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:2441
float cal_min
calibrated min
Definition: libfs.h:4370
std::vector< float > vertex_coords(const size_t vertex) const
Get all coordinates of the vertex, given by its index.
Definition: libfs.h:2210
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:1764
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:2186
float scl_inter
scaling intercept
Definition: libfs.h:4365
Mgh(std::vector< float > curv_data)
Definition: libfs.h:2668
std::vector< std::string > read_subjectsfile(const std::string &filename)
Read a vector of subject identifiers from a FreeSurfer subjects file.
Definition: libfs.h:2844
std::vector< float > value
the value of the label, can represent continuous data like a p-value, or sometimes simply 1...
Definition: libfs.h:5134
std::vector< int32_t > r
red channel of RGBA color
Definition: libfs.h:2452
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:2794
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:2334
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:1153
int32_t dtype
the MRI data type
Definition: libfs.h:2625
size_t num_faces() const
Return the number of faces in this mesh.
Definition: libfs.h:2148
void write_label(const Label &label, std::ostream &os)
Write label data to a stream.
Definition: libfs.h:5330
const int16_t NIFTI_DT_INT8
Definition: libfs.h:4307
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:5130
std::string to_ply(const std::vector< uint8_t > col) const
Return string representing the mesh in PLY format.
Definition: libfs.h:2270
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:2588
void write_annot(const Annot &annot, std::ostream &os)
Write a FreeSurfer annotation (brain surface parcellation) to a stream.
Definition: libfs.h:3907
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:2535
static fs::Mesh construct_cube()
Construct and return a simple cube mesh.
Definition: libfs.h:939
int32_t dim4length
size of data along 4th dimension
Definition: libfs.h:2623
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:2651
Mgh nifti_to_mgh(const std::string &filename)
Convert a NIfTI-1 file directly to MGH by reading it.
Definition: libfs.h:5086
char regular
unused
Definition: libfs.h:4352
void read_mgh(Mgh *mgh, std::istream *is)
Read MGH data from a stream.
Definition: libfs.h:2896
MghData(std::vector< float > curv_data)
constructor to create MghData from MRI_FLOAT (float) data.
Definition: libfs.h:2649
void write_subjectsfile(const std::string &filename, const std::vector< std::string > &subjects)
Write a vector of subject identifiers to a FreeSurfer subjects file.
Definition: libfs.h:2873
int16_t bitpix
bits per voxel
Definition: libfs.h:4360
MghData(std::vector< short > curv_data)
constructor to create MghData from MRI_SHORT (short) data.
Definition: libfs.h:2648
int32_t dim2length
size of data along 2nd dimension
Definition: libfs.h:2621
float scl_slope
scaling slope
Definition: libfs.h:4364
NIfTI-1 header structure (348 bytes, packed).
Definition: libfs.h:4345
const std::string LOGTAG_ERROR
Logging threshold for error messages.
Definition: libfs.h:276
int16_t slice_start
first slice index
Definition: libfs.h:4361
Mesh()
Construct an empty Mesh.
Definition: libfs.h:923
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:1471
int32_t dim3length
size of data along 3rd dimension
Definition: libfs.h:2622
void read_nifti(Mgh *, std::istream *, bool force_standard=false)
Read a NIfTI-1 file into an Mgh struct (stream overload).
Definition: libfs.h:4628
void read_nifti(Mgh *, const std::string &, bool force_standard=false)
Read a NIfTI-1 file into an Mgh struct (filename overload).
Definition: libfs.h:4778
float intent_p1
intent parameter 1
Definition: libfs.h:4355
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:2652
Label(std::vector< int > vertices)
Construct a Label from the given vertices / voxel numbers.
Definition: libfs.h:5121
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:1398
void to_obj_file(const std::string &filename) const
Export this mesh to a file in Wavefront OBJ format.
Definition: libfs.h:1443
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:3254
Curv()
Construct an empty Curv instance.
Definition: libfs.h:2432
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:533
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:2504
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:2414
float srow_x[4]
affine transform row x
Definition: libfs.h:4385
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:3360
std::vector< uint8_t > vertex_colors
n x 3 vector of RGB color values, 3 per vertex (v0_r, v0_g, v0_b, v1_r, ...). Empty if no vertex colo...
Definition: libfs.h:927
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:2470
int32_t num_faces
The number of faces of the mesh to which this belongs, typically irrelevant and ignored.
Definition: libfs.h:2435
float srow_z[4]
affine transform row z
Definition: libfs.h:4387
float ysize
size of voxels along 2nd axis (y or a)
Definition: libfs.h:2636
const int16_t NIFTI_DT_UINT16
Definition: libfs.h:4311
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:2653
float qoffset_z
quaternion z shift
Definition: libfs.h:4384
#define LIBFS_MAX_COLORTABLE_ENTRIES
Maximum number of entries in an annotation colortable.
Definition: libfs.h:91
unsigned int num_values() const
Get number of values/voxels.
Definition: libfs.h:2719
Colortable colortable
A Colortable defining the regions (most importantly, the region name and visualization color)...
Definition: libfs.h:2501
MghHeader header
Header for this MGH instance.
Definition: libfs.h:2660
Mesh(std::vector< std::vector< float >> cvertices, std::vector< std::vector< int32_t >> cfaces)
Construct a Mesh from 2-D vertex and face lists.
Definition: libfs.h:916
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:2627
void log(std::string const &message, std::string const loglevel="INFO")
Log a message, goes to stdout.
Definition: libfs.h:293
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:2236
std::vector< T > vflatten(std::vector< std::vector< T >> values)
Flatten 2D vector.
Definition: libfs.h:434
Label(std::vector< int > vertices, std::vector< float > values)
Construct a Label from the given vertices / voxel numbers and values.
Definition: libfs.h:5110
const int16_t NIFTI_DT_BINARY
Definition: libfs.h:4268
std::vector< int32_t > a
alpha channel of RGBA color
Definition: libfs.h:2455
const int16_t NIFTI_DT_RGB24
Definition: libfs.h:4302
int16_t intent_code
NIfTI intent code.
Definition: libfs.h:4358
const std::string LOGTAG_CRITICAL
Logging threshold for critical messages.
Definition: libfs.h:273
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:2483
Label()
Default constructor for a label.
Definition: libfs.h:5107
float toffset
time offset
Definition: libfs.h:4372
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:1009
float pixdim[8]
voxel dimensions (mm)
Definition: libfs.h:4362
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:926
char xyzt_units
units for pixdim[] dimensions
Definition: libfs.h:4368
void to_off_file(const std::string &filename) const
Export this mesh to a file in OFF format.
Definition: libfs.h:2407
#define LIBFS_APPTAG
Application tag prepended to every debug message from libfs.
Definition: libfs.h:168
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:2500
MghData(Curv curv)
constructor to create MghData from a Curv instance
Definition: libfs.h:2650
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:5133
Array4D(unsigned int d1, unsigned int d2, unsigned int d3, unsigned int d4)
Definition: libfs.h:2684
const int16_t NIFTI_DT_COMPLEX256
Definition: libfs.h:4339
const int16_t NIFTI_DT_INT32
Definition: libfs.h:4282
MghData(std::vector< int32_t > curv_data)
constructor to create MghData from MRI_INT (int32_t) data.
Definition: libfs.h:2646
std::vector< float > read_desc_data(const std::string &filename)
Read per-vertex brain morphometry data from a FreeSurfer curv, MGH, or NIfTI format file...
Definition: libfs.h:3690
float qoffset_y
quaternion y shift
Definition: libfs.h:4383
Mgh()
Empty default constuctor.
Definition: libfs.h:2662
int32_t glmin
global min (unused)
Definition: libfs.h:4374
char slice_code
slice timing code
Definition: libfs.h:4367
int16_t qform_code
quaternion transform code (>0 = valid)
Definition: libfs.h:4377
std::string to_off() const
Return string representing the mesh in OFF format. Overload that works without passing a color vector...
Definition: libfs.h:2347
std::vector< std::string > name
region name
Definition: libfs.h:2451
MghData data
4D data for this MGH instance.
Definition: libfs.h:2661
unsigned int d4
size of data along 4th dimension
Definition: libfs.h:2727
int16_t dim[8]
dim[0]=ndim, dim[1..7]=dimensions
Definition: libfs.h:4354
size_t num_vertices() const
Return the number of vertices in this mesh.
Definition: libfs.h:2134
float intent_p3
intent parameter 3
Definition: libfs.h:4357
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:3099