PlusLib  2.9.0
Software library for tracked ultrasound image acquisition, calibration, and processing.
vtkPlusOpenIGTLinkTracker.cxx
Go to the documentation of this file.
1 /*=Plus=header=begin======================================================
2 Program: Plus
3 Copyright (c) Laboratory for Percutaneous Surgery. All rights reserved.
4 See License.txt for details.
5 =========================================================Plus=header=end*/
6 
7 #include "PlusConfigure.h"
9 
10 #include "igtlPositionMessage.h"
11 #include "igtlTrackingDataMessage.h"
12 #include "igtlTransformMessage.h"
13 #include "vtkMatrix4x4.h"
14 #include "vtkObjectFactory.h"
15 #include "vtkPlusDataSource.h"
17 
18 #include <set>
19 
21 
22 //----------------------------------------------------------------------------
24  : UseLastTransformsOnReceiveTimeout(false)
25 {
26  SetToolReferenceFrameName("Reference");
27 }
28 
29 //----------------------------------------------------------------------------
31 {
32 }
33 
34 //----------------------------------------------------------------------------
35 void vtkPlusOpenIGTLinkTracker::PrintSelf(ostream& os, vtkIndent indent)
36 {
37  os << indent << "UseLastTransformsOnReceiveTimeout: " << this->UseLastTransformsOnReceiveTimeout;
38 
39  Superclass::PrintSelf(os, indent);
40 }
41 
42 //----------------------------------------------------------------------------
44 {
45  LOG_TRACE("vtkPlusOpenIGTLinkTracker::Disconnect");
46  if (this->IsTDataMessageType())
47  {
48  // If we need TDATA, request server to stop streaming.
49  auto stpMsg = igtl::StopTrackingDataMessage::New();
50  stpMsg->SetDeviceName("");
51  stpMsg->Pack();
52 
53  int retValue = 0;
54  {
55  igsioLockGuard<vtkIGSIORecursiveCriticalSection> socketGuard(this->SocketMutex);
56  RETRY_UNTIL_TRUE(
57  (retValue = this->ClientSocket->Send(stpMsg->GetBufferPointer(), stpMsg->GetBufferSize())) != 0,
59  }
60 
61  if (retValue == 0)
62  {
63  LOG_ERROR("Failed to send STP_TDATA message to server!");
64  return PLUS_FAIL;
65  }
66  }
67 
69 }
70 
71 //----------------------------------------------------------------------------
73 {
74  LOG_TRACE("vtkPlusOpenIGTLinkTracker::InternalUpdate");
75 
76  if (!this->Recording)
77  {
78  LOG_ERROR("called Update() when not tracking");
79  return PLUS_FAIL;
80  }
81 
82  if (this->IsTDataMessageType())
83  {
84  return this->InternalUpdateTData();
85  }
86  else
87  {
88  return this->InternalUpdateGeneral();
89  }
90 }
91 
92 //----------------------------------------------------------------------------
94 {
95  LOG_TRACE("vtkPlusOpenIGTLinkTracker::InternalUpdateTData");
96 
97  igtl::MessageBase::Pointer bodyMsg;
98  igtl::MessageHeader::Pointer headerMsg;
99 
100  while (true)
101  {
103 
104  if (headerMsg.IsNull())
105  {
106  // Has not received data
108  {
109  // The server only sends update if a transform is modified, it's not an error
110  LOG_TRACE("No OpenIGTLink message has been received in device " << this->GetDeviceId());
111  // Store the last known transform values (useful when the server only notifies about transform changes
112  double unfilteredTimestamp = vtkIGSIOAccurateTimer::GetSystemTime();
113  StoreMostRecentTransformValues(unfilteredTimestamp);
114  return PLUS_SUCCESS;
115  }
116  else
117  {
119  igsioLockGuard<vtkIGSIORecursiveCriticalSection> socketGuard(this->SocketMutex);
120  if (!this->ClientSocket->GetConnected())
121  {
122  // Could not restore the connection, set transform status to INVALID
123  double unfilteredTimestamp = vtkIGSIOAccurateTimer::GetSystemTime();
124  StoreInvalidTransforms(unfilteredTimestamp);
125  }
126  return PLUS_FAIL;
127  }
128  }
129 
130  // We've received valid header data
131  headerMsg->Unpack(this->IgtlMessageCrcCheckEnabled);
132 
133  bodyMsg = this->MessageFactory->CreateReceiveMessage(headerMsg);
134  if (typeid(*bodyMsg) == typeid(igtl::TrackingDataMessage))
135  {
136  // received a TDATA message
137  break;
138  }
139 
140  // data type is unknown, ignore it
141  igsioLockGuard<vtkIGSIORecursiveCriticalSection> socketGuard(this->SocketMutex);
142  this->ClientSocket->Skip(headerMsg->GetBodySizeToRead(), 0);
143  }
144 
145  // TDATA message
146  igtl::TrackingDataMessage::Pointer tdataMsg = dynamic_cast<igtl::TrackingDataMessage*>(bodyMsg.GetPointer());
147  tdataMsg->SetMessageHeader(headerMsg);
148  tdataMsg->AllocateBuffer();
149 
150  {
151  bool timeout(false);
152  igsioLockGuard<vtkIGSIORecursiveCriticalSection> socketGuard(this->SocketMutex);
153  this->ClientSocket->Receive(tdataMsg->GetBufferBodyPointer(), tdataMsg->GetBufferBodySize(), timeout);
154  }
155  int c = tdataMsg->Unpack(this->IgtlMessageCrcCheckEnabled);
156  if (!(c & igtl::MessageHeader::UNPACK_BODY))
157  {
158  LOG_ERROR("Couldn't receive TDATA message from server!");
159  return PLUS_FAIL;
160  }
161 
162  // for now just use system time, all coordinates will be sequential.
163  double unfilteredTimestamp = vtkIGSIOAccurateTimer::GetSystemTime();
164  double filteredTimestamp = unfilteredTimestamp; // No need to filter already filtered timestamped items received over OpenIGTLink
165  // We store the list of identified tools (tools we get information about from the tracker).
166  // The tools that are missing from the tracker message are assumed to be out of view.
167  std::set<std::string> identifiedToolSourceIds;
168  for (int i = 0; i < tdataMsg->GetNumberOfTrackingDataElements(); ++ i)
169  {
170  auto tdataElem = igtl::TrackingDataElement::New();
171  tdataMsg->GetTrackingDataElement(i, tdataElem);
172 
173  igtl::Matrix4x4 igtlMatrix;
174  tdataElem->GetMatrix(igtlMatrix);
175  vtkSmartPointer<vtkMatrix4x4> toolMatrix = vtkSmartPointer<vtkMatrix4x4>::New();
176  // convert igtl matrix to vtk matrix
177  for (int r = 0; r < 4; r++)
178  {
179  for (int c = 0; c < 4; c++)
180  {
181  toolMatrix->SetElement(r, c, igtlMatrix[r][c]);
182  }
183  }
184 
185  // Get timestamp
186  auto igtlTimestamp = igtl::TimeStamp::New();
187  tdataMsg->GetTimeStamp(igtlTimestamp);
188 
189  // Get igtl transform name
190  std::string igtlTransformName = tdataElem->GetName();
191 
192  // Set internal transform name
193  igsioTransformName transformName;
194  if (igtlTransformName.find("To") != std::string::npos)
195  {
196  // Plus style transform name sent
197  transformName = igtlTransformName;
198  }
199  else
200  {
201  // Brainlab style transform name sent
202  transformName = igsioTransformName(igtlTransformName.c_str(), this->ToolReferenceFrameName);
203  }
204 
205  if (this->ToolTimeStampedUpdateWithoutFiltering(transformName.GetTransformName().c_str(), toolMatrix, TOOL_OK, unfilteredTimestamp, filteredTimestamp) == PLUS_SUCCESS)
206  {
207  identifiedToolSourceIds.insert(transformName.GetTransformName());
208  }
209  else
210  {
211  LOG_INFO("ToolTimeStampedUpdate failed for tool: " << transformName.From() << " with timestamp: " << std::fixed << unfilteredTimestamp);
212  // DO NOT return here: we want to update the other tools.
213  }
214  }
215  // Set status for non-detected tools
216  vtkSmartPointer<vtkMatrix4x4> toolMatrix = vtkSmartPointer<vtkMatrix4x4>::New();
217  toolMatrix->Identity();
218  for (DataSourceContainerConstIterator it = this->GetToolIteratorBegin(); it != this->GetToolIteratorEnd(); ++it)
219  {
220  if (identifiedToolSourceIds.find(it->second->GetId()) != identifiedToolSourceIds.end())
221  {
222  // this tool has been found and update has been already called with the correct transform
223  LOG_TRACE("Tool " << it->second->GetId() << ": found");
224  continue;
225  }
226  LOG_TRACE("Tool " << it->second->GetId() << ": not found");
227  this->ToolTimeStampedUpdateWithoutFiltering(it->second->GetId(), toolMatrix, TOOL_OUT_OF_VIEW, unfilteredTimestamp, filteredTimestamp);
228  }
229  return PLUS_SUCCESS;
230 }
231 
232 //----------------------------------------------------------------------------
234 {
235  LOG_TRACE("vtkPlusOpenIGTLinkTracker::InternalUpdateGeneral");
236 
237  double unfilteredTimestamp = vtkIGSIOAccurateTimer::GetSystemTime();
238 
239  double maxAllocatedProcessingTime = 2.0;
240  // set maxAllocatedProcessingTime to 2 acquisition periods to allow reading all transforms even when there aare slight delays
241  if (this->GetAcquisitionRate() > 2.0 / maxAllocatedProcessingTime)
242  {
243  maxAllocatedProcessingTime = 2.0 / this->GetAcquisitionRate();
244  }
245 
246  igtl::MessageHeader::Pointer headerMsg;
247 
248  bool moreMessagesPossible = true;
249  while (moreMessagesPossible)
250  {
251  if (ProcessTransformMessageGeneral(moreMessagesPossible) != PLUS_SUCCESS)
252  {
253  // an error occurred, stop processing the messages in this update iteration
254  break;
255  }
256  if (vtkIGSIOAccurateTimer::GetSystemTime() - unfilteredTimestamp > maxAllocatedProcessingTime)
257  {
258  // no more time for processing messages in this iteration
259  break;
260  }
261  }
262 
264  {
265  // Store all the other transforms with the last known value
266  // that has not been updated in this update iteration
267  StoreMostRecentTransformValues(unfilteredTimestamp);
268  }
269  else
270  {
271  // Set all those transforms to invalid that contains stale transform values
272  StoreInvalidTransforms(unfilteredTimestamp);
273  }
274 
275  if (!this->ClientSocket->GetConnected())
276  {
277  // Could not restore the connection, set transform status to INVALID
278  double unfilteredTimestamp = vtkIGSIOAccurateTimer::GetSystemTime();
279  StoreInvalidTransforms(unfilteredTimestamp);
280  return PLUS_FAIL;
281  }
282 
283  return PLUS_SUCCESS;
284 }
285 
286 //----------------------------------------------------------------------------
288 {
289  igtl::MessageHeader::Pointer headerMsg;
291  if (headerMsg.IsNull())
292  {
293  // did not receive transform message, so there are no more available
294  moreMessagesPossible = false;
295  return PLUS_SUCCESS;
296  }
297 
298  moreMessagesPossible = true;
299 
300  // We've received valid header data
301  headerMsg->Unpack(this->IgtlMessageCrcCheckEnabled);
302 
303  // Accept TRANSFORM or POSITION message
304  double unfilteredTimestampUtc = 0;
305  vtkSmartPointer<vtkMatrix4x4> toolMatrix = vtkSmartPointer<vtkMatrix4x4>::New();
306  std::string igtlTransformName;
307  ToolStatus toolStatus(TOOL_UNKNOWN);
308 
309  igtl::MessageBase::Pointer bodyMsg = this->MessageFactory->CreateReceiveMessage(headerMsg);
310  if (typeid(*bodyMsg) == typeid(igtl::TransformMessage))
311  {
312  if (vtkPlusIgtlMessageCommon::UnpackTransformMessage(bodyMsg, this->ClientSocket.GetPointer(), toolMatrix, toolStatus, igtlTransformName, unfilteredTimestampUtc, this->IgtlMessageCrcCheckEnabled) != PLUS_SUCCESS)
313  {
314  LOG_ERROR("Couldn't receive transform message from server!");
315  return PLUS_FAIL;
316  }
317  }
318  else if (typeid(*bodyMsg) == typeid(igtl::PositionMessage))
319  {
320  if (vtkPlusIgtlMessageCommon::UnpackPositionMessage(bodyMsg, this->ClientSocket.GetPointer(), toolMatrix, igtlTransformName, toolStatus, unfilteredTimestampUtc, this->IgtlMessageCrcCheckEnabled) != PLUS_SUCCESS)
321  {
322  LOG_ERROR("Couldn't receive position message from server!");
323  return PLUS_FAIL;
324  }
325  }
326  else
327  {
328  // if the data type is unknown, skip reading.
329  igsioLockGuard<vtkIGSIORecursiveCriticalSection> socketGuard(this->SocketMutex);
330  this->ClientSocket->Skip(headerMsg->GetBodySizeToRead(), 0);
331  return PLUS_SUCCESS;
332  }
333 
334  // Set transform name
335  igsioTransformName transformName;
336  if (transformName.SetTransformName(igtlTransformName.c_str()) != PLUS_SUCCESS)
337  {
338  LOG_ERROR("Failed to update tracker tool - unrecognized transform name: " << igtlTransformName);
339  return PLUS_FAIL;
340  }
341 
342  double unfilteredTimestamp = 0;
343  if (this->UseReceivedTimestamps)
344  {
345  // Use the timestamp in the OpenIGTLink message
346  // The received timestamp is in UTC and timestamps in the buffer are in system time, so conversion is needed
347  unfilteredTimestamp = vtkIGSIOAccurateTimer::GetSystemTimeFromUniversalTime(unfilteredTimestampUtc);
348  }
349  else
350  {
351  unfilteredTimestamp = vtkIGSIOAccurateTimer::GetSystemTime();
352  }
353 
354  // No need to filter already filtered timestamped items received over OpenIGTLink
355  // If the original timestamps are not used it's still safer not to use filtering, as filtering assumes uniform frame rate, which is not guaranteed
356  double filteredTimestamp = unfilteredTimestamp;
357 
358  // Store the transform that we've just received
359  // TODO: we should not write it into the buffer until we have all the tools ready (if we are not using the original timestamps)
360  if (this->ToolTimeStampedUpdateWithoutFiltering(transformName.GetTransformName().c_str(), toolMatrix, toolStatus, unfilteredTimestamp, filteredTimestamp) != PLUS_SUCCESS)
361  {
362  LOG_INFO("ToolTimeStampedUpdate failed for tool: " << transformName.GetTransformName() << " with timestamp: " << std::fixed << unfilteredTimestamp);
363  return PLUS_FAIL;
364  }
365 
366  return PLUS_SUCCESS;
367 }
368 
369 //----------------------------------------------------------------------------
371 {
373  {
374  return PLUS_FAIL;
375  }
376 
377  // NOTE: Command above sends clientinfo message, which implicitly stops TDATA sending
378  // Command below must be sent after the clientinfo message is sent
379 
380  // If we need TDATA, request server to start streaming.
381  if (this->IsTDataMessageType())
382  {
383  auto sttMsg = igtl::StartTrackingDataMessage::New();
384  sttMsg->SetDeviceName("");
385  sttMsg->SetResolution(50);
386  sttMsg->SetCoordinateName(this->ToolReferenceFrameName.c_str());
387  sttMsg->Pack();
388 
389  int retValue = 0;
390  {
391  igsioLockGuard<vtkIGSIORecursiveCriticalSection> socketGuard(this->SocketMutex);
392  RETRY_UNTIL_TRUE(
393  (retValue = this->ClientSocket->Send(sttMsg->GetBufferPointer(), sttMsg->GetBufferSize())) != 0,
395  }
396 
397  if (retValue == 0)
398  {
399  LOG_ERROR("Failed to send STT_TDATA message to server!");
400  return PLUS_FAIL;
401  }
402  }
403 
404  return PLUS_SUCCESS;
405 }
406 
407 //----------------------------------------------------------------------------
409 {
410  PlusStatus status = PLUS_SUCCESS;
411  // Set transform values for tools with non-detected markers
412  for (DataSourceContainerConstIterator it = this->GetToolIteratorBegin(); it != this->GetToolIteratorEnd(); ++it)
413  {
414  vtkSmartPointer<vtkMatrix4x4> toolMatrix = vtkSmartPointer<vtkMatrix4x4>::New();
415  // retrieve latest transform value from the buffer
416  if (it->second->GetNumberOfItems() > 0)
417  {
418  BufferItemUidType latestItemUid = it->second->GetLatestItemUidInBuffer();
419  StreamBufferItem item;
420  if (it->second->GetStreamBufferItem(latestItemUid, &item) == ITEM_OK)
421  {
422  if (item.GetUnfilteredTimestamp(this->GetLocalTimeOffsetSec()) >= unfilteredTimestamp)
423  {
424  // this item already has an updated value for this timestamp, no need to store it again
425  continue;
426  }
427  if (item.GetMatrix(toolMatrix) != PLUS_SUCCESS)
428  {
429  LOG_WARNING("Failed to get matrix from buffer item with UID: " << latestItemUid);
430  status = PLUS_FAIL;
431  }
432  }
433  else
434  {
435  LOG_WARNING("Failed to get buffer item with UID: " << latestItemUid);
436  status = PLUS_FAIL;
437  }
438  }
439  if (this->ToolTimeStampedUpdateWithoutFiltering(it->second->GetId(), toolMatrix, TOOL_OK, unfilteredTimestamp, unfilteredTimestamp) != PLUS_SUCCESS)
440  {
441  LOG_INFO("ToolTimeStampedUpdate failed for tool: " << it->second->GetId() << " with timestamp: " << std::fixed << unfilteredTimestamp);
442  status = PLUS_FAIL;
443  }
444  }
445 
446  return status;
447 }
448 
449 //----------------------------------------------------------------------------
451 {
452  PlusStatus status = PLUS_SUCCESS;
453 
454  // If the item is delayed with maximum itemArrivalDelayTolerance then we don't set its status to invalid yet
455  // itemArrivalDelayTolerance is one frame period (but maximum 3 seconds).
456  double itemArrivalDelayToleranceSec = 3.0;
457  if (this->GetAcquisitionRate() > 1.0 / itemArrivalDelayToleranceSec)
458  {
459  itemArrivalDelayToleranceSec = 1.0 / this->GetAcquisitionRate();
460  }
461 
462  // Set invalid status for tools with non-detected markers
463  for (DataSourceContainerConstIterator it = this->GetToolIteratorBegin(); it != this->GetToolIteratorEnd(); ++it)
464  {
465  vtkSmartPointer<vtkMatrix4x4> toolMatrix = vtkSmartPointer<vtkMatrix4x4>::New();
466  // retrieve latest transform value from the buffer
467  if (it->second->GetNumberOfItems() > 0)
468  {
469  BufferItemUidType latestItemUid = it->second->GetLatestItemUidInBuffer();
470  StreamBufferItem item;
471  if (it->second->GetStreamBufferItem(latestItemUid, &item) == ITEM_OK)
472  {
473  if (item.GetUnfilteredTimestamp(this->GetLocalTimeOffsetSec()) + itemArrivalDelayToleranceSec >= unfilteredTimestamp)
474  {
475  // this item already has an updated value for this timestamp, no need to store it again
476  continue;
477  }
478  if (item.GetMatrix(toolMatrix) != PLUS_SUCCESS)
479  {
480  LOG_WARNING("Failed to get matrix from buffer item with UID: " << latestItemUid);
481  status = PLUS_FAIL;
482  }
483  }
484  else
485  {
486  LOG_WARNING("Failed to get buffer item with UID: " << latestItemUid);
487  status = PLUS_FAIL;
488  }
489  }
490 
491  LOG_TRACE("No update was received for tool: " << it->second->GetId() << ", assume that it does not provide valid transform anymore");
492  if (this->ToolTimeStampedUpdateWithoutFiltering(it->second->GetId(), toolMatrix, TOOL_INVALID, unfilteredTimestamp, unfilteredTimestamp) != PLUS_SUCCESS)
493  {
494  LOG_INFO("ToolTimeStampedUpdate failed for tool: " << it->second->GetId() << " with timestamp: " << std::fixed << unfilteredTimestamp);
495  status = PLUS_FAIL;
496  }
497  }
498 
499  return status;
500 }
501 
502 //----------------------------------------------------------------------------
503 PlusStatus vtkPlusOpenIGTLinkTracker::ReadConfiguration(vtkXMLDataElement* rootConfigElement)
504 {
505  XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_READING(deviceConfig, rootConfigElement);
506  XML_READ_BOOL_ATTRIBUTE_OPTIONAL(UseLastTransformsOnReceiveTimeout, deviceConfig);
507  return PLUS_SUCCESS;
508 }
509 
510 //----------------------------------------------------------------------------
511 PlusStatus vtkPlusOpenIGTLinkTracker::WriteConfiguration(vtkXMLDataElement* rootConfigElement)
512 {
513  XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_WRITING(deviceConfig, rootConfigElement);
514  deviceConfig->SetAttribute("UseLastTransformsOnReceiveTimeout", this->UseLastTransformsOnReceiveTimeout ? "true" : "false");
515  return PLUS_SUCCESS;
516 }
517 
518 //----------------------------------------------------------------------------
520 {
521  if (this->MessageType.empty())
522  {
523  return false;
524  }
525  return (igsioCommon::IsEqualInsensitive(this->MessageType, "TDATA"));
526 }
DataSourceContainer::const_iterator DataSourceContainerConstIterator
std::string ToolReferenceFrameName
vtkStandardNewMacro(vtkPlusOpenIGTLinkTracker)
void ReceiveMessageHeaderWithErrorHandling(igtl::MessageHeader::Pointer &headerMsg)
static PlusStatus UnpackTransformMessage(igtl::MessageHeader::Pointer headerMsg, igtl::Socket *socket, vtkMatrix4x4 *transformMatrix, ToolStatus &toolStatus, std::string &transformName, double &timestamp, int crccheck)
#define XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_WRITING(deviceConfig, rootConfigElement)
virtual PlusStatus ToolTimeStampedUpdateWithoutFiltering(const std::string &aToolSourceId, vtkMatrix4x4 *matrix, ToolStatus status, double unfilteredtimestamp, double filteredtimestamp, const igsioFieldMapType *customFields=NULL)
PlusStatus StoreMostRecentTransformValues(double unfilteredTimestamp)
virtual PlusStatus ReadConfiguration(vtkXMLDataElement *config)
igsioStatus PlusStatus
Definition: PlusCommon.h:40
vtkSmartPointer< vtkPlusIgtlMessageFactory > MessageFactory
virtual PlusStatus WriteConfiguration(vtkXMLDataElement *config)
virtual std::string GetDeviceId() const
virtual PlusStatus InternalDisconnect()
vtkSmartPointer< vtkIGSIORecursiveCriticalSection > SocketMutex
for i
static PlusStatus UnpackPositionMessage(igtl::MessageHeader::Pointer headerMsg, igtl::Socket *socket, vtkMatrix4x4 *transformMatrix, std::string &transformName, ToolStatus &toolStatus, double &timestamp, int crccheck)
#define PLUS_FAIL
Definition: PlusCommon.h:43
virtual double GetAcquisitionRate() const
virtual void PrintSelf(ostream &os, vtkIndent indent)
virtual PlusStatus SendRequestedMessageTypes()
PlusStatus StoreInvalidTransforms(double unfilteredTimestamp)
#define PLUS_SUCCESS
Definition: PlusCommon.h:44
PlusStatus ProcessTransformMessageGeneral(bool &moreMessagesPossible)
PlusStatus GetMatrix(vtkMatrix4x4 *outputMatrix)
#define XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_READING(deviceConfig, rootConfigElement)
virtual void PrintSelf(ostream &os, vtkIndent indent) VTK_OVERRIDE
double GetUnfilteredTimestamp(double localTimeOffsetSec)
igtl::ClientSocket::Pointer ClientSocket
DataSourceContainerConstIterator GetToolIteratorBegin() const
OpenIGTLink tracker client.
virtual PlusStatus SendRequestedMessageTypes()
void SetToolReferenceFrameName(const std::string &frameName)
unsigned long long BufferItemUidType
DataSourceContainerConstIterator GetToolIteratorEnd() const