Goby v2
hdf5.cpp
1 // Copyright 2009-2018 Toby Schneider (http://gobysoft.org/index.wt/people/toby)
2 // GobySoft, LLC (2013-)
3 // Massachusetts Institute of Technology (2007-2014)
4 //
5 //
6 // This file is part of the Goby Underwater Autonomy Project Binaries
7 // ("The Goby Binaries").
8 //
9 // The Goby Binaries are free software: you can redistribute them and/or modify
10 // them under the terms of the GNU General Public License as published by
11 // the Free Software Foundation, either version 2 of the License, or
12 // (at your option) any later version.
13 //
14 // The Goby Binaries are distributed in the hope that they will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 // GNU General Public License for more details.
18 //
19 // You should have received a copy of the GNU General Public License
20 // along with Goby. If not, see <http://www.gnu.org/licenses/>.
21 
22 #include <cstdlib>
23 #include <dlfcn.h>
24 #include <iostream>
25 
26 #include "goby/common/logger.h"
27 
28 #include "hdf5.h"
29 
30 void* plugin_handle = 0;
31 
32 using namespace goby::common::logger;
33 using goby::glog;
34 
35 int main(int argc, char* argv[])
36 {
37  // load plugin driver from environmental variable GOBY_HDF5_PLUGIN
38  const char* plugin_path = getenv("GOBY_HDF5_PLUGIN");
39  if (plugin_path)
40  {
41  std::cerr << "Loading plugin library: " << plugin_path << std::endl;
42  plugin_handle = dlopen(plugin_path, RTLD_LAZY);
43  if (!plugin_handle)
44  {
45  std::cerr << "Failed to open library: " << plugin_path << std::endl;
46  exit(EXIT_FAILURE);
47  }
48  }
49  else
50  {
51  std::cerr << "Environmental variable GOBY_HDF5_PLUGIN must be set with name of the dynamic "
52  "library containing the specific frontend plugin to use."
53  << std::endl;
54  exit(EXIT_FAILURE);
55  }
56 
58  return goby::run<goby::common::hdf5::Writer>(argc, argv, &cfg);
59 }
60 
61 void goby::common::hdf5::Channel::add_message(const goby::common::HDF5ProtobufEntry& entry)
62 {
63  const std::string& msg_name = entry.msg->GetDescriptor()->full_name();
64  typedef std::map<std::string, MessageCollection>::iterator It;
65  It it = entries.find(msg_name);
66  if (it == entries.end())
67  {
68  std::pair<It, bool> itpair =
69  entries.insert(std::make_pair(msg_name, MessageCollection(msg_name)));
70  it = itpair.first;
71  }
72  it->second.entries.insert(std::make_pair(entry.time, entry.msg));
73 }
74 
75 H5::Group& goby::common::hdf5::GroupFactory::fetch_group(const std::string& group_path)
76 {
77  std::deque<std::string> nodes;
78  std::string clean_path = boost::trim_copy_if(group_path, boost::algorithm::is_space() ||
79  boost::algorithm::is_any_of("/"));
80  boost::split(nodes, clean_path, boost::is_any_of("/"));
81  return root_group_.fetch_group(nodes);
82 }
83 
84 H5::Group&
85 goby::common::hdf5::GroupFactory::GroupWrapper::fetch_group(std::deque<std::string>& nodes)
86 {
87  if (nodes.empty())
88  {
89  return group_;
90  }
91  else
92  {
93  typedef std::map<std::string, GroupWrapper>::iterator It;
94  It it = children_.find(nodes[0]);
95  if (it == children_.end())
96  {
97  std::pair<It, bool> itpair =
98  children_.insert(std::make_pair(nodes[0], GroupWrapper(nodes[0], group_)));
99  it = itpair.first;
100  }
101  nodes.pop_front();
102  return it->second.fetch_group(nodes);
103  }
104 }
105 
106 goby::common::hdf5::Writer::Writer(goby::common::protobuf::HDF5Config* cfg)
107  : ApplicationBase(cfg), cfg_(*cfg), h5file_(cfg_.output_file(), H5F_ACC_TRUNC),
108  group_factory_(h5file_)
109 {
110  load();
111  collect();
112  write();
113  quit();
114 }
115 
116 void goby::common::hdf5::Writer::load()
117 {
119  plugin_func plugin_ptr = (plugin_func)dlsym(plugin_handle, "goby_hdf5_load");
120 
121  if (!plugin_ptr)
122  glog.is(DIE) &&
123  glog << "Function goby_hdf5_load in library defined in GOBY_HDF5_PLUGIN does not exist."
124  << std::endl;
125 
126  plugin_.reset((*plugin_ptr)(&cfg_));
127 
128  if (!plugin_)
129  glog.is(DIE) && glog << "Function goby_hdf5_load in library defined in GOBY_HDF5_PLUGIN "
130  "returned a null pointer."
131  << std::endl;
132 }
133 
134 void goby::common::hdf5::Writer::collect()
135 {
137  while (plugin_->provide_entry(&entry))
138  {
139  boost::trim_if(entry.channel,
140  boost::algorithm::is_space() || boost::algorithm::is_any_of("/"));
141 
142  typedef std::map<std::string, goby::common::hdf5::Channel>::iterator It;
143  It it = channels_.find(entry.channel);
144  if (it == channels_.end())
145  {
146  std::pair<It, bool> itpair = channels_.insert(
147  std::make_pair(entry.channel, goby::common::hdf5::Channel(entry.channel)));
148  it = itpair.first;
149  }
150 
151  it->second.add_message(entry);
152  entry.clear();
153  }
154 }
155 
156 void goby::common::hdf5::Writer::write()
157 {
158  for (std::map<std::string, goby::common::hdf5::Channel>::const_iterator it = channels_.begin(),
159  end = channels_.end();
160  it != end; ++it)
161  write_channel("/" + it->first, it->second);
162 }
163 
164 void goby::common::hdf5::Writer::write_channel(const std::string& group,
165  const goby::common::hdf5::Channel& channel)
166 {
167  for (std::map<std::string, goby::common::hdf5::MessageCollection>::const_iterator
168  it = channel.entries.begin(),
169  end = channel.entries.end();
170  it != end; ++it)
171  write_message_collection(group + "/" + it->first, it->second);
172 }
173 
174 void goby::common::hdf5::Writer::write_message_collection(
175  const std::string& group, const goby::common::hdf5::MessageCollection& message_collection)
176 {
177  write_time(group, message_collection);
178 
179  const google::protobuf::Descriptor* desc =
180  message_collection.entries.begin()->second->GetDescriptor();
181  for (int i = 0, n = desc->field_count(); i < n; ++i)
182  {
183  const google::protobuf::FieldDescriptor* field_desc = desc->field(i);
184 
185  std::vector<const google::protobuf::Message*> messages;
186  for (std::multimap<goby::uint64,
187  boost::shared_ptr<google::protobuf::Message> >::const_iterator
188  it = message_collection.entries.begin(),
189  end = message_collection.entries.end();
190  it != end; ++it)
191  { messages.push_back(it->second.get()); } std::vector<hsize_t> hs;
192  hs.push_back(messages.size());
193  write_field_selector(group, field_desc, messages, hs);
194  }
195 }
196 
197 void goby::common::hdf5::Writer::write_embedded_message(
198  const std::string& group, const google::protobuf::FieldDescriptor* field_desc,
199  const std::vector<const google::protobuf::Message*> messages, std::vector<hsize_t>& hs)
200 {
201  const google::protobuf::Descriptor* sub_desc = field_desc->message_type();
202  if (field_desc->is_repeated())
203  {
204  int max_field_size = 0;
205  for (unsigned i = 0, n = messages.size(); i < n; ++i)
206  {
207  if (messages[i])
208  {
209  const google::protobuf::Reflection* refl = messages[i]->GetReflection();
210  int field_size = refl->FieldSize(*messages[i], field_desc);
211  if (field_size > max_field_size)
212  max_field_size = field_size;
213  }
214  }
215 
216  hs.push_back(max_field_size);
217 
218  for (int i = 0, n = sub_desc->field_count(); i < n; ++i)
219  {
220  const google::protobuf::FieldDescriptor* sub_field_desc = sub_desc->field(i);
221 
222  std::vector<const google::protobuf::Message*> sub_messages(
223  messages.size() * max_field_size, (const google::protobuf::Message*)0);
224 
225  for (unsigned i = 0, n = messages.size(); i < n; ++i)
226  {
227  if (messages[i])
228  {
229  const google::protobuf::Reflection* refl = messages[i]->GetReflection();
230  int field_size = refl->FieldSize(*messages[i], field_desc);
231 
232  for (int j = 0; j < field_size; ++j)
233  {
234  const google::protobuf::Message& sub_msg =
235  refl->GetRepeatedMessage(*messages[i], field_desc, j);
236  sub_messages[i * max_field_size + j] = &sub_msg;
237  }
238  }
239  }
240  write_field_selector(group + "/" + field_desc->name(), sub_field_desc, sub_messages,
241  hs);
242  }
243  hs.pop_back();
244  }
245  else
246  {
247  for (int i = 0, n = sub_desc->field_count(); i < n; ++i)
248  {
249  const google::protobuf::FieldDescriptor* sub_field_desc = sub_desc->field(i);
250  std::vector<const google::protobuf::Message*> sub_messages;
251  for (std::vector<const google::protobuf::Message*>::const_iterator
252  it = messages.begin(),
253  end = messages.end();
254  it != end; ++it)
255  {
256  if (*it)
257  {
258  const google::protobuf::Reflection* refl = (*it)->GetReflection();
259  const google::protobuf::Message& sub_msg = refl->GetMessage(**it, field_desc);
260  sub_messages.push_back(&sub_msg);
261  }
262  else
263  {
264  sub_messages.push_back(0);
265  }
266  }
267  write_field_selector(group + "/" + field_desc->name(), sub_field_desc, sub_messages,
268  hs);
269  }
270  }
271 }
272 
273 void goby::common::hdf5::Writer::write_field_selector(
274  const std::string& group, const google::protobuf::FieldDescriptor* field_desc,
275  const std::vector<const google::protobuf::Message*>& messages, std::vector<hsize_t>& hs)
276 {
277  switch (field_desc->cpp_type())
278  {
279  case google::protobuf::FieldDescriptor::CPPTYPE_MESSAGE:
280  write_embedded_message(group, field_desc, messages, hs);
281  break;
282 
283  case google::protobuf::FieldDescriptor::CPPTYPE_ENUM:
284  {
285  // google uses int for the enum value type, we'll assume that's an int32 here
286  write_field<goby::int32>(group, field_desc, messages, hs);
287  write_enum_attributes(group, field_desc);
288  break;
289  }
290 
291  case google::protobuf::FieldDescriptor::CPPTYPE_INT32:
292  write_field<goby::int32>(group, field_desc, messages, hs);
293  break;
294 
295  case google::protobuf::FieldDescriptor::CPPTYPE_INT64:
296  write_field<goby::int64>(group, field_desc, messages, hs);
297  break;
298 
299  case google::protobuf::FieldDescriptor::CPPTYPE_UINT32:
300  write_field<goby::uint32>(group, field_desc, messages, hs);
301  break;
302 
303  case google::protobuf::FieldDescriptor::CPPTYPE_UINT64:
304  write_field<goby::uint64>(group, field_desc, messages, hs);
305  break;
306 
307  case google::protobuf::FieldDescriptor::CPPTYPE_BOOL:
308  write_field<unsigned char>(group, field_desc, messages, hs);
309  break;
310 
311  case google::protobuf::FieldDescriptor::CPPTYPE_STRING:
312  if (cfg_.include_string_fields())
313  write_field<std::string>(group, field_desc, messages, hs);
314  else
315  // placeholder for users to know that the field exists, even if the data are omitted
316  write_vector(group, field_desc->name(), std::vector<unsigned char>(),
317  std::vector<hsize_t>(1, 0), (unsigned char)0);
318  break;
319 
320  case google::protobuf::FieldDescriptor::CPPTYPE_FLOAT:
321  write_field<float>(group, field_desc, messages, hs);
322  break;
323 
324  case google::protobuf::FieldDescriptor::CPPTYPE_DOUBLE:
325  write_field<double>(group, field_desc, messages, hs);
326  break;
327  }
328 }
329 
330 void goby::common::hdf5::Writer::write_enum_attributes(
331  const std::string& group, const google::protobuf::FieldDescriptor* field_desc)
332 {
333  // write enum names and values to attributes
334  H5::Group& grp = group_factory_.fetch_group(group);
335  H5::DataSet ds = grp.openDataSet(field_desc->name());
336 
337  const google::protobuf::EnumDescriptor* enum_desc = field_desc->enum_type();
338 
339  std::vector<const char*> names(enum_desc->value_count(), (const char*)(0));
340  std::vector<goby::int32> values(enum_desc->value_count(), 0);
341 
342  for (int i = 0, n = enum_desc->value_count(); i < n; ++i)
343  {
344  names[i] = enum_desc->value(i)->name().c_str();
345  values[i] = enum_desc->value(i)->number();
346  }
347 
348  {
349  const int rank = 1;
350  hsize_t hs[] = {names.size()};
351  H5::DataSpace att_space(rank, hs, hs);
352  H5::StrType att_type(H5::PredType::C_S1, H5T_VARIABLE);
353  H5::Attribute att = ds.createAttribute("enum_names", att_type, att_space);
354  att.write(att_type, &names[0]);
355  }
356  {
357  const int rank = 1;
358  hsize_t hs[] = {values.size()};
359  H5::DataSpace att_space(rank, hs, hs);
360  H5::IntType att_type(predicate<goby::int32>());
361  H5::Attribute att = ds.createAttribute("enum_values", att_type, att_space);
362  att.write(att_type, &values[0]);
363  }
364 }
365 
366 void goby::common::hdf5::Writer::write_time(
367  const std::string& group, const goby::common::hdf5::MessageCollection& message_collection)
368 {
369  std::vector<goby::uint64> utime(message_collection.entries.size(), 0);
370  std::vector<double> datenum(message_collection.entries.size(), 0);
371  int i = 0;
372  for (std::multimap<goby::uint64, boost::shared_ptr<google::protobuf::Message> >::const_iterator
373  it = message_collection.entries.begin(),
374  end = message_collection.entries.end();
375  it != end; ++it)
376  {
377  utime[i] = it->first;
378  // datenum(1970, 1, 1, 0, 0, 0)
379  const double datenum_unix_epoch = 719529;
380  const double seconds_in_day = 86400;
381  goby::uint64 utime_sec = utime[i] / 1000000;
382  goby::uint64 utime_frac = utime[i] - utime_sec * 1000000;
383  datenum[i] = datenum_unix_epoch + static_cast<double>(utime_sec) / seconds_in_day +
384  static_cast<double>(utime_frac) / 1000000 / seconds_in_day;
385  ++i;
386  }
387 
388  std::vector<hsize_t> hs;
389  hs.push_back(message_collection.entries.size());
390  write_vector(group, "_utime_", utime, hs, (goby::uint64)0);
391  write_vector(group, "_datenum_", datenum, hs, (double)0);
392 }
393 
394 void goby::common::hdf5::Writer::write_vector(const std::string& group,
395  const std::string dataset_name,
396  const std::vector<std::string>& data,
397  const std::vector<hsize_t>& hs,
398  const std::string& default_value)
399 {
400  std::vector<const char*> data_c_str;
401  for (unsigned i = 0, n = data.size(); i < n; ++i) data_c_str.push_back(data[i].c_str());
402 
403  H5::DataSpace dataspace(hs.size(), hs.data(), hs.data());
404  H5::StrType datatype(H5::PredType::C_S1, H5T_VARIABLE);
405  H5::Group& grp = group_factory_.fetch_group(group);
406  H5::DataSet dataset = grp.createDataSet(dataset_name, datatype, dataspace);
407 
408  if (data_c_str.size())
409  dataset.write(data_c_str.data(), datatype);
410 
411  const int rank = 1;
412  hsize_t att_hs[] = {1};
413  H5::DataSpace att_space(rank, att_hs, att_hs);
414  H5::StrType att_datatype(H5::PredType::C_S1, default_value.size() + 1);
415  H5::Attribute att = dataset.createAttribute("default_value", att_datatype, att_space);
416  const H5std_string strbuf(default_value);
417  att.write(att_datatype, strbuf);
418 }
void quit()
Requests a clean (return 0) exit.
common::FlexOstream glog
Access the Goby logger through this object.
google::protobuf::uint64 uint64
an unsigned 64 bit integer