CustusX  2023.01.05-dev+develop.0da12
An IGT application
cxNetworkHandler.cpp
Go to the documentation of this file.
1 /*=========================================================================
2 This file is part of CustusX, an Image Guided Therapy Application.
3 
4 Copyright (c) SINTEF Department of Medical Technology.
5 All rights reserved.
6 
7 CustusX is released under a BSD 3-Clause license.
8 
9 See Lisence.txt (https://github.com/SINTEFMedtek/CustusX/blob/master/License.txt) for details.
10 =========================================================================*/
11 
12 #include "cxNetworkHandler.h"
13 
14 #include <QTimer>
15 
16 #include "igtlioLogic.h"
17 #include "igtlioImageDevice.h"
18 #include "igtlioTransformDevice.h"
19 #include "igtlioStatusDevice.h"
20 #include "igtlioStringDevice.h"
21 
22 #include "igtlioConnector.h"
23 
24 #include "igtlioImageConverter.h"
25 #include "igtlioTransformConverter.h"
26 #include "igtlioCommandConverter.h"
27 #include "igtlioStatusConverter.h"
28 #include "igtlioStringConverter.h"
29 #include "igtlioUsSectorDefinitions.h"
30 
31 #include "cxLogger.h"
32 #include "cxProbeSector.h"
33 #include "cxProbeDefinition.h"
34 
35 namespace cx
36 {
37 
38 NetworkHandler::NetworkHandler(igtlioLogicPointer logic) :
39  mTimer(new QTimer(this)),
40  mProbeDefinitionFromStringMessages(ProbeDefinitionFromStringMessagesPtr(new ProbeDefinitionFromStringMessages)),
41  mGotTimeOffset(false),
42  mTimestampOffsetMS(0),
43  mGotMoreThanOneImage(false),
44  mProbeDefinition(ProbeDefinitionPtr()),
45  mZeroesInImage(true),
46  mUSMask(nullptr),
47  mSkippedImages(0)
48 {
49  qRegisterMetaType<Transform3D>("Transform3D");
50  qRegisterMetaType<ImagePtr>("ImagePtr");
51 
52  mLogic = logic;
53 
55  this->connectToDeviceEvents();
56 
57  connect(mTimer, SIGNAL(timeout()), this, SLOT(periodicProcess()));
58  mTimer->start(5);
59 }
60 
62 {
63  mTimer->stop();
64 }
65 
66 igtlioSessionPointer NetworkHandler::requestConnectToServer(std::string serverHost, int serverPort, IGTLIO_SYNCHRONIZATION_TYPE sync, double timeout_s)
67 {
68  mSession = mLogic->ConnectToServer(serverHost, serverPort, sync, timeout_s);
69  return mSession;
70 }
71 
73 {
74  if (mSession->GetConnector() && mSession->GetConnector()->GetState()!=igtlioConnector::STATE_OFF)
75  {
76  CX_LOG_DEBUG() << "NetworkHandler: Disconnecting from server" << mSession->GetConnector()->GetName();
77  igtlioConnectorPointer connector = mSession->GetConnector();
78  connector->Stop();
79  mLogic->RemoveConnector(connector);
80  }
82 }
83 
85 {
86  mGotTimeOffset = false;
88 };
89 
90 double NetworkHandler::synchronizedTimestamp(double receivedTimestampSec)
91 {
92  double receivedTimestampMS = receivedTimestampSec * 1000;
93  if(!mGotTimeOffset)
94  {
95  qint64 systemTime = QDateTime::currentDateTime().toMSecsSinceEpoch();
96  mTimestampOffsetMS = systemTime - receivedTimestampMS;
97  CX_LOG_DEBUG() << "NetworkHandler: Doing timestamp synchronization - adding fixed offset of " << mTimestampOffsetMS << " ms";
98  mGotTimeOffset = true;
99  }
100  receivedTimestampMS = receivedTimestampMS + mTimestampOffsetMS;
101 
102  verifyTimestamp(receivedTimestampMS);
103  return receivedTimestampMS;
104 }
105 
106 
107 bool NetworkHandler::verifyTimestamp(double &timestampMS)
108 {
109  qint64 latestSystemTime = QDateTime::currentDateTime().toMSecsSinceEpoch();
110  double diff = timestampMS - latestSystemTime;
111  if(fabs(diff) > 1000)
112  {
113  // BK stops the time whenever the image is frozen, or a parameter is changed
114  //CX_LOG_WARNING() << "NetworkHandler: Detected difference between system time and timestamp after synchronization. Difference: " << diff
115  // << " The reason for this may be messages with different timestamp formats. "
116  // << " System time will be used instead of received timestamp.";
117  timestampMS = latestSystemTime;
118  return false;
119  }
120 
121  return true;
122 }
123 
124 void NetworkHandler::onDeviceReceived(vtkObject* caller_device, void* unknown, unsigned long event , void*)
125 {
126  Q_UNUSED(unknown);
127  Q_UNUSED(event);
128  vtkSmartPointer<igtlioDevice> receivedDevice(reinterpret_cast<igtlioDevice*>(caller_device));
129 
130  igtlioBaseConverter::HeaderData header = receivedDevice->GetHeader();
131  std::string device_type = receivedDevice->GetDeviceType();
132 
133  double timestampMS = synchronizedTimestamp(header.timestamp);
134 
135 
136 // CX_LOG_DEBUG() << "Device is modified, device type: " << device_type << " on device: " << receivedDevice->GetDeviceName() << " equipmentId: " << header.equipmentId;
137 
138  // Currently the only id available is the Device name defined in Plus xml. Looking like this: Probe_sToReference_s
139  // Use this for all message types for now, instead of equipmentId.
140  // Anser integration may send equipmentId, so this is checked for when we get a transform.
141  QString deviceName(receivedDevice->GetDeviceName().c_str());
142 
143  if(device_type == igtlioImageConverter::GetIGTLTypeName())
144  {
145  igtlioImageDevicePointer imageDevice = igtlioImageDevice::SafeDownCast(receivedDevice);
146 
147  igtlioImageConverter::ContentData content = imageDevice->GetContent();
148 
149 // QString deviceName(header.deviceName.c_str());
150 // QString deviceName(header.equipmentId.c_str());//Use equipmentId
151  ImagePtr cximage = ImagePtr(new Image(deviceName, content.image));
152  cximage->setAcquisitionTime( QDateTime::fromMSecsSinceEpoch(qint64(timestampMS)));
153  cximage->setModality(imUS);
154  //this->decode_rMd(msg, retval);
155 
156 
157  //Use the igtlio meta data from the image message
158  std::string metaLabel;
159  std::string metaDataValue;
160  QStringList igtlioLabels;
161 
162  igtlioLabels << IGTLIO_KEY_PROBE_TYPE;
163  igtlioLabels << IGTLIO_KEY_ORIGIN;
164  igtlioLabels << IGTLIO_KEY_ANGLES;
165  igtlioLabels << IGTLIO_KEY_BOUNDING_BOX;
166  igtlioLabels << IGTLIO_KEY_DEPTHS;
167  igtlioLabels << IGTLIO_KEY_LINEAR_WIDTH;
168  igtlioLabels << IGTLIO_KEY_SPACING_X;
169  igtlioLabels << IGTLIO_KEY_SPACING_Y;
170  igtlioLabels << QString("SpacingZ"); //IGTLIO_KEY_SPACING_Z;
171  //TODO: Use deciveNameLong when this is defined in IGTLIO and sent with Plus
172 
173  mProbeDefinitionFromStringMessages->setImage(cximage);
174 
175  for (int i = 0; i < igtlioLabels.size(); ++i)
176  {
177  metaLabel = igtlioLabels[i].toStdString();
178  bool gotMetaData = receivedDevice->GetMetaDataElement(metaLabel, metaDataValue);
179  if(!gotMetaData)
180  {
182  CX_LOG_WARNING() << "Cannot get needed igtlio meta information: " << metaLabel;
183  }
184  else
185  {
186  mProbeDefinitionFromStringMessages->parseValue(metaLabel.c_str(), metaDataValue.c_str());
187  //CX_LOG_DEBUG() << "Read variable " << metaLabel << " = " << metaDataValue;
188  }
189  }
190  mGotMoreThanOneImage = true;
191 
192  processImageAndEmitProbeDefinition(cximage, deviceName);//Use equipmentId?
193  emit image(cximage);
194 
195  // CX-366: Currenly we don't use the transform from the image message, because there is no specification of what this transform should be.
196  // Only the transforms from the transform messages are used.
197 // double timestamp = header.timestamp;
198 // Transform3D cxtransform = Transform3D::fromVtkMatrix(content.transform);
199 // emit transform(deviceName, cxtransform, timestamp);
200  }
201  else if(device_type == igtlioTransformConverter::GetIGTLTypeName())
202  {
203  igtlioTransformDevicePointer transformDevice = igtlioTransformDevice::SafeDownCast(receivedDevice);
204  igtlioTransformConverter::ContentData content = transformDevice->GetContent();
205 
206 // QString deviceName(content.deviceName.c_str());
207 // QString deviceName(header.equipmentId.c_str());//Use equipmentId
208  //QString streamIdTo(content.streamIdTo.c_str());
209  //QString streamIdFrom(content.streamIdFrom.c_str());
210  Transform3D cxtransform = Transform3D::fromVtkMatrix(content.transform);
211 
212 // CX_LOG_DEBUG() << "TRANSFORM: " << " equipmentId: " << header.equipmentId
213 // << " streamIdTo: " << content.streamIdTo
214 // << " streamIdFrom: " << content.streamIdFrom
215 // << " deviceName: " << deviceName
216 // << " transform: " << cxtransform;
217 
218 
219  // Try to use equipmentId from OpenIGTLink meta data. If not presnet use deviceName.
220  // Having equipmentId in OpenIGTLink meta data is something we would like to have a part of the OpenIGTLinkIO standard,
221  // and added to the messages from Plus.
222  std::string openigtlinktransformid;
223  bool gotTransformId = receivedDevice->GetMetaDataElement("equipmentId", openigtlinktransformid);
224 
225  if (gotTransformId)
226  emit transform(qstring_cast(openigtlinktransformid), cxtransform, timestampMS);
227  else
228  emit transform(deviceName, cxtransform, timestampMS);
229  }
230  else if(device_type == igtlioStatusConverter::GetIGTLTypeName())
231  {
232  igtlioStatusDevicePointer status = igtlioStatusDevice::SafeDownCast(receivedDevice);
233 
234  igtlioStatusConverter::ContentData content = status->GetContent();
235 
236  CX_LOG_DEBUG() << "STATUS: " << " code: " << content.code
237  << " subcode: " << content.subcode
238  << " errorname: " << content.errorname
239  << " statusstring: " << content.statusstring;
240 
241  }
242  else if(device_type == igtlioStringConverter::GetIGTLTypeName())
243  {
244  igtlioStringDevicePointer string = igtlioStringDevice::SafeDownCast(receivedDevice);
245 
246  igtlioStringConverter::ContentData content = string->GetContent();
247 
248 // CX_LOG_DEBUG() << "STRING: " << " equipmentId: " << header.equipmentId
249 // << " encoding: " << content.encoding
250 // << " string: " << content.string_msg;
251 
252  QString message(content.string_msg.c_str());
253 
254  //Allow string messages to modify probe definition, as well as meta info.
255  mProbeDefinitionFromStringMessages->parseStringMessage(header, message);
256  emit string_message(message);
257  }
258  else
259  {
260  CX_LOG_WARNING() << "Found unhandled devicetype: " << device_type;
261  }
262 
263 }
264 
265 void NetworkHandler::onConnectionEvent(vtkObject* caller, void* connector, unsigned long event , void*)
266 {
267  Q_UNUSED(caller);
268  Q_UNUSED(connector);
269  if (event==igtlioLogic::ConnectionAddedEvent)
270  {
271  emit connected();
272  }
273  if (event==igtlioLogic::ConnectionAboutToBeRemovedEvent)
274  {
275  emit disconnected();
276  }
277 }
278 
279 void NetworkHandler::onDeviceAddedOrRemoved(vtkObject* caller, void* void_device, unsigned long event, void* callData)
280 {
281  Q_UNUSED(caller);
282  Q_UNUSED(callData);
283  if (event==igtlioLogic::NewDeviceEvent)
284  {
285  igtlioDevicePointer device(reinterpret_cast<igtlioDevice*>(void_device));
286  if(device)
287  {
288  CX_LOG_DEBUG() << " NetworkHandler is listening to " << device->GetDeviceName();
289  qvtkReconnect(NULL, device, igtlioDevice::ReceiveEvent, this, SLOT(onDeviceReceived(vtkObject*, void*, unsigned long, void*)));
290  }
291  }
292  if (event==igtlioLogic::RemovedDeviceEvent)
293  {
294  CX_LOG_WARNING() << "TODO: on remove device event, not implemented";
295  }
296 }
297 
298 void NetworkHandler::periodicProcess()
299 {
300  mLogic->PeriodicProcess();
301 }
302 
304 {
305  foreach(int eventId, QList<int>()
306  << igtlioLogic::ConnectionAddedEvent
307  << igtlioLogic::ConnectionAboutToBeRemovedEvent
308  )
309  {
310  qvtkReconnect(NULL, mLogic, eventId,
311  this, SLOT(onConnectionEvent(vtkObject*, void*, unsigned long, void*)));
312  }
313 }
314 
316 {
317  foreach(int eventId, QList<int>()
318  << igtlioLogic::NewDeviceEvent
319  << igtlioLogic::RemovedDeviceEvent
320  )
321  {
322  qvtkReconnect(NULL, mLogic, eventId,
323  this, SLOT(onDeviceAddedOrRemoved(vtkObject*, void*, unsigned long, void*)));
324  }
325 }
326 
327 //TODO: Consider moving these image changing functions out of the class
329 {
330  bool probeDefinitionHaveChanged = emitProbeDefinitionIfChanged(deviceName);
331 
332  if (probeDefinitionHaveChanged)
333  {
334  mZeroesInImage = true;
335  mSkippedImages = 0;
336  this->createMask(); //Only create mask once for each probeDefinition
337  }
338 
339  // Turn off zero conversion if we get a frame without zeroes. Recheck for zeroes every 30 images
340  if(mZeroesInImage || (mSkippedImages > 30))
341  {
342  // CX_LOG_DEBUG() << "*** Removing zeroes from US image ***";
344  mSkippedImages = 0;
345  }
346  else
347  {
348  ++mSkippedImages;
349  // CX_LOG_DEBUG() << "No zeroes in incoming US image";
350  }
351 }
352 
354 {
355  if (mProbeDefinitionFromStringMessages->haveValidValues() && mProbeDefinitionFromStringMessages->haveChanged())
356  {
357  mProbeDefinition = mProbeDefinitionFromStringMessages->createProbeDefintion(deviceName);
358  emit probedefinition(deviceName, mProbeDefinition);
359  return true;
360  }
361  return false;
362 }
363 
364 bool NetworkHandler::convertZeroesInsideSectorToOnes(ImagePtr cximage, int threshold, int newValue)
365 {
366  bool retval = false;
367  if(!mUSMask)
368  return retval;
369 
370  Eigen::Array3i maskDims(mUSMask->GetDimensions());
371  unsigned char* maskPtr = static_cast<unsigned char*> (mUSMask->GetScalarPointer());
372  unsigned char* imagePtr = static_cast<unsigned char*> (cximage->getBaseVtkImageData()->GetScalarPointer());
373  unsigned components = cximage->getBaseVtkImageData()->GetNumberOfScalarComponents();
374  unsigned dimX = maskDims[0];
375  unsigned dimY = maskDims[1];
376  for (unsigned x = 0; x < dimX; x++)
377  for (unsigned y = 0; y < dimY; y++)
378  {
379  unsigned pos = x + y * dimX;
380  unsigned imagePos = pos*components;
381  unsigned char maskVal = maskPtr[pos];
382  unsigned char imageVal = imagePtr[imagePos];
383  if (maskVal != 0 && imageVal <= threshold)
384  {
385  for(unsigned i=0; i < components; ++i)
386  if(i < 3) //Only set RGB components, not Alpha
387  {
388  imagePtr[imagePos + i] = newValue;
389  retval = true;
390  }
391  }
392  }
393  return retval;
394 }
395 
397 {
398  if(!mProbeDefinition)
399  {
400  CX_LOG_WARNING() << "No ProbeDefinition";
401  return false;
402  }
403  ProbeSector probeSector;
404  probeSector.setData(*mProbeDefinition.get());
405 
406  mUSMask = probeSector.getMask();
407  return true;
408 }
409 
410 } // namespace cx
QString qstring_cast(const T &val)
Transform3D Transform3D
Transform3D is a representation of an affine 3D transform.
boost::shared_ptr< class Image > ImagePtr
Definition: cxDicomWidget.h:27
igtlioSessionPointer requestConnectToServer(std::string serverHost, int serverPort=-1, IGTLIO_SYNCHRONIZATION_TYPE sync=IGTLIO_BLOCKING, double timeout_s=5)
igtlioLogicPointer mLogic
bool convertZeroesInsideSectorToOnes(ImagePtr cximage, int threshold=0, int newValue=1)
bool emitProbeDefinitionIfChanged(QString deviceName)
vtkImageDataPtr mUSMask
void string_message(QString message)
void processImageAndEmitProbeDefinition(ImagePtr cximage, QString deviceName)
double synchronizedTimestamp(double receivedTimestampSec)
igtlioSessionPointer mSession
ProbeDefinitionFromStringMessagesPtr mProbeDefinitionFromStringMessages
bool verifyTimestamp(double &timestampMS)
Synchronize with system clock: Calculate a fixed offset, and apply this to all timestamps.
A volumetric data set.
Definition: cxImage.h:45
void transform(QString devicename, Transform3D transform, double timestamp)
void probedefinition(QString devicename, ProbeDefinitionPtr definition)
boost::shared_ptr< class ProbeDefinitionFromStringMessages > ProbeDefinitionFromStringMessagesPtr
#define CX_LOG_DEBUG
Definition: cxLogger.h:95
imUS
#define CX_LOG_WARNING
Definition: cxLogger.h:98
vtkImageDataPtr getMask()
NetworkHandler(igtlioLogicPointer logic)
Utility functions for drawing an US Probe sector.
Definition: cxProbeSector.h:38
boost::shared_ptr< class ProbeDefinition > ProbeDefinitionPtr
ProbeDefinitionPtr mProbeDefinition
void clearTimestampSynchronization()
void image(ImagePtr image)
void setData(ProbeDefinition data)
Namespace for all CustusX production code.