Blue Brain BioExplorer
VolumeLoader.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2015-2024, EPFL/Blue Brain Project
3  * All rights reserved. Do not distribute without permission.
4  *
5  * This file is part of Blue Brain BioExplorer <https://github.com/BlueBrain/BioExplorer>
6  *
7  * This library is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU Lesser General Public License version 3.0 as published
9  * by the Free Software Foundation.
10  *
11  * This library is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13  * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
14  * details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this library; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20 
21 #include "VolumeLoader.h"
22 
29 
30 #include <fstream>
31 #include <map>
32 #include <sstream>
33 #include <string>
34 
35 namespace
36 {
37 using Property = core::Property;
38 const Property PROP_DIMENSIONS = {"dimensions", std::array<int32_t, 3>({{0, 0, 0}}), {"Dimensions"}};
39 const Property PROP_SPACING = {"spacing", std::array<double, 3>({{1, 1, 1}}), {"Spacing"}};
40 const Property PROP_TYPE = {"type",
42  core::enumNames<core::DataType>(),
43  {"Type"}};
44 } // namespace
45 
46 namespace core
47 {
48 namespace
49 {
50 template <size_t M, typename T>
51 std::string to_string(const glm::vec<M, T>& vec)
52 {
53  std::stringstream stream;
54  stream << vec;
55  return stream.str();
56 }
57 
58 template <typename T>
59 std::array<T, 3> parseArray3(const std::string& str, std::function<T(std::string)> conv)
60 {
61  const auto v = core::string_utils::split(str, ' ');
62  if (v.size() != 3)
63  throw std::runtime_error("Not exactly 3 values for mhd array");
64  return {{conv(v[0]), conv(v[1]), conv(v[2])}};
65 }
66 
67 std::map<std::string, std::string> parseMHD(const std::string& filename)
68 {
69  std::ifstream infile(filename);
70  if (!infile.good())
71  throw std::runtime_error("Could not open file " + filename);
72 
73  // Sample MHD File:
74  //
75  // ObjectType = Image
76  // DimSize = 1 2 3
77  // ElementSpacing = 0.1 0.2 0.3
78  // ElementType = MET_USHORT
79  // ElementDataFile = BS39.raw
80 
81  std::map<std::string, std::string> result;
82  std::string line;
83  size_t ctr = 1;
84  while (std::getline(infile, line))
85  {
86  const auto v = string_utils::split(line, '=');
87  if (v.size() != 2)
88  throw std::runtime_error("Could not parse line " + std::to_string(ctr));
89  auto key = v[0];
90  auto value = v[1];
91  string_utils::trim(key);
92  string_utils::trim(value);
93 
94  result[key] = value;
95  ++ctr;
96  }
97 
98  return result;
99 }
100 
101 DataType dataTypeFromMET(const std::string& type)
102 {
103  if (type == "MET_FLOAT")
104  return DataType::FLOAT;
105  else if (type == "MET_DOUBLE")
106  return DataType::DOUBLE;
107  else if (type == "MET_UCHAR")
108  return DataType::UINT8;
109  else if (type == "MET_USHORT")
110  return DataType::UINT16;
111  else if (type == "MET_UINT")
112  return DataType::UINT32;
113  else if (type == "MET_CHAR")
114  return DataType::INT8;
115  else if (type == "MET_SHORT")
116  return DataType::INT16;
117  else if (type == "MET_INT")
118  return DataType::INT32;
119  else
120  throw std::runtime_error("Unknown data type " + type);
121 }
122 
123 Vector2f dataRangeFromType(DataType type)
124 {
125  switch (type)
126  {
127  case DataType::UINT8:
128  return {std::numeric_limits<uint8_t>::min(), std::numeric_limits<uint8_t>::max()};
129  case DataType::UINT16:
130  return {std::numeric_limits<uint16_t>::min(), std::numeric_limits<uint16_t>::max()};
131  case DataType::UINT32:
132  return {std::numeric_limits<uint32_t>::min() / 100, std::numeric_limits<uint32_t>::max() / 100};
133  case DataType::INT8:
134  return {std::numeric_limits<int8_t>::min(), std::numeric_limits<int8_t>::max()};
135  case DataType::INT16:
136  return {std::numeric_limits<int16_t>::min(), std::numeric_limits<int16_t>::max()};
137  case DataType::INT32:
138  return {std::numeric_limits<int32_t>::min() / 100, std::numeric_limits<int32_t>::max() / 100};
139  case DataType::FLOAT:
140  case DataType::DOUBLE:
141  default:
142  return {0, 1};
143  }
144 }
145 } // namespace
146 
148  : Loader(scene)
149 {
150 }
151 
152 bool RawVolumeLoader::isSupported(const std::string& storage, const std::string& extension) const
153 {
154  return extension == "raw";
155 }
156 
158  const PropertyMap& properties) const
159 {
160  return _loadVolume(blob.name, callback, properties,
161  [&blob](auto volume) { volume->mapData(std::move(blob.data)); });
162 }
163 
164 ModelDescriptorPtr RawVolumeLoader::importFromStorage(const std::string& storage, const LoaderProgress& callback,
165  const PropertyMap& properties) const
166 {
167  return _loadVolume(storage, callback, properties, [storage](auto volume) { volume->mapData(storage); });
168 }
169 
170 ModelDescriptorPtr RawVolumeLoader::_loadVolume(const std::string& filename, const LoaderProgress& callback,
171  const PropertyMap& propertiesTmp,
172  const std::function<void(SharedDataVolumePtr)>& mapData) const
173 {
174  // Fill property map since the actual property types are known now.
175  PropertyMap properties = getProperties();
176  properties.merge(propertiesTmp);
177 
178  callback.updateProgress("Parsing volume file ...", 0.f);
179 
180  const auto dimensions = toGlmVec(properties.getProperty<std::array<int32_t, 3>>(PROP_DIMENSIONS.name));
181  const auto spacing = toGlmVec(properties.getProperty<std::array<double, 3>>(PROP_SPACING.name));
182  const auto type = stringToEnum<DataType>(properties.getProperty<std::string>(PROP_TYPE.name));
183 
184  if (glm::compMul(dimensions) == 0)
185  throw std::runtime_error("Volume dimensions are empty");
186 
187  auto dataRange = dataRangeFromType(type);
188  auto model = _scene.createModel();
189  auto volume = model->createSharedDataVolume(dimensions, spacing, type);
190  volume->setDataRange(dataRange);
191 
192  callback.updateProgress("Loading voxels ...", 0.5f);
193  mapData(volume);
194 
195  callback.updateProgress("Adding model ...", 1.f);
196  model->addVolume(VOLUME_MATERIAL_ID, volume);
197 
198  Transformation transformation;
199  transformation.setRotationCenter(model->getBounds().getCenter());
200  dataRange = volume->getDataRange();
201  auto modelDescriptor = std::make_shared<ModelDescriptor>(
202  std::move(model), filename,
203  ModelMetadata{{"Data type", properties.getProperty<std::string>(PROP_TYPE.name)},
204  {"Dimensions", std::to_string(dimensions.x) + "," + std::to_string(dimensions.y) + "," +
205  std::to_string(dimensions.z)},
206  {"Element Spacing",
207  std::to_string(spacing.x) + "," + std::to_string(spacing.y) + "," + std::to_string(spacing.z)},
208  {"Data range", std::to_string(dataRange.x) + "," + std::to_string(dataRange.y)}});
209  modelDescriptor->setTransformation(transformation);
210  return modelDescriptor;
211 }
212 
213 std::string RawVolumeLoader::getName() const
214 {
215  return "raw-volume";
216 }
217 
218 std::vector<std::string> RawVolumeLoader::getSupportedStorage() const
219 {
220  return {"raw"};
221 }
222 
224 {
225  PropertyMap pm;
226  pm.setProperty(PROP_DIMENSIONS);
227  pm.setProperty(PROP_SPACING);
228  pm.setProperty(PROP_TYPE);
229  return pm;
230 }
232 
234  : Loader(scene)
235 {
236 }
237 
238 bool MHDVolumeLoader::isSupported(const std::string& storage, const std::string& extension) const
239 {
240  return extension == "mhd";
241 }
242 
244  const PropertyMap& properties) const
245 {
246  throw std::runtime_error("Volume loading from blob is not supported");
247 }
248 
249 ModelDescriptorPtr MHDVolumeLoader::importFromStorage(const std::string& storage, const LoaderProgress& callback,
250  const PropertyMap&) const
251 {
252  std::string volumeFile = storage;
253  const auto mhd = parseMHD(storage);
254 
255  // Check all keys present
256  for (const auto key : {"ObjectType", "DimSize", "ElementSpacing", "ElementType", "ElementDataFile"})
257  if (mhd.find(key) == mhd.end())
258  throw std::runtime_error("Missing key " + std::string(key));
259 
260  if (mhd.at("ObjectType") != "Image")
261  throw std::runtime_error("Wrong object type for mhd file");
262 
263  const auto dimensions = parseArray3<int32_t>(mhd.at("DimSize"), [](const auto& s) { return stoi(s); });
264  const auto spacing = parseArray3<double>(mhd.at("ElementSpacing"), [](const auto& s) { return stod(s); });
265  const auto type = dataTypeFromMET(mhd.at("ElementType"));
266 
267  fs::path path = mhd.at("ElementDataFile");
268  if (!path.is_absolute())
269  {
270  auto basePath = fs::path(storage).parent_path();
271  path = fs::canonical(basePath / path);
272  }
273  volumeFile = path.string();
274 
275  PropertyMap properties;
276  properties.setProperty({PROP_DIMENSIONS.name, dimensions, PROP_DIMENSIONS.metaData});
277  properties.setProperty({PROP_SPACING.name, spacing, PROP_SPACING.metaData});
278  properties.setProperty({PROP_TYPE.name, core::enumToString(type), PROP_TYPE.enums, PROP_TYPE.metaData});
279 
280  return RawVolumeLoader(_scene).importFromStorage(volumeFile, callback, properties);
281 }
282 
283 std::string MHDVolumeLoader::getName() const
284 {
285  return "mhd-volume";
286 }
287 
288 std::vector<std::string> MHDVolumeLoader::getSupportedStorage() const
289 {
290  return {"mhd"};
291 }
292 } // namespace core
void updateProgress(const std::string &message, const float fraction) const
Definition: Loader.h:58
Scene & _scene
Definition: Loader.h:126
ModelDescriptorPtr importFromBlob(Blob &&blob, const LoaderProgress &callback, const PropertyMap &properties) const final
bool isSupported(const std::string &storage, const std::string &extension) const final
ModelDescriptorPtr importFromStorage(const std::string &storage, const LoaderProgress &callback, const PropertyMap &properties) const final
MHDVolumeLoader(Scene &scene)
std::vector< std::string > getSupportedStorage() const final
std::string getName() const final
void setProperty(const Property &newProperty)
Definition: PropertyMap.h:307
T getProperty(const std::string &name, T valIfNotFound) const
Definition: PropertyMap.h:323
void merge(const PropertyMap &input)
PropertyMap getProperties() const final
ModelDescriptorPtr importFromBlob(Blob &&blob, const LoaderProgress &callback, const PropertyMap &properties) const final
std::vector< std::string > getSupportedStorage() const final
std::string getName() const final
RawVolumeLoader(Scene &scene)
bool isSupported(const std::string &storage, const std::string &extension) const final
ModelDescriptorPtr importFromStorage(const std::string &storage, const LoaderProgress &callback, const PropertyMap &properties) const final
Scene object This object contains collections of geometries, materials and light sources that are use...
Definition: Scene.h:43
virtual PLATFORM_API ModelPtr createModel() const =0
Factory method to create an engine-specific model.
Defines the translation, rotation and scale parameters to be applied to a scene asset.
void setRotationCenter(const Vector3d &value)
void trim(std::string &s)
std::vector< std::string > split(const std::string &s, char delim)
std::string enumToString(const EnumT v)
Definition: EnumUtils.h:64
std::map< std::string, std::string > ModelMetadata
Definition: Types.h:104
glm::vec2 Vector2f
Definition: MathTypes.h:136
std::shared_ptr< SharedDataVolume > SharedDataVolumePtr
Definition: Types.h:154
DataType
Definition: Types.h:344
std::shared_ptr< ModelDescriptor > ModelDescriptorPtr
Definition: Types.h:112
glm::vec< M, T > toGlmVec(const std::array< T, M > &input)
Definition: Utils.h:37