Chaste  Release::3.4
PostProcessingWriter.cpp
1 /*
2 
3 Copyright (c) 2005-2016, University of Oxford.
4 All rights reserved.
5 
6 University of Oxford means the Chancellor, Masters and Scholars of the
7 University of Oxford, having an administrative office at Wellington
8 Square, Oxford OX1 2JD, UK.
9 
10 This file is part of Chaste.
11 
12 Redistribution and use in source and binary forms, with or without
13 modification, are permitted provided that the following conditions are met:
14  * Redistributions of source code must retain the above copyright notice,
15  this list of conditions and the following disclaimer.
16  * Redistributions in binary form must reproduce the above copyright notice,
17  this list of conditions and the following disclaimer in the documentation
18  and/or other materials provided with the distribution.
19  * Neither the name of the University of Oxford nor the names of its
20  contributors may be used to endorse or promote products derived from this
21  software without specific prior written permission.
22 
23 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
27 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
29 GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
32 OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 
34 */
35 
36 #include "UblasCustomFunctions.hpp"
37 #include "HeartConfig.hpp"
38 #include "PostProcessingWriter.hpp"
39 #include "PetscTools.hpp"
40 #include "OutputFileHandler.hpp"
41 #include "DistanceMapCalculator.hpp"
42 #include "PseudoEcgCalculator.hpp"
43 #include "Version.hpp"
44 #include "HeartEventHandler.hpp"
45 #include "Hdf5DataWriter.hpp"
46 #include "Hdf5ToMeshalyzerConverter.hpp"
47 #include "Hdf5ToVtkConverter.hpp"
48 
49 #include <iostream>
50 
51 template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
53  const FileFinder& rDirectory,
54  const std::string& rHdf5FileName,
55  const std::string& rVoltageName)
56  : mDirectory(rDirectory),
57  mHdf5File(rHdf5FileName),
58  mVoltageName(rVoltageName),
59  mrMesh(rMesh)
60 {
61  mLo = mrMesh.GetDistributedVectorFactory()->GetLow();
62  mHi = mrMesh.GetDistributedVectorFactory()->GetHigh();
65  // Check that the hdf file was generated by simulations from (probably) the same mesh.
66  assert(mpDataReader->GetNumberOfRows() == mrMesh.GetNumNodes());
67 }
68 
69 template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
71 {
72  //Check that post-processing is really needed
73  assert(HeartConfig::Instance()->IsPostProcessingRequested());
74 
75  // Please note that only the master processor should write to file.
76  // Each of the private methods called here takes care of checking.
77  if (HeartConfig::Instance()->IsApdMapsRequested())
78  {
79  std::vector<std::pair<double,double> > apd_maps;
80  HeartConfig::Instance()->GetApdMaps(apd_maps);
81  for (unsigned i=0; i<apd_maps.size(); i++)
82  {
83  WriteApdMapFile(apd_maps[i].first, apd_maps[i].second);
84  }
85  }
86 
87  if (HeartConfig::Instance()->IsUpstrokeTimeMapsRequested())
88  {
89  std::vector<double> upstroke_time_maps;
90  HeartConfig::Instance()->GetUpstrokeTimeMaps(upstroke_time_maps);
91  for (unsigned i=0; i<upstroke_time_maps.size(); i++)
92  {
93  WriteUpstrokeTimeMap(upstroke_time_maps[i]);
94  }
95  }
96 
97  if (HeartConfig::Instance()->IsMaxUpstrokeVelocityMapRequested())
98  {
99  std::vector<double> upstroke_velocity_maps;
100  HeartConfig::Instance()->GetMaxUpstrokeVelocityMaps(upstroke_velocity_maps);
101  for (unsigned i=0; i<upstroke_velocity_maps.size(); i++)
102  {
103  WriteMaxUpstrokeVelocityMap(upstroke_velocity_maps[i]);
104  }
105  }
106 
107  if (HeartConfig::Instance()->IsConductionVelocityMapsRequested())
108  {
109  std::vector<unsigned> conduction_velocity_maps;
110  HeartConfig::Instance()->GetConductionVelocityMaps(conduction_velocity_maps);
111 
112  //get the mesh here
113  DistanceMapCalculator<ELEMENT_DIM, SPACE_DIM> dist_map_calculator(mrMesh);
114 
115  for (unsigned i=0; i<conduction_velocity_maps.size(); i++)
116  {
117  std::vector<double> distance_map;
118  std::vector<unsigned> origin_surface;
119  origin_surface.push_back(conduction_velocity_maps[i]);
120  dist_map_calculator.ComputeDistanceMap(origin_surface, distance_map);
121  WriteConductionVelocityMap(conduction_velocity_maps[i], distance_map);
122  }
123  }
124 
125  if (HeartConfig::Instance()->IsAnyNodalTimeTraceRequested())
126  {
127  std::vector<unsigned> requested_nodes;
129  WriteVariablesOverTimeAtNodes(requested_nodes);
130  }
131 
132  if (HeartConfig::Instance()->IsPseudoEcgCalculationRequested())
133  {
134  std::vector<ChastePoint<SPACE_DIM> > electrodes;
136 
139  delete mpDataReader;
140 
141  for (unsigned i=0; i<electrodes.size(); i++)
142  {
144  electrodes[i],
145  mDirectory,
146  mHdf5File,
147  mVoltageName);
148  calculator.WritePseudoEcg();
149  }
150 
152  mpDataReader = new Hdf5DataReader(mDirectory, mHdf5File);
153  mpCalculator->SetHdf5DataReader(mpDataReader);
154  }
155 }
156 
157 
158 template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
160 {
161  delete mpDataReader;
162  delete mpCalculator;
163 }
164 
165 template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
166 void PostProcessingWriter<ELEMENT_DIM, SPACE_DIM>::WriteOutputDataToHdf5(const std::vector<std::vector<double> >& rDataPayload,
167  const std::string& rDatasetName,
168  const std::string& rDatasetUnit,
169  const std::string& rUnlimitedVariableName,
170  const std::string& rUnlimitedVariableUnit)
171 {
172  DistributedVectorFactory* p_factory = mrMesh.GetDistributedVectorFactory();
173  FileFinder test_output("", RelativeTo::ChasteTestOutput);
174  Hdf5DataWriter writer(*p_factory,
175  mDirectory.GetRelativePath(test_output), // Path relative to CHASTE_TEST_OUTPUT
176  mHdf5File,
177  false, // to wiping
178  true, // to extending
179  rDatasetName); // dataset name
180 
181  /* Probe define mode. We asked to extend, so if the writer is currently in define mode it means the
182  * dataset doesn't exist yet and needs creating. If it is NOT in define mode, it means the dataset
183  * exists, so we'll empty it and calculate new postprocessing data. */
184  int apd_id;
185  if ( writer.IsInDefineMode() )
186  {
187  apd_id = writer.DefineVariable(rDatasetName, rDatasetUnit);
188  writer.DefineFixedDimension(mrMesh.GetNumNodes());
189  writer.DefineUnlimitedDimension(rUnlimitedVariableName, rUnlimitedVariableUnit);
190  writer.EndDefineMode();
191  }
192  else
193  {
194  apd_id = writer.GetVariableByName(rDatasetName);
195  writer.EmptyDataset();
196  }
197 
198  //Determine the maximum number of paces
199  unsigned local_max_paces = 0u;
200  for (unsigned node_index = 0; node_index < rDataPayload.size(); ++node_index)
201  {
202  if (rDataPayload[node_index].size() > local_max_paces)
203  {
204  local_max_paces = rDataPayload[node_index].size();
205  }
206  }
207 
208  unsigned max_paces = 0u;
209  MPI_Allreduce(&local_max_paces, &max_paces, 1, MPI_UNSIGNED, MPI_MAX, PETSC_COMM_WORLD);
210 
211  for (unsigned pace_idx = 0; pace_idx < max_paces; pace_idx++)
212  {
213  Vec apd_vec = p_factory->CreateVec();
214  DistributedVector distributed_vector = p_factory->CreateDistributedVector(apd_vec);
215  for (DistributedVector::Iterator index = distributed_vector.Begin();
216  index!= distributed_vector.End();
217  ++index)
218  {
219  unsigned node_idx = index.Local;
220  // pad with -999 if no pace defined at this node
221  if (pace_idx < rDataPayload[node_idx].size() )
222  {
223  distributed_vector[index] = rDataPayload[node_idx][pace_idx];
224  }
225  else
226  {
227  distributed_vector[index] = -999.0;
228  }
229  }
230  writer.PutVector(apd_id, apd_vec);
231  PetscTools::Destroy(apd_vec);
232  writer.PutUnlimitedVariable(pace_idx);
233  writer.AdvanceAlongUnlimitedDimension();
234  }
235  writer.Close();
236 }
237 
238 template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
239 void PostProcessingWriter<ELEMENT_DIM, SPACE_DIM>::WriteApdMapFile(double repolarisationPercentage, double threshold)
240 {
241  std::vector<std::vector<double> > local_output_data = mpCalculator->CalculateAllActionPotentialDurationsForNodeRange(repolarisationPercentage, mLo, mHi, threshold);
242 
243  // HDF5 shouldn't have minus signs in the data names..
244  std::stringstream hdf5_dataset_name;
245  hdf5_dataset_name << "Apd_" << repolarisationPercentage;
246 
247  WriteOutputDataToHdf5(local_output_data,
248  hdf5_dataset_name.str() + ConvertToHdf5FriendlyString(threshold) + "_Map",
249  "msec");
250 }
251 
252 template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
254 {
255  std::vector<std::vector<double> > output_data;
256  //Fill in data
257  for (unsigned node_index = mLo; node_index < mHi; node_index++)
258  {
259  std::vector<double> upstroke_times;
260  try
261  {
262  upstroke_times = mpCalculator->CalculateUpstrokeTimes(node_index, threshold);
263  assert(upstroke_times.size() != 0);
264  }
265  catch(Exception&)
266  {
267  upstroke_times.push_back(0);
268  assert(upstroke_times.size() == 1);
269  }
270  output_data.push_back(upstroke_times);
271  }
272 
273  WriteOutputDataToHdf5(output_data,
274  "UpstrokeTimeMap" + ConvertToHdf5FriendlyString(threshold),
275  "msec");
276 }
277 
278 template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
280 {
281  std::vector<std::vector<double> > output_data;
282  //Fill in data
283  for (unsigned node_index = mLo; node_index < mHi; node_index++)
284  {
285  std::vector<double> upstroke_velocities;
286  try
287  {
288  upstroke_velocities = mpCalculator->CalculateAllMaximumUpstrokeVelocities(node_index, threshold);
289  assert(upstroke_velocities.size() != 0);
290  }
291  catch(Exception&)
292  {
293  upstroke_velocities.push_back(0);
294  assert(upstroke_velocities.size() ==1);
295  }
296  output_data.push_back(upstroke_velocities);
297  }
298 
299  WriteOutputDataToHdf5(output_data,
300  "MaxUpstrokeVelocityMap" + ConvertToHdf5FriendlyString(threshold),
301  "mV_per_msec");
302 }
303 
304 template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
305 void PostProcessingWriter<ELEMENT_DIM, SPACE_DIM>::WriteConductionVelocityMap(unsigned originNode, std::vector<double> distancesFromOriginNode)
306 {
307  //Fill in data
308  std::vector<std::vector<double> > output_data;
309  for (unsigned dest_node = mLo; dest_node < mHi; dest_node++)
310  {
311  std::vector<double> conduction_velocities;
312  try
313  {
314  conduction_velocities = mpCalculator->CalculateAllConductionVelocities(originNode, dest_node, distancesFromOriginNode[dest_node]);
315  assert(conduction_velocities.size() != 0);
316  }
317  catch(Exception&)
318  {
319  conduction_velocities.push_back(0);
320  assert(conduction_velocities.size() == 1);
321  }
322  output_data.push_back(conduction_velocities);
323  }
324  std::stringstream filename_stream;
325  filename_stream << "ConductionVelocityFromNode" << originNode;
326 
327  WriteOutputDataToHdf5(output_data, filename_stream.str(), "cm_per_msec");
328 }
329 
330 template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
332 {
333  std::vector<std::vector<double> > output_data;
334 
335  //Fill in data
336  for (unsigned node_index = mLo; node_index < mHi; node_index++)
337  {
338  std::vector<double> upstroke_velocities;
339  std::vector<unsigned> above_threshold_depolarisations;
340  std::vector<double> output_item;
341  bool no_upstroke_occurred = false;
342 
343  try
344  {
345  upstroke_velocities = mpCalculator->CalculateAllMaximumUpstrokeVelocities(node_index, threshold);
346  assert(upstroke_velocities.size() != 0);
347  }
348  catch(Exception&)
349  {
350  upstroke_velocities.push_back(0);
351  assert(upstroke_velocities.size() == 1);
352  no_upstroke_occurred = true;
353  }
354  // This method won't throw any exception, so there is no need to put it into the try/catch:
355  above_threshold_depolarisations = mpCalculator->CalculateAllAboveThresholdDepolarisations(node_index, threshold);
356 
357  // Count the total above threshold depolarisations
358  unsigned total_number_of_above_threshold_depolarisations = 0;
359  for (unsigned ead_index = 0; ead_index< above_threshold_depolarisations.size();ead_index++)
360  {
361  total_number_of_above_threshold_depolarisations = total_number_of_above_threshold_depolarisations + above_threshold_depolarisations[ead_index];
362  }
363 
364  // For this item, push back the number of upstrokes...
365  if (no_upstroke_occurred)
366  {
367  output_item.push_back(0);
368  }
369  else
370  {
371  output_item.push_back((double)upstroke_velocities.size());
372  }
373  //... and the number of above threshold depolarisations
374  output_item.push_back((double) total_number_of_above_threshold_depolarisations);
375 
376  output_data.push_back(output_item);
377  }
378 
379  // we just use meshalyzer format so that something is generated in simple column format for use with gnuplot etc.
380  WriteGenericFileToMeshalyzer(output_data, "", "AboveThresholdDepolarisations" + ConvertToHdf5FriendlyString(threshold) +
381  ".dat");
382 }
383 
384 template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
386 {
387  std::stringstream dataset_stream;
388  std::string extra_message = "_";
389  if (threshold < 0.0)
390  {
391  extra_message += "minus_";
392  threshold = -threshold;
393  }
394 
395  // if threshold is a decimal eg: because using phenomenological model
396  if (threshold - floor(threshold) > 1e-8)
397  {
398  // give the answer to 2dp
399  dataset_stream << extra_message << floor(threshold) << "pt" << floor(threshold*100)-(floor(threshold)*100);
400  }
401  else
402  {
403  dataset_stream << extra_message << threshold;
404  }
405 
406  return dataset_stream.str();
407 }
408 
409 template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
411 {
412  std::vector<std::string> variable_names = mpDataReader->GetVariableNames();
413 
414  //we will write one file per variable in the hdf5 file
415  for (unsigned name_index=0; name_index < variable_names.size(); name_index++)
416  {
417  std::vector<std::vector<double> > output_data;
418  if (PetscTools::AmMaster())//only master process fills the data structure
419  {
420  //allocate memory: NXM matrix where N = number of time steps and M number of requested nodes
421  output_data.resize( mpDataReader->GetUnlimitedDimensionValues().size() );
422  for (unsigned j = 0; j < mpDataReader->GetUnlimitedDimensionValues().size(); j++)
423  {
424  output_data[j].resize(rNodeIndices.size());
425  }
426 
427  for (unsigned requested_index = 0; requested_index < rNodeIndices.size(); requested_index++)
428  {
429  unsigned node_index = rNodeIndices[requested_index];
430 
431  //handle permutation, if any
432  if ( (mrMesh.rGetNodePermutation().size() != 0) &&
434  {
435  node_index = mrMesh.rGetNodePermutation()[ rNodeIndices[requested_index] ];
436  }
437 
438  //grab the data from the hdf5 file.
439  std::vector<double> time_series = mpDataReader->GetVariableOverTime(variable_names[name_index], node_index);
440  assert ( time_series.size() == mpDataReader->GetUnlimitedDimensionValues().size());
441 
442  //fill the output_data data structure
443  for (unsigned time_step = 0; time_step < time_series.size(); time_step++)
444  {
445  output_data[time_step][requested_index] = time_series[time_step];
446  }
447  }
448  }
449  std::stringstream filename_stream;
450  filename_stream << "NodalTraces_" << variable_names[name_index] << ".dat";
451  // we just use meshalyzer format so that something is generated in simple column format for use with gnuplot etc.
452  WriteGenericFileToMeshalyzer(output_data, "", filename_stream.str());
453  }
454 }
455 
456 template<unsigned ELEMENT_DIM, unsigned SPACE_DIM>
457 void PostProcessingWriter<ELEMENT_DIM, SPACE_DIM>::WriteGenericFileToMeshalyzer(std::vector<std::vector<double> >& rDataPayload, const std::string& rFolder, const std::string& rFileName)
458 {
459  OutputFileHandler output_file_handler(HeartConfig::Instance()->GetOutputDirectory() + "/" + rFolder, false);
461  {
462  out_stream p_file=out_stream(NULL);
463  //Open file
464  if (PetscTools::AmMaster())
465  {
466  // Open the file for the first time
467  p_file = output_file_handler.OpenOutputFile(rFileName);
468  }
469  else
470  {
471  // Append data to the existing file opened by master
472  p_file = output_file_handler.OpenOutputFile(rFileName, std::ios::app);
473  }
474  // Write data
475  for (unsigned line_number=0; line_number<rDataPayload.size(); line_number++)
476  {
477  for (unsigned i = 0; i < rDataPayload[line_number].size(); i++)
478  {
479  *p_file << rDataPayload[line_number][i] << "\t";
480  }
481  *p_file << std::endl;
482  }
483 
484  // Last processor appends comment line
485  if (PetscTools::AmTopMost())
486  {
487  std::string comment = "# " + ChasteBuildInfo::GetProvenanceString();
488  *p_file << comment;
489  }
490  p_file->close();
491  }
492  //There's a barrier included here: Process i+1 waits for process i to close the file
494 }
495 
496 
498 // Explicit instantiation
500 
501 template class PostProcessingWriter<1,1>;
502 template class PostProcessingWriter<1,2>;
503 template class PostProcessingWriter<2,2>;
504 template class PostProcessingWriter<1,3>;
505 template class PostProcessingWriter<3,3>;
AbstractTetrahedralMesh< ELEMENT_DIM, SPACE_DIM > & mrMesh
void WriteVariablesOverTimeAtNodes(std::vector< unsigned > &rNodeIndices)
unsigned GetNumberOfRows()
DistributedVector CreateDistributedVector(Vec vec, bool readOnly=false)
void GetConductionVelocityMaps(std::vector< unsigned > &rConductionVelocityMaps) const
bool GetOutputUsingOriginalNodeOrdering()
static bool AmMaster()
Definition: PetscTools.cpp:120
void WriteOutputDataToHdf5(const std::vector< std::vector< double > > &rDataPayload, const std::string &rDatasetName, const std::string &rDatasetUnit, const std::string &rUnlimitedVariableName="PaceNumber", const std::string &rUnlimitedVariableUnit="dimensionless")
std::string ConvertToHdf5FriendlyString(double threshold)
void GetUpstrokeTimeMaps(std::vector< double > &rUpstrokeTimeMaps) const
void WriteMaxUpstrokeVelocityMap(double threshold)
out_stream OpenOutputFile(const std::string &rFileName, std::ios_base::openmode mode=std::ios::out|std::ios::trunc) const
void GetNodalTimeTraceRequested(std::vector< unsigned > &rRequestedNodes) const
void WriteConductionVelocityMap(unsigned originNode, std::vector< double > distancesFromOriginNode)
void WriteUpstrokeTimeMap(double threshold)
PostProcessingWriter(AbstractTetrahedralMesh< ELEMENT_DIM, SPACE_DIM > &rMesh, const FileFinder &rDirectory, const std::string &rHdf5FileName, const std::string &rVoltageName="V")
void WriteGenericFileToMeshalyzer(std::vector< std::vector< double > > &rDataPayload, const std::string &rFolder, const std::string &rFileName)
static void EndRoundRobin()
Definition: PetscTools.cpp:160
void GetPseudoEcgElectrodePositions(std::vector< ChastePoint< SPACE_DIM > > &rPseudoEcgElectrodePositions) const
static void BeginRoundRobin()
Definition: PetscTools.cpp:150
int DefineVariable(const std::string &rVariableName, const std::string &rVariableUnits)
PropagationPropertiesCalculator * mpCalculator
static void Destroy(Vec &rVec)
Definition: PetscTools.hpp:351
void GetApdMaps(std::vector< std::pair< double, double > > &rApdMaps) const
void WriteApdMapFile(double repolarisationPercentage, double threshold)
static bool AmTopMost()
Definition: PetscTools.cpp:126
static std::string GetProvenanceString()
void GetMaxUpstrokeVelocityMaps(std::vector< double > &rUpstrokeVelocityMaps) const
void ComputeDistanceMap(const std::vector< unsigned > &rSourceNodeIndices, std::vector< double > &rNodeDistances)
Hdf5DataReader * mpDataReader
static HeartConfig * Instance()
void WriteAboveThresholdDepolarisationFile(double threshold)