iMSTK
Interactive Medical Simulation Toolkit
imstkLevelSetModel.cpp
1 /*
2 ** This file is part of the Interactive Medical Simulation Toolkit (iMSTK)
3 ** iMSTK is distributed under the Apache License, Version 2.0.
4 ** See accompanying NOTICE for details.
5 */
6 
7 #include "imstkDataArray.h"
8 #include "imstkImageData.h"
9 #include "imstkLevelSetModel.h"
10 #include "imstkLogger.h"
11 #include "imstkMacros.h"
12 #include "imstkParallelFor.h"
13 #include "imstkTaskGraph.h"
14 
15 namespace imstk
16 {
17 LevelSetModel::LevelSetModel() :
18  m_config(std::make_shared<LevelSetModelConfig>())
19 {
20  // If given an image data
21  m_validGeometryTypes = { "ImageData", "SignedDistanceField" };
22 
23  // Expresses a location to compute velocities, so other methods may know when velocities are done
24  m_generateVelocitiesBegin = std::make_shared<TaskNode>(nullptr, "Compute Velocities Begin");
25  m_generateVelocitiesEnd = std::make_shared<TaskNode>(nullptr, "Compute Velocities End");
26 
27  // By default the level set defines a function for evolving the distances, this can be removed in subclasses
28  m_evolveQuantitiesNodes.push_back(std::make_shared<TaskNode>(std::bind(&LevelSetModel::evolve, this), "Evolve Distances"));
29 
30  m_taskGraph->addNode(m_generateVelocitiesBegin);
31  m_taskGraph->addNode(m_generateVelocitiesEnd);
32  m_taskGraph->addNode(m_evolveQuantitiesNodes.back());
33 }
34 
35 bool
37 {
38  if (m_geometry == nullptr)
39  {
40  LOG(WARNING) << "Levelset missing geometry";
41  return false;
42  }
43 
44  if (auto imageData = std::dynamic_pointer_cast<ImageData>(m_geometry))
45  {
46  if (imageData->getScalarType() != IMSTK_DOUBLE)
47  {
48  LOG(WARNING) << "Levelset only works with double image types";
49  return false;
50  }
51 
52  m_mesh = std::make_shared<SignedDistanceField>(imageData);
53  }
54  else
55  {
56  m_mesh = std::dynamic_pointer_cast<ImplicitGeometry>(m_geometry);
57  }
58  m_forwardGrad.setFunction(m_mesh);
59  m_backwardGrad.setFunction(m_mesh);
60  m_curvature.setFunction(m_mesh);
61 
62  if (auto sdf = std::dynamic_pointer_cast<SignedDistanceField>(m_mesh))
63  {
64  std::shared_ptr<ImageData> sdfImage = sdf->getImage();
65  if (!m_config->m_sparseUpdate)
66  {
67  m_gradientMagnitudes = std::make_shared<ImageData>();
68  m_gradientMagnitudes->allocate(IMSTK_DOUBLE, 2, sdfImage->getDimensions(), sdfImage->getSpacing(), sdfImage->getOrigin());
69 
70  /* m_curvatures = std::make_shared<ImageData>();
71  m_curvatures->allocate(IMSTK_DOUBLE, 1, sdfImage->getDimensions(), sdfImage->getSpacing(), sdfImage->getOrigin());*/
72 
73  m_velocities = std::make_shared<ImageData>();
74  m_velocities->allocate(IMSTK_DOUBLE, 1, sdfImage->getDimensions(), sdfImage->getSpacing(), sdfImage->getOrigin());
75  }
76 
77  const Vec3d actualSpacing = sdf->getImage()->getSpacing();// *sdf->getScale();
78  m_forwardGrad.setDx(Vec3i(1, 1, 1), actualSpacing);
79  m_backwardGrad.setDx(Vec3i(1, 1, 1), actualSpacing);
80  m_curvature.setDx(Vec3i(1, 1, 1), actualSpacing);
81  }
82 
83  m_nodeUpdatePool.resize(5000);
84  noteUpdatePoolSize = 0;
85 
86  return true;
87 }
88 
89 void
90 LevelSetModel::configure(std::shared_ptr<LevelSetModelConfig> config)
91 {
92  LOG_IF(FATAL, (!this->getModelGeometry())) << "LevelSetModel::configure - Set LevelSetModel geometry before configuration!";
93 
94  m_config = config;
95 }
96 
97 void
98 LevelSetModel::evolve()
99 {
100  auto sdf = std::dynamic_pointer_cast<SignedDistanceField>(m_mesh);
101  auto imageData = std::dynamic_pointer_cast<ImageData>(sdf->getImage());
102  double* imgPtr = static_cast<double*>(imageData->getVoidPointer());
103  const Vec3i& dim = imageData->getDimensions();
104  const double dt = m_config->m_dt / m_config->m_substeps;
105  //const double k = m_config->m_k;
106 
107  if (m_config->m_sparseUpdate)
108  {
109  // Sparse update
110  if (m_nodesToUpdate.size() == 0)
111  {
112  return;
113  }
114 
115  // Setup a map of 0 based index -> image sparse index m_nodesToUpdate to parallelize
116  std::vector<size_t> baseIndexToImageIndex;
117  baseIndexToImageIndex.reserve(m_nodesToUpdate.size());
118  for (std::unordered_map<size_t, std::tuple<Vec3i, double>>::iterator iter = m_nodesToUpdate.begin(); iter != m_nodesToUpdate.end(); iter++)
119  {
120  baseIndexToImageIndex.push_back(iter->first);
121  }
122  DISABLE_WARNING_PUSH
123  DISABLE_WARNING_PADDING
124 
125  // Compute gradients
126  const double constantVel = m_config->m_constantVelocity;
127  std::tuple<size_t, Vec3i, double, Vec2d> val;
128  for (int j = 0; j < m_config->m_substeps; j++)
129  {
130  ParallelUtils::parallelFor(baseIndexToImageIndex.size(), [&](const size_t i)
131  {
132  std::tuple<size_t, Vec3i, double, Vec2d, double>& outputVal = m_nodeUpdatePool[i];
133  const size_t& index = std::get<0>(outputVal) = baseIndexToImageIndex[i];
134 
135  std::tuple<Vec3i, double>& inputVal = m_nodesToUpdate[index];
136 
137  const Vec3i& coords = std::get<1>(outputVal) = std::get<0>(inputVal);
138  std::get<2>(outputVal) = std::get<1>(inputVal);
139 
140  // Gradients
141  const Vec3d gradPos = m_forwardGrad(Vec3d(coords[0], coords[1], coords[2]));
142  const Vec3d gradNeg = m_backwardGrad(Vec3d(coords[0], coords[1], coords[2]));
143 
144  Vec3d gradNegMax = gradNeg.cwiseMax(0.0);
145  Vec3d gradNegMin = gradNeg.cwiseMin(0.0);
146  Vec3d gradPosMax = gradPos.cwiseMax(0.0);
147  Vec3d gradPosMin = gradPos.cwiseMin(0.0);
148 
149  // Square them
150  gradNegMax = gradNegMax.cwiseProduct(gradNegMax);
151  gradNegMin = gradNegMin.cwiseProduct(gradNegMin);
152  gradPosMax = gradPosMax.cwiseProduct(gradPosMax);
153  gradPosMin = gradPosMin.cwiseProduct(gradPosMin);
154 
155  const double posMag =
156  gradNegMax[0] + gradNegMax[1] + gradNegMax[2] +
157  gradPosMin[0] + gradPosMin[1] + gradPosMin[2];
158 
159  const double negMag =
160  gradNegMin[0] + gradNegMin[1] + gradNegMin[2] +
161  gradPosMax[0] + gradPosMax[1] + gradPosMax[2];
162 
163  std::get<3>(outputVal) = Vec2d(negMag, posMag);
164 
165  // Curvature
166  //const double kappa = m_curvature(Vec3d(coords[0], coords[1], coords[2]));
167  }, baseIndexToImageIndex.size() > 50);
168 
169  // Update levelset
170  ParallelUtils::parallelFor(baseIndexToImageIndex.size(), [&](const size_t& i)
171  {
172  const size_t index = std::get<0>(m_nodeUpdatePool[i]);
173  const double vel = std::get<2>(m_nodeUpdatePool[i]) + constantVel;
174  const Vec2d& g = std::get<3>(m_nodeUpdatePool[i]);
175  //const double kappa = std::get<4>(nodeUpdates[i]);
176 
177  // If speed function positive use forward difference (posMag)
178  if (vel > 0.0)
179  {
180  imgPtr[index] += dt * (vel * std::sqrt(g[0]) /*+ kappa * k*/);
181  }
182  // If speed function negative use backward difference (negMag)
183  else if (vel < 0.0)
184  {
185  imgPtr[index] += dt * (vel * std::sqrt(g[1]) /*+ kappa * k*/);
186  }
187  }, noteUpdatePoolSize > m_maxVelocitiesParallel);
188  }
189  DISABLE_WARNING_POP
190  m_nodesToUpdate.clear();
191  }
192  else
193  {
194  // Dense update
195  double* gradientMagPtr = static_cast<double*>(m_gradientMagnitudes->getScalars()->getVoidPointer());
196  //double* curvaturesPtr = static_cast<double*>(m_curvatures->getScalars()->getVoidPointer());
197  double* velocityMagPtr = static_cast<double*>(m_velocities->getScalars()->getVoidPointer());
198 
199  // Compute gradients
200  ParallelUtils::parallelFor(dim[2],
201  [&](const int& z)
202  {
203  int i = z * dim[0] * dim[1];
204  for (int y = 0; y < dim[1]; y++)
205  {
206  for (int x = 0; x < dim[0]; x++, i++)
207  {
208  // Gradients
209  const Vec3d gradPos = m_forwardGrad(Vec3d(x, y, z));
210  const Vec3d gradNeg = m_backwardGrad(Vec3d(x, y, z));
211  //curvaturesPtr[i] = m_curvature(Vec3d(x, y, z));
212 
213  Vec3d gradNegMax = gradNeg.cwiseMax(0.0);
214  Vec3d gradNegMin = gradNeg.cwiseMin(0.0);
215  Vec3d gradPosMax = gradPos.cwiseMax(0.0);
216  Vec3d gradPosMin = gradPos.cwiseMin(0.0);
217 
218  // Square them
219  gradNegMax = gradNegMax.cwiseProduct(gradNegMax);
220  gradNegMin = gradNegMin.cwiseProduct(gradNegMin);
221  gradPosMax = gradPosMax.cwiseProduct(gradPosMax);
222  gradPosMin = gradPosMin.cwiseProduct(gradPosMin);
223 
224  // Pos
225  gradientMagPtr[i * 2 + 1] =
226  gradNegMax[0] + gradNegMax[1] + gradNegMax[2] +
227  gradPosMin[0] + gradPosMin[1] + gradPosMin[2];
228 
229  // Neg
230  gradientMagPtr[i * 2] =
231  gradNegMin[0] + gradNegMin[1] + gradNegMin[2] +
232  gradPosMax[0] + gradPosMax[1] + gradPosMax[2];
233  }
234  }
235  });
236 
237  const double constantVel = m_config->m_constantVelocity;
238  ParallelUtils::parallelFor(dim[0] * dim[1] * dim[2],
239  [&](const int& i)
240  {
241  const double vel = constantVel + velocityMagPtr[i];
242  // If speed function positive use forward difference
243  if (constantVel > 0.0)
244  {
245  imgPtr[i] += dt * (vel * std::sqrt(gradientMagPtr[i * 2]) /*+ curvaturesPtr[i] * k*/);
246  }
247  // If speed function negative use backward difference
248  else if (constantVel < 0.0)
249  {
250  imgPtr[i] += dt * (vel * std::sqrt(gradientMagPtr[i * 2 + 1]) /*+ curvaturesPtr[i] * k*/);
251  }
252  });
253  }
254 }
255 
256 void
257 LevelSetModel::addImpulse(const Vec3i& coord, double f)
258 {
259  auto sdf = std::dynamic_pointer_cast<SignedDistanceField>(m_mesh);
260  auto imageData = std::dynamic_pointer_cast<ImageData>(sdf->getImage());
261  const Vec3i& dim = imageData->getDimensions();
262 
263  if (coord[0] >= 0 && coord[0] < dim[0]
264  && coord[1] >= 0 && coord[1] < dim[1]
265  && coord[2] >= 0 && coord[2] < dim[2])
266  {
267  const size_t index = coord[0] + coord[1] * dim[0] + coord[2] * dim[0] * dim[1];
268  if (m_config->m_sparseUpdate)
269  {
270  if (m_nodesToUpdate.count(index) > 0)
271  {
272  m_nodesToUpdate[index] = std::tuple<Vec3i, double>(coord, std::max(std::get<1>(m_nodesToUpdate[index]), f));
273  }
274  else
275  {
276  m_nodesToUpdate[index] = std::tuple<Vec3i, double>(coord, f);
277  }
278  }
279  else
280  {
281  double* velocitiesPtr = static_cast<double*>(m_velocities->getScalars()->getVoidPointer());
282  velocitiesPtr[index] = std::max(velocitiesPtr[index], f);
283  }
284  }
285 }
286 
287 void
288 LevelSetModel::setImpulse(const Vec3i& coord, double f)
289 {
290  auto sdf = std::dynamic_pointer_cast<SignedDistanceField>(m_mesh);
291  auto imageData = std::dynamic_pointer_cast<ImageData>(sdf->getImage());
292  const Vec3i& dim = imageData->getDimensions();
293 
294  if (coord[0] >= 0 && coord[0] < dim[0]
295  && coord[1] >= 0 && coord[1] < dim[1]
296  && coord[2] >= 0 && coord[2] < dim[2])
297  {
298  const size_t index = coord[0] + coord[1] * dim[0] + coord[2] * dim[0] * dim[1];
299  if (m_config->m_sparseUpdate)
300  {
301  m_nodesToUpdate[index] = std::tuple<Vec3i, double>(coord, f);
302  }
303  else
304  {
305  double* velocitiesPtr = static_cast<double*>(m_velocities->getScalars()->getVoidPointer());
306  velocitiesPtr[index] = f;
307  }
308  }
309 }
310 
311 void
313 {
314  // Due to having to store a copy of the initial image which is quite large reset is not implemented
315  LOG(WARNING) << "LevelSetModel cannot reset";
316 }
317 
318 void
319 LevelSetModel::initGraphEdges(std::shared_ptr<TaskNode> source, std::shared_ptr<TaskNode> sink)
320 {
321  m_taskGraph->addEdge(source, m_generateVelocitiesBegin);
322  m_taskGraph->addEdge(m_generateVelocitiesBegin, m_generateVelocitiesEnd);
323 
324  // Given no fields are interacting all quantities should be able to update in parallel
325  for (size_t i = 0; i < m_evolveQuantitiesNodes.size(); i++)
326  {
327  m_taskGraph->addEdge(m_generateVelocitiesEnd, m_evolveQuantitiesNodes[i]);
328  m_taskGraph->addEdge(m_evolveQuantitiesNodes[i], sink);
329  }
330 }
331 } // namespace imstk
void setImpulse(const Vec3i &coord, double f)
Set the impulse in the velocity field, replaces.
void configure(std::shared_ptr< LevelSetModelConfig > config)
Configure the model.
void resetToInitialState() override
Reset the current state to the initial state.
std::shared_ptr< Geometry > getModelGeometry() const
Gets the model geometry.
const Vec3i & getDimensions() const
Returns the dimensions of the image.
Compound Geometry.
void addImpulse(const Vec3i &coord, double f)
Add an impulse to the velocity field of the levelset This actually takes the max of the current and p...
bool initialize() override
Initialize the LevelSet model.
std::set< std::string > m_validGeometryTypes
Valid geometry types of this model.
Structured field of signed distances implemented with ImageData The SDF differs in that when you scal...
Class that can represent the geometry of multiple implicit geometries as boolean functions One may su...
std::shared_ptr< Geometry > m_geometry
Physics geometry of the model.
Class to represent 1, 2, or 3D image data (i.e. structured points)
void initGraphEdges()
Initializes the edges of the graph.