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 
19 #define LIBFS_VERSION "0.3.4"
20 #define LIBFS_VERISION_MAJOR 0
21 #define LIBFS_VERISION_MINOR 3
22 #define LIBFS_VERISION_PATCH 4
23 
26 
86 // Set apptag (printed as prefix of debug messages) for debug messages.
87 // Users can overwrite this by defining LIBFS_APPTAG before including 'libfs.h'.
88 #ifndef LIBFS_APPTAG
89 #define LIBFS_APPTAG "[libfs] "
90 #endif
91 
92 // Set default.
93 #define LIBFS_DBG_WARNING
94 
95 // If the user wants something below our default, remove our default.
96 #ifdef LIBFS_DBG_NONE
97 #undef LIBFS_DBG_WARNING
98 #endif
99 
100 #ifdef LIBFS_DBG_CRITICAL
101 #undef LIBFS_DBG_WARNING
102 #endif
103 
104 #ifdef LIBFS_DBG_ERROR
105 #undef LIBFS_DBG_WARNING
106 #endif
107 
108 // Ensure that the user does not have to define all debug levels
109 // up to the one they actually want, by defining all lower ones for them.
110 #ifdef LIBFS_DBG_EXCESSIVE
111 #define LIBFS_DBG_VERBOSE
112 #endif
113 
114 #ifdef LIBFS_DBG_VERBOSE
115 #define LIBFS_DBG_INFO
116 #endif
117 
118 #ifdef LIBFS_DBG_INFO
119 #define LIBFS_DBG_WARNING
120 #endif
121 
122 #ifdef LIBFS_DBG_WARNING
123 #define LIBFS_DBG_ERROR
124 #endif
125 
126 #ifdef LIBFS_DBG_ERROR
127 #define LIBFS_DBG_CRITICAL
128 #endif
129 
130 // End of debug handling.
131 
132 namespace fs
133 {
134 
135  namespace util
136  {
137 
141  tm _localtime(const std::time_t &time)
142  {
143  std::tm tm_snapshot;
144 #if (defined(WIN32) || defined(_WIN32) || defined(__WIN32__))
145  ::localtime_s(&tm_snapshot, &time);
146 #else
147  ::localtime_r(&time, &tm_snapshot); // POSIX
148 #endif
149  return tm_snapshot;
150  }
151 
160  std::string time_tag(std::chrono::system_clock::time_point t)
161  {
162  auto as_time_t = std::chrono::system_clock::to_time_t(t);
163  struct tm tm;
164  char time_buffer[64];
165  // if (::gmtime_r(&as_time_t, &tm)) {
166  tm = _localtime(as_time_t);
167  if (std::strftime(time_buffer, sizeof(time_buffer), "%F %T", &tm))
168  {
169  return std::string{time_buffer};
170  }
171  throw std::runtime_error("Failed to get current date as string");
172  }
173 
175  const std::string LOGTAG_CRITICAL = "CRITICAL";
176 
178  const std::string LOGTAG_ERROR = "ERROR";
179 
181  const std::string LOGTAG_WARNING = "WARNING";
182 
184  const std::string LOGTAG_INFO = "INFO";
185 
187  const std::string LOGTAG_VERBOSE = "VERBOSE";
188 
190  const std::string LOGTAG_EXCESSIVE = "EXCESSIVE";
191 
195  inline void log(std::string const &message, std::string const loglevel = "INFO")
196  {
197  std::cout << LIBFS_APPTAG << "[" << loglevel << "] [" << fs::util::time_tag(std::chrono::system_clock::now()) << "] " << message << "\n";
198  }
199 
208  inline bool ends_with(std::string const &value, std::string const &suffix)
209  {
210  if (suffix.size() > value.size())
211  return false;
212  return std::equal(suffix.rbegin(), suffix.rend(), value.rbegin());
213  }
214 
223  inline bool ends_with(std::string const &value, std::initializer_list<std::string> suffixes)
224  {
225  for (auto suffix : suffixes)
226  {
227  if (ends_with(value, suffix))
228  {
229  return true;
230  }
231  }
232  return false;
233  }
234 
247  template <typename T>
248  std::vector<std::vector<T>> v2d(std::vector<T> values, size_t num_cols)
249  {
250  std::vector<std::vector<T>> result;
251  for (std::size_t i = 0; i < values.size(); ++i)
252  {
253  if (i % num_cols == 0)
254  {
255  result.resize(result.size() + 1);
256  }
257  result[i / num_cols].push_back(values[i]);
258  }
259  return result;
260  }
261 
272  template <typename T>
273  std::vector<T> vflatten(std::vector<std::vector<T>> values)
274  {
275  size_t total_size = 0;
276  for (std::size_t i = 0; i < values.size(); i++)
277  {
278  total_size += values[i].size();
279  }
280 
281  std::vector<T> result = std::vector<T>(total_size);
282  size_t cur_idx = 0;
283  for (std::size_t i = 0; i < values.size(); i++)
284  {
285  for (std::size_t j = 0; j < values[i].size(); j++)
286  {
287  result[cur_idx] = values[i][j];
288  cur_idx++;
289  }
290  }
291  return result;
292  }
293 
304  inline bool starts_with(std::string const &value, std::string const &prefix)
305  {
306  if (prefix.length() > value.length())
307  return false;
308  return value.rfind(prefix, 0) == 0;
309  }
310 
321  inline bool starts_with(std::string const &value, std::initializer_list<std::string> prefixes)
322  {
323  for (auto prefix : prefixes)
324  {
325  if (starts_with(value, prefix))
326  {
327  return true;
328  }
329  }
330  return false;
331  }
332 
344  inline bool file_exists(const std::string &name)
345  {
346  if (FILE *file = fopen(name.c_str(), "r"))
347  {
348  fclose(file);
349  return true;
350  }
351  else
352  {
353  return false;
354  }
355  }
356 
372  std::string fullpath(std::initializer_list<std::string> path_components, std::string path_sep = std::string("/"))
373  {
374  std::string fp;
375  if (path_components.size() == 0)
376  {
377  throw std::invalid_argument("The 'path_components' must not be empty.");
378  }
379 
380  std::string comp;
381  std::string comp_mod;
382  size_t idx = 0;
383  for (auto comp : path_components)
384  {
385  comp_mod = comp;
386  if (idx != 0)
387  { // We keep a leading slash intact for the first element (absolute path).
388  if (starts_with(comp, path_sep))
389  {
390  comp_mod = comp.substr(1, comp.size() - 1);
391  }
392  }
393 
394  if (ends_with(comp_mod, path_sep))
395  {
396  comp_mod = comp_mod.substr(0, comp_mod.size() - 1);
397  }
398 
399  fp += comp_mod;
400  if (idx < path_components.size() - 1)
401  {
402  fp += path_sep;
403  }
404  idx++;
405  }
406  return fp;
407  }
408 
419  void str_to_file(const std::string &filename, const std::string rep)
420  {
421  std::ofstream ofs;
422  ofs.open(filename, std::ofstream::out);
423 #ifdef LIBFS_DBG_VERBOSE
424  std::cout << LIBFS_APPTAG << "Opening file '" << filename << "' for writing.\n";
425 #endif
426  if (ofs.is_open())
427  {
428  ofs << rep;
429  ofs.close();
430  }
431  else
432  {
433  throw std::runtime_error("Unable to open file '" + filename + "' for writing.\n");
434  }
435  }
436 
437  } // End namespace util.
438 
439  // MRI data types, used by the MGH functions.
440 
442  const int MRI_UCHAR = 0;
443 
445  const int MRI_INT = 1;
446 
448  const int MRI_FLOAT = 3;
449 
451  const int MRI_SHORT = 4;
452 
453  // Forward declarations.
454  int _fread3(std::istream &);
455  template <typename T>
456  T _freadt(std::istream &);
457  std::string _freadstringnewline(std::istream &);
458  std::string _freadfixedlengthstring(std::istream &, size_t, bool);
459  bool _ends_with(std::string const &fullString, std::string const &ending);
460  size_t _vidx_2d(size_t, size_t, size_t);
461  struct MghHeader;
462 
480  struct Mesh
481  {
482 
484  Mesh(std::vector<float> cvertices, std::vector<int32_t> cfaces)
485  {
486  vertices = cvertices;
487  faces = cfaces;
488  }
489 
490  // Construct from 2D vectors (Nx3).
491  Mesh(std::vector<std::vector<float>> cvertices, std::vector<std::vector<int32_t>> cfaces)
492  {
493  vertices = util::vflatten(cvertices);
494  faces = util::vflatten(cfaces);
495  }
496 
498  Mesh() {}
499 
500  std::vector<float> vertices;
501  std::vector<int32_t> faces;
502 
514  {
515  fs::Mesh mesh;
516  mesh.vertices = {1.0, 1.0, 1.0,
517  1.0, 1.0, -1.0,
518  1.0, -1.0, 1.0,
519  1.0, -1.0, -1.0,
520  -1.0, 1.0, 1.0,
521  -1.0, 1.0, -1.0,
522  -1.0, -1.0, 1.0,
523  -1.0, -1.0, -1.0};
524  mesh.faces = {0, 2, 3,
525  3, 1, 0,
526  4, 6, 7,
527  7, 5, 4,
528  0, 4, 5,
529  5, 1, 0,
530  2, 6, 7,
531  7, 3, 2,
532  0, 4, 6,
533  6, 2, 0,
534  1, 5, 7,
535  7, 3, 1};
536  return mesh;
537  }
538 
551  {
552  fs::Mesh mesh;
553  mesh.vertices = {0.0, 0.0, 0.0, // start with 4x base
554  0.0, 1.0, 0.0,
555  1.0, 1.0, 0.0,
556  1.0, 0.0, 0.0,
557  0.5, 0.5, 1.0}; // apex
558  mesh.faces = {0, 2, 1, // start with 2 base faces
559  0, 3, 2,
560  0, 4, 1, // now the 4 wall faces
561  1, 4, 2,
562  3, 2, 4,
563  0, 3, 4};
564  return mesh;
565  }
566 
583  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)
584  {
585  if (nx < 2 || ny < 2)
586  {
587  throw std::runtime_error("Parameters nx and ny must be at least 2.");
588  }
589  fs::Mesh mesh;
590  size_t num_vertices = nx * ny;
591  size_t num_faces = ((nx - 1) * (ny - 1)) * 2;
592  std::vector<float> vertices;
593  vertices.reserve(num_vertices * 3);
594  std::vector<int> faces;
595  faces.reserve(num_faces * 3);
596 
597  // Create vertices.
598  float cur_x, cur_y, cur_z;
599  cur_x = cur_y = cur_z = 0.0;
600  for (size_t i = 0; i < nx; i++)
601  {
602  for (size_t j = 0; j < ny; j++)
603  {
604  vertices.push_back(cur_x);
605  vertices.push_back(cur_y);
606  vertices.push_back(cur_z);
607  cur_y += disty;
608  }
609  cur_x += distx;
610  }
611 
612  // Create faces.
613  for (size_t i = 0; i < num_vertices; i++)
614  {
615  if ((i + 1) % ny == 0 || i >= num_vertices - ny)
616  {
617  // Do not use the last ones in row or column as source.
618  continue;
619  }
620  // Add the upper left triangle of this grid cell.
621  faces.push_back(int(i));
622  faces.push_back(int(i + ny + 1));
623  faces.push_back(int(i + 1));
624  // Add the lower right triangle of this grid cell.
625  faces.push_back(int(i));
626  faces.push_back(int(i + ny + 1));
627  faces.push_back(int(i + ny));
628  }
629 
630  mesh.vertices = vertices;
631  mesh.faces = faces;
632  return mesh;
633  }
634 
645  std::string to_obj() const
646  {
647  std::stringstream objs;
648  for (size_t vidx = 0; vidx < this->vertices.size(); vidx += 3)
649  { // vertex coords
650  objs << "v " << vertices[vidx] << " " << vertices[vidx + 1] << " " << vertices[vidx + 2] << "\n";
651  }
652  for (size_t fidx = 0; fidx < this->faces.size(); fidx += 3)
653  { // faces: vertex indices, 1-based
654  objs << "f " << faces[fidx] + 1 << " " << faces[fidx + 1] + 1 << " " << faces[fidx + 2] + 1 << "\n";
655  }
656  return (objs.str());
657  }
658 
670  std::vector<std::vector<bool>> as_adjmatrix() const
671  {
672  std::vector<std::vector<bool>> adjm = std::vector<std::vector<bool>>(this->num_vertices(), std::vector<bool>(this->num_vertices(), false));
673  for (size_t fidx = 0; fidx < this->faces.size(); fidx += 3)
674  { // faces: vertex indices
675  adjm[faces[fidx]][faces[fidx + 1]] = true;
676  adjm[faces[fidx + 1]][faces[fidx]] = true;
677  adjm[faces[fidx + 1]][faces[fidx + 2]] = true;
678  adjm[faces[fidx + 2]][faces[fidx + 1]] = true;
679  adjm[faces[fidx + 2]][faces[fidx]] = true;
680  adjm[faces[fidx]][faces[fidx + 2]] = true;
681  }
682  return adjm;
683  }
684 
686  struct _tupleHashFunction
687  {
688  size_t operator()(const std::tuple<size_t, size_t> &x) const
689  {
690  return std::get<0>(x) ^ std::get<1>(x);
691  }
692  };
693 
696  typedef std::unordered_set<std::tuple<size_t, size_t>, _tupleHashFunction> edge_set;
697 
709  edge_set as_edgelist() const
710  {
711  edge_set edges;
712  for (size_t fidx = 0; fidx < this->faces.size(); fidx += 3)
713  { // faces: vertex indices
714  edges.insert(std::make_tuple(faces[fidx], faces[fidx + 1]));
715  edges.insert(std::make_tuple(faces[fidx + 1], faces[fidx]));
716 
717  edges.insert(std::make_tuple(faces[fidx + 1], faces[fidx + 2]));
718  edges.insert(std::make_tuple(faces[fidx + 2], faces[fidx + 1]));
719 
720  edges.insert(std::make_tuple(faces[fidx], faces[fidx + 2]));
721  edges.insert(std::make_tuple(faces[fidx + 2], faces[fidx]));
722  }
723  return edges;
724  }
725 
738  std::vector<std::vector<size_t>> as_adjlist(const bool via_matrix = true) const
739  {
740  if (!via_matrix)
741  {
742  return (this->_as_adjlist_via_edgeset());
743  }
744  std::vector<std::vector<bool>> adjm = this->as_adjmatrix();
745  std::vector<std::vector<size_t>> adjl = std::vector<std::vector<size_t>>(this->num_vertices(), std::vector<size_t>());
746  size_t nv = adjm.size();
747  for (size_t i = 0; i < nv; i++)
748  {
749  for (size_t j = i + 1; j < nv; j++)
750  {
751  if (adjm[i][j] == true)
752  {
753  adjl[i].push_back(j);
754  adjl[j].push_back(i);
755  }
756  }
757  }
758  return adjl;
759  }
760 
771  std::vector<std::vector<size_t>> _as_adjlist_via_edgeset() const
772  {
773  edge_set edges = this->as_edgelist();
774  std::vector<std::vector<size_t>> adjl = std::vector<std::vector<size_t>>(this->num_vertices(), std::vector<size_t>());
775  for (const std::tuple<size_t, size_t> &e : edges)
776  {
777  adjl[std::get<0>(e)].push_back(std::get<1>(e));
778  }
779  return adjl;
780  }
781 
797  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
798  {
799 
800  const std::vector<std::vector<size_t>> adjlist = this->as_adjlist(via_matrix);
801  return fs::Mesh::smooth_pvd_nn(adjlist, pvd, num_iter, with_nan, detect_nan);
802  }
803 
820  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)
821  {
822  assert(pvd.size() == mesh_adj.size());
823  bool final_with_nan = with_nan;
824  if (detect_nan)
825  {
826  final_with_nan = false;
827  for (size_t i = 0; i < pvd.size(); i++)
828  {
829  if (std::isnan(pvd[i]))
830  {
831  final_with_nan = true;
832  break;
833  }
834  }
835  }
836  if (final_with_nan)
837  {
838  return fs::Mesh::_smooth_pvd_nn_nan(mesh_adj, pvd, num_iter);
839  }
840  std::vector<float> current_pvd_source;
841  std::vector<float> current_pvd_smoothed = std::vector<float>(pvd.size());
842 
843  float val_sum;
844  size_t num_neigh;
845  for (size_t i = 0; i < num_iter; i++)
846  {
847  if (i == 0)
848  {
849  current_pvd_source = pvd;
850  }
851  else
852  {
853  current_pvd_source = current_pvd_smoothed;
854  }
855  for (size_t v_idx = 0; v_idx < mesh_adj.size(); v_idx++)
856  {
857  num_neigh = mesh_adj[v_idx].size();
858  val_sum = current_pvd_source[v_idx] / (num_neigh + 1);
859  for (size_t neigh_rel_idx = 0; neigh_rel_idx < num_neigh; neigh_rel_idx++)
860  {
861  val_sum += current_pvd_source[mesh_adj[v_idx][neigh_rel_idx]] / (num_neigh + 1);
862  }
863  current_pvd_smoothed[v_idx] = val_sum;
864  }
865  }
866  return current_pvd_smoothed;
867  }
868 
885  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)
886  {
887  std::vector<float> current_pvd_source;
888  std::vector<float> current_pvd_smoothed = std::vector<float>(pvd.size());
889 
890  float val_sum;
891  size_t num_neigh;
892  size_t num_non_nan_values;
893  float neigh_val;
894  for (size_t i = 0; i < num_iter; i++)
895  {
896 
897  if (i == 0)
898  {
899  current_pvd_source = pvd;
900  }
901  else
902  {
903  current_pvd_source = current_pvd_smoothed;
904  }
905 
906  for (size_t v_idx = 0; v_idx < mesh_adj.size(); v_idx++)
907  {
908  if (std::isnan(current_pvd_source[v_idx]))
909  {
910  current_pvd_smoothed[v_idx] = NAN;
911  continue;
912  }
913  val_sum = current_pvd_source[v_idx];
914  num_non_nan_values = 1; // If we get here, the source vertex value is not NAN.
915  num_neigh = mesh_adj[v_idx].size();
916  for (size_t neigh_rel_idx = 0; neigh_rel_idx < num_neigh; neigh_rel_idx++)
917  {
918  neigh_val = current_pvd_source[mesh_adj[v_idx][neigh_rel_idx]];
919  if (std::isnan(neigh_val))
920  {
921  continue;
922  }
923  else
924  {
925  val_sum += neigh_val;
926  num_non_nan_values++;
927  }
928  }
929  current_pvd_smoothed[v_idx] = val_sum / (float)num_non_nan_values;
930  }
931  }
932  return current_pvd_smoothed;
933  }
934 
941  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>>())
942  {
943  size_t num_vertices = mesh_adj.size();
944  if (mesh_adj_ext.size() == 0)
945  {
946  mesh_adj_ext = mesh_adj;
947  }
948  std::vector<size_t> neighborhood;
949  std::vector<size_t> ext_neighborhood;
950  for (size_t ext_idx = 0; ext_idx < extend_by; ext_idx++)
951  {
952  for (size_t source_vert_idx = 0; source_vert_idx < num_vertices; source_vert_idx++)
953  {
954  neighborhood = mesh_adj_ext[source_vert_idx]; // copy needed so we do not modify during iteration.
955  // Extension: add all neighbors in distance one for all vertices in the neighborhood.
956  for (size_t neigh_vert_rel_idx = 0; neigh_vert_rel_idx < neighborhood.size(); neigh_vert_rel_idx++)
957  {
958  for (size_t canidate_rel_idx = 0; canidate_rel_idx < mesh_adj[neighborhood[neigh_vert_rel_idx]].size(); canidate_rel_idx++)
959  {
960  if (mesh_adj[neighborhood[neigh_vert_rel_idx]][canidate_rel_idx] != source_vert_idx)
961  {
962  mesh_adj_ext[source_vert_idx].push_back(mesh_adj[neighborhood[neigh_vert_rel_idx]][canidate_rel_idx]);
963  }
964  }
965  }
966  // We need to remove duplicates.
967  std::sort(mesh_adj_ext[source_vert_idx].begin(), mesh_adj_ext[source_vert_idx].end());
968  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());
969  }
970  }
971  return mesh_adj_ext;
972  }
973 
986  void to_obj_file(const std::string &filename) const
987  {
988  fs::util::str_to_file(filename, this->to_obj());
989  }
990 
1007  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
1008  {
1009  fs::Mesh submesh;
1010  std::vector<float> new_vertices;
1011  std::vector<int> new_faces;
1012  std::unordered_map<int32_t, int32_t> vertex_index_map_full2submesh;
1013  int32_t new_vertex_idx = 0;
1014  for (size_t i = 0; i < old_vertex_indices.size(); i++)
1015  {
1016  vertex_index_map_full2submesh[old_vertex_indices[i]] = new_vertex_idx;
1017  new_vertices.push_back(this->vertices[size_t(old_vertex_indices[i]) * 3]);
1018  new_vertices.push_back(this->vertices[size_t(old_vertex_indices[i]) * 3 + 1]);
1019  new_vertices.push_back(this->vertices[size_t(old_vertex_indices[i]) * 3 + 2]);
1020  new_vertex_idx++;
1021  }
1022  int face_v0;
1023  int face_v1;
1024  int face_v2;
1025  for (size_t i = 0; i < this->num_faces(); i++)
1026  {
1027  face_v0 = this->faces[i * 3];
1028  face_v1 = this->faces[i * 3 + 1];
1029  face_v2 = this->faces[i * 3 + 2];
1030  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()))
1031  {
1032  new_faces.push_back(vertex_index_map_full2submesh[face_v0]);
1033  new_faces.push_back(vertex_index_map_full2submesh[face_v1]);
1034  new_faces.push_back(vertex_index_map_full2submesh[face_v2]);
1035  }
1036  }
1037  submesh.vertices = new_vertices;
1038  submesh.faces = new_faces;
1039 
1040  std::pair<std::unordered_map<int32_t, int32_t>, fs::Mesh> result;
1041  if (!mapdir_fulltosubmesh)
1042  { // Compute the new2old (reverse) vertex index map:
1043  std::unordered_map<int32_t, int32_t> vertex_index_map_submesh2full;
1044  for (auto const &pair : vertex_index_map_full2submesh)
1045  {
1046  vertex_index_map_submesh2full[pair.second] = pair.first;
1047  }
1048  result = std::pair<std::unordered_map<int32_t, int32_t>, fs::Mesh>(vertex_index_map_submesh2full, submesh);
1049  }
1050  else
1051  {
1052  result = std::pair<std::unordered_map<int32_t, int32_t>, fs::Mesh>(vertex_index_map_full2submesh, submesh);
1053  }
1054 
1055  return result;
1056  }
1057 
1064  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())
1065  {
1066 
1067  if (submesh_to_orig_mapping.size() != data_submesh.size())
1068  {
1069  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()) + ".");
1070  }
1071 
1072  std::vector<float> data_orig_mesh(orig_mesh_num_vertices, fill_value);
1073  for (size_t i = 0; i < data_submesh.size(); i++)
1074  {
1075  auto got = submesh_to_orig_mapping.find(int(i));
1076  if (got != submesh_to_orig_mapping.end())
1077  {
1078  data_orig_mesh[got->second] = data_submesh[i];
1079  }
1080  }
1081  return (data_orig_mesh);
1082  }
1083 
1098  static void from_obj(Mesh *mesh, std::istream *is)
1099  {
1100  std::string line;
1101  int line_idx = -1;
1102 
1103  std::vector<float> vertices;
1104  std::vector<int> faces;
1105 
1106 #ifdef LIBFS_DBG_INFO
1107  size_t num_lines_ignored = 0; // Not comments, but custom extensions or material data lines which are ignored by libfs.
1108 #endif
1109 
1110  while (std::getline(*is, line))
1111  {
1112  line_idx += 1;
1113  std::istringstream iss(line);
1114  if (fs::util::starts_with(line, "#"))
1115  {
1116  continue; // skip comment.
1117  }
1118  else
1119  {
1120  if (fs::util::starts_with(line, "v "))
1121  {
1122  std::string elem_type_identifier;
1123  float x, y, z;
1124  if (!(iss >> elem_type_identifier >> x >> y >> z))
1125  {
1126  throw std::domain_error("Could not parse vertex line " + std::to_string(line_idx + 1) + " of OBJ data, invalid format.\n");
1127  }
1128  assert(elem_type_identifier == "v");
1129  vertices.push_back(x);
1130  vertices.push_back(y);
1131  vertices.push_back(z);
1132  }
1133  else if (fs::util::starts_with(line, "f "))
1134  {
1135  std::string elem_type_identifier, v0raw, v1raw, v2raw;
1136  int v0, v1, v2;
1137  if (!(iss >> elem_type_identifier >> v0raw >> v1raw >> v2raw))
1138  {
1139  throw std::domain_error("Could not parse face line " + std::to_string(line_idx + 1) + " of OBJ data, invalid format.\n");
1140  }
1141  assert(elem_type_identifier == "f");
1142 
1143  // The OBJ format allows to specifiy face indices with slashes to also set normal and material indices.
1144  // 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'.
1145  // We need to extract the stuff before the first slash and interprete it as int to get the vertex index we are looking for.
1146  std::size_t found_v0 = v0raw.find("/");
1147  std::size_t found_v1 = v1raw.find("/");
1148  std::size_t found_v2 = v2raw.find("/");
1149  if (found_v0 != std::string::npos)
1150  {
1151  v0raw = v0raw.substr(0, found_v0);
1152  }
1153  if (found_v1 != std::string::npos)
1154  {
1155  v1raw = v1raw.substr(0, found_v1);
1156  }
1157  if (found_v2 != std::string::npos)
1158  {
1159  v2raw = v0raw.substr(0, found_v2);
1160  }
1161  v0 = std::stoi(v0raw);
1162  v1 = std::stoi(v1raw);
1163  v2 = std::stoi(v2raw);
1164 
1165  // The vertex indices in Wavefront OBJ files are 1-based, so we have to substract 1 here.
1166  faces.push_back(v0 - 1);
1167  faces.push_back(v1 - 1);
1168  faces.push_back(v2 - 1);
1169  }
1170  else
1171  {
1172 #ifdef LIBFS_DBG_INFO
1173  num_lines_ignored++;
1174 #endif
1175 
1176  continue;
1177  }
1178  }
1179  }
1180 #ifdef LIBFS_DBG_INFO
1181  if (num_lines_ignored > 0)
1182  {
1183  std::cout << LIBFS_APPTAG << "Ignored " << num_lines_ignored << " lines in Wavefront OBJ format mesh file.\n";
1184  }
1185 #endif
1186  mesh->vertices = vertices;
1187  mesh->faces = faces;
1188  }
1189 
1204  static void from_obj(Mesh *mesh, const std::string &filename)
1205  {
1206 #ifdef LIBFS_DBG_INFO
1207  std::cout << LIBFS_APPTAG << "Reading brain mesh from Wavefront object format file " << filename << ".\n";
1208 #endif
1209  std::ifstream input(filename, std::fstream::in);
1210  if (input.is_open())
1211  {
1212  Mesh::from_obj(mesh, &input);
1213  input.close();
1214  }
1215  else
1216  {
1217  throw std::runtime_error("Could not open Wavefront object format mesh file '" + filename + "' for reading.\n");
1218  }
1219  }
1220 
1227  static void from_off(Mesh *mesh, std::istream *is, const std::string &source_filename = "")
1228  {
1229 
1230  std::string msg_source_file_part = source_filename.empty() ? "" : "'" + source_filename + "'";
1231 
1232  std::string line;
1233  int line_idx = -1;
1234  int noncomment_line_idx = -1;
1235 
1236  std::vector<float> vertices;
1237  std::vector<int> faces;
1238  size_t num_vertices = 0;
1239  size_t num_faces = 0;
1240  size_t num_edges = 0;
1241  size_t num_verts_parsed = 0;
1242  size_t num_faces_parsed = 0;
1243  float x, y, z; // vertex xyz coords
1244  // bool has_color;
1245  // int r, g, b, a; // vertex colors
1246  int num_verts_this_face, v0, v1, v2; // face, defined by number of vertices and vertex indices.
1247 
1248  while (std::getline(*is, line))
1249  {
1250  line_idx++;
1251  std::istringstream iss(line);
1252  if (fs::util::starts_with(line, "#"))
1253  {
1254  continue; // skip comment.
1255  }
1256  else
1257  {
1258  noncomment_line_idx++;
1259  if (noncomment_line_idx == 0)
1260  {
1261  std::string off_header_magic;
1262  if (!(iss >> off_header_magic))
1263  {
1264  throw std::domain_error("Could not parse first header line " + std::to_string(line_idx + 1) + " of OFF data, invalid format.\n");
1265  }
1266  if (!(off_header_magic == "OFF" || off_header_magic == "COFF"))
1267  {
1268  throw std::domain_error("OFF magic string invalid, file " + msg_source_file_part + " not in OFF format.\n");
1269  }
1270  // has_color = off_header_magic == "COFF";
1271  }
1272  else if (noncomment_line_idx == 1)
1273  {
1274  if (!(iss >> num_vertices >> num_faces >> num_edges))
1275  {
1276  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");
1277  }
1278  }
1279  else
1280  {
1281 
1282  if (num_verts_parsed < num_vertices)
1283  {
1284  if (!(iss >> x >> y >> z))
1285  {
1286  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");
1287  }
1288  vertices.push_back(x);
1289  vertices.push_back(y);
1290  vertices.push_back(z);
1291  num_verts_parsed++;
1292  }
1293  else
1294  {
1295  if (num_faces_parsed < num_faces)
1296  {
1297  if (!(iss >> num_verts_this_face >> v0 >> v1 >> v2))
1298  {
1299  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");
1300  }
1301  if (num_verts_this_face != 3)
1302  {
1303  throw std::domain_error("At OFF data " + msg_source_file_part + " line " + std::to_string(line_idx + 1) + ": only triangular meshes supported.\n");
1304  }
1305  faces.push_back(v0);
1306  faces.push_back(v1);
1307  faces.push_back(v2);
1308  num_faces_parsed++;
1309  }
1310  }
1311  }
1312  }
1313  }
1314  if (num_verts_parsed < num_vertices)
1315  {
1316  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");
1317  }
1318  if (num_faces_parsed < num_faces)
1319  {
1320  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");
1321  }
1322  mesh->vertices = vertices;
1323  mesh->faces = faces;
1324  }
1325 
1340  static void from_off(Mesh *mesh, const std::string &filename)
1341  {
1342 #ifdef LIBFS_DBG_INFO
1343  std::cout << LIBFS_APPTAG << "Reading brain mesh from OFF format file " << filename << ".\n";
1344 #endif
1345  std::ifstream input(filename, std::fstream::in);
1346  if (input.is_open())
1347  {
1348  Mesh::from_off(mesh, &input);
1349  input.close();
1350  }
1351  else
1352  {
1353  throw std::runtime_error("Could not open Object file format (OFF) mesh file '" + filename + "' for reading.\n");
1354  }
1355  }
1356 
1362  static void from_ply(Mesh *mesh, std::istream *is)
1363  {
1364  std::string line;
1365  int line_idx = -1;
1366  int noncomment_line_idx = -1;
1367 
1368  std::vector<float> vertices;
1369  std::vector<int> faces;
1370 
1371  bool in_header = true; // current status
1372  int num_verts = -1;
1373  int num_faces = -1;
1374  while (std::getline(*is, line))
1375  {
1376  line_idx += 1;
1377  std::istringstream iss(line);
1378  if (fs::util::starts_with(line, "comment"))
1379  {
1380  continue; // skip comment.
1381  }
1382  else
1383  {
1384  noncomment_line_idx++;
1385  if (in_header)
1386  {
1387  if (noncomment_line_idx == 0)
1388  {
1389  if (line != "ply")
1390  throw std::domain_error("Invalid PLY file");
1391  }
1392  else if (noncomment_line_idx == 1)
1393  {
1394  if (line != "format ascii 1.0")
1395  throw std::domain_error("Unsupported PLY file format, only format 'format ascii 1.0' is supported.");
1396  }
1397 
1398  if (line == "end_header")
1399  {
1400  in_header = false;
1401  }
1402  else if (fs::util::starts_with(line, "element vertex"))
1403  {
1404  std::string elem, elem_type_identifier;
1405  if (!(iss >> elem >> elem_type_identifier >> num_verts))
1406  {
1407  throw std::domain_error("Could not parse element vertex line of PLY header, invalid format.\n");
1408  }
1409  }
1410  else if (fs::util::starts_with(line, "element face"))
1411  {
1412  std::string elem, elem_type_identifier;
1413  if (!(iss >> elem >> elem_type_identifier >> num_faces))
1414  {
1415  throw std::domain_error("Could not parse element face line of PLY header, invalid format.\n");
1416  }
1417  } // Other properties like vertex colors and normals are ignored for now.
1418  }
1419  else
1420  { // in data part.
1421  if (num_verts < 1 || num_faces < 1)
1422  {
1423  throw std::domain_error("Invalid PLY file: missing element count lines of header.");
1424  }
1425  // Read vertices
1426  if (vertices.size() < (size_t)num_verts * 3)
1427  {
1428  float x, y, z;
1429  if (!(iss >> x >> y >> z))
1430  {
1431  throw std::domain_error("Could not parse vertex line " + std::to_string(line_idx) + " of PLY data, invalid format.\n");
1432  }
1433  vertices.push_back(x);
1434  vertices.push_back(y);
1435  vertices.push_back(z);
1436  }
1437  else
1438  {
1439  if (faces.size() < (size_t)num_faces * 3)
1440  {
1441  int verts_per_face, v0, v1, v2;
1442  if (!(iss >> verts_per_face >> v0 >> v1 >> v2))
1443  {
1444  throw std::domain_error("Could not parse face line " + std::to_string(line_idx) + " of PLY data, invalid format.\n");
1445  }
1446  if (verts_per_face != 3)
1447  {
1448  throw std::domain_error("Only triangular meshes are supported: PLY faces lines must contain exactly 3 vertex indices.\n");
1449  }
1450  faces.push_back(v0);
1451  faces.push_back(v1);
1452  faces.push_back(v2);
1453  }
1454  }
1455  }
1456  }
1457  }
1458  if (vertices.size() != (size_t)num_verts * 3)
1459  {
1460  std::cerr << "PLY header mentions " << num_verts << " vertices, but found " << vertices.size() / 3 << ".\n";
1461  }
1462  if (faces.size() != (size_t)num_faces * 3)
1463  {
1464  std::cerr << "PLY header mentions " << num_faces << " faces, but found " << faces.size() / 3 << ".\n";
1465  }
1466  mesh->vertices = vertices;
1467  mesh->faces = faces;
1468  }
1469 
1483  static void from_ply(Mesh *mesh, const std::string &filename)
1484  {
1485 #ifdef LIBFS_DBG_INFO
1486  std::cout << LIBFS_APPTAG << "Reading brain mesh from PLY format file " << filename << ".\n";
1487 #endif
1488  std::ifstream input(filename, std::fstream::in);
1489  if (input.is_open())
1490  {
1491  Mesh::from_ply(mesh, &input);
1492  input.close();
1493  }
1494  else
1495  {
1496  throw std::runtime_error("Could not open Stanford PLY format mesh file '" + filename + "' for reading.\n");
1497  }
1498  }
1499 
1509  size_t num_vertices() const
1510  {
1511  return (this->vertices.size() / 3);
1512  }
1513 
1523  size_t num_faces() const
1524  {
1525  return (this->faces.size() / 3);
1526  }
1527 
1540  const int32_t &fm_at(const size_t i, const size_t j) const
1541  {
1542  size_t idx = _vidx_2d(i, j, 3);
1543  if (idx > this->faces.size() - 1)
1544  {
1545  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");
1546  }
1547  return (this->faces[idx]);
1548  }
1549 
1561  std::vector<int32_t> face_vertices(const size_t face) const
1562  {
1563  if (face > this->num_faces() - 1)
1564  {
1565  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");
1566  }
1567  std::vector<int32_t> fv(3);
1568  fv[0] = this->fm_at(face, 0);
1569  fv[1] = this->fm_at(face, 1);
1570  fv[2] = this->fm_at(face, 2);
1571  return (fv);
1572  }
1573 
1585  std::vector<float> vertex_coords(const size_t vertex) const
1586  {
1587  if (vertex > this->num_vertices() - 1)
1588  {
1589  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");
1590  }
1591  std::vector<float> vc(3);
1592  vc[0] = this->vm_at(vertex, 0);
1593  vc[1] = this->vm_at(vertex, 1);
1594  vc[2] = this->vm_at(vertex, 2);
1595  return (vc);
1596  }
1597 
1611  const float &vm_at(const size_t i, const size_t j) const
1612  {
1613  size_t idx = _vidx_2d(i, j, 3);
1614  if (idx > this->vertices.size() - 1)
1615  {
1616  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");
1617  }
1618  return (this->vertices[idx]);
1619  }
1620 
1629  std::string to_ply() const
1630  {
1631  std::vector<uint8_t> empty_col;
1632  return (this->to_ply(empty_col));
1633  }
1634 
1645  std::string to_ply(const std::vector<uint8_t> col) const
1646  {
1647  bool use_vertex_colors = col.size() != 0;
1648  std::stringstream plys;
1649  plys << "ply\nformat ascii 1.0\n";
1650  plys << "element vertex " << this->num_vertices() << "\n";
1651  plys << "property float x\nproperty float y\nproperty float z\n";
1652  if (use_vertex_colors)
1653  {
1654  if (col.size() != this->vertices.size())
1655  {
1656  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()) + ".");
1657  }
1658  plys << "property uchar red\nproperty uchar green\nproperty uchar blue\n";
1659  }
1660  plys << "element face " << this->num_faces() << "\n";
1661  plys << "property list uchar int vertex_index\n";
1662  plys << "end_header\n";
1663 
1664 #ifdef LIBFS_DBG_DEBUG
1665  fs::util::log("Writing " + std::to_string(this->vertices.size() / 3) + " PLY format vertices.", "INFO");
1666 #endif
1667 
1668  for (size_t vidx = 0; vidx < this->vertices.size(); vidx += 3)
1669  { // vertex coords
1670  plys << vertices[vidx] << " " << vertices[vidx + 1] << " " << vertices[vidx + 2];
1671  if (use_vertex_colors)
1672  {
1673  plys << " " << (int)col[vidx] << " " << (int)col[vidx + 1] << " " << (int)col[vidx + 2];
1674  }
1675  plys << "\n";
1676  }
1677 
1678 #ifdef LIBFS_DBG_DEBUG
1679  fs::util::log("Writing " + std::to_string(this->faces.size() / 3) + " PLY format faces.", "INFO");
1680 #endif
1681 
1682  const int num_vertices_per_face = 3;
1683  for (size_t fidx = 0; fidx < this->faces.size(); fidx += 3)
1684  { // faces: vertex indices, 0-based
1685  plys << num_vertices_per_face << " " << faces[fidx] << " " << faces[fidx + 1] << " " << faces[fidx + 2] << "\n";
1686  }
1687  return (plys.str());
1688  }
1689 
1699  void to_ply_file(const std::string &filename) const
1700  {
1701 #ifdef LIBFS_DBG_INFO
1702  fs::util::log("Writing mesh to PLY file '" + filename + "'.", "INFO");
1703 #endif
1704  fs::util::str_to_file(filename, this->to_ply());
1705  }
1706 
1709  void to_ply_file(const std::string &filename, const std::vector<uint8_t> col) const
1710  {
1711  fs::util::str_to_file(filename, this->to_ply(col));
1712  }
1713 
1722  std::string to_off() const
1723  {
1724  std::vector<uint8_t> empty_col;
1725  return (this->to_off(empty_col));
1726  }
1727 
1731  std::string to_off(const std::vector<uint8_t> col) const
1732  {
1733  bool use_vertex_colors = col.size() != 0;
1734  std::stringstream offs;
1735  if (use_vertex_colors)
1736  {
1737 #ifdef LIBFS_DBG_INFO
1738  fs::util::log("Writing OFF representation of mesh with vertex colors.", "INFO");
1739 #endif
1740  if (col.size() != this->vertices.size())
1741  {
1742  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()) + ".");
1743  }
1744  offs << "COFF\n";
1745  }
1746  else
1747  {
1748 #ifdef LIBFS_DBG_INFO
1749  fs::util::log("Writing OFF representation of mesh without vertex colors.", "INFO");
1750 #endif
1751  offs << "OFF\n";
1752  }
1753  offs << this->num_vertices() << " " << this->num_faces() << " 0\n";
1754 
1755  for (size_t vidx = 0; vidx < this->vertices.size(); vidx += 3)
1756  { // vertex coords
1757  offs << vertices[vidx] << " " << vertices[vidx + 1] << " " << vertices[vidx + 2];
1758  if (use_vertex_colors)
1759  {
1760  offs << " " << (int)col[vidx] << " " << (int)col[vidx + 1] << " " << (int)col[vidx + 2] << " 255";
1761  }
1762  offs << "\n";
1763  }
1764 
1765  const int num_vertices_per_face = 3;
1766  for (size_t fidx = 0; fidx < this->faces.size(); fidx += 3)
1767  { // faces: vertex indices, 0-based
1768  offs << num_vertices_per_face << " " << faces[fidx] << " " << faces[fidx + 1] << " " << faces[fidx + 2] << "\n";
1769  }
1770  return (offs.str());
1771  }
1772 
1782  void to_off_file(const std::string &filename) const
1783  {
1784  fs::util::str_to_file(filename, this->to_off());
1785  }
1786 
1789  void to_off_file(const std::string &filename, const std::vector<uint8_t> col) const
1790  {
1791  fs::util::str_to_file(filename, this->to_off(col));
1792  }
1793  };
1794 
1796  struct Curv
1797  {
1798 
1800  Curv(std::vector<float> curv_data) : num_faces(100000), num_vertices(0), num_values_per_vertex(1)
1801  {
1802  data = curv_data;
1803  num_vertices = int(data.size());
1804  }
1805 
1807  Curv() : num_faces(100000), num_vertices(0), num_values_per_vertex(1) {}
1808 
1810  int32_t num_faces;
1811 
1813  std::vector<float> data;
1814 
1816  int32_t num_vertices;
1817 
1820  };
1821 
1823  struct Colortable
1824  {
1825  std::vector<int32_t> id;
1826  std::vector<std::string> name;
1827  std::vector<int32_t> r;
1828  std::vector<int32_t> g;
1829  std::vector<int32_t> b;
1830  std::vector<int32_t> a;
1831  std::vector<int32_t> label;
1832 
1834  size_t num_entries() const
1835  {
1836  size_t num_ids = this->id.size();
1837  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)
1838  {
1839  std::cerr << "Inconsistent Colortable, vector sizes do not match.\n";
1840  }
1841  return num_ids;
1842  }
1843 
1845  int32_t get_region_idx(const std::string &query_name) const
1846  {
1847  for (size_t i = 0; i < this->num_entries(); i++)
1848  {
1849  if (this->name[i] == query_name)
1850  {
1851  return (int32_t)i;
1852  }
1853  }
1854  return (-1);
1855  }
1856 
1858  int32_t get_region_idx(int32_t query_label) const
1859  {
1860  for (size_t i = 0; i < this->num_entries(); i++)
1861  {
1862  if (this->label[i] == query_label)
1863  {
1864  return (int32_t)i;
1865  }
1866  }
1867  return (-1);
1868  }
1869  };
1870 
1872  struct Annot
1873  {
1874  std::vector<int32_t> vertex_indices;
1875  std::vector<int32_t> vertex_labels;
1877 
1879  std::vector<int32_t> region_vertices(const std::string &region_name) const
1880  {
1881  int32_t region_idx = this->colortable.get_region_idx(region_name);
1882  if (region_idx >= 0)
1883  {
1884  return (this->region_vertices(this->colortable.label[region_idx]));
1885  }
1886  else
1887  {
1888  std::cerr << "No such region in annot, returning empty vector.\n";
1889  std::vector<int32_t> empty;
1890  return (empty);
1891  }
1892  }
1893 
1895  std::vector<int32_t> region_vertices(int32_t region_label) const
1896  {
1897  std::vector<int32_t> reg_verts;
1898  for (size_t i = 0; i < this->vertex_labels.size(); i++)
1899  {
1900  if (this->vertex_labels[i] == region_label)
1901  {
1902  reg_verts.push_back(int(i));
1903  }
1904  }
1905  return (reg_verts);
1906  }
1907 
1910  std::vector<uint8_t> vertex_colors(bool alpha = false) const
1911  {
1912  int num_channels = alpha ? 4 : 3;
1913  std::vector<uint8_t> col;
1914  col.reserve(this->num_vertices() * num_channels);
1915  std::vector<size_t> vertex_region_indices = this->vertex_regions();
1916  for (size_t i = 0; i < this->num_vertices(); i++)
1917  {
1918  col.push_back(this->colortable.r[vertex_region_indices[i]]);
1919  col.push_back(this->colortable.g[vertex_region_indices[i]]);
1920  col.push_back(this->colortable.b[vertex_region_indices[i]]);
1921  if (alpha)
1922  {
1923  col.push_back(this->colortable.a[vertex_region_indices[i]]);
1924  }
1925  }
1926  return (col);
1927  }
1928 
1931  size_t num_vertices() const
1932  {
1933  size_t nv = this->vertex_indices.size();
1934  if (this->vertex_labels.size() != nv)
1935  {
1936  throw std::runtime_error("Inconsistent annot, number of vertex indices and labels does not match.\n");
1937  }
1938  return nv;
1939  }
1940 
1943  std::vector<size_t> vertex_regions() const
1944  {
1945  std::vector<size_t> vert_reg;
1946  for (size_t i = 0; i < this->num_vertices(); i++)
1947  {
1948  vert_reg.push_back(0); // init with zeros.
1949  }
1950  for (size_t region_idx = 0; region_idx < this->colortable.num_entries(); region_idx++)
1951  {
1952  std::vector<int32_t> reg_vertices = this->region_vertices(this->colortable.label[region_idx]);
1953  for (size_t region_vert_local_idx = 0; region_vert_local_idx < reg_vertices.size(); region_vert_local_idx++)
1954  {
1955  int32_t region_vert_idx = reg_vertices[region_vert_local_idx];
1956  vert_reg[region_vert_idx] = region_idx;
1957  }
1958  }
1959  return vert_reg;
1960  }
1961 
1963  std::vector<std::string> vertex_region_names() const
1964  {
1965  std::vector<std::string> region_names;
1966  std::vector<size_t> vertex_region_indices = this->vertex_regions();
1967  for (size_t i = 0; i < this->num_vertices(); i++)
1968  {
1969  region_names.push_back(this->colortable.name[vertex_region_indices[i]]);
1970  }
1971  return (region_names);
1972  }
1973  };
1974 
1976  struct MghHeader
1977  {
1980  {
1981  dim1length = curv.data.size();
1982  dim2length = 1;
1983  dim3length = 1;
1984  dim4length = 1;
1985  dtype = fs::MRI_FLOAT;
1986  }
1987  MghHeader(std::vector<float> curv_data)
1988  {
1989  dim1length = curv_data.size();
1990  dim2length = 1;
1991  dim3length = 1;
1992  dim4length = 1;
1993  dtype = fs::MRI_FLOAT;
1994  }
1995  int32_t dim1length = 0;
1996  int32_t dim2length = 0;
1997  int32_t dim3length = 0;
1998  int32_t dim4length = 0;
1999 
2000  int32_t dtype = 0;
2001  int32_t dof = 0;
2002  int16_t ras_good_flag = 0;
2003 
2005  size_t num_values() const
2006  {
2007  return ((size_t)dim1length * dim2length * dim3length * dim4length);
2008  }
2009 
2010  float xsize = 0.0;
2011  float ysize = 0.0;
2012  float zsize = 0.0;
2013  std::vector<float> Mdc;
2014  std::vector<float> Pxyz_c;
2015  };
2016 
2018  struct MghData
2019  {
2020  MghData() {}
2021  MghData(std::vector<int32_t> curv_data) { data_mri_int = curv_data; }
2022  explicit MghData(std::vector<uint8_t> curv_data) { data_mri_uchar = curv_data; }
2023  explicit MghData(std::vector<short> curv_data) { data_mri_short = curv_data; }
2024  MghData(std::vector<float> curv_data) { data_mri_float = curv_data; }
2025  MghData(Curv curv) { data_mri_float = curv.data; }
2026  std::vector<int32_t> data_mri_int;
2027  std::vector<uint8_t> data_mri_uchar;
2028  std::vector<float> data_mri_float;
2029  std::vector<short> data_mri_short;
2030  };
2031 
2033  struct Mgh
2034  {
2037  Mgh() {}
2038  Mgh(Curv curv)
2039  {
2040  header = MghHeader(curv);
2041  data = MghData(curv);
2042  }
2043  Mgh(std::vector<float> curv_data)
2044  {
2045  header = MghHeader(curv_data);
2046  data = MghData(curv_data);
2047  }
2048  };
2049 
2052  template <class T>
2053  struct Array4D
2054  {
2056  Array4D(unsigned int d1, unsigned int d2, unsigned int d3, unsigned int d4) : d1(d1), d2(d2), d3(d3), d4(d4), data(d1 * d2 * d3 * d4) {}
2057 
2059  Array4D(MghHeader *mgh_header) : d1(mgh_header->dim1length), d2(mgh_header->dim2length), d3(mgh_header->dim3length), d4(mgh_header->dim4length), data(d1 * d2 * d3 * d4) {}
2060 
2062  Array4D(Mgh *mgh) : // This does NOT init the data atm.
2063  d1(mgh->header.dim1length), d2(mgh->header.dim2length), d3(mgh->header.dim3length), d4(mgh->header.dim4length), data(d1 * d2 * d3 * d4)
2064  {
2065  }
2066 
2068  const T &at(const unsigned int i1, const unsigned int i2, const unsigned int i3, const unsigned int i4) const
2069  {
2070  return data[get_index(i1, i2, i3, i4)];
2071  }
2072 
2074  unsigned int get_index(const unsigned int i1, const unsigned int i2, const unsigned int i3, const unsigned int i4) const
2075  {
2076  assert(i1 >= 0 && i1 < d1);
2077  assert(i2 >= 0 && i2 < d2);
2078  assert(i3 >= 0 && i3 < d3);
2079  assert(i4 >= 0 && i4 < d4);
2080  return (((i1 * d2 + i2) * d3 + i3) * d4 + i4);
2081  }
2082 
2084  unsigned int num_values() const
2085  {
2086  return (d1 * d2 * d3 * d4);
2087  }
2088 
2089  unsigned int d1;
2090  unsigned int d2;
2091  unsigned int d3;
2092  unsigned int d4;
2093  std::vector<T> data;
2094  };
2095 
2096  // More declarations, should also go to separate header.
2097  void read_mgh_header(MghHeader *, const std::string &);
2098  void read_mgh_header(MghHeader *, std::istream *);
2099  template <typename T>
2100  std::vector<T> _read_mgh_data(MghHeader *, const std::string &);
2101  template <typename T>
2102  std::vector<T> _read_mgh_data(MghHeader *, std::istream *);
2103  std::vector<int32_t> _read_mgh_data_int(MghHeader *, const std::string &);
2104  std::vector<int32_t> _read_mgh_data_int(MghHeader *, std::istream *);
2105  std::vector<uint8_t> _read_mgh_data_uchar(MghHeader *, const std::string &);
2106  std::vector<uint8_t> _read_mgh_data_uchar(MghHeader *, std::istream *);
2107  std::vector<short> _read_mgh_data_short(MghHeader *, const std::string &);
2108  std::vector<short> _read_mgh_data_short(MghHeader *, std::istream *);
2109  std::vector<float> _read_mgh_data_float(MghHeader *, const std::string &);
2110  std::vector<float> _read_mgh_data_float(MghHeader *, std::istream *);
2111 
2124  void read_mgh(Mgh *mgh, const std::string &filename)
2125  {
2126  MghHeader mgh_header;
2127  read_mgh_header(&mgh_header, filename);
2128  mgh->header = mgh_header;
2129  if (mgh->header.dtype == MRI_INT)
2130  {
2131  std::vector<int32_t> data = _read_mgh_data_int(&mgh_header, filename);
2132  mgh->data.data_mri_int = data;
2133  }
2134  else if (mgh->header.dtype == MRI_UCHAR)
2135  {
2136  std::vector<uint8_t> data = _read_mgh_data_uchar(&mgh_header, filename);
2137  mgh->data.data_mri_uchar = data;
2138  }
2139  else if (mgh->header.dtype == MRI_FLOAT)
2140  {
2141  std::vector<float> data = _read_mgh_data_float(&mgh_header, filename);
2142  mgh->data.data_mri_float = data;
2143  }
2144  else if (mgh->header.dtype == MRI_SHORT)
2145  {
2146  std::vector<short> data = _read_mgh_data_short(&mgh_header, filename);
2147  mgh->data.data_mri_short = data;
2148  }
2149  else
2150  {
2151 #ifdef LIBFS_DBG_INFO
2152  if (fs::util::ends_with(filename, ".mgz"))
2153  {
2154  std::cout << LIBFS_APPTAG << "Note: your MGH filename ends with '.mgz'. Keep in mind that MGZ format is not supported directly. You can ignore this message if you wrapped a gz stream.\n";
2155  }
2156 #endif
2157  throw std::runtime_error("Not reading MGH data from file '" + filename + "', data type " + std::to_string(mgh->header.dtype) + " not supported yet.\n");
2158  }
2159  }
2160 
2170  std::vector<std::string> read_subjectsfile(const std::string &filename)
2171  {
2172  std::vector<std::string> subjects;
2173  std::ifstream input(filename, std::fstream::in);
2174  std::string line;
2175 
2176  if (!input.is_open())
2177  {
2178  throw std::runtime_error("Could not open subjects file '" + filename + "'.\n");
2179  }
2180 
2181  while (std::getline(input, line))
2182  {
2183  subjects.push_back(line);
2184  }
2185  return (subjects);
2186  }
2187 
2193  void read_mgh(Mgh *mgh, std::istream *is)
2194  {
2195  MghHeader mgh_header;
2196  read_mgh_header(&mgh_header, is);
2197  mgh->header = mgh_header;
2198  if (mgh->header.dtype == MRI_INT)
2199  {
2200  std::vector<int32_t> data = _read_mgh_data_int(&mgh_header, is);
2201  mgh->data.data_mri_int = data;
2202  }
2203  else if (mgh->header.dtype == MRI_UCHAR)
2204  {
2205  std::vector<uint8_t> data = _read_mgh_data_uchar(&mgh_header, is);
2206  mgh->data.data_mri_uchar = data;
2207  }
2208  else if (mgh->header.dtype == MRI_FLOAT)
2209  {
2210  std::vector<float> data = _read_mgh_data_float(&mgh_header, is);
2211  mgh->data.data_mri_float = data;
2212  }
2213  else if (mgh->header.dtype == MRI_SHORT)
2214  {
2215  std::vector<short> data = _read_mgh_data_short(&mgh_header, is);
2216  mgh->data.data_mri_short = data;
2217  }
2218  else
2219  {
2220  throw std::runtime_error("Not reading data from MGH stream, data type " + std::to_string(mgh->header.dtype) + " not supported yet.\n");
2221  }
2222  }
2223 
2229  void read_mgh_header(MghHeader *mgh_header, std::istream *is)
2230  {
2231  const int MGH_VERSION = 1;
2232 
2233  int format_version = _freadt<int32_t>(*is);
2234  if (format_version != MGH_VERSION)
2235  {
2236  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");
2237  }
2238  mgh_header->dim1length = _freadt<int32_t>(*is);
2239  mgh_header->dim2length = _freadt<int32_t>(*is);
2240  mgh_header->dim3length = _freadt<int32_t>(*is);
2241  mgh_header->dim4length = _freadt<int32_t>(*is);
2242 
2243  mgh_header->dtype = _freadt<int32_t>(*is);
2244  mgh_header->dof = _freadt<int32_t>(*is);
2245 
2246  int unused_header_space_size_left = 256; // in bytes
2247  mgh_header->ras_good_flag = _freadt<int16_t>(*is);
2248  unused_header_space_size_left -= 2; // for the ras_good_flag
2249 
2250  // Read the RAS part of the header.
2251  if (mgh_header->ras_good_flag == 1)
2252  {
2253  mgh_header->xsize = _freadt<float>(*is);
2254  mgh_header->ysize = _freadt<float>(*is);
2255  mgh_header->zsize = _freadt<float>(*is);
2256 
2257  for (int i = 0; i < 9; i++)
2258  {
2259  mgh_header->Mdc.push_back(_freadt<float>(*is));
2260  }
2261  for (int i = 0; i < 3; i++)
2262  {
2263  mgh_header->Pxyz_c.push_back(_freadt<float>(*is));
2264  }
2265  unused_header_space_size_left -= 60;
2266  }
2267 
2268  // Advance to data part. We do not seek here because that is not
2269  // possible if the stream is gzip-wrapped with zstr, as in the read_mgz example.
2270  uint8_t discarded;
2271  while (unused_header_space_size_left > 0)
2272  {
2273  discarded = _freadt<uint8_t>(*is);
2274  unused_header_space_size_left -= 1;
2275  }
2276  (void)discarded; // Suppress warnings about unused variable.
2277  }
2278 
2283  std::vector<int32_t> _read_mgh_data_int(MghHeader *mgh_header, const std::string &filename)
2284  {
2285  if (mgh_header->dtype != MRI_INT)
2286  {
2287  std::cerr << "Expected MRI data type " << MRI_INT << ", but found " << mgh_header->dtype << ".\n";
2288  }
2289  return (_read_mgh_data<int32_t>(mgh_header, filename));
2290  }
2291 
2296  std::vector<int32_t> _read_mgh_data_int(MghHeader *mgh_header, std::istream *is)
2297  {
2298  if (mgh_header->dtype != MRI_INT)
2299  {
2300  std::cerr << "Expected MRI data type " << MRI_INT << ", but found " << mgh_header->dtype << ".\n";
2301  }
2302  return (_read_mgh_data<int32_t>(mgh_header, is));
2303  }
2304 
2309  std::vector<short> _read_mgh_data_short(MghHeader *mgh_header, const std::string &filename)
2310  {
2311  if (mgh_header->dtype != MRI_SHORT)
2312  {
2313  std::cerr << "Expected MRI data type " << MRI_SHORT << ", but found " << mgh_header->dtype << ".\n";
2314  }
2315  return (_read_mgh_data<short>(mgh_header, filename));
2316  }
2317 
2322  std::vector<short> _read_mgh_data_short(MghHeader *mgh_header, std::istream *is)
2323  {
2324  if (mgh_header->dtype != MRI_SHORT)
2325  {
2326  std::cerr << "Expected MRI data type " << MRI_SHORT << ", but found " << mgh_header->dtype << ".\n";
2327  }
2328  return (_read_mgh_data<short>(mgh_header, is));
2329  }
2330 
2337  void read_mgh_header(MghHeader *mgh_header, const std::string &filename)
2338  {
2339  std::ifstream ifs;
2340  ifs.open(filename, std::ios_base::in | std::ios::binary);
2341  if (ifs.is_open())
2342  {
2343  read_mgh_header(mgh_header, &ifs);
2344  ifs.close();
2345  }
2346  else
2347  {
2348  throw std::runtime_error("Unable to open MGH file '" + filename + "'.\n");
2349  }
2350  }
2351 
2357  template <typename T>
2358  std::vector<T> _read_mgh_data(MghHeader *mgh_header, const std::string &filename)
2359  {
2360  std::ifstream ifs;
2361  ifs.open(filename, std::ios_base::in | std::ios::binary);
2362  if (ifs.is_open())
2363  {
2364  ifs.seekg(284, ifs.beg); // skip to end of header and beginning of data
2365 
2366  int num_values = int(mgh_header->num_values());
2367  std::vector<T> data;
2368  for (int i = 0; i < num_values; i++)
2369  {
2370  data.push_back(_freadt<T>(ifs));
2371  }
2372  ifs.close();
2373  return (data);
2374  }
2375  else
2376  {
2377  throw std::runtime_error("Unable to open MGH file '" + filename + "'.\n");
2378  }
2379  }
2380 
2385  template <typename T>
2386  std::vector<T> _read_mgh_data(MghHeader *mgh_header, std::istream *is)
2387  {
2388  int num_values = int(mgh_header->num_values());
2389  std::vector<T> data;
2390  for (int i = 0; i < num_values; i++)
2391  {
2392  data.push_back(_freadt<T>(*is));
2393  }
2394  return (data);
2395  }
2396 
2401  std::vector<float> _read_mgh_data_float(MghHeader *mgh_header, const std::string &filename)
2402  {
2403  if (mgh_header->dtype != MRI_FLOAT)
2404  {
2405  std::cerr << "Expected MRI data type " << MRI_FLOAT << ", but found " << mgh_header->dtype << ".\n";
2406  }
2407  return (_read_mgh_data<float>(mgh_header, filename));
2408  }
2409 
2414  std::vector<float> _read_mgh_data_float(MghHeader *mgh_header, std::istream *is)
2415  {
2416  if (mgh_header->dtype != MRI_FLOAT)
2417  {
2418  std::cerr << "Expected MRI data type " << MRI_FLOAT << ", but found " << mgh_header->dtype << ".\n";
2419  }
2420  return (_read_mgh_data<float>(mgh_header, is));
2421  }
2422 
2427  std::vector<uint8_t> _read_mgh_data_uchar(MghHeader *mgh_header, const std::string &filename)
2428  {
2429  if (mgh_header->dtype != MRI_UCHAR)
2430  {
2431  std::cerr << "Expected MRI data type " << MRI_UCHAR << ", but found " << mgh_header->dtype << ".\n";
2432  }
2433  return (_read_mgh_data<uint8_t>(mgh_header, filename));
2434  }
2435 
2440  std::vector<uint8_t> _read_mgh_data_uchar(MghHeader *mgh_header, std::istream *is)
2441  {
2442  if (mgh_header->dtype != MRI_UCHAR)
2443  {
2444  std::cerr << "Expected MRI data type " << MRI_UCHAR << ", but found " << mgh_header->dtype << ".\n";
2445  }
2446  return (_read_mgh_data<uint8_t>(mgh_header, is));
2447  }
2448 
2462  void read_surf(Mesh *surface, const std::string &filename)
2463  {
2464  const int SURF_TRIS_MAGIC = 16777214;
2465  std::ifstream is;
2466  is.open(filename, std::ios_base::in | std::ios::binary);
2467  if (is.is_open())
2468  {
2469  int magic = _fread3(is);
2470  if (magic != SURF_TRIS_MAGIC)
2471  {
2472  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");
2473  }
2474  std::string created_line = _freadstringnewline(is);
2475  std::string comment_line = _freadstringnewline(is);
2476  int num_verts = _freadt<int32_t>(is);
2477  int num_faces = _freadt<int32_t>(is);
2478 #ifdef LIBFS_DBG_INFO
2479  std::cout << LIBFS_APPTAG << "Read surface file with " << num_verts << " vertices, " << num_faces << " faces.\n";
2480 #endif
2481  std::vector<float> vdata;
2482  for (int i = 0; i < (num_verts * 3); i++)
2483  {
2484  vdata.push_back(_freadt<float>(is));
2485  }
2486  std::vector<int> fdata;
2487  for (int i = 0; i < (num_faces * 3); i++)
2488  {
2489  fdata.push_back(_freadt<int32_t>(is));
2490  }
2491  is.close();
2492  surface->vertices = vdata;
2493  surface->faces = fdata;
2494  }
2495  else
2496  {
2497  throw std::runtime_error("Unable to open surface file '" + filename + "'.\n");
2498  }
2499  }
2500 
2513  void read_mesh(Mesh *surface, const std::string &filename)
2514  {
2515  if (fs::util::ends_with(filename, ".obj"))
2516  {
2517  fs::Mesh::from_obj(surface, filename);
2518  }
2519  else if (fs::util::ends_with(filename, ".ply"))
2520  {
2521  fs::Mesh::from_ply(surface, filename);
2522  }
2523  else if (fs::util::ends_with(filename, ".off"))
2524  {
2525  fs::Mesh::from_off(surface, filename);
2526  }
2527  else
2528  {
2529  read_surf(surface, filename);
2530  }
2531  }
2532 
2538  bool _is_bigendian()
2539  {
2540  short int number = 0x1;
2541  char *numPtr = (char *)&number;
2542  // std::cout << "Platform is big endian: " << (numPtr[0] != 1) << ".\n";
2543  return (numPtr[0] != 1);
2544  }
2545 
2551  void read_curv(Curv *curv, std::istream *is, const std::string &source_filename = "")
2552  {
2553  const std::string msg_source_file_part = source_filename.empty() ? "" : "'" + source_filename + "' ";
2554  const int CURV_MAGIC = 16777215;
2555  int magic = _fread3(*is);
2556  if (magic != CURV_MAGIC)
2557  {
2558  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");
2559  }
2560  curv->num_vertices = _freadt<int32_t>(*is);
2561  curv->num_faces = _freadt<int32_t>(*is);
2562  curv->num_values_per_vertex = _freadt<int32_t>(*is);
2563 #ifdef LIBFS_DBG_INFO
2564  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";
2565 #endif
2566  if (curv->num_values_per_vertex != 1)
2567  { // 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.
2568  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");
2569  }
2570  std::vector<float> data;
2571  for (int i = 0; i < curv->num_vertices; i++)
2572  {
2573  data.push_back(_freadt<float>(*is));
2574  }
2575  curv->data = data;
2576  }
2577 
2590  void read_curv(Curv *curv, const std::string &filename)
2591  {
2592  std::ifstream is(filename, std::fstream::in | std::fstream::binary);
2593  if (is.is_open())
2594  {
2595  read_curv(curv, &is, filename);
2596  is.close();
2597  }
2598  else
2599  {
2600  throw std::runtime_error("Could not open curv file '" + filename + "' for reading.\n");
2601  }
2602  }
2603 
2606  void _read_annot_colortable(Colortable *colortable, std::istream *is, int32_t num_entries)
2607  {
2608  int32_t num_chars_orig_filename = _freadt<int32_t>(*is); // The number of characters of the file this annot was built from.
2609 
2610  // It follows the name of the file this annot was built from. This is development metadata and irrelevant afaik. We skip it.
2611  uint8_t discarded;
2612  for (int32_t i = 0; i < num_chars_orig_filename; i++)
2613  {
2614  discarded = _freadt<uint8_t>(*is);
2615  }
2616  (void)discarded; // Suppress warnings about unused variable.
2617 
2618  int32_t num_entries_duplicated = _freadt<int32_t>(*is); // Yes, once more.
2619  if (num_entries != num_entries_duplicated)
2620  {
2621  std::cerr << "Warning: the two num_entries header fields of this annotation do not match. Use with care.\n";
2622  }
2623 
2624  int32_t entry_num_chars;
2625  for (int32_t i = 0; i < num_entries; i++)
2626  {
2627  colortable->id.push_back(_freadt<int32_t>(*is));
2628  entry_num_chars = _freadt<int32_t>(*is);
2629  colortable->name.push_back(_freadfixedlengthstring(*is, entry_num_chars, true));
2630  colortable->r.push_back(_freadt<int32_t>(*is));
2631  colortable->g.push_back(_freadt<int32_t>(*is));
2632  colortable->b.push_back(_freadt<int32_t>(*is));
2633  colortable->a.push_back(_freadt<int32_t>(*is));
2634  colortable->label.push_back(colortable->r[i] + colortable->g[i] * 256 + colortable->b[i] * 65536 + colortable->a[i] * 16777216);
2635  }
2636  }
2637 
2640  size_t _vidx_2d(size_t row, size_t column, size_t row_length = 3)
2641  {
2642  return (row + 1) * row_length - row_length + column;
2643  }
2644 
2650  void read_annot(Annot *annot, std::istream *is)
2651  {
2652 
2653  int32_t num_vertices = _freadt<int32_t>(*is);
2654  std::vector<int32_t> vertices;
2655  std::vector<int32_t> labels;
2656  for (int32_t i = 0; i < (num_vertices * 2); i++)
2657  { // The vertices and their labels are stored directly after one another: v1,v1_label,v2,v2_label,...
2658  if (i % 2 == 0)
2659  {
2660  vertices.push_back(_freadt<int32_t>(*is));
2661  }
2662  else
2663  {
2664  labels.push_back(_freadt<int32_t>(*is));
2665  }
2666  }
2667  annot->vertex_indices = vertices;
2668  annot->vertex_labels = labels;
2669  int32_t has_colortable = _freadt<int32_t>(*is);
2670  if (has_colortable == 1)
2671  {
2672  int32_t num_colortable_entries_old_format = _freadt<int32_t>(*is);
2673  if (num_colortable_entries_old_format > 0)
2674  {
2675  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");
2676  }
2677  else
2678  {
2679  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.
2680  if (colortable_format_version == 2)
2681  {
2682  int32_t num_colortable_entries = _freadt<int32_t>(*is); // This time for real.
2683  _read_annot_colortable(&annot->colortable, is, num_colortable_entries);
2684  }
2685  else
2686  {
2687  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");
2688  }
2689  }
2690  }
2691  else
2692  {
2693  throw std::domain_error("Reading annotation without colortable not supported. Maybe invalid annotation file?\n");
2694  }
2695  }
2696 
2710  void read_annot(Annot *annot, const std::string &filename)
2711  {
2712  std::ifstream is(filename, std::fstream::in | std::fstream::binary);
2713  if (is.is_open())
2714  {
2715  read_annot(annot, &is);
2716  is.close();
2717  }
2718  else
2719  {
2720  throw std::runtime_error("Could not open annot file '" + filename + "' for reading.\n");
2721  }
2722  }
2723 
2736  std::vector<float> read_curv_data(const std::string &filename)
2737  {
2738  Curv curv;
2739  read_curv(&curv, filename);
2740  return (curv.data);
2741  }
2742 
2756  std::vector<float> read_desc_data(const std::string &filename)
2757  {
2758  if (fs::util::ends_with(filename, {".MGH", ".mgh"}))
2759  {
2760  fs::Mgh mgh;
2761  fs::read_mgh(&mgh, filename);
2762  assert(mgh.header.dtype == fs::MRI_FLOAT);
2763  int num_gt_1 = 0;
2764  std::vector<int> dims = {mgh.header.dim1length, mgh.header.dim2length, mgh.header.dim3length, mgh.header.dim4length};
2765  for (size_t i = 0; i < dims.size(); i++)
2766  {
2767  if (dims[i] > 1)
2768  {
2769  num_gt_1++;
2770  }
2771  }
2772  if (num_gt_1 > 1)
2773  {
2774  std::cerr << "MGH file '" << filename << "' contains more than one non-empty dimension. Returning concatinated data.\n";
2775  }
2776  return mgh.data.data_mri_float;
2777  }
2778  else
2779  {
2780  Curv curv;
2781  read_curv(&curv, filename);
2782  return (curv.data);
2783  }
2784  }
2785 
2790  template <typename T>
2791  T _swap_endian(T u)
2792  {
2793 
2794  static_assert(CHAR_BIT == 8, "CHAR_BIT != 8");
2795 
2796  union
2797  {
2798  T u;
2799  unsigned char u8[sizeof(T)];
2800  } source, dest;
2801 
2802  source.u = u;
2803 
2804  for (size_t k = 0; k < sizeof(T); k++)
2805  dest.u8[k] = source.u8[sizeof(T) - k - 1];
2806 
2807  return (dest.u);
2808  }
2809 
2814  template <typename T>
2815  T _freadt(std::istream &is)
2816  {
2817  T t;
2818  is.read(reinterpret_cast<char *>(&t), sizeof(t));
2819  if (!_is_bigendian())
2820  {
2821  t = _swap_endian<T>(t);
2822  }
2823  return (t);
2824  }
2825 
2830  int _fread3(std::istream &is)
2831  {
2832  uint32_t i;
2833  is.read(reinterpret_cast<char *>(&i), 3);
2834  if (!_is_bigendian())
2835  {
2836  i = _swap_endian<std::uint32_t>(i);
2837  }
2838  i = ((i >> 8) & 0xffffff);
2839  return (i);
2840  }
2841 
2846  template <typename T>
2847  void _fwritet(std::ostream &os, T t)
2848  {
2849  if (!_is_bigendian())
2850  {
2851  t = _swap_endian<T>(t);
2852  }
2853  os.write(reinterpret_cast<const char *>(&t), sizeof(t));
2854  }
2855 
2856  // Write big endian 24 bit integer to a stream, extracted from the first 3 bytes of an unsigned 32 bit integer.
2857  //
2858  // THIS FUNCTION IS INTERNAL AND SHOULD NOT BE CALLED BY API CLIENTS.
2860  void _fwritei3(std::ostream &os, uint32_t i)
2861  {
2862  unsigned char b1 = (i >> 16) & 255;
2863  unsigned char b2 = (i >> 8) & 255;
2864  unsigned char b3 = i & 255;
2865 
2866  if (!_is_bigendian())
2867  {
2868  b1 = _swap_endian<unsigned char>(b1);
2869  b2 = _swap_endian<unsigned char>(b2);
2870  b3 = _swap_endian<unsigned char>(b3);
2871  }
2872 
2873  os.write(reinterpret_cast<const char *>(&b1), sizeof(b1));
2874  os.write(reinterpret_cast<const char *>(&b2), sizeof(b2));
2875  os.write(reinterpret_cast<const char *>(&b3), sizeof(b3));
2876  }
2877 
2882  std::string _freadstringnewline(std::istream &is)
2883  {
2884  std::string s;
2885  std::getline(is, s, '\n');
2886  return s;
2887  }
2888 
2892  std::string _freadfixedlengthstring(std::istream &is, size_t length, bool strip_last_char = true)
2893  {
2894  std::string str;
2895  str.resize(length);
2896  is.read(&str[0], length);
2897  if (strip_last_char)
2898  {
2899  str = str.substr(0, length - 1);
2900  }
2901  return str;
2902  }
2903 
2909  void write_curv(std::ostream &os, std::vector<float> curv_data, int32_t num_faces = 100000)
2910  {
2911  const uint32_t CURV_MAGIC = 16777215;
2912  _fwritei3(os, CURV_MAGIC);
2913  _fwritet<int32_t>(os, int(curv_data.size()));
2914  _fwritet<int32_t>(os, num_faces);
2915  _fwritet<int32_t>(os, 1); // Number of values per vertex.
2916  for (size_t i = 0; i < curv_data.size(); i++)
2917  {
2918  _fwritet<float>(os, curv_data[i]);
2919  }
2920  }
2921 
2936  void write_curv(const std::string &filename, std::vector<float> curv_data, const int32_t num_faces = 100000)
2937  {
2938  std::ofstream ofs;
2939  ofs.open(filename, std::ofstream::out | std::ofstream::binary);
2940  if (ofs.is_open())
2941  {
2942  write_curv(ofs, curv_data, num_faces);
2943  ofs.close();
2944  }
2945  else
2946  {
2947  throw std::runtime_error("Unable to open curvature file '" + filename + "' for writing.\n");
2948  }
2949  }
2950 
2956  void write_mgh(const Mgh &mgh, std::ostream &os)
2957  {
2958  _fwritet<int32_t>(os, 1); // MGH file format version
2959  _fwritet<int32_t>(os, mgh.header.dim1length);
2960  _fwritet<int32_t>(os, mgh.header.dim2length);
2961  _fwritet<int32_t>(os, mgh.header.dim3length);
2962  _fwritet<int32_t>(os, mgh.header.dim4length);
2963 
2964  _fwritet<int32_t>(os, mgh.header.dtype);
2965  _fwritet<int32_t>(os, mgh.header.dof);
2966 
2967  size_t unused_header_space_size_left = 256; // in bytes
2968  _fwritet<int16_t>(os, mgh.header.ras_good_flag);
2969  unused_header_space_size_left -= 2; // for RAS flag
2970 
2971  // Write RAS part of of header if flag is 1.
2972  if (mgh.header.ras_good_flag == 1)
2973  {
2974  _fwritet<float>(os, mgh.header.xsize);
2975  _fwritet<float>(os, mgh.header.ysize);
2976  _fwritet<float>(os, mgh.header.zsize);
2977 
2978  for (int i = 0; i < 9; i++)
2979  {
2980  _fwritet<float>(os, mgh.header.Mdc[i]);
2981  }
2982  for (int i = 0; i < 3; i++)
2983  {
2984  _fwritet<float>(os, mgh.header.Pxyz_c[i]);
2985  }
2986 
2987  unused_header_space_size_left -= 60;
2988  }
2989 
2990  for (size_t i = 0; i < unused_header_space_size_left; i++)
2991  { // Fill rest of header space.
2992  _fwritet<uint8_t>(os, 0);
2993  }
2994 
2995  // Write data
2996  size_t num_values = mgh.header.num_values();
2997  if (mgh.header.dtype == MRI_INT)
2998  {
2999  if (mgh.data.data_mri_int.size() != num_values)
3000  {
3001  throw std::logic_error("Detected mismatch of MRI_INT data size and MGH header dim length values.\n");
3002  }
3003  for (size_t i = 0; i < num_values; i++)
3004  {
3005  _fwritet<int32_t>(os, mgh.data.data_mri_int[i]);
3006  }
3007  }
3008  else if (mgh.header.dtype == MRI_FLOAT)
3009  {
3010  if (mgh.data.data_mri_float.size() != num_values)
3011  {
3012  throw std::logic_error("Detected mismatch of MRI_FLOAT data size and MGH header dim length values.\n");
3013  }
3014  for (size_t i = 0; i < num_values; i++)
3015  {
3016  _fwritet<float>(os, mgh.data.data_mri_float[i]);
3017  }
3018  }
3019  else if (mgh.header.dtype == MRI_UCHAR)
3020  {
3021  if (mgh.data.data_mri_uchar.size() != num_values)
3022  {
3023  throw std::logic_error("Detected mismatch of MRI_UCHAR data size and MGH header dim length values.\n");
3024  }
3025  for (size_t i = 0; i < num_values; i++)
3026  {
3027  _fwritet<uint8_t>(os, mgh.data.data_mri_uchar[i]);
3028  }
3029  }
3030  else if (mgh.header.dtype == MRI_SHORT)
3031  {
3032  if (mgh.data.data_mri_short.size() != num_values)
3033  {
3034  throw std::logic_error("Detected mismatch of MRI_SHORT data size and MGH header dim length values.\n");
3035  }
3036  for (size_t i = 0; i < num_values; i++)
3037  {
3038  _fwritet<short>(os, mgh.data.data_mri_short[i]);
3039  }
3040  }
3041  else
3042  {
3043  throw std::domain_error("Unsupported MRI data type " + std::to_string(mgh.header.dtype) + ", cannot write MGH data.\n");
3044  }
3045  }
3046 
3062  void write_mgh(const Mgh &mgh, const std::string &filename)
3063  {
3064  std::ofstream ofs;
3065  ofs.open(filename, std::ofstream::out | std::ofstream::binary);
3066  if (ofs.is_open())
3067  {
3068  write_mgh(mgh, ofs);
3069  ofs.close();
3070  }
3071  else
3072  {
3073  throw std::runtime_error("Unable to open MGH file '" + filename + "' for writing.\n");
3074  }
3075  }
3076 
3083  struct Label
3084  {
3085 
3087  Label() {}
3088 
3090  Label(std::vector<int> vertices, std::vector<float> values)
3091  {
3092  assert(vertices.size() == values.size());
3093  vertex = vertices;
3094  value = values;
3095  coord_x = std::vector<float>(vertices.size(), 0.0f);
3096  coord_y = std::vector<float>(vertices.size(), 0.0f);
3097  coord_z = std::vector<float>(vertices.size(), 0.0f);
3098  }
3099 
3101  Label(std::vector<int> vertices)
3102  {
3103  vertex = vertices;
3104  value = std::vector<float>(vertices.size(), 0.0f);
3105  coord_x = std::vector<float>(vertices.size(), 0.0f);
3106  coord_y = std::vector<float>(vertices.size(), 0.0f);
3107  coord_z = std::vector<float>(vertices.size(), 0.0f);
3108  }
3109 
3110  std::vector<int> vertex;
3111  std::vector<float> coord_x;
3112  std::vector<float> coord_y;
3113  std::vector<float> coord_z;
3114  std::vector<float> value;
3115 
3117  std::vector<bool> vert_in_label(size_t surface_num_verts) const
3118  {
3119  if (surface_num_verts < this->vertex.size())
3120  { // nonsense, so we warn (but don't throw, maybe the user really wants this).
3121  std::cerr << "Invalid number of vertices for surface, must be at least " << this->vertex.size() << "\n";
3122  }
3123  std::vector<bool> is_in = std::vector<bool>(surface_num_verts, false);
3124 
3125  for (size_t i = 0; i < this->vertex.size(); i++)
3126  {
3127  is_in[this->vertex[i]] = true;
3128  }
3129  return (is_in);
3130  }
3131 
3133  size_t num_entries() const
3134  {
3135  size_t num_ent = this->vertex.size();
3136  if (this->coord_x.size() != num_ent || this->coord_y.size() != num_ent || this->coord_z.size() != num_ent || this->value.size() != num_ent)
3137  {
3138  std::cerr << "Inconsistent label: sizes of property vectors do not match.\n";
3139  }
3140  return (num_ent);
3141  }
3142  };
3143 
3150  void write_surf(std::vector<float> vertices, std::vector<int32_t> faces, std::ostream &os)
3151  {
3152  const uint32_t SURF_TRIS_MAGIC = 16777214;
3153  _fwritei3(os, SURF_TRIS_MAGIC);
3154  std::string created_and_comment_lines = "Created by fslib\n\n";
3155  os << created_and_comment_lines;
3156  _fwritet<int32_t>(os, int(vertices.size() / 3)); // number of vertices
3157  _fwritet<int32_t>(os, int(faces.size() / 3)); // number of faces
3158  for (size_t i = 0; i < vertices.size(); i++)
3159  {
3160  _fwritet<float>(os, vertices[i]);
3161  }
3162  for (size_t i = 0; i < faces.size(); i++)
3163  {
3164  _fwritet<int32_t>(os, faces[i]);
3165  }
3166  }
3167 
3181  void write_surf(std::vector<float> vertices, std::vector<int32_t> faces, const std::string &filename)
3182  {
3183  std::ofstream ofs;
3184  ofs.open(filename, std::ofstream::out | std::ofstream::binary);
3185  if (ofs.is_open())
3186  {
3187  write_surf(vertices, faces, ofs);
3188  ofs.close();
3189  }
3190  else
3191  {
3192  throw std::runtime_error("Unable to open surf file '" + filename + "' for writing.\n");
3193  }
3194  }
3195 
3208  void write_surf(const Mesh &mesh, const std::string &filename)
3209  {
3210  std::ofstream ofs;
3211  ofs.open(filename, std::ofstream::out | std::ofstream::binary);
3212  if (ofs.is_open())
3213  {
3214  write_surf(mesh.vertices, mesh.faces, ofs);
3215  ofs.close();
3216  }
3217  else
3218  {
3219  throw std::runtime_error("Unable to open surf file '" + filename + "' for writing.\n");
3220  }
3221  }
3222 
3229  void read_label(Label *label, std::istream *is)
3230  {
3231  std::string line;
3232  int line_idx = -1;
3233  size_t num_entries_header = 0; // number of vertices/voxels according to header
3234  size_t num_entries = 0; // number of vertices/voxels for which the file contains label entries.
3235  while (std::getline(*is, line))
3236  {
3237  line_idx += 1;
3238  std::istringstream iss(line);
3239  if (line_idx == 0)
3240  {
3241  continue; // skip comment.
3242  }
3243  else
3244  {
3245  if (line_idx == 1)
3246  {
3247  if (!(iss >> num_entries_header))
3248  {
3249  throw std::domain_error("Could not parse entry count from label file, invalid format.\n");
3250  }
3251  }
3252  else
3253  {
3254  int vertex;
3255  float x, y, z, value;
3256  if (!(iss >> vertex >> x >> y >> z >> value))
3257  {
3258  throw std::domain_error("Could not parse line " + std::to_string(line_idx + 1) + " of label file, invalid format.\n");
3259  }
3260  label->vertex.push_back(vertex);
3261  label->coord_x.push_back(x);
3262  label->coord_y.push_back(y);
3263  label->coord_z.push_back(z);
3264  label->value.push_back(value);
3265  num_entries++;
3266  }
3267  }
3268  }
3269  if (num_entries != num_entries_header)
3270  {
3271  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");
3272  }
3273  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)
3274  {
3275  throw std::domain_error("Expected " + std::to_string(num_entries) + " entries in all Label vectors, but some did not match.\n");
3276  }
3277  }
3278 
3292  void read_label(Label *label, const std::string &filename)
3293  {
3294  std::ifstream infile(filename, std::fstream::in);
3295  if (infile.is_open())
3296  {
3297  read_label(label, &infile);
3298  infile.close();
3299  }
3300  else
3301  {
3302  throw std::runtime_error("Could not open label file '" + filename + "' for reading.\n");
3303  }
3304  }
3305 
3310  void write_label(const Label &label, std::ostream &os)
3311  {
3312  const size_t num_entries = label.num_entries();
3313  os << "#!ascii label from subject anonymous\n"
3314  << num_entries << "\n";
3315  for (size_t i = 0; i < num_entries; i++)
3316  {
3317  os << label.vertex[i] << " " << label.coord_x[i] << " " << label.coord_y[i] << " " << label.coord_z[i] << " " << label.value[i] << "\n";
3318  }
3319  }
3320 
3334  void write_label(const Label &label, const std::string &filename)
3335  {
3336  std::ofstream ofs;
3337  ofs.open(filename, std::ofstream::out);
3338  if (ofs.is_open())
3339  {
3340  write_label(label, ofs);
3341  ofs.close();
3342  }
3343  else
3344  {
3345  throw std::runtime_error("Unable to open label file '" + filename + "' for writing.\n");
3346  }
3347  }
3348 
3364  void write_mesh(const Mesh &mesh, const std::string &filename)
3365  {
3366  if (fs::util::ends_with(filename, {".ply", ".PLY"}))
3367  {
3368  mesh.to_ply_file(filename);
3369  }
3370  else if (fs::util::ends_with(filename, {".obj", ".OBJ"}))
3371  {
3372  mesh.to_obj_file(filename);
3373  }
3374  else if (fs::util::ends_with(filename, {".off", ".OFF"}))
3375  {
3376  mesh.to_off_file(filename);
3377  }
3378  else
3379  {
3380  fs::write_surf(mesh, filename);
3381  }
3382  }
3383 
3384 } // 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:2909
std::vector< float > Mdc
matrix
Definition: libfs.h:2013
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:500
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:1874
size_t num_entries() const
Get the number of enties (regions) in this Colortable.
Definition: libfs.h:1834
Mgh(Curv curv)
Definition: libfs.h:2038
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:3111
std::string to_off(const std::vector< uint8_t > col) const
Return string representing the mesh in PLY format.
Definition: libfs.h:1731
Models a FreeSurfer curv file that contains per-vertex float data.
Definition: libfs.h:1796
const int MRI_SHORT
MRI data type representing a 16 bit signed integer.
Definition: libfs.h:451
std::vector< float > Pxyz_c
x,y,z coordinates of central vertex
Definition: libfs.h:2014
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:3112
int32_t dof
typically ignored
Definition: libfs.h:2001
unsigned int d2
size of data along 2nd dimension
Definition: libfs.h:2090
const std::string LOGTAG_EXCESSIVE
Logging threshold for warning messages.
Definition: libfs.h:190
An annotation, also known as a brain surface parcellation. Assigns to each vertex a region...
Definition: libfs.h:1872
const std::string LOGTAG_VERBOSE
Logging threshold for warning messages.
Definition: libfs.h:187
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:2551
edge_set as_edgelist() const
Return edge list representation of this mesh.
Definition: libfs.h:709
Definition: libfs.h:3083
MghHeader()
Empty default constuctor.
Definition: libfs.h:1978
std::vector< int32_t > label
label integer computed from rgba values. Maps to the Annot.vertex_label field.
Definition: libfs.h:1831
Array4D(MghHeader *mgh_header)
Constructor for creating an empty 4D array based on dimensions specified in an fs::MghHeader.
Definition: libfs.h:2059
std::string to_obj() const
Return string representing the mesh in Wavefront Object (.obj) format.
Definition: libfs.h:645
std::string time_tag(std::chrono::system_clock::time_point t)
Get current time as string, e.g. for log messages.
Definition: libfs.h:160
static void from_obj(Mesh *mesh, const std::string &filename)
Read a brainmesh from a Wavefront object format mesh file.
Definition: libfs.h:1204
const std::string LOGTAG_WARNING
Logging threshold for warning messages.
Definition: libfs.h:181
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:820
bool file_exists(const std::string &name)
Check whether a file exists (can be read) at given path.
Definition: libfs.h:344
const std::string LOGTAG_INFO
Logging threshold for warning messages.
Definition: libfs.h:184
A simple 4D array datastructure, useful for representing volume data.
Definition: libfs.h:2053
std::vector< float > data
The curvature data, one value per vertex. Something like the cortical thickness at each vertex...
Definition: libfs.h:1813
static void from_ply(Mesh *mesh, std::istream *is)
Read a brainmesh from a Stanford PLY format stream.
Definition: libfs.h:1362
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:1540
std::vector< T > data
the data, as a 1D vector. Use fs::Array4D::at for easy access in 4D.
Definition: libfs.h:2093
std::vector< int32_t > b
green channel of RGBA color
Definition: libfs.h:1829
static void from_ply(Mesh *mesh, const std::string &filename)
Read a brainmesh from a Stanford PLY format mesh file.
Definition: libfs.h:1483
std::vector< int32_t > g
blue channel of RGBA color
Definition: libfs.h:1828
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:2736
void write_mgh(const Mgh &mgh, std::ostream &os)
Write MGH data to a stream.
Definition: libfs.h:2956
static void from_off(Mesh *mesh, const std::string &filename)
Read a brainmesh from an OFF format mesh file.
Definition: libfs.h:1340
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:3117
static std::vector< float > curv_data_for_orig_mesh(const std::vector< float > data_submesh, const std::unordered_map< int32_t, int32_t > submesh_to_orig_mapping, const int32_t orig_mesh_num_vertices, const float fill_value=std::numeric_limits< float >::quiet_NaN())
Given per-vertex data for a submesh, add NAN values inbetween to restore the original mesh size...
Definition: libfs.h:1064
MghHeader(Curv curv)
Definition: libfs.h:1979
MghHeader(std::vector< float > curv_data)
Definition: libfs.h:1987
int32_t dim1length
size of data along 1st dimension
Definition: libfs.h:1995
Models a triangular mesh, used for brain surface meshes.
Definition: libfs.h:480
const int MRI_UCHAR
MRI data type representing an 8 bit unsigned integer.
Definition: libfs.h:442
float xsize
size of voxels along 1st axis (x or r)
Definition: libfs.h:2010
size_t num_entries() const
Return the number of entries (vertices/voxels) in this label.
Definition: libfs.h:3133
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:2074
Models the data of an MGH file. Currently these are 1D vectors, but one can compute the 4D array usin...
Definition: libfs.h:2018
void read_label(Label *label, std::istream *is)
Read a FreeSurfer ASCII label from a stream.
Definition: libfs.h:3229
void to_ply_file(const std::string &filename) const
Export this mesh to a file in Stanford PLY format.
Definition: libfs.h:1699
MghData(std::vector< uint8_t > curv_data)
constructor to create MghData from MRI_UCHAR (uint8_t) data.
Definition: libfs.h:2022
Mesh(std::vector< float > cvertices, std::vector< int32_t > cfaces)
Construct a Mesh from the given vertices and faces.
Definition: libfs.h:484
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:1943
std::vector< std::vector< bool > > as_adjmatrix() const
Return adjacency matrix representation of this mesh.
Definition: libfs.h:670
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:2068
size_t num_values() const
Compute the number of values based on the dim*length header fields.
Definition: libfs.h:2005
Array4D(Mgh *mgh)
Constructor for creating an empty 4D array based on dimensions specified in the header of an fs::Mgh...
Definition: libfs.h:2062
unsigned int d1
size of data along 1st dimension
Definition: libfs.h:2089
Models the header of an MGH file.
Definition: libfs.h:1976
Definition: libfs.h:132
std::string to_ply() const
Return string representing the mesh in PLY format. Overload that works without passing a color vector...
Definition: libfs.h:1629
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:1823
const int MRI_FLOAT
MRI data type representing a 32 bit float.
Definition: libfs.h:448
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:3150
std::vector< std::vector< size_t > > as_adjlist(const bool via_matrix=true) const
Return adjacency list representation of this mesh.
Definition: libfs.h:738
static fs::Mesh construct_pyramid()
Construct and return a simple pyramidal mesh.
Definition: libfs.h:550
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:797
std::vector< int32_t > id
internal region index
Definition: libfs.h:1825
void write_mesh(const Mesh &mesh, const std::string &filename)
Write a mesh to a file in different formats.
Definition: libfs.h:3364
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:1895
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:2029
const int MRI_INT
MRI data type representing a 32 bit signed integer.
Definition: libfs.h:445
float zsize
size of voxels along 3rd axis (z or s)
Definition: libfs.h:2012
static void from_obj(Mesh *mesh, std::istream *is)
Read a brainmesh from a Wavefront object format stream.
Definition: libfs.h:1098
Curv(std::vector< float > curv_data)
Construct a Curv instance from the given per-vertex data.
Definition: libfs.h:1800
unsigned int d3
size of data along 3rd dimension
Definition: libfs.h:2091
Models a whole MGH file.
Definition: libfs.h:2033
size_t num_vertices() const
Get the number of vertices of this parcellation (or the associated surface).
Definition: libfs.h:1931
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:1819
void write_surf(const Mesh &mesh, const std::string &filename)
Write a mesh to a binary file in FreeSurfer surf format.
Definition: libfs.h:3208
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:419
void read_annot(Annot *annot, std::istream *is)
Read a FreeSurfer annotation or brain surface parcellation from an annot stream.
Definition: libfs.h:2650
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:1816
std::vector< float > vertex_coords(const size_t vertex) const
Get all coordinates of the vertex, given by its index.
Definition: libfs.h:1585
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:1227
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:1561
Mgh(std::vector< float > curv_data)
Definition: libfs.h:2043
std::vector< std::string > read_subjectsfile(const std::string &filename)
Read a vector of subject identifiers from a FreeSurfer subjects file.
Definition: libfs.h:2170
std::vector< float > value
the value of the label, can represent continuous data like a p-value, or sometimes simply 1...
Definition: libfs.h:3114
std::vector< int32_t > r
red channel of RGBA color
Definition: libfs.h:1827
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:2124
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:1709
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:696
int32_t dtype
the MRI data type
Definition: libfs.h:2000
size_t num_faces() const
Return the number of faces in this mesh.
Definition: libfs.h:1523
void write_label(const Label &label, std::ostream &os)
Write label data to a stream.
Definition: libfs.h:3310
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:3110
std::string to_ply(const std::vector< uint8_t > col) const
Return string representing the mesh in PLY format.
Definition: libfs.h:1645
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:1963
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:1910
static fs::Mesh construct_cube()
Construct and return a simple cube mesh.
Definition: libfs.h:513
int32_t dim4length
size of data along 4th dimension
Definition: libfs.h:1998
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:2026
void read_mgh(Mgh *mgh, std::istream *is)
Read MGH data from a stream.
Definition: libfs.h:2193
MghData(std::vector< float > curv_data)
constructor to create MghData from MRI_FLOAT (float) data.
Definition: libfs.h:2024
MghData(std::vector< short > curv_data)
constructor to create MghData from MRI_SHORT (short) data.
Definition: libfs.h:2023
int32_t dim2length
size of data along 2nd dimension
Definition: libfs.h:1996
const std::string LOGTAG_ERROR
Logging threshold for error messages.
Definition: libfs.h:178
Mesh()
Construct an empty Mesh.
Definition: libfs.h:498
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:1007
int32_t dim3length
size of data along 3rd dimension
Definition: libfs.h:1997
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:2027
Label(std::vector< int > vertices)
Construct a Label from the given vertices / voxel numbers.
Definition: libfs.h:3101
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:941
void to_obj_file(const std::string &filename) const
Export this mesh to a file in Wavefront OBJ format.
Definition: libfs.h:986
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:2462
Curv()
Construct an empty Curv instance.
Definition: libfs.h:1807
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:372
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:1879
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:1789
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:2513
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:1845
int32_t num_faces
The number of faces of the mesh to which this belongs, typically irrelevant and ignored.
Definition: libfs.h:1810
float ysize
size of voxels along 2nd axis (y or a)
Definition: libfs.h:2011
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:2028
unsigned int num_values() const
Get number of values/voxels.
Definition: libfs.h:2084
Colortable colortable
A Colortable defining the regions (most importantly, the region name and visualization color)...
Definition: libfs.h:1876
MghHeader header
Header for this MGH instance.
Definition: libfs.h:2035
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:2002
void log(std::string const &message, std::string const loglevel="INFO")
Log a message, goes to stdout.
Definition: libfs.h:195
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:1611
std::vector< T > vflatten(std::vector< std::vector< T >> values)
Flatten 2D vector.
Definition: libfs.h:273
Label(std::vector< int > vertices, std::vector< float > values)
Construct a Label from the given vertices / voxel numbers and values.
Definition: libfs.h:3090
std::vector< int32_t > a
alpha channel of RGBA color
Definition: libfs.h:1830
const std::string LOGTAG_CRITICAL
Logging threshold for critical messages.
Definition: libfs.h:175
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:1858
Label()
Default constructor for a label.
Definition: libfs.h:3087
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:583
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:501
void to_off_file(const std::string &filename) const
Export this mesh to a file in OFF format.
Definition: libfs.h:1782
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:1875
MghData(Curv curv)
constructor to create MghData from a Curv instance
Definition: libfs.h:2025
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:3113
Array4D(unsigned int d1, unsigned int d2, unsigned int d3, unsigned int d4)
Constructor for creating an empty 4D array of the given dimensions.
Definition: libfs.h:2056
MghData(std::vector< int32_t > curv_data)
constructor to create MghData from MRI_INT (int32_t) data.
Definition: libfs.h:2021
std::vector< float > read_desc_data(const std::string &filename)
Read per-vertex brain morphometry data from a FreeSurfer curv format or MGH format file...
Definition: libfs.h:2756
Mgh()
Empty default constuctor.
Definition: libfs.h:2037
std::string to_off() const
Return string representing the mesh in OFF format. Overload that works without passing a color vector...
Definition: libfs.h:1722
std::vector< std::string > name
region name
Definition: libfs.h:1826
MghData data
4D data for this MGH instance.
Definition: libfs.h:2036
unsigned int d4
size of data along 4th dimension
Definition: libfs.h:2092
size_t num_vertices() const
Return the number of vertices in this mesh.
Definition: libfs.h:1509
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:2337