PlusLib  2.9.0
Software library for tracked ultrasound image acquisition, calibration, and processing.
vtkPlusAzureKinect.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"
8 #include "vtkPlusAzureKinect.h"
9 
10 // Local includes
11 #include "vtkPlusChannel.h"
12 #include "vtkPlusDataSource.h"
13 
14 // VTK includes
15 #include <vtkImageData.h>
16 #include <vtkObjectFactory.h>
17 
18 // Kinect azure includes
19 #include <k4a/k4a.hpp>
20 
21 // Stl includes
22 #include <vector>
23 #include <string>
24 #include <memory>
25 #include <algorithm>
26 
27 //----------------------------------------------------------------------------
29 
30 namespace
31 {
33  {
34  RGB = 1 << 0,
35  DEPTH = 1 << 1,
36  PCL = 1 << 2
37  };
38 
39  struct KinectStreamConfig
40  {
41  KinectSourceType Type;
42  std::string Id;
43  uint32_t FrameRate{0};
44  uint32_t Width{0};
45  uint32_t Height{0};
46  vtkPlusDataSource* Source{nullptr};
47  };
48 
49  std::string KinectSourceTypeAsString(KinectSourceType type)
50  {
51  switch (type)
52  {
53  case KinectSourceType::RGB:
54  return "RGB";
55  case KinectSourceType::DEPTH:
56  return "DEPTH";
57  case KinectSourceType::PCL:
58  return "PCL";
59  default:
60  return "";
61  }
62  }
63 
64  std::tuple<bool, k4a_fps_t> ToKinectFrameRate(int rate)
65  {
66  if (rate == 5)
67  {
68  return std::make_tuple(true, K4A_FRAMES_PER_SECOND_5);
69  }
70  else if (rate == 15)
71  {
72  return std::make_tuple(true, K4A_FRAMES_PER_SECOND_15);
73  }
74  else if (rate == 30)
75  {
76  return std::make_tuple(true, K4A_FRAMES_PER_SECOND_30);
77  }
78 
79  return std::make_tuple(false, K4A_FRAMES_PER_SECOND_5);
80  }
81 
82  std::tuple<bool, k4a_depth_mode_t, int> ToKinectDepthMode(int width)
83  {
84  if (width == 320)
85  {
86  return std::make_tuple(true, K4A_DEPTH_MODE_NFOV_2X2BINNED, 288);
87  }
88  else if (width == 640)
89  {
90  return std::make_tuple(true, K4A_DEPTH_MODE_NFOV_UNBINNED, 576);
91  }
92  else if (width == 512)
93  {
94  return std::make_tuple(true, K4A_DEPTH_MODE_WFOV_2X2BINNED, 512);
95  }
96  else if (width == 1024)
97  {
98  return std::make_tuple(true, K4A_DEPTH_MODE_WFOV_UNBINNED, 1024);
99  }
100 
101  return std::make_tuple(false, K4A_DEPTH_MODE_OFF, 0);
102  }
103 
104  std::tuple<bool, k4a_color_resolution_t, int> ToKinectColorMode(int width)
105  {
106  if (width == 1280)
107  {
108  return std::make_tuple(true, K4A_COLOR_RESOLUTION_720P, 720);
109  }
110  else if (width == 1920)
111  {
112  return std::make_tuple(true, K4A_COLOR_RESOLUTION_1080P, 1080);
113  }
114  else if (width == 2560)
115  {
116  return std::make_tuple(true, K4A_COLOR_RESOLUTION_1440P, 1440);
117  }
118  else if (width == 2048)
119  {
120  return std::make_tuple(true, K4A_COLOR_RESOLUTION_1536P, 1536);
121  }
122  else if (width == 3840)
123  {
124  return std::make_tuple(true, K4A_COLOR_RESOLUTION_2160P, 2160);
125  }
126  else if (width == 4096)
127  {
128  return std::make_tuple(true, K4A_COLOR_RESOLUTION_3072P, 3072);
129  }
130 
131  return std::make_tuple(false, K4A_COLOR_RESOLUTION_OFF, 0);
132  }
133 }
134 
135 //----------------------------------------------------------------------------
136 class vtkPlusAzureKinect::vtkInternal
137 {
138 public:
139  vtkPlusAzureKinect* External;
140  std::vector<KinectStreamConfig> StreamList;
141  k4a::device Device;
142  k4a_device_configuration_t DeviceConfig;
143  k4a::calibration Calibration;
144  k4a::transformation Transformation;
145  k4a::image ColorImage;
146  k4a::image DepthImage;
147  k4a::image PointsCloudImage;
148  bool AlignDepthStream{false};
149  int AvailableStreamsFlag{0};
150 
151  vtkInternal(vtkPlusAzureKinect* external)
152  : External(external)
153  {
154  this->DeviceConfig.color_format = K4A_IMAGE_FORMAT_COLOR_BGRA32;
155  this->DeviceConfig.synchronized_images_only = true;
156  this->DeviceConfig.depth_mode = K4A_DEPTH_MODE_OFF;
157  this->DeviceConfig.color_resolution = K4A_COLOR_RESOLUTION_OFF;
158  this->DeviceConfig.wired_sync_mode = K4A_WIRED_SYNC_MODE_STANDALONE;
159  this->DeviceConfig.depth_delay_off_color_usec = 0;
160  this->DeviceConfig.subordinate_delay_off_master_usec = 0;
161  }
162 
163  PlusStatus UpdateConfig()
164  {
165  bool status{false};
166  std::vector<uint32_t> frameRates;
167  for (auto& stream : this->StreamList)
168  {
169  std::tie(status, DeviceConfig.camera_fps) = ToKinectFrameRate(stream.FrameRate);
170  if (!status)
171  {
172  LOG_ERROR("Invalid frame rate detected: 5, 15, 30 expected");
173  return PLUS_FAIL;
174  }
175 
176  frameRates.emplace_back(stream.FrameRate);
177 
178  if (stream.Type == KinectSourceType::RGB)
179  {
180  std::tie(status, DeviceConfig.color_resolution, stream.Height) = ToKinectColorMode(stream.Width);
181  }
182  else if (stream.Type == KinectSourceType::DEPTH)
183  {
184  std::tie(status, DeviceConfig.depth_mode, stream.Height) = ToKinectDepthMode(stream.Width);
185  }
186 
187  if (!status)
188  {
189  LOG_ERROR("Invalid stream configuration");
190  return PLUS_FAIL;
191  }
192  }
193 
194  auto rgbStream = std::find_if(std::begin(this->StreamList), std::end(this->StreamList), [](const KinectStreamConfig & stream)
195  {
196  return stream.Type == KinectSourceType::RGB;
197  });
198  auto depthStream = std::find_if(std::begin(this->StreamList), std::end(this->StreamList), [](const KinectStreamConfig & stream)
199  {
200  return stream.Type == KinectSourceType::DEPTH;
201  });
202  auto pclStream = std::find_if(std::begin(this->StreamList), std::end(this->StreamList), [](const KinectStreamConfig & stream)
203  {
204  return stream.Type == KinectSourceType::PCL;
205  });
206 
207  if (rgbStream != std::end(this->StreamList))
208  {
209  this->AvailableStreamsFlag |= KinectSourceType::RGB;
210  }
211 
212  if (depthStream != std::end(this->StreamList))
213  {
214  this->AvailableStreamsFlag |= KinectSourceType::DEPTH;
215  }
216 
217  if (pclStream != std::end(this->StreamList))
218  {
219  this->AvailableStreamsFlag |= KinectSourceType::PCL;
220  }
221 
222  if ((this->AvailableStreamsFlag & KinectSourceType::PCL) && !(this->AvailableStreamsFlag & KinectSourceType::DEPTH))
223  {
224  LOG_ERROR("Invalid configuration: PCL stream is required but no Depth stream set");
225  return PLUS_FAIL;
226  }
227 
228  if (!(this->AvailableStreamsFlag & KinectSourceType::RGB) || !(this->AvailableStreamsFlag & KinectSourceType::DEPTH))
229  {
230  DeviceConfig.synchronized_images_only = false;
231  }
232 
233  if (AlignDepthStream)
234  {
235  if (!DeviceConfig.synchronized_images_only)
236  {
237  LOG_ERROR("Invalid configuration: AlignDepthStream set to TRUE but no RGB or Depth stream set");
238  return PLUS_FAIL;
239  }
240 
241  depthStream->Width = rgbStream->Width;
242  depthStream->Height = rgbStream->Height;
243  }
244 
245  if ((this->AvailableStreamsFlag & KinectSourceType::PCL) && (this->AvailableStreamsFlag & KinectSourceType::RGB))
246  {
247  pclStream->Width = rgbStream->Width;
248  pclStream->Height = rgbStream->Height;
249  }
250  else if (this->AvailableStreamsFlag & KinectSourceType::PCL)
251  {
252  pclStream->Width = depthStream->Width;
253  pclStream->Height = depthStream->Height;
254  }
255 
256  std::sort(std::begin(frameRates), std::end(frameRates));
257  frameRates.erase(std::unique(std::begin(frameRates), std::end(frameRates)), std::end(frameRates));
258 
259  if (frameRates.size() > 1)
260  {
261  LOG_WARNING("Detecting different frame rates for the streams: selecting the lowest rate (" << frameRates[0] << " fps)");
262  }
263 
264  this->External->InternalUpdateRate = frameRates[0];
265  this->External->AcquisitionRate = frameRates[0];
266 
267  return PLUS_SUCCESS;
268  }
269 
271  {
272  try
273  {
274  this->Device = k4a::device::open(0);
275  }
276  catch (const k4a::error& err)
277  {
278  LOG_ERROR("Failed to connect: " << err.what());
279  return PLUS_FAIL;
280  }
281 
282  return PLUS_SUCCESS;
283  }
284 
286  {
287  this->Device.close();
288  return PLUS_SUCCESS;
289  }
290 
291  PlusStatus Start()
292  {
293  try
294  {
295  this->Device.start_cameras(&(this->DeviceConfig));
296  this->Calibration = this->Device.get_calibration(this->DeviceConfig.depth_mode, this->DeviceConfig.color_resolution);
297  this->Transformation = k4a::transformation(this->Calibration);
298  }
299  catch (const k4a::error& err)
300  {
301  LOG_ERROR("Failed to start camera: " << err.what());
302  return PLUS_FAIL;
303  }
304 
305  return PLUS_SUCCESS;
306  }
307 
308  PlusStatus Stop()
309  {
310  this->Device.stop_cameras();
311  return PLUS_SUCCESS;
312  }
313 
314  void Update()
315  {
316  k4a::capture capture;
317  if (!this->Device.get_capture(&capture, std::chrono::milliseconds(0)))
318  {
319  return;
320  }
321 
322  if (this->AvailableStreamsFlag & KinectSourceType::DEPTH)
323  {
324  this->DepthImage = capture.get_depth_image();
325  }
326 
327  if (this->AvailableStreamsFlag & KinectSourceType::RGB)
328  {
329  this->ColorImage = capture.get_color_image();
330  }
331 
332  if (this->ColorImage && this->DepthImage && this->AlignDepthStream)
333  {
334  this->DepthImage = this->Transformation.depth_image_to_color_camera(this->DepthImage);
335  }
336 
337  if (!(this->AvailableStreamsFlag & KinectSourceType::PCL))
338  {
339  return;
340  }
341 
342  if (this->AvailableStreamsFlag & KinectSourceType::RGB)
343  {
344  this->PointsCloudImage = this->Transformation.depth_image_to_point_cloud(
345  (this->AlignDepthStream ? this->DepthImage : this->Transformation.depth_image_to_color_camera(this->DepthImage)), K4A_CALIBRATION_TYPE_COLOR);
346  }
347  else
348  {
349  this->PointsCloudImage = this->Transformation.depth_image_to_point_cloud(this->DepthImage,
350  K4A_CALIBRATION_TYPE_DEPTH);
351  }
352  }
353 };
354 
355 //----------------------------------------------------------------------------
357  : Internal(new vtkInternal(this))
358 {
360  this->StartThreadForInternalUpdates = true;
361 }
362 
363 //----------------------------------------------------------------------------
365 {
366  delete this->Internal;
367  this->Internal = nullptr;
368 }
369 
370 //----------------------------------------------------------------------------
371 void vtkPlusAzureKinect::PrintSelf(ostream& os, vtkIndent indent)
372 {
373  this->Superclass::PrintSelf(os, indent);
374 
375  os << indent << "Kinect Azure Configuration" << std::endl;
376  os << indent << "AlignDepthStream: " << this->Internal->AlignDepthStream << std::endl;
377  for (const auto& config : this->Internal->StreamList)
378  {
379  os << indent << "Source: " << config.Id << std::endl;
380  os << indent << indent << "Type: " << KinectSourceTypeAsString(config.Type) << std::endl;
381  os << indent << indent << "Frame Rate: " << config.FrameRate << std::endl;
382  os << indent << indent << "Frame Width: " << config.Width << std::endl;
383  os << indent << indent << "Frame Height: " << config.Height << std::endl;
384  }
385 }
386 
387 //-----------------------------------------------------------------------------
388 PlusStatus vtkPlusAzureKinect::ReadConfiguration(vtkXMLDataElement* rootConfigElement)
389 {
390  XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_READING(deviceConfig, rootConfigElement);
391  XML_READ_BOOL_ATTRIBUTE_NONMEMBER_OPTIONAL(AlignDepthStream, this->Internal->AlignDepthStream, deviceConfig);
392  XML_FIND_NESTED_ELEMENT_REQUIRED(dataSourcesElement, deviceConfig, "DataSources");
393 
394  LOG_TRACE("Reading custom configuration fields in " << dataSourcesElement->GetNumberOfNestedElements() << " nested elements");
395  for (int nestedElementIndex = 0; nestedElementIndex < dataSourcesElement->GetNumberOfNestedElements(); ++nestedElementIndex)
396  {
397  vtkXMLDataElement* dataElement = dataSourcesElement->GetNestedElement(nestedElementIndex);
398  if (STRCASECMP(dataElement->GetName(), "DataSource") != 0)
399  {
400  continue;
401  }
402 
403  LOG_TRACE("Found a new data source");
404 
405  if (dataElement->GetAttribute("Type") != NULL && STRCASECMP(dataElement->GetAttribute("Type"), "Video") == 0)
406  {
407  const char* toolId = dataElement->GetAttribute("Id");
408  if (toolId == nullptr)
409  {
410  // tool doesn't have ID needed to generate transform
411  LOG_ERROR("Failed to initialize Kinect Azure DataSource: Id is missing");
412  continue;
413  }
414 
415  LOG_TRACE("Data source name: " << toolId);
416 
417  KinectStreamConfig config;
418  config.Id = toolId;
419  XML_READ_ENUM3_ATTRIBUTE_NONMEMBER_REQUIRED(FrameType, config.Type, dataElement, "RGB", KinectSourceType::RGB, "DEPTH", KinectSourceType::DEPTH, "PCL", KinectSourceType::PCL);
420  XML_READ_SCALAR_ATTRIBUTE_NONMEMBER_REQUIRED(int, FrameRate, config.FrameRate, dataElement);
421  XML_READ_SCALAR_ATTRIBUTE_NONMEMBER_REQUIRED(int, FrameSize, config.Width, dataElement);
422  this->Internal->StreamList.push_back(config);
423  }
424  else
425  {
426  LOG_ERROR("DataSource with unknown Type.");
427  return PLUS_FAIL;
428  }
429  }
430  return PLUS_SUCCESS;
431 }
432 
433 //-----------------------------------------------------------------------------
434 PlusStatus vtkPlusAzureKinect::WriteConfiguration(vtkXMLDataElement* rootConfigElement)
435 {
436  XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_WRITING(deviceConfig, rootConfigElement);
437 
438  // write bool AlignDepthStream
439  if (this->Internal->AlignDepthStream)
440  {
441  deviceConfig->SetAttribute("AlignDepthStream", "TRUE");
442  }
443  else
444  {
445  deviceConfig->SetAttribute("AlignDepthStream", "FALSE");
446  }
447  return PLUS_SUCCESS;
448 }
449 
451 {
452  return this->Internal->UpdateConfig();
453 }
454 
455 //----------------------------------------------------------------------------
457 {
458  for (auto& stream : this->Internal->StreamList)
459  {
460  GetVideoSource(stream.Id.c_str(), stream.Source);
461  if (stream.Source == nullptr)
462  {
463  LOG_ERROR("Invalid source for tool id " << stream.Id);
464  return PLUS_FAIL;
465  }
466  }
467  return this->Internal->Connect();
468 }
469 
470 //----------------------------------------------------------------------------
472 {
473  return this->Internal->Disconnect();
474 }
475 
476 //----------------------------------------------------------------------------
478 {
479  this->FrameNumber = 0;
480 
481  return this->Internal->Start();
482 }
483 
484 //----------------------------------------------------------------------------
486 {
487  return this->Internal->Stop();
488 }
489 
490 //----------------------------------------------------------------------------
491 
492 //----------------------------------------------------------------------------
494 {
495  this->Internal->Update();
496 
497  // forward video data to PlusDataSource
498  for (auto& stream : this->Internal->StreamList)
499  {
500  if (stream.Source->GetNumberOfItems() == 0 && stream.Type == KinectSourceType::RGB)
501  {
502  LOG_TRACE("Setting up color frame");
503  stream.Source->SetImageType(US_IMG_RGB_COLOR);
504  stream.Source->SetPixelType(VTK_UNSIGNED_CHAR);
505  stream.Source->SetNumberOfScalarComponents(3);
506  stream.Source->SetInputFrameSize(stream.Width, stream.Height, 1);
507  }
508  else if (stream.Source->GetNumberOfItems() == 0 && stream.Type == KinectSourceType::DEPTH)
509  {
510  // depth output is raw depth data
511  LOG_TRACE("Setting up depth frame");
512  stream.Source->SetImageType(US_IMG_BRIGHTNESS);
513  stream.Source->SetPixelType(VTK_TYPE_UINT16);
514  stream.Source->SetNumberOfScalarComponents(1);
515  stream.Source->SetInputFrameSize(stream.Width, stream.Height, 1);
516  }
517  else if (stream.Source->GetNumberOfItems() == 0 && stream.Type == KinectSourceType::PCL)
518  {
519  // depth output is raw pcl data
520  LOG_TRACE("Setting up pcl frame");
521  stream.Source->SetImageType(US_IMG_BRIGHTNESS);
522  stream.Source->SetPixelType(VTK_TYPE_INT16);
523  stream.Source->SetNumberOfScalarComponents(3);
524  stream.Source->SetInputFrameSize(stream.Width, stream.Height, 1);
525  }
526 
527  if (stream.Type == KinectSourceType::RGB)
528  {
529  if (!this->Internal->ColorImage)
530  {
531  if (stream.Source->GetNumberOfItems() == 0)
532  {
533  LOG_TRACE("Waiting for KinectAzure RGB images");
534  }
535  else
536  {
537  LOG_WARNING("Failed to get KinectAzure RGB image");
538  }
539  return PLUS_FAIL;
540  }
541 
542  // Kinect Azure has a BGRA layout thus convert to RGB
543  const auto* bgraBuffer = this->Internal->ColorImage.get_buffer();
544  unsigned rgbSize = this->Internal->ColorImage.get_size() * 3 / 4;
545  std::unique_ptr<uint8_t[]> rgbBuffer(new uint8_t[rgbSize]);
546 
547  for (auto i = 0U; i < this->Internal->ColorImage.get_size(); i += 4)
548  {
549  rgbBuffer[3 * i / 4] = bgraBuffer[i + 2];
550  rgbBuffer[3 * i / 4 + 1] = bgraBuffer[i + 1];
551  rgbBuffer[3 * i / 4 + 2] = bgraBuffer[i];
552  }
553 
554  FrameSizeType frameSizeColor = {stream.Width, stream.Height, 1};
555  if (stream.Source->AddItem(reinterpret_cast<void*>(rgbBuffer.get()), stream.Source->GetInputImageOrientation(), frameSizeColor, VTK_UNSIGNED_CHAR, 3, US_IMG_RGB_COLOR, 0, this->FrameNumber) == PLUS_FAIL)
556  {
557  LOG_ERROR("Unable to send RGB image. Skipping frame.");
558  return PLUS_FAIL;
559  }
560  this->Modified();
561  }
562  else if (stream.Type == KinectSourceType::DEPTH)
563  {
564  if (!this->Internal->DepthImage)
565  {
566  if (stream.Source->GetNumberOfItems() == 0)
567  {
568  LOG_TRACE("Waiting for KinectAzure DEPTH images");
569  }
570  else
571  {
572  LOG_WARNING("Failed to get KinectAzure DEPTH image");
573  }
574  return PLUS_FAIL;
575  }
576 
577  FrameSizeType frameSizeDepth = {stream.Width, stream.Height, 1};
578  if (stream.Source->AddItem(reinterpret_cast<void*>(this->Internal->DepthImage.get_buffer()), stream.Source->GetInputImageOrientation(), frameSizeDepth, VTK_TYPE_UINT16, 1, US_IMG_BRIGHTNESS, 0, this->FrameNumber) == PLUS_FAIL)
579  {
580  LOG_ERROR("Unable to send DEPTH image. Skipping frame.");
581  return PLUS_FAIL;
582  }
583  this->Modified();
584  }
585  else if (stream.Type == KinectSourceType::PCL)
586  {
587  if (!this->Internal->PointsCloudImage)
588  {
589  if (stream.Source->GetNumberOfItems() == 0)
590  {
591  LOG_TRACE("Waiting for KinectAzure PCL images");
592  }
593  else
594  {
595  LOG_WARNING("Failed to get KinectAzure PCL image");
596  }
597  return PLUS_FAIL;
598  }
599 
600  FrameSizeType frameSizeDepth = {stream.Width, stream.Height, 1};
601  if (stream.Source->AddItem(reinterpret_cast<void*>(this->Internal->PointsCloudImage.get_buffer()), stream.Source->GetInputImageOrientation(), frameSizeDepth, VTK_TYPE_INT16, 3, US_IMG_BRIGHTNESS, 0, this->FrameNumber) == PLUS_FAIL)
602  {
603  LOG_ERROR("Unable to send PCL image. Skipping frame.");
604  return PLUS_FAIL;
605  }
606  this->Modified();
607  }
608  }
609 
610  this->FrameNumber++;
611  return PLUS_SUCCESS;
612 }
PlusStatus InternalStopRecording()
vtkStandardNewMacro(vtkPlusAzureKinect)
virtual void PrintSelf(ostream &os, vtkIndent indent) VTK_OVERRIDE
PlusStatus InternalStartRecording()
PlusStatus WriteConfiguration(vtkXMLDataElement *config)
#define XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_WRITING(deviceConfig, rootConfigElement)
igsioStatus PlusStatus
Definition: PlusCommon.h:40
bool RequireImageOrientationInConfiguration
for i
double AcquisitionRate
#define PLUS_FAIL
Definition: PlusCommon.h:43
virtual PlusStatus ReadConfiguration(vtkXMLDataElement *config)
virtual PlusStatus Disconnect()
#define PLUS_SUCCESS
Definition: PlusCommon.h:44
virtual PlusStatus Connect()
double InternalUpdateRate
#define XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_READING(deviceConfig, rootConfigElement)
PhidgetLCD_Font int * width
Definition: phidget22.h:4275
virtual PlusStatus InternalDisconnect()
Interface class to Kinect Azure cameras.
virtual PlusStatus InternalUpdate()
bool StartThreadForInternalUpdates
virtual PlusStatus InternalConnect()
virtual void PrintSelf(ostream &os, vtkIndent indent)
PlusStatus GetVideoSource(const char *aSourceId, vtkPlusDataSource *&aVideoSource)
KinectSourceType
virtual PlusStatus NotifyConfigured()
Interface to a 3D positioning tool, video source, or generalized data stream.