CustusX  22.04-rc3
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  //this->decode_rMd(msg, retval);
154 
155 
156  //Use the igtlio meta data from the image message
157  std::string metaLabel;
158  std::string metaDataValue;
159  QStringList igtlioLabels;
160 
161  igtlioLabels << IGTLIO_KEY_PROBE_TYPE;
162  igtlioLabels << IGTLIO_KEY_ORIGIN;
163  igtlioLabels << IGTLIO_KEY_ANGLES;
164  igtlioLabels << IGTLIO_KEY_BOUNDING_BOX;
165  igtlioLabels << IGTLIO_KEY_DEPTHS;
166  igtlioLabels << IGTLIO_KEY_LINEAR_WIDTH;
167  igtlioLabels << IGTLIO_KEY_SPACING_X;
168  igtlioLabels << IGTLIO_KEY_SPACING_Y;
169  igtlioLabels << QString("SpacingZ"); //IGTLIO_KEY_SPACING_Z;
170  //TODO: Use deciveNameLong when this is defined in IGTLIO and sent with Plus
171 
172  mProbeDefinitionFromStringMessages->setImage(cximage);
173 
174  for (int i = 0; i < igtlioLabels.size(); ++i)
175  {
176  metaLabel = igtlioLabels[i].toStdString();
177  bool gotMetaData = receivedDevice->GetMetaDataElement(metaLabel, metaDataValue);
178  if(!gotMetaData)
179  {
181  CX_LOG_WARNING() << "Cannot get needed igtlio meta information: " << metaLabel;
182  }
183  else
184  {
185  mProbeDefinitionFromStringMessages->parseValue(metaLabel.c_str(), metaDataValue.c_str());
186  //CX_LOG_DEBUG() << "Read variable " << metaLabel << " = " << metaDataValue;
187  }
188  }
189  mGotMoreThanOneImage = true;
190 
191  processImageAndEmitProbeDefinition(cximage, deviceName);//Use equipmentId?
192  emit image(cximage);
193 
194  // CX-366: Currenly we don't use the transform from the image message, because there is no specification of what this transform should be.
195  // Only the transforms from the transform messages are used.
196 // double timestamp = header.timestamp;
197 // Transform3D cxtransform = Transform3D::fromVtkMatrix(content.transform);
198 // emit transform(deviceName, cxtransform, timestamp);
199  }
200  else if(device_type == igtlioTransformConverter::GetIGTLTypeName())
201  {
202  igtlioTransformDevicePointer transformDevice = igtlioTransformDevice::SafeDownCast(receivedDevice);
203  igtlioTransformConverter::ContentData content = transformDevice->GetContent();
204 
205 // QString deviceName(content.deviceName.c_str());
206 // QString deviceName(header.equipmentId.c_str());//Use equipmentId
207  //QString streamIdTo(content.streamIdTo.c_str());
208  //QString streamIdFrom(content.streamIdFrom.c_str());
209  Transform3D cxtransform = Transform3D::fromVtkMatrix(content.transform);
210 
211 // CX_LOG_DEBUG() << "TRANSFORM: " << " equipmentId: " << header.equipmentId
212 // << " streamIdTo: " << content.streamIdTo
213 // << " streamIdFrom: " << content.streamIdFrom
214 // << " deviceName: " << deviceName
215 // << " transform: " << cxtransform;
216 
217 
218  // Try to use equipmentId from OpenIGTLink meta data. If not presnet use deviceName.
219  // Having equipmentId in OpenIGTLink meta data is something we would like to have a part of the OpenIGTLinkIO standard,
220  // and added to the messages from Plus.
221  std::string openigtlinktransformid;
222  bool gotTransformId = receivedDevice->GetMetaDataElement("equipmentId", openigtlinktransformid);
223 
224  if (gotTransformId)
225  emit transform(qstring_cast(openigtlinktransformid), cxtransform, timestampMS);
226  else
227  emit transform(deviceName, cxtransform, timestampMS);
228  }
229  else if(device_type == igtlioStatusConverter::GetIGTLTypeName())
230  {
231  igtlioStatusDevicePointer status = igtlioStatusDevice::SafeDownCast(receivedDevice);
232 
233  igtlioStatusConverter::ContentData content = status->GetContent();
234 
235  CX_LOG_DEBUG() << "STATUS: " << " code: " << content.code
236  << " subcode: " << content.subcode
237  << " errorname: " << content.errorname
238  << " statusstring: " << content.statusstring;
239 
240  }
241  else if(device_type == igtlioStringConverter::GetIGTLTypeName())
242  {
243  igtlioStringDevicePointer string = igtlioStringDevice::SafeDownCast(receivedDevice);
244 
245  igtlioStringConverter::ContentData content = string->GetContent();
246 
247 // CX_LOG_DEBUG() << "STRING: " << " equipmentId: " << header.equipmentId
248 // << " encoding: " << content.encoding
249 // << " string: " << content.string_msg;
250 
251  QString message(content.string_msg.c_str());
252 
253  //Allow string messages to modify probe definition, as well as meta info.
254  mProbeDefinitionFromStringMessages->parseStringMessage(header, message);
255  emit string_message(message);
256  }
257  else
258  {
259  CX_LOG_WARNING() << "Found unhandled devicetype: " << device_type;
260  }
261 
262 }
263 
264 void NetworkHandler::onConnectionEvent(vtkObject* caller, void* connector, unsigned long event , void*)
265 {
266  Q_UNUSED(caller);
267  Q_UNUSED(connector);
268  if (event==igtlioLogic::ConnectionAddedEvent)
269  {
270  emit connected();
271  }
272  if (event==igtlioLogic::ConnectionAboutToBeRemovedEvent)
273  {
274  emit disconnected();
275  }
276 }
277 
278 void NetworkHandler::onDeviceAddedOrRemoved(vtkObject* caller, void* void_device, unsigned long event, void* callData)
279 {
280  Q_UNUSED(caller);
281  Q_UNUSED(callData);
282  if (event==igtlioLogic::NewDeviceEvent)
283  {
284  igtlioDevicePointer device(reinterpret_cast<igtlioDevice*>(void_device));
285  if(device)
286  {
287  CX_LOG_DEBUG() << " NetworkHandler is listening to " << device->GetDeviceName();
288  qvtkReconnect(NULL, device, igtlioDevice::ReceiveEvent, this, SLOT(onDeviceReceived(vtkObject*, void*, unsigned long, void*)));
289  }
290  }
291  if (event==igtlioLogic::RemovedDeviceEvent)
292  {
293  CX_LOG_WARNING() << "TODO: on remove device event, not implemented";
294  }
295 }
296 
297 void NetworkHandler::periodicProcess()
298 {
299  mLogic->PeriodicProcess();
300 }
301 
303 {
304  foreach(int eventId, QList<int>()
305  << igtlioLogic::ConnectionAddedEvent
306  << igtlioLogic::ConnectionAboutToBeRemovedEvent
307  )
308  {
309  qvtkReconnect(NULL, mLogic, eventId,
310  this, SLOT(onConnectionEvent(vtkObject*, void*, unsigned long, void*)));
311  }
312 }
313 
315 {
316  foreach(int eventId, QList<int>()
317  << igtlioLogic::NewDeviceEvent
318  << igtlioLogic::RemovedDeviceEvent
319  )
320  {
321  qvtkReconnect(NULL, mLogic, eventId,
322  this, SLOT(onDeviceAddedOrRemoved(vtkObject*, void*, unsigned long, void*)));
323  }
324 }
325 
326 //TODO: Consider moving these image changing functions out of the class
328 {
329  bool probeDefinitionHaveChanged = emitProbeDefinitionIfChanged(deviceName);
330 
331  if (probeDefinitionHaveChanged)
332  {
333  mZeroesInImage = true;
334  mSkippedImages = 0;
335  this->createMask(); //Only create mask once for each probeDefinition
336  }
337 
338  // Turn off zero conversion if we get a frame without zeroes. Recheck for zeroes every 30 images
339  if(mZeroesInImage || (mSkippedImages > 30))
340  {
341  // CX_LOG_DEBUG() << "*** Removing zeroes from US image ***";
343  mSkippedImages = 0;
344  }
345  else
346  {
347  ++mSkippedImages;
348  // CX_LOG_DEBUG() << "No zeroes in incoming US image";
349  }
350 }
351 
353 {
354  if (mProbeDefinitionFromStringMessages->haveValidValues() && mProbeDefinitionFromStringMessages->haveChanged())
355  {
356  mProbeDefinition = mProbeDefinitionFromStringMessages->createProbeDefintion(deviceName);
357  emit probedefinition(deviceName, mProbeDefinition);
358  return true;
359  }
360  return false;
361 }
362 
363 bool NetworkHandler::convertZeroesInsideSectorToOnes(ImagePtr cximage, int threshold, int newValue)
364 {
365  bool retval = false;
366  if(!mUSMask)
367  return retval;
368 
369  Eigen::Array3i maskDims(mUSMask->GetDimensions());
370  unsigned char* maskPtr = static_cast<unsigned char*> (mUSMask->GetScalarPointer());
371  unsigned char* imagePtr = static_cast<unsigned char*> (cximage->getBaseVtkImageData()->GetScalarPointer());
372  unsigned components = cximage->getBaseVtkImageData()->GetNumberOfScalarComponents();
373  unsigned dimX = maskDims[0];
374  unsigned dimY = maskDims[1];
375  for (unsigned x = 0; x < dimX; x++)
376  for (unsigned y = 0; y < dimY; y++)
377  {
378  unsigned pos = x + y * dimX;
379  unsigned imagePos = pos*components;
380  unsigned char maskVal = maskPtr[pos];
381  unsigned char imageVal = imagePtr[imagePos];
382  if (maskVal != 0 && imageVal <= threshold)
383  {
384  for(unsigned i=0; i < components; ++i)
385  if(i < 3) //Only set RGB components, not Alpha
386  {
387  imagePtr[imagePos + i] = newValue;
388  retval = true;
389  }
390  }
391  }
392  return retval;
393 }
394 
396 {
397  if(!mProbeDefinition)
398  {
399  CX_LOG_WARNING() << "No ProbeDefinition";
400  return false;
401  }
402  ProbeSector probeSector;
403  probeSector.setData(*mProbeDefinition.get());
404 
405  mUSMask = probeSector.getMask();
406  return true;
407 }
408 
409 } // 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
#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.