PlusLib  2.9.0
Software library for tracked ultrasound image acquisition, calibration, and processing.
vtkPlusOptiTrack.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 // Local includes
8 #include "PlusConfigure.h"
9 #include "vtkPlusOptiTrack.h"
10 
11 // VTK includes
12 #include <vtkSmartPointer.h>
13 #include <vtkMatrix4x4.h>
14 #include <vtkMath.h>
15 #include <vtkXMLDataElement.h>
16 #include <vtksys/Encoding.hxx>
17 
18 // Motive API includes
19 #include <NatNetClient.h>
20 #include <NatNetTypes.h>
21 #if MOTIVE_VERSION_MAJOR < 3
22 #include <NPTrackingTools.h>
23 #define ResultType NPRESULT
24 #define ResultSuccess NPRESULT_SUCCESS
25 #elif MOTIVE_VERSION_MAJOR >= 3
26 #include <MotiveAPI.h>
27 #define ResultType eMotiveAPIResult
28 #define ResultSuccess kApiResult_Success
29 #endif
30 
31 // std includes
32 #include <set>
33 
35 
36 //----------------------------------------------------------------------------
37 class vtkPlusOptiTrack::vtkInternal
38 {
39 public:
40  vtkPlusOptiTrack* External;
41 
42  vtkInternal(vtkPlusOptiTrack* external)
43  : External(external)
44  , NNClient(nullptr)
45  , UnitsToMm(1.0)
46  , MotiveDataDescriptionsUpdateTimeSec(1.0)
47  , LastMotiveDataDescriptionsUpdateTimestamp(-1)
48  {
49  }
50 
51  virtual ~vtkInternal()
52  {
53  }
54 
55  // NatNet client, parameters, and callback function
56  NatNetClient* NNClient;
57  float UnitsToMm;
58 
59  // Motive Files
60 #if MOTIVE_VERSION_MAJOR >= 2
61  std::string Profile;
62  std::string Calibration;
63 #else
64  std::string ProjectFile;
65 #endif
66 
67  std::string CalibrationFile;
68  std::vector<std::string> AdditionalRigidBodyFiles;
69 
70  // Maps rigid body names to transform names
71  std::map<int, igsioTransformName> MapRBNameToTransform;
72 
73  // Flag to run Motive in background if user doesn't need GUI
74  bool AttachToRunningMotive;
75 
76  // Time of last tool update
77  double LastMotiveDataDescriptionsUpdateTimestamp;
78  double MotiveDataDescriptionsUpdateTimeSec;
79 
83  std::string GetMotiveErrorMessage(ResultType result);
84 
88  static void InternalCallback(sFrameOfMocapData* data, void* pUserData);
89 
90  void UpdateMotiveDataDescriptions();
91 };
92 
93 //-----------------------------------------------------------------------
94 std::string vtkPlusOptiTrack::vtkInternal::GetMotiveErrorMessage(ResultType result)
95 {
96  return "";
97  //return std::wstring(TT_GetResultString(result));
98 }
99 
100 //-----------------------------------------------------------------------
101 void vtkPlusOptiTrack::vtkInternal::UpdateMotiveDataDescriptions()
102 {
103  LOG_TRACE("vtkPlusOptiTrack::vtkInternal::MatchTrackedTools");
104 
105  std::string referenceFrame = this->External->GetToolReferenceFrameName();
106  this->MapRBNameToTransform.clear();
107  sDataDescriptions* dataDescriptions;
108  this->NNClient->GetDataDescriptions(&dataDescriptions);
109  for (int i = 0; i < dataDescriptions->nDataDescriptions; ++i)
110  {
111  sDataDescription currentDescription = dataDescriptions->arrDataDescriptions[i];
112  if (currentDescription.type == Descriptor_RigidBody)
113  {
114  // Map the numerical ID of the tracked tool from motive to the name of the tool
115  igsioTransformName toolToTracker = igsioTransformName(currentDescription.Data.RigidBodyDescription->szName, referenceFrame);
116  this->MapRBNameToTransform[currentDescription.Data.RigidBodyDescription->ID] = toolToTracker;
117  }
118  }
119 
120  this->LastMotiveDataDescriptionsUpdateTimestamp = vtkIGSIOAccurateTimer::GetSystemTime();
121 }
122 
123 //-----------------------------------------------------------------------
125  : vtkPlusDevice()
126  , Internal(new vtkInternal(this))
127 {
128  this->FrameNumber = 0;
129  // always uses NatNet's callback to update
130  this->InternalUpdateRate = 120;
131  this->StartThreadForInternalUpdates = false;
132 }
133 
134 //----------------------------------------------------------------------------
136 {
137  delete Internal;
138  Internal = nullptr;
139 }
140 
141 //----------------------------------------------------------------------------
142 void vtkPlusOptiTrack::PrintSelf(ostream& os, vtkIndent indent)
143 {
144  Superclass::PrintSelf(os, indent);
145 }
146 
147 //----------------------------------------------------------------------------
148 PlusStatus vtkPlusOptiTrack::ReadConfiguration(vtkXMLDataElement* rootConfigElement)
149 {
150  LOG_TRACE("vtkPlusOptiTrack::ReadConfiguration");
151  XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_READING(deviceConfig, rootConfigElement);
152 
153 #if MOTIVE_VERSION_MAJOR >= 2
154  XML_READ_STRING_ATTRIBUTE_NONMEMBER_REQUIRED(Profile, this->Internal->Profile, deviceConfig);
155  XML_READ_STRING_ATTRIBUTE_NONMEMBER_REQUIRED(Calibration, this->Internal->Calibration, deviceConfig);
156 #else
157  XML_READ_STRING_ATTRIBUTE_NONMEMBER_REQUIRED(ProjectFile, this->Internal->ProjectFile, deviceConfig);
158 #endif
159  XML_READ_BOOL_ATTRIBUTE_NONMEMBER_REQUIRED(AttachToRunningMotive, this->Internal->AttachToRunningMotive, deviceConfig);
160  XML_READ_SCALAR_ATTRIBUTE_NONMEMBER_OPTIONAL(double, MotiveDataDescriptionsUpdateTimeSec, this->Internal->MotiveDataDescriptionsUpdateTimeSec, deviceConfig);
161 
162  XML_FIND_NESTED_ELEMENT_REQUIRED(dataSourcesElement, deviceConfig, "DataSources");
163  for (int nestedElementIndex = 0; nestedElementIndex < dataSourcesElement->GetNumberOfNestedElements(); nestedElementIndex++)
164  {
165  vtkXMLDataElement* toolDataElement = dataSourcesElement->GetNestedElement(nestedElementIndex);
166  if (STRCASECMP(toolDataElement->GetName(), "DataSource") != 0)
167  {
168  // if this is not a data source element, skip it
169  continue;
170  }
171  if (toolDataElement->GetAttribute("Type") != NULL && STRCASECMP(toolDataElement->GetAttribute("Type"), "Tool") != 0)
172  {
173  // if this is not a Tool element, skip it
174  continue;
175  }
176 
177  std::string toolId(toolDataElement->GetAttribute("Id"));
178  if (toolId.empty())
179  {
180  // tool doesn't have ID needed to generate transform
181  LOG_ERROR("Failed to initialize OptiTrack tool: DataSource Id is missing. This should be the name of the Motive Rigid Body that tracks the tool.");
182  continue;
183  }
184 
185  if (toolDataElement->GetAttribute("RigidBodyFile") != NULL)
186  {
187  // this tool has an associated rigid body definition
188  const char* rigidBodyFile = toolDataElement->GetAttribute("RigidBodyFile");
189  this->Internal->AdditionalRigidBodyFiles.push_back(rigidBodyFile);
190  }
191  }
192 
193  return PLUS_SUCCESS;
194 }
195 
196 //----------------------------------------------------------------------------
197 PlusStatus vtkPlusOptiTrack::WriteConfiguration(vtkXMLDataElement* rootConfigElement)
198 {
199  LOG_TRACE("vtkPlusOptiTrack::WriteConfiguration");
200  XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_WRITING(deviceConfig, rootConfigElement);
201  return PLUS_SUCCESS;
202 }
203 
204 //----------------------------------------------------------------------------
206 {
207  LOG_TRACE("vtkPlusOptiTrack::Probe");
208  return PLUS_SUCCESS;
209 }
210 
211 //-------------------------------------------------------------------------
213 {
214  LOG_TRACE("vtkPlusOptiTrack::InternalConnect");
215  if (!this->Internal->AttachToRunningMotive)
216  {
217 #if MOTIVE_VERSION_MAJOR >= 2
218  if (TT_TestSoftwareMutex() != ResultSuccess)
219  {
220  LOG_ERROR("Failed to start Motive. Another instance is already running.");
221  return PLUS_FAIL;
222  }
223 #endif
224 
225  // RUN MOTIVE IN BACKGROUND
226  // initialize the API
227  if (TT_Initialize() != ResultSuccess)
228  {
229  LOG_ERROR("Failed to start Motive.");
230  return PLUS_FAIL;
231  }
232 
233  // pick up recently-arrived cameras
234  TT_Update();
235 
236 #if MOTIVE_VERSION_MAJOR >= 2
237  // open project file
238  std::string profilePath = vtkPlusConfig::GetInstance()->GetDeviceSetConfigurationPath(this->Internal->Profile);
239 #if MOTIVE_VERSION_MAJOR < 3
240  ResultType profileLoad = TT_LoadProfile(profilePath.c_str());
241 #else
242  std::wstring wProfilePath = vtksys::Encoding::ToWide(profilePath);
243  ResultType profileLoad = TT_LoadProfile(wProfilePath.c_str());
244 #endif
245  if (profileLoad != ResultSuccess)
246  {
247 #if MOTIVE_VERSION_MAJOR < 3
248  LOG_ERROR("Failed to load Motive profile. Motive error: " << TT_GetResultString(profileLoad));
249 #else
250  LOG_ERROR_W("Failed to load Motive profile. Motive error: " << TT_GetResultString(profileLoad));
251 #endif
252  return PLUS_FAIL;
253  }
254 
255  // load calibration
256  std::string calibrationPath = vtkPlusConfig::GetInstance()->GetDeviceSetConfigurationPath(this->Internal->Calibration);
257 #if MOTIVE_VERSION_MAJOR < 3
258  ResultType calLoad = TT_LoadCalibration(calibrationPath.c_str());
259 #else
260  std::wstring wCalibrationPath = vtksys::Encoding::ToWide(calibrationPath);
261  ResultType calLoad = TT_LoadCalibration(wCalibrationPath.c_str());
262 #endif
263  if (profileLoad != ResultSuccess)
264  {
265 #if MOTIVE_VERSION_MAJOR < 3
266  LOG_ERROR("Failed to load Motive calibration. Motive error: " << TT_GetResultString(profileLoad));
267 #else
268  LOG_ERROR_W("Failed to load Motive calibration. Motive error: " << TT_GetResultString(profileLoad));
269 #endif
270  return PLUS_FAIL;
271  }
272 #else
273  // open project file
274  std::string projectFilePath = vtkPlusConfig::GetInstance()->GetDeviceSetConfigurationPath(this->Internal->ProjectFile);
275  NPRESULT ttpLoad = TT_LoadProject(projectFilePath.c_str());
276  if (ttpLoad != NPRESULT_SUCCESS)
277  {
278  LOG_ERROR("Failed to load Motive project file. Motive error: " << TT_GetResultString(ttpLoad));
279  return PLUS_FAIL;
280  }
281 #endif
282 
283  // enforce NatNet streaming enabled, this is required for PLUS tracking
284  // this is the equivalent to checking the "Broadcast Frame Data" button in the Motive GUI
285  ResultType streamEnable = TT_StreamNP(true);
286  if (streamEnable != ResultSuccess)
287  {
288 #if MOTIVE_VERSION_MAJOR < 3
289  LOG_ERROR("Failed to enable NatNet streaming. Motive error: " << TT_GetResultString(streamEnable));
290 #else
291  LOG_ERROR_W("Failed to enable NatNet streaming. Motive error: " << TT_GetResultString(streamEnable));
292 #endif
293  return PLUS_FAIL;
294  }
295 
296  // add any additional rigid body files to project
297  std::string rbFilePath;
298  for (auto it = this->Internal->AdditionalRigidBodyFiles.begin(); it != this->Internal->AdditionalRigidBodyFiles.end(); it++)
299  {
301 #if MOTIVE_VERSION_MAJOR < 3
302  ResultType addRBResult = TT_AddRigidBodies(rbFilePath.c_str());
303 #else
304  std::wstring wRBFilePath = vtksys::Encoding::ToWide(rbFilePath);
305  ResultType addRBResult = TT_AddRigidBodies(wRBFilePath.c_str());
306 #endif
307  if (addRBResult != ResultSuccess)
308  {
309  LOG_ERROR("Failed to load rigid body file located at: " << rbFilePath << ". Motive error message: " << this->Internal->GetMotiveErrorMessage(addRBResult));
310  return PLUS_FAIL;
311  }
312  }
313 
314  LOG_INFO("\n---------------------------------MOTIVE SETTINGS--------------------------------");
315  // list connected cameras
316  LOG_INFO("Connected cameras:");
317  for (int i = 0; i < TT_CameraCount(); i++)
318  {
319 #if MOTIVE_VERSION_MAJOR < 3
320  LOG_INFO(i << ": " << TT_CameraName(i));
321 #else
322  wchar_t cameraName[256];
323  TT_CameraName(i, cameraName, (int)sizeof(cameraName));
324  LOG_INFO_W(i << L": " << cameraName);
325 #endif
326  }
327  // list project file
328 #if MOTIVE_VERSION_MAJOR >= 2
329  LOG_INFO("\nUsing Motive profile located at:\n" << profilePath);
330  LOG_INFO("\nUsing Motive calibration located at:\n" << calibrationPath);
331 #else
332  LOG_INFO("\nUsing Motive project file located at:\n" << projectFilePath);
333 #endif
334  // list rigid bodies
335  LOG_INFO("\nTracked rigid bodies:");
336  for (int i = 0; i < TT_RigidBodyCount(); ++i)
337  {
338 #if MOTIVE_VERSION_MAJOR < 3
339  LOG_INFO(TT_RigidBodyName(i));
340 #else
341  wchar_t rigidBodyName[256];
342  TT_RigidBodyName(i, rigidBodyName, (int)sizeof(rigidBodyName));
343  LOG_INFO_W(rigidBodyName);
344 #endif
345  }
346  LOG_INFO("--------------------------------------------------------------------------------\n");
347 
348  this->StartThreadForInternalUpdates = true;
349  }
350 
351  // CONFIGURE NATNET CLIENT
352  this->Internal->NNClient = new NatNetClient(ConnectionType_Multicast);
353  this->Internal->NNClient->SetVerbosityLevel(Verbosity_None);
354  this->Internal->NNClient->SetVerbosityLevel(Verbosity_Warning);
355  this->Internal->NNClient->SetDataCallback(vtkPlusOptiTrack::vtkInternal::InternalCallback, this);
356 
357  int retCode = this->Internal->NNClient->Initialize("127.0.0.1", "127.0.0.1");
358 
359  void* response;
360  int nBytes;
361  if (this->Internal->NNClient->SendMessageAndWait("UnitsToMillimeters", &response, &nBytes) == ErrorCode_OK)
362  {
363  this->Internal->UnitsToMm = (*(float*)response);
364  }
365  else
366  {
367  // Fail if motive is not running
368  LOG_ERROR("Failed to connect to Motive. Please either set AttachToRunningMotive=FALSE or ensure that Motive is running and streaming is enabled.");
369  return PLUS_FAIL;
370  }
371 
372  // verify all rigid bodies in Motive have unique names
373  std::set<std::string> rigidBodies;
374  sDataDescriptions* dataDescriptions;
375  this->Internal->NNClient->GetDataDescriptions(&dataDescriptions);
376  for (int i = 0; i < dataDescriptions->nDataDescriptions; ++i)
377  {
378  sDataDescription currentDescription = dataDescriptions->arrDataDescriptions[i];
379  if (currentDescription.type == Descriptor_RigidBody)
380  {
381  // Map the numerical ID of the tracked tool from motive to the name of the tool
382  if (!rigidBodies.insert(currentDescription.Data.RigidBodyDescription->szName).second)
383  {
384  LOG_ERROR("Duplicate rigid bodies with name: " << currentDescription.Data.RigidBodyDescription->szName);
385  return PLUS_FAIL;
386  }
387  }
388  }
389 
390  // cause update of tools from Motive
391  this->Internal->LastMotiveDataDescriptionsUpdateTimestamp = -1;
392 
393  return PLUS_SUCCESS;
394 }
395 
396 //-------------------------------------------------------------------------
398 {
399  LOG_TRACE("vtkPlusOptiTrack::InternalDisconnect");
400  if (!this->Internal->AttachToRunningMotive)
401  {
402  TT_Shutdown();
403  }
404 
405  return PLUS_SUCCESS;
406 }
407 
408 //----------------------------------------------------------------------------
409 PlusStatus vtkPlusOptiTrack::InternalStartRecording()
410 {
411  LOG_TRACE("vtkPlusOptiTrack::InternalStartRecording");
412  return PLUS_SUCCESS;
413 }
414 
415 //----------------------------------------------------------------------------
416 PlusStatus vtkPlusOptiTrack::InternalStopRecording()
417 {
418  return PLUS_SUCCESS;
419 }
420 
421 //----------------------------------------------------------------------------
423 {
424  LOG_TRACE("vtkPlusOptiTrack::InternalUpdate");
425  // InternalUpdate is only called if using Motive API.
426 #if MOTIVE_VERSION_MAJOR >= 3
427  TT_Update(); // In Motive 3.X, only updates the latest frame.
428 #elif MOTIVE_VERSION_MAJOR >= 2 && MOTIVE_VERSION_MINOR >= 3
429  TT_UpdateLatestFrame();
430 #else
431  TT_Update();
432 #endif
433  return PLUS_SUCCESS;
434 }
435 
436 //-------------------------------------------------------------------------
437 void vtkPlusOptiTrack::vtkInternal::InternalCallback(sFrameOfMocapData* data, void* pUserData)
438 {
439  vtkPlusOptiTrack* self = (vtkPlusOptiTrack*)pUserData;
440 
441  LOG_TRACE("vtkPlusOptiTrack::InternalCallback");
442  const double unfilteredTimestamp = vtkIGSIOAccurateTimer::GetSystemTime();
443 
444  if (self->Internal->LastMotiveDataDescriptionsUpdateTimestamp < 0)
445  {
446  // do an initial match of tracked tools
447  self->Internal->UpdateMotiveDataDescriptions();
448  }
449 
450  if (self->Internal->AttachToRunningMotive && self->Internal->MotiveDataDescriptionsUpdateTimeSec >= 0)
451  {
452  double timeSinceMotiveDataDescriptionsUpdate = unfilteredTimestamp - self->Internal->LastMotiveDataDescriptionsUpdateTimestamp;
453  if (timeSinceMotiveDataDescriptionsUpdate > self->Internal->MotiveDataDescriptionsUpdateTimeSec)
454  {
455  self->Internal->UpdateMotiveDataDescriptions();
456  }
457  }
458 
459  int numberOfRigidBodies = data->nRigidBodies;
460  sRigidBodyData* rigidBodies = data->RigidBodies;
461 
462  // identity transform for tools out of view
463  vtkSmartPointer<vtkMatrix4x4> rigidBodyToTrackerMatrix = vtkSmartPointer<vtkMatrix4x4>::New();
464 
465  for (int rigidBodyId = 0; rigidBodyId < numberOfRigidBodies; ++rigidBodyId)
466  {
467  // TOOL IN VIEW
468  rigidBodyToTrackerMatrix->Identity();
469  sRigidBodyData currentRigidBody = rigidBodies[rigidBodyId];
470 
471  if (currentRigidBody.MeanError != 0)
472  {
473  // convert translation to mm
474  double translation[3] = { currentRigidBody.x * self->Internal->UnitsToMm, currentRigidBody.y * self->Internal->UnitsToMm, currentRigidBody.z * self->Internal->UnitsToMm };
475 
476  // convert rotation from quaternion to 3x3 matrix
477  double quaternion[4] = { currentRigidBody.qw, currentRigidBody.qx, currentRigidBody.qy, currentRigidBody.qz };
478  double rotation[3][3] = { 0,0,0, 0,0,0, 0,0,0 };
479  vtkMath::QuaternionToMatrix3x3(quaternion, rotation);
480 
481  // construct the transformation matrix from the rotation and translation components
482  for (int i = 0; i < 3; ++i)
483  {
484  for (int j = 0; j < 3; ++j)
485  {
486  rigidBodyToTrackerMatrix->SetElement(i, j, rotation[i][j]);
487  }
488  rigidBodyToTrackerMatrix->SetElement(i, 3, translation[i]);
489  }
490 
491  // check if tool is in view
492  bool bTrackingValid = currentRigidBody.params & 0x01;
493 
494  // make sure the tool was specified in the Config file
495  igsioTransformName toolToTracker = self->Internal->MapRBNameToTransform[currentRigidBody.ID];
496  self->ToolTimeStampedUpdate(toolToTracker.GetTransformName(), rigidBodyToTrackerMatrix, (bTrackingValid ? TOOL_OK : TOOL_INVALID), self->FrameNumber, unfilteredTimestamp);
497  }
498  else
499  {
500  // TOOL OUT OF VIEW
501  igsioTransformName toolToTracker = self->Internal->MapRBNameToTransform[currentRigidBody.ID];
502  self->ToolTimeStampedUpdate(toolToTracker.GetTransformName(), rigidBodyToTrackerMatrix, TOOL_OUT_OF_VIEW, self->FrameNumber, unfilteredTimestamp);
503  }
504 
505  }
506 
507  self->FrameNumber++;
508 }
const uint32_t * data
Definition: phidget22.h:3971
virtual void PrintSelf(ostream &os, vtkIndent indent) VTK_OVERRIDE
Abstract interface for tracker and video devices.
Definition: vtkPlusDevice.h:60
#define XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_WRITING(deviceConfig, rootConfigElement)
virtual PlusStatus InternalDisconnect()
#define ResultSuccess
Interface to the OptiTrack trackers This class talks with a OptiTrack Tracker over the NatNet SDK....
igsioStatus PlusStatus
Definition: PlusCommon.h:40
for i
virtual PlusStatus ReadConfiguration(vtkXMLDataElement *config)
#define PLUS_FAIL
Definition: PlusCommon.h:43
static vtkPlusConfig * GetInstance()
unsigned long FrameNumber
#define PLUS_SUCCESS
Definition: PlusCommon.h:44
double InternalUpdateRate
#define XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_READING(deviceConfig, rootConfigElement)
#define ResultType
bool StartThreadForInternalUpdates
void PrintSelf(ostream &os, vtkIndent indent)
vtkStandardNewMacro(vtkPlusOptiTrack)
std::string GetDeviceSetConfigurationPath(const std::string &subPath)
PlusStatus InternalConnect()
PlusStatus InternalUpdate()
virtual PlusStatus WriteConfiguration(vtkXMLDataElement *config)