TTK
Loading...
Searching...
No Matches
ttkWebSocketIO.cpp
Go to the documentation of this file.
1#include <ttkWebSocketIO.h>
2
3#include <vtkInformation.h>
4
5#include <vtkImageData.h>
6#include <vtkMultiBlockDataSet.h>
7#include <vtkPolyData.h>
8#include <vtkSmartPointer.h>
9#include <vtkStringArray.h>
10#include <vtkUnstructuredGrid.h>
11
12#include <vtkCellData.h>
13#include <vtkDoubleArray.h>
14#include <vtkPointData.h>
15#include <vtkUnsignedCharArray.h>
16
17#include <vtkCellArray.h>
18#include <vtkPoints.h>
19
20#include <boost/algorithm/string/replace.hpp>
21#include <boost/lexical_cast.hpp>
22#include <boost/optional/optional.hpp>
23#include <boost/property_tree/json_parser.hpp>
24#include <boost/property_tree/ptree.hpp>
25
26#include <ttkUtils.h>
27
29
33
34 this->SetNumberOfInputPorts(1);
35 this->SetNumberOfOutputPorts(1);
36}
37
39
40int ttkWebSocketIO::FillInputPortInformation(int port, vtkInformation *info) {
41 if(port == 0) {
42 info->Remove(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE());
43 info->Append(
44 vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkMultiBlockDataSet");
45 info->Append(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkDataSet");
46 return 1;
47 }
48 return 0;
49}
50
51int ttkWebSocketIO::FillOutputPortInformation(int port, vtkInformation *info) {
52 if(port == 0) {
53 info->Set(vtkDataObject::DATA_TYPE_NAME(), "vtkMultiBlockDataSet");
54 return 1;
55 }
56 return 0;
57}
58
59int ttkWebSocketIO::RequestData(vtkInformation *ttkNotUsed(request),
60 vtkInformationVector **inputVector,
61 vtkInformationVector *outputVector) {
62 const bool neededUpdate = this->GetNeedsUpdate();
63 this->SetNeedsUpdate(false);
64
65 // close existing server if it has a different port
66 if(this->isListening() && (this->getPortNumber() != this->PortNumber))
67 this->stopServer();
68
69 try {
70 if(!this->isListening())
71 this->startServer(this->PortNumber);
72 } catch(const std::exception &e) {
73 this->printErr("Unable to start server:\n" + std::string(e.what()));
74 return 0;
75 }
76
77 if(neededUpdate) {
78 // if needed update then the client send data to the server which was parsed
79 // and is now passed on as the output of the filter
80 auto output = vtkMultiBlockDataSet::GetData(outputVector);
81 output->ShallowCopy(this->LastOutput);
82 } else {
83 // in any other case (i.e., if the input or parameters changed) then send
84 // the last input to the client
85 auto input = vtkDataObject::GetData(inputVector[0]);
86 this->LastInput
87 = vtkSmartPointer<vtkDataObject>::Take(input->NewInstance());
88 this->LastInput->ShallowCopy(input);
89 if(!this->processEvent("on_message", "RequestInputVtkDataSet"))
90 return 0;
91 }
92 return 1;
93}
94
95int ttkWebSocketIO::processEvent(const std::string &eventName,
96 const std::string &eventData) {
97 if(eventData.compare("RequestInputVtkDataSet") == 0) {
98 if(!this->SendVtkDataObject(this->LastInput))
99 return 0;
100 } else if(eventData.rfind("{\"vtkDataSet", 12) == 0) {
101 if(!this->ParseVtkDataObjectFromJSON(eventData))
102 return 0;
103 }
104
105 return WebSocketIO::processEvent(eventName, eventData);
106}
107
108template <typename T>
109T jsonGetValue(const boost::property_tree::ptree &pt,
110 const boost::property_tree::ptree::key_type &key) {
111 return pt.get_child(key).get_value<T>();
112}
113
114template <typename T>
115void jsonArrayToArray(const boost::property_tree::ptree &pt,
116 const boost::property_tree::ptree::key_type &key,
117 T *result) {
118 std::vector<double> values;
120 jsonGetValue<std::string>(pt, key), values);
121
122 for(size_t i = 0, j = values.size(); i < j; i++) {
123 result[i] = (T)values[i];
124 }
125}
126
127static bool jsonHasChild(const boost::property_tree::ptree &pt,
128 const boost::property_tree::ptree::key_type &key) {
129 return pt.find(key) != pt.not_found();
130}
131
132int ttkWebSocketIO::ParseVtkDataObjectFromJSON(const std::string &json) {
133 ttk::Timer timer;
134 this->printMsg("Parsing vtkDataObject from JSON string", 0, 0,
136
138
139 // parse lastClientInput into json
140 boost::property_tree::ptree pt;
141 try {
142 std::stringstream ss;
143 ss << json;
144 boost::property_tree::read_json(ss, pt);
145 } catch(const std::exception &e) {
146 this->printErr("Unable to parse JSON message: " + json);
147 return 0;
148 }
149
150 // check if a path exists in property_tree or a key exists in json object
151
152 size_t b = 0;
153 for(auto it = pt.begin(); it != pt.end(); ++it) {
155
156 auto jsonVtkDataSet = it->second;
157 if(!jsonHasChild(jsonVtkDataSet, "className")
158 || !jsonHasChild(jsonVtkDataSet, "points")
159 || !jsonHasChild(jsonVtkDataSet, "cells")
160 || !jsonHasChild(jsonVtkDataSet, "pointData")
161 || !jsonHasChild(jsonVtkDataSet, "cellData")
162 || !jsonHasChild(jsonVtkDataSet, "fieldData")) {
163 this->printErr("Invalid vtkDataSet serialization.");
164 return 0;
165 }
166
167 if(jsonGetValue<std::string>(jsonVtkDataSet, "className")
168 .compare("vtkUnstructuredGrid")
169 != 0) {
170 this->printErr(
171 "Currently WebSocketIO only supports parsing 'vtkUnstructuredGrids'.");
172 return 0;
173 }
174
175 // parse points
176 {
177 auto nPoints
178 = jsonGetValue<int>(jsonVtkDataSet, "points.coordinates.nTuples");
179
180 vtkSmartPointer<vtkPoints> const points
182 points->SetNumberOfPoints(nPoints);
183
184 jsonArrayToArray<float>(jsonVtkDataSet, "points.coordinates.data",
185 (float *)ttkUtils::GetVoidPointer(points));
186
187 block->SetPoints(points);
188 }
189
190 // parse cells
191 {
192 // VTK CELL TYPES MAP
193 constexpr int vtkCellsTypeHash[20] = {VTK_EMPTY_CELL,
194 VTK_VERTEX,
195 VTK_LINE,
196 VTK_TRIANGLE,
197 VTK_TETRA,
198 VTK_CONVEX_POINT_SET,
199 VTK_CONVEX_POINT_SET,
200 VTK_CONVEX_POINT_SET,
201 VTK_VOXEL,
202 VTK_CONVEX_POINT_SET,
203 VTK_CONVEX_POINT_SET,
204 VTK_CONVEX_POINT_SET,
205 VTK_CONVEX_POINT_SET,
206 VTK_CONVEX_POINT_SET,
207 VTK_CONVEX_POINT_SET,
208 VTK_CONVEX_POINT_SET,
209 VTK_CONVEX_POINT_SET,
210 VTK_CONVEX_POINT_SET,
211 VTK_CONVEX_POINT_SET,
212 VTK_CONVEX_POINT_SET};
213
214 auto nOffsets
215 = jsonGetValue<int>(jsonVtkDataSet, "cells.offsetsArray.nTuples");
217 offsets->SetNumberOfTuples(nOffsets);
218 auto offsetsData = (vtkIdType *)ttkUtils::GetVoidPointer(offsets);
219 jsonArrayToArray<vtkIdType>(
220 jsonVtkDataSet, "cells.offsetsArray.data", offsetsData);
221
222 auto nConnectivity
223 = jsonGetValue<int>(jsonVtkDataSet, "cells.connectivityArray.nTuples");
224 auto connectivity = vtkSmartPointer<vtkIdTypeArray>::New();
225 connectivity->SetNumberOfTuples(nConnectivity);
226 jsonArrayToArray<vtkIdType>(
227 jsonVtkDataSet, "cells.connectivityArray.data",
228 (vtkIdType *)ttkUtils::GetVoidPointer(connectivity));
229
231 cells->SetData(offsets, connectivity);
232
234 cellTypes->SetNumberOfTuples(nOffsets - 1);
235 auto cellTypesData = (unsigned char *)ttkUtils::GetVoidPointer(cellTypes);
236 for(int i = 1; i < nOffsets; i++) {
237 cellTypesData[i - 1]
238 = vtkCellsTypeHash[offsetsData[i] - offsetsData[i - 1]];
239 }
240
241 block->SetCells(cellTypes, cells);
242 }
243
244 auto addArraysToAssociation = [](vtkFieldData *fd,
245 const boost::property_tree::ptree &fdJSON,
246 const std::string &fdKey) {
247 // parse point data
248 for(const auto &pda : fdJSON.get_child(fdKey)) {
249 const auto &jsonArray = pda.second;
250 size_t const nComponents
251 = jsonGetValue<size_t>(jsonArray, "nComponents");
252 size_t const nTuples = jsonGetValue<size_t>(jsonArray, "nTuples");
253
255 vtkDataArray::CreateArray(jsonGetValue<int>(jsonArray, "dataType")));
256
257 array->SetName(jsonGetValue<std::string>(jsonArray, "name").data());
258 array->SetNumberOfComponents(nComponents);
259 array->SetNumberOfTuples(nTuples);
260
261 if(auto dataArray = vtkDataArray::SafeDownCast(array)) {
262 switch(dataArray->GetDataType()) {
263 vtkTemplateMacro(jsonArrayToArray<VTK_TT>(
264 jsonArray, "data",
265 (VTK_TT *)ttkUtils::GetVoidPointer(dataArray)));
266 }
267 } else if(auto stringArray = vtkStringArray::SafeDownCast(array)) {
268 std::vector<std::string> values;
270 jsonGetValue<std::string>(jsonArray, "data"), values);
271
272 for(size_t i = 0; i < values.size(); i++)
273 stringArray->SetValue(i, values[i]);
274 } else {
275 return 0;
276 }
277
278 fd->AddArray(array);
279 }
280 return 1;
281 };
282
283 if(!addArraysToAssociation(
284 block->GetPointData(), jsonVtkDataSet, "pointData")) {
285 this->printErr("Unsupported data type.");
286 return 0;
287 }
288 if(!addArraysToAssociation(
289 block->GetCellData(), jsonVtkDataSet, "cellData")) {
290 this->printErr("Unsupported data type.");
291 return 0;
292 }
293 if(!addArraysToAssociation(
294 block->GetFieldData(), jsonVtkDataSet, "fieldData")) {
295 this->printErr("Unsupported data type.");
296 return 0;
297 }
298
299 this->LastOutput->SetBlock(b++, block);
300 }
301
302 this->printMsg(
303 "Parsing vtkDataObject from JSON string", 1, timer.getElapsedTime());
304
305 this->SetNeedsUpdate(true);
306
307 return 1;
308}
309
310int ttkWebSocketIO::SendVtkDataObject(vtkDataObject *object) {
311 ttk::Timer timer;
312 this->printMsg(
313 "Serializing vtkDataObject", 0, 0, ttk::debug::LineMode::REPLACE);
314
315 // clear queue
316 this->clearMessageQueue();
317
319 if(object->IsA("vtkMultiBlockDataSet"))
320 objectAsMB->ShallowCopy(object);
321 else
322 objectAsMB->SetBlock(0, object);
323
324 const size_t nBlocks = objectAsMB->GetNumberOfBlocks();
325 for(size_t b = 0; b < nBlocks; b++) {
326 auto block = vtkDataSet::SafeDownCast(objectAsMB->GetBlock(b));
327 if(!block) {
328 this->printErr("Input `vtkDataObject` is not a `vtkDataSet` or a "
329 "`vtkMultiBlockDataSet` containing only `vtkDataSets`.");
330 return 0;
331 }
332
333 // send class name
334 this->queueMessage("{ \"className\": \""
335 + std::string(block->GetClassName()) + "\" }");
336
337 this->printMsg(
338 "Serializing vtkDataObject", 0.2, 0, ttk::debug::LineMode::REPLACE);
339
340 // send image meta data if last input is a vtkImageData
341 if(block->IsA("vtkImageData")) {
342 auto lastInputAsID = vtkImageData::SafeDownCast(block);
343 int dimension[3];
344 double origin[3];
345 double spacing[3];
346 lastInputAsID->GetDimensions(dimension);
347 lastInputAsID->GetOrigin(origin);
348 lastInputAsID->GetSpacing(spacing);
349
350 this->queueMessage(
351 "{"
352 "\"dimension\": ["
353 + std::to_string(dimension[0]) + "," + std::to_string(dimension[1])
354 + "," + std::to_string(dimension[2])
355 + "],"
356 "\"origin\": ["
357 + std::to_string(origin[0]) + "," + std::to_string(origin[1]) + ","
358 + std::to_string(origin[2])
359 + "],"
360 "\"spacing\": ["
361 + std::to_string(spacing[0]) + "," + std::to_string(spacing[1]) + ","
362 + std::to_string(spacing[2])
363 + "]"
364 "}");
365 }
366
367 this->printMsg(
368 "Serializing vtkDataObject", 0.4, 0, ttk::debug::LineMode::REPLACE);
369
370 // send points if last input is a vtkPointSet
371 auto blockAsPS = vtkPointSet::SafeDownCast(block);
372 if(blockAsPS != nullptr) {
373 auto points = blockAsPS->GetPoints();
374 this->queueMessage(
375 "{"
376 "\"target\": \"points\","
377 "\"name\": \"coordinates\","
378 "\"dataType\":"
379 + std::to_string(points ? points->GetDataType() : VTK_FLOAT)
380 + ","
381 "\"nTuples\":"
382 + std::to_string(blockAsPS->GetNumberOfPoints())
383 + ","
384 "\"nComponents\": 3"
385 "}");
386 if(blockAsPS->GetNumberOfPoints() > 0 && points != nullptr)
387 switch(points->GetDataType()) {
388 vtkTemplateMacro(this->queueMessage(
389 blockAsPS->GetNumberOfPoints() * sizeof(VTK_TT) * 3,
390 ttkUtils::GetVoidPointer(points)));
391 }
392 }
393
394 this->printMsg(
395 "Serializing vtkDataObject", 0.6, 0, ttk::debug::LineMode::REPLACE);
396
397 // send cells if last input is vtkUnstructuredGrid or vtkPolyData
398 {
399 vtkCellArray *cells = nullptr;
400
401 if(block->IsA("vtkUnstructuredGrid")) {
402 auto blockAsUG = vtkUnstructuredGrid::SafeDownCast(block);
403 if(blockAsUG != nullptr) {
404 cells = blockAsUG->GetCells();
405 }
406 } else if(block->IsA("vtkPolyData")) {
407 auto blockAsPD = vtkPolyData::SafeDownCast(block);
408 if(blockAsPD != nullptr) {
409 cells = blockAsPD->GetPolys() ? blockAsPD->GetPolys()
410 : blockAsPD->GetLines();
411 }
412 }
413
414 if(cells) {
415 {
416 auto connectivityArray = cells->GetConnectivityArray();
417 this->queueMessage(
418 "{"
419 "\"target\": \"cells\","
420 "\"name\": \"connectivityArray\","
421 "\"dataType\":"
422 + std::to_string(VTK_ID_TYPE)
423 + ","
424 "\"nTuples\":"
425 + std::to_string(connectivityArray->GetNumberOfTuples())
426 + ","
427 "\"nComponents\": 1"
428 "}");
429 if(connectivityArray->GetNumberOfTuples() > 0)
430 this->queueMessage(
431 connectivityArray->GetNumberOfTuples() * sizeof(vtkIdType),
432 ttkUtils::GetVoidPointer(connectivityArray));
433 }
434
435 {
436 auto offsetsArray = cells->GetOffsetsArray();
437 this->queueMessage("{"
438 "\"target\": \"cells\","
439 "\"name\": \"offsetsArray\","
440 "\"dataType\":"
441 + std::to_string(VTK_ID_TYPE)
442 + ","
443 "\"nTuples\":"
444 + std::to_string(offsetsArray->GetNumberOfTuples())
445 + ","
446 "\"nComponents\": 1"
447 "}");
448 if(offsetsArray->GetNumberOfTuples() > 0)
449 this->queueMessage(
450 offsetsArray->GetNumberOfTuples() * sizeof(vtkIdType),
451 ttkUtils::GetVoidPointer(offsetsArray));
452 }
453 }
454 }
455
456 this->printMsg(
457 "Serializing vtkDataObject", 0.8, 0, ttk::debug::LineMode::REPLACE);
458
459 // send point, cell, and field data
460 {
461 std::vector<std::pair<std::string, vtkFieldData *>> attributes
462 = {{"pointData", block->GetPointData()},
463 {"cellData", block->GetCellData()},
464 {"fieldData", block->GetFieldData()}};
465
466 for(auto &attribute : attributes) {
467 size_t const nArrays = attribute.second->GetNumberOfArrays();
468
469 for(size_t i = 0; i < nArrays; i++) {
470 auto array = attribute.second->GetAbstractArray(i);
471
472 if(array->IsA("vtkDataArray")) {
473 auto dataArray = vtkDataArray::SafeDownCast(array);
474 if(dataArray == nullptr) {
475 continue;
476 }
477 this->queueMessage(
478 "{"
479 "\"target\":\""
480 + attribute.first
481 + "\","
482 "\"name\":\""
483 + std::string(dataArray->GetName())
484 + "\","
485 "\"dataType\":"
486 + std::to_string(dataArray->GetDataType())
487 + ","
488 "\"nTuples\":"
489 + std::to_string(dataArray->GetNumberOfTuples())
490 + ","
491 "\"nComponents\":"
492 + std::to_string(dataArray->GetNumberOfComponents()) + "}");
493 if(dataArray->GetNumberOfValues() > 0)
494 switch(dataArray->GetDataType()) {
495 vtkTemplateMacro(this->queueMessage(
496 dataArray->GetNumberOfValues() * sizeof(VTK_TT),
497 ttkUtils::GetVoidPointer(dataArray)));
498 }
499 } else if(array->IsA("vtkStringArray")) {
500 auto stringArray = vtkStringArray::SafeDownCast(array);
501 std::string values;
502 values += stringArray->GetValue(0);
503 for(int j = 1; j < stringArray->GetNumberOfValues(); j++)
504 values += "," + stringArray->GetValue(j);
505
506 this->queueMessage(
507 "{"
508 "\"target\":\""
509 + attribute.first
510 + "\","
511 "\"name\":\""
512 + std::string(stringArray->GetName())
513 + "\","
514 "\"dataType\":"
515 + std::to_string(stringArray->GetDataType())
516 + ","
517 "\"nTuples\":"
518 + std::to_string(stringArray->GetNumberOfTuples())
519 + ","
520 "\"nComponents\":"
521 + std::to_string(stringArray->GetNumberOfComponents())
522 + ","
523 "\"data\":\""
524 + boost::replace_all_copy(values, "\"", "\\\"")
525 + "\""
526 "}");
527 }
528 }
529 }
530 }
531 }
532
533 this->printMsg("Serializing vtkDataObject", 1, timer.getElapsedTime());
534
535 this->processMessageQueue();
536
537 return 1;
538}
#define ttkNotUsed(x)
Mark function/method parameters that are not used in the function body at all.
Definition BaseClass.h:47
static void * GetVoidPointer(vtkDataArray *array, vtkIdType start=0)
Definition ttkUtils.cpp:226
static int stringListToDoubleVector(const std::string &iString, std::vector< double > &v)
Definition ttkUtils.cpp:133
static int stringListToVector(const std::string &iString, std::vector< std::string > &v)
Definition ttkUtils.cpp:115
int FillOutputPortInformation(int port, vtkInformation *info) override
virtual void SetNeedsUpdate(bool)
int RequestData(vtkInformation *request, vtkInformationVector **inputVector, vtkInformationVector *outputVector) override
int processEvent(const std::string &eventName, const std::string &eventData="") override
virtual bool GetNeedsUpdate()
int SendVtkDataObject(vtkDataObject *object)
~ttkWebSocketIO() override
int FillInputPortInformation(int port, vtkInformation *info) override
int ParseVtkDataObjectFromJSON(const std::string &json)
int printErr(const std::string &msg, const debug::LineMode &lineMode=debug::LineMode::NEW, std::ostream &stream=std::cerr) const
Definition Debug.h:149
double getElapsedTime()
Definition Timer.h:15
int queueMessage(const std::string &msg)
int startServer(int PortNumber)
T jsonGetValue(const boost::property_tree::ptree &pt, const boost::property_tree::ptree::key_type &key)
vtkStandardNewMacro(ttkWebSocketIO)
void jsonArrayToArray(const boost::property_tree::ptree &pt, const boost::property_tree::ptree::key_type &key, T *result)
printMsg(debug::output::BOLD+" | | | | | . \\ | | (__| | / __/| |_| / __/|__ _|"+debug::output::ENDCOLOR, debug::Priority::PERFORMANCE, debug::LineMode::NEW, stream)