libvisiontransfer  10.6.0
parameterserialization.cpp
1 /*******************************************************************************
2  * Copyright (c) 2023 Allied Vision Technologies GmbH
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *******************************************************************************/
14 
15 #include <vector>
16 #include <string>
17 #include <sstream>
18 #include <regex>
19 #include <iostream>
20 
21 #include <visiontransfer/parameterserialization.h>
22 #include <visiontransfer/tokenizer.h>
23 
24 namespace visiontransfer {
25 namespace internal {
26 
27 using namespace param;
28 
29 std::string escapeString(const std::string& str) {
30  auto s = str;
31  s = std::regex_replace(s, std::regex("\\\\"), "\\\\");
32  s = std::regex_replace(s, std::regex("\\n"), "\\n");
33  s = std::regex_replace(s, std::regex("\\t"), "\\t");
34  return s;
35 }
36 
37 std::string unescapeString(const std::string& str) {
38  auto s = str;
39  s = std::regex_replace(s, std::regex("([^\\\\])\\\\t"), "$1\t");
40  s = std::regex_replace(s, std::regex("([^\\\\])\\\\n"), "$1\n");
41  s = std::regex_replace(s, std::regex("\\\\\\\\"), "\\\\");
42  return s;
43 }
44 
45 // String serialization of full parameter info (line header "I") or metadata update (line header "M")
46 void ParameterSerialization::serializeParameterFullUpdate(std::stringstream& ss, const Parameter& param, const std::string& leader) {
47  // 1 Uid
48  ss << leader << "\t" << param.getUid() << "\t";
49  // 2, 3 Access mode RW vs RO (for WebIf, then API) -- NOTE: this function should never even be invoked for an ACCESS_NONE param for the respective receiver
50  switch (param.getAccessForConfig()) {
51  case Parameter::ACCESS_READWRITE: ss << "2\t"; break;
52  case Parameter::ACCESS_READONLY: ss << "1\t"; break;
53  default: ss << "0\t";
54  }
55  switch (param.getAccessForApi()) {
56  case Parameter::ACCESS_READWRITE: ss << "2\t"; break;
57  case Parameter::ACCESS_READONLY: ss << "1\t"; break;
58  default: ss << "0\t";
59  }
60  // 4 Interaction hint: parameter invisible/inactive/active
61  ss << ((int) param.getInteractionHint()) << "\t";
62  // 5 Modified flag (unsaved changes)
63  ss << (param.getIsModified() ? "1" : "0") << "\t";
64  // 6 Display name
65  ss << param.getName() << "\t";
66  // 7 Module name
67  ss << param.getModuleName() << "\t";
68  // 8 Category name
69  ss << param.getCategoryName() << "\t";
70  // 9 Type
71  switch (param.getType()) {
72  case ParameterValue::TYPE_INT:
73  ss << "i\t";
74  break;
75  case ParameterValue::TYPE_DOUBLE:
76  ss << "d\t";
77  break;
78  case ParameterValue::TYPE_BOOL:
79  ss << "b\t";
80  break;
81  case ParameterValue::TYPE_STRING:
82  ss << "s\t";
83  break;
84  case ParameterValue::TYPE_SAFESTRING:
85  ss << "S\t";
86  break;
87  case ParameterValue::TYPE_TENSOR:
88  ss << "T\t";
89  break;
90  case ParameterValue::TYPE_COMMAND:
91  ss << "C\t";
92  break;
93  default:
94  // should not happen
95  ss << "?\t";
96  break;
97  }
98  // 10 Unit
99  ss << param.getUnit() << "\t";
100  // 11 Description
101  ss << escapeString(param.getDescription()) << "\t";
102  // 12 Default value
103  if (!param.isTensor()) {
104  ss << param.getDefault<std::string>() << "\t";
105  } else {
106  auto shape = param.getTensorShape();
107  ss << param.getTensorDimension() << " ";
108  for (unsigned int i=0; i<param.getTensorDimension(); ++i) {
109  ss << shape[i] << " ";
110  }
111  bool first=true;
112  if (param.hasDefault()) {
113  for (auto e: param.getTensorDefaultData()) {
114  if (first) first = false;
115  else ss << " ";
116  ss << std::setprecision(std::numeric_limits<double>::max_digits10 - 1) << e;
117  }
118  } else {
119  // All-zero fallback
120  for (int i=0; i<(int) param.getTensorNumElements(); ++i) {
121  if (first) first = false;
122  else ss << " ";
123  ss << "0.0";
124  }
125  }
126  ss << "\t";
127  }
128  // 13, 14, 15 Min, Max, Increment
129  if (param.isScalar()) {
130  if (param.hasRange()) {
131  ss << param.getMin<std::string>() << "\t";
132  ss << param.getMax<std::string>() << "\t";
133  } else {
134  ss << "\t\t";
135  }
136  if (param.hasIncrement()) {
137  ss << param.getIncrement<std::string>() << "\t";
138  } else {
139  ss << "\t";
140  }
141  } else {
142  ss << "\t\t\t"; // Tensor bounds undefined
143  }
144  // 16, 17 Option Values, Option Descriptions (;-separated lists)
145  auto opts = param.getOptions<std::string>();
146  for (unsigned int i=0; i<opts.size(); ++i) {
147  if (i) ss << ";";
148  ss << opts[i];
149  }
150  ss << "\t";
151  auto descrs = param.getOptionDescriptions();
152  for (unsigned int i=0; i<descrs.size(); ++i) {
153  if (i) ss << ";";
154  ss << descrs[i];
155  }
156  ss << "\t";
157  // 18 Current value
158  if (!param.isTensor()) {
159  if (param.hasCurrent()) {
160  ss << param.getCurrent<std::string>();
161  } else {
162  ss << param.getDefault<std::string>();
163  }
164  } else {
165  auto shape = param.getTensorShape();
166  ss << param.getTensorDimension() << " ";
167  for (unsigned int i=0; i<param.getTensorDimension(); ++i) {
168  ss << shape[i] << " ";
169  }
170  bool first=true;
171  if (param.hasCurrent()) {
172  for (auto e: param.getTensorData()) {
173  if (first) first = false;
174  else ss << " ";
175  ss << std::setprecision(std::numeric_limits<double>::max_digits10 - 1) << e;
176  }
177  } else {
178  // All-zero fallback
179  for (int i=0; i<(int) param.getTensorNumElements(); ++i) {
180  if (first) first = false;
181  else ss << " ";
182  ss << "0.0";
183  }
184  }
185  }
186 }
187 
188 // expecting a tab-tokenization of a parameter info
189 Parameter ParameterSerialization::deserializeParameterFullUpdate(const std::vector<std::string>& toks, const std::string& leader) {
191  static visiontransfer::internal::Tokenizer tokrSemi;
192  tokrSemi.separators({";"});
193  if (toks.size() < 19) {
194  throw std::runtime_error("deserializeParameterFullUpdate: parameter info string tokens missing");
195  }
196  // toks[0] should be "I" for full updates and "M" for metadata-only updates, but double-check
197  if (toks[0] != std::string(leader)) throw std::runtime_error("deserializeParameterFullUpdate: attempted deserialization of a non-parameter");
198 
199  // 1 Param with UID (putting in place of reference)
200  if (toks[1].size() < 1) throw std::runtime_error("deserializeParameterFullUpdate: malformed UID field");
201  Parameter param = Parameter(toks[1]);
202  // 2 Access rights, WebIf
203  if (toks[2].size() != 1) throw std::runtime_error("deserializeParameterFullUpdate: malformed access field");
204  if (toks[2]=="2") {
205  param.setAccessForConfig(Parameter::ACCESS_READWRITE);
206  } else if (toks[2]=="1") {
207  param.setAccessForConfig(Parameter::ACCESS_READONLY);
208  } else {
209  param.setAccessForConfig(Parameter::ACCESS_NONE);
210  }
211  // 3 Access rights, API
212  if (toks[3].size() != 1) throw std::runtime_error("deserializeParameterFullUpdate: malformed access field");
213  if (toks[3]=="2") {
214  param.setAccessForApi(Parameter::ACCESS_READWRITE);
215  } else if (toks[3]=="1") {
216  param.setAccessForApi(Parameter::ACCESS_READONLY);
217  } else {
218  param.setAccessForApi(Parameter::ACCESS_NONE);
219  }
220  // 4 Interaction hint: parameter invisible/inactive/active
221  int hint = atol(toks[4].c_str());
222  if ((hint<-1) || (hint>1)) {
223  throw std::runtime_error("deserializeParameterFullUpdate: invalid interaction hint");
224  }
225  // 5 Modified flag (unsaved changes)
226  param.setIsModified(toks[5] == "1");
227  // 6, 7, 8 Display name, Module name, Category name
228  param.setName(toks[6]);
229  param.setModuleName(toks[7]);
230  param.setCategoryName(toks[8]);
231  // 9 Type
232  if (toks[9].size() != 1) throw std::runtime_error("deserializeParameterFullUpdate: malformed type field");
233  char typ = toks[9][0];
234  bool isTensor = typ == 'T'; // for conditional processing of current value further down
235  switch (typ) {
236  case 'i': param.setType(ParameterValue::TYPE_INT); break;
237  case 'd': param.setType(ParameterValue::TYPE_DOUBLE); break;
238  case 'b': param.setType(ParameterValue::TYPE_BOOL); break;
239  case 's': param.setType(ParameterValue::TYPE_STRING); break;
240  case 'S': param.setType(ParameterValue::TYPE_SAFESTRING); break;
241  case 'T': param.setType(ParameterValue::TYPE_TENSOR); break;
242  case 'C': param.setType(ParameterValue::TYPE_COMMAND); break;
243  default: throw std::runtime_error("deserializeParameterFullUpdate: unhandled type");
244  }
245  // 10, 11 Unit, Description
246  param.setUnit(toks[10]);
247  param.setDescription(unescapeString(toks[11]));
248  // 12 Default value
249  if (!isTensor) {
250  param.setDefault<std::string>(toks[12]);
251  }
252  // 13, 14, 15 Min, Max, Increment
253  if (param.isScalar()) {
254  if ((toks[13].size()>0) && (toks[13]!="-") &&
255  (toks[14].size()>0) && (toks[14]!="-")) {
256  param.setRange<std::string>(toks[13], toks[14]);
257  }
258  if ((toks[15].size()>0) && (toks[15]!="-")) {
259  param.setIncrement<std::string>(toks[15]);
260  }
261  }
262  // 16, 17 Option values / descriptions, semicolon-separated
263  if (!isTensor) {
264  auto optvals = tokrSemi.tokenize(toks[16]);
265  auto optdescrs = tokrSemi.tokenize(toks[17]);
266  if ((optvals.size() > 0) && (optvals[0] != "")) {
267  param.setOptions<std::string>(optvals, optdescrs);
268  }
269  }
270  // 18 Current value
271  if (!isTensor) {
272  param.setCurrent<std::string>(toks[18]);
273  } else {
274  auto dataToks = tokr.tokenize(toks[18]);
275  if (dataToks.size() < 1) {
276  throw std::runtime_error("deserializeParameterFullUpdate: tensor with empty specification");
277  } else {
278  int dim = atol(dataToks[0].c_str());
279  if (dataToks.size() < (unsigned long) 1+dim) {
280  throw std::runtime_error("deserializeParameterFullUpdate: tensor with incomplete specification");
281  } else {
282  std::vector<unsigned int> shape;
283  for (int i=0; i<dim; ++i) {
284  shape.push_back((unsigned int) atol(dataToks[1+i].c_str()));
285  }
286  param.setAsTensor(shape);
287  int tensorsize = param.getTensorNumElements();
288  if (dataToks.size() != (unsigned long) tensorsize + 1 + dim) {
289  throw std::runtime_error("deserializeParameterFullUpdate: tensor with mismatching data size");
290  } else {
291  std::vector<double> data;
292  for (int i=0; i<tensorsize; ++i) {
293  data.push_back(atof(dataToks[i+1+dim].c_str()));
294  }
295  param.setTensorData(data);
296  }
297  }
298  }
299  }
300  return param;
301 }
302 
303 // String serialization of current-value-only modification (line header "V")
304 void ParameterSerialization::serializeParameterValueChange(std::stringstream& ss, const Parameter& param) {
305  if (param.isScalar()) {
306  ss << "V" << "\t" << param.getUid() << "\t" << (param.getIsModified()?"1":"0") << "\t" << param.getCurrent<std::string>();
307  } else {
308  // Tensor with current shape + data
309  ss << "V" << "\t" << param.getUid() << "\t" << (param.getIsModified()?"1":"0") << "\t";
310  auto shape = param.getTensorShape();
311  ss << param.getTensorDimension() << " ";
312  for (unsigned int i=0; i<param.getTensorDimension(); ++i) {
313  ss << shape[i] << " ";
314  }
315  bool first=true;
316  for (auto e: param.getTensorData()) {
317  if (first) first = false;
318  else ss << " ";
319  ss << std::setprecision(std::numeric_limits<double>::max_digits10 - 1) << e;
320  }
321  }
322 }
323 
324 void ParameterSerialization::deserializeParameterValueChange(const std::vector<std::string>& toks, Parameter& param) {
326  if (toks.size() < 4) throw std::runtime_error("deserializeParameterValueChange: incomplete data");
327  // toks[0] should be "V", but double-check
328  if (toks[0] != "V") throw std::runtime_error("deserializeParameterValueChange: cannot deserialize, not a value change");
329  if (toks[1] != param.getUid()) throw std::runtime_error("deserializeParameterValueChange: UID mismatch (bug)");
330  param.setIsModified(toks[2] == "1");
331  if (param.isTensor()) {
332  auto dataToks = tokr.tokenize(toks[3]);
333  if (dataToks.size() < 1) {
334  throw std::runtime_error("deserializeParameterValueChange: tensor with empty specification");
335  } else {
336  int dim = atol(dataToks[0].c_str());
337  if (dataToks.size() < (unsigned long) 1+dim) {
338  throw std::runtime_error("deserializeParameterValueChange: tensor with incomplete specification");
339  } else {
340  // Verify shape
341  int tensorsize = param.getTensorNumElements();
342  int elems = 1;
343  for (int i=0; i<dim; ++i) {
344  elems *= (unsigned int) atol(dataToks[1+i].c_str());
345  }
346  if (elems != tensorsize) {
347  // Mismatch between reported data shape and known shape
348  throw std::runtime_error("deserializeParameterValueChange: tensor with mismatching shape");
349  }
350  if (dataToks.size() != (unsigned long) tensorsize + 1 + dim) {
351  // Insufficient or extraneous data elements
352  throw std::runtime_error("deserializeParameterValueChange: tensor with mismatching data size");
353  } else {
354  std::vector<double> data;
355  for (int i=0; i<tensorsize; ++i) {
356  data.push_back(atof(dataToks[i+1+dim].c_str()));
357  }
358  param.setTensorData(data);
359  }
360  }
361  }
362  } else {
363  param.setCurrent<std::string>(toks[3]);
364  }
365 }
366 
367 void ParameterSerialization::serializeAsyncResult(std::stringstream& ss, const std::string& requestId, bool success, const std::string& message) {
368  ss << "R\t" << requestId << "\t" << (success?"1\t":"0\t") << message;
369 }
370 
371 void ParameterSerialization::deserializeAsyncResult(const std::vector<std::string>& toks, std::string& requestId, bool& success, std::string& message) {
372  if (toks.size() < 4) throw std::runtime_error("deserializeAsyncResult: incomplete data");
373  if (toks[0] != "R") throw std::runtime_error("deserializeAsyncResult: cannot deserialize, not an async result");
374  requestId = toks[1];
375  success = (toks[2] == "1");
376  message = toks[3];
377 }
378 
379 } // namespace internal
380 } // namespace visiontransfer
381 
Allied Vision