PlusLib  2.9.0
Software library for tracked ultrasound image acquisition, calibration, and processing.
vtkPlusPhilips3DProbeVideoSource.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 /*=========================================================================
8 The following copyright notice is applicable to parts of this file:
9 Copyright (c) 2014, Robarts Research Institute, The University of Western Ontario, London, Ontario, Canada
10 All rights reserved.
11 Authors include:
12 * Elvis Chen (Robarts Research Institute and The University of Western Ontario)
13 * Adam Rankin (Robarts Research Institute and The University of Western Ontario)
14 =========================================================================*/
15 
16 // Plus includes
17 #include "PlusConfigure.h"
18 #include "vtkObjectFactory.h"
20 #include "vtkPlusDataSource.h"
21 
22 // STL includes
23 #include <future>
24 
25 // Philips API includes
26 #include "StreamMgr.h"
27 #include "vtkPlusIEEListener.h"
28 
29 // System includes
30 #ifdef _WIN32
31  #include <inaddr.h>
32  #include <WS2tcpip.h>
33 #else
34  #include <arpa/inet.h>
35 #endif
36 
37 //----------------------------------------------------------------------------
38 
39 vtkPlusPhilips3DProbeVideoSource* vtkPlusPhilips3DProbeVideoSource::ActiveDevice = NULL;
40 
41 //----------------------------------------------------------------------------
42 
43 namespace
44 {
45  vtkImageData* streamedImageData = NULL;
46  static double LastValidTimestamp(0);
47  static double LastRetryTime(0);
48 
49  const double TIMEOUT = 1.0; // 1 seconds;
50  const double RETRY_TIMER = 0.5; // 1/2 second
51 }
52 
53 //----------------------------------------------------------------------------
54 bool vtkPlusPhilips3DProbeVideoSource::StreamCallback(_int64 id, SClient3DArray* ed, SClient3DArray* cd)
55 {
56  if (vtkPlusPhilips3DProbeVideoSource::ActiveDevice == NULL)
57  {
58  LOG_ERROR("No Philips device has been created, but the callback was still called.");
59  return false;
60  }
61 
62  int dimensions[3] = {ed->width_padded, ed->height_padded, ed->depth_padded};
63 
64  /*
65  * This is way smaller than what Qlab reports. Perhaps the streaming volume
66  * is way smaller than the recorded one:
67  *
68  * 112 x 48 x 112
69  */
70  if (streamedImageData == NULL)
71  {
72  // let the calibration matrix handle the spacing and orientation of the volume in 3-space
73  double spacing[3] = {1.0, 1.0, 1.0};
74  double origin[3] = {0.0, 0.0, 0.0};
75 
76  streamedImageData = vtkImageData::New();
77  streamedImageData->SetSpacing(spacing);
78  streamedImageData->SetExtent(0, dimensions[0] - 1, 0, dimensions[1] - 1, 0, dimensions[2] - 1);
79  streamedImageData->SetOrigin(origin);
80 #if VTK_MAJOR_VERSION > 5
81  streamedImageData->AllocateScalars(VTK_UNSIGNED_CHAR, 1);
82 #else
83  streamedImageData->SetScalarTypeToUnsignedChar();
84  streamedImageData->SetNumberOfScalarComponents(1);
85  streamedImageData->AllocateScalars();
86 #endif
87 
88  vtkPlusDataSource* videoSource(NULL);
89  vtkPlusPhilips3DProbeVideoSource::ActiveDevice->GetFirstVideoSource(videoSource);
90  if (dimensions[0] < 0 || dimensions[1] < 0 || dimensions[2] < 0)
91  {
92  LOG_ERROR("Negative dimensions received from Philips ultrasound device.");
93  return false;
94  }
95  videoSource->SetInputFrameSize(static_cast<unsigned int>(dimensions[0]), static_cast<unsigned int>(dimensions[1]), static_cast<unsigned int>(dimensions[2]));
96  videoSource->SetPixelType(VTK_UNSIGNED_CHAR);
97  videoSource->SetNumberOfScalarComponents(1);
98  }
99  else
100  {
101  int streamedDimensions[3] = {0, 0, 0};
102  streamedImageData->GetDimensions(streamedDimensions);
103 
104  if (dimensions[0] != streamedDimensions[0] || dimensions[1] != streamedDimensions[1] || dimensions[2] != streamedDimensions[2])
105  {
106  LOG_ERROR("Dimensions of new frame do not match dimensions of previous frames. Cannot add frame to buffer.");
107  return false;
108  }
109  }
110 
111  size_t size = dimensions[0] * dimensions[1] * dimensions[2];
112  unsigned char* src = ed->pData;
113  unsigned char* dst = (unsigned char*)streamedImageData->GetScalarPointer();
114 
115  memcpy((void*)dst, (void*)src, size);
116 
117  vtkPlusPhilips3DProbeVideoSource::ActiveDevice->CallbackAddFrame(streamedImageData);
118 
119  LastValidTimestamp = vtkIGSIOAccurateTimer::GetSystemTime();
120 
121  return true;
122 }
123 
124 //----------------------------------------------------------------------------
125 
127 
128 //----------------------------------------------------------------------------
130  : Listener(NULL)
131  , FrameNumber(0)
132  , IPAddress(NULL)
133  , Port(-1)
134  , ForceZQuantize(false)
135  , ResolutionFactor(2.5)
136  , IntegerZ(true)
137  , Isotropic(false)
138  , QuantizeDim(true)
139  , ZDecimation(2)
140  , Set4PtFIR(true)
141  , LatAndElevSmoothingIndex(4)
142 {
143  this->StartThreadForInternalUpdates = true;
144  this->AcquisitionRate = 10;
145 
146  // This effectively forces only one Philips 3D source at a time, but it paves the way
147  // for a non-singleton architecture when the SDK supports it
148  if (vtkPlusPhilips3DProbeVideoSource::ActiveDevice != NULL)
149  {
150  LOG_WARNING("There is already an active vtkPlusPhilips3DProbeVideoSource device. Philips API only supports one connection at a time, so the existing device is now deactivated and the newly created class is activated instead.");
151  }
152 
153  vtkPlusPhilips3DProbeVideoSource::ActiveDevice = this;
154 }
155 
156 //----------------------------------------------------------------------------
158 {
159  if (this->Connected)
160  {
161  this->Disconnect();
162  }
163 
164  if (this->Listener)
165  {
166  this->Listener->Delete();
167  this->Listener = NULL;
168  }
169 
170  vtkPlusPhilips3DProbeVideoSource::ActiveDevice = NULL;
171 }
172 
173 //----------------------------------------------------------------------------
174 void vtkPlusPhilips3DProbeVideoSource::PrintSelf(ostream& os, vtkIndent indent)
175 {
176  this->Superclass::PrintSelf(os, indent);
177 
178  if (this->Listener)
179  {
180  this->Listener->PrintSelf(os, indent);
181  }
182 }
183 
184 //----------------------------------------------------------------------------
186 {
187  LOG_TRACE("vtkPlusPhilips3DProbeVideoSource::InternalConnect");
188 
190  this->Listener->SetMachineName(this->IPAddress);
191  this->Listener->SetPortNumber(this->Port);
192  if (this->Listener->Connect(&vtkPlusPhilips3DProbeVideoSource::StreamCallback) == PLUS_FAIL)
193  {
194  LOG_ERROR("Unable to connect to Philips device.");
195  return PLUS_FAIL;
196  }
197 
198  return PLUS_SUCCESS;
199 }
200 
201 //----------------------------------------------------------------------------
203 {
204  LOG_TRACE("vtkPlusPhilips3DProbeVideoSource::InternalDisconnect");
205 
206  this->Listener->Disconnect();
207 
208  if (streamedImageData != NULL)
209  {
210  streamedImageData->Delete();
211  streamedImageData = NULL;
212  }
213 
214  return PLUS_SUCCESS;
215 }
216 
217 //-----------------------------------------------------------------------------
219 {
220  if (this->Listener->IsConnected())
221  {
222  // Listener thinks it's connected, let's check for timeouts
223  if (vtkIGSIOAccurateTimer::GetSystemTime() - LastValidTimestamp >= TIMEOUT)
224  {
225  LOG_INFO("Philips iE33: 3D mode timeout disconnected. Did you switch to another mode?");
226  // Don't call a full disconnect because that stops the InternalUpdate loop
227  // Only disconnect the listener and periodically try again
228  this->Listener->Disconnect();
229  LastRetryTime = vtkIGSIOAccurateTimer::GetSystemTime();
230  }
231  }
232  else if (vtkIGSIOAccurateTimer::GetSystemTime() - LastRetryTime >= RETRY_TIMER)
233  {
234  LOG_INFO("Philips iE33: Retrying connection to 3D mode.");
235  // Retry connection if it's time
236  LastRetryTime = vtkIGSIOAccurateTimer::GetSystemTime();
237  if (this->Listener->Connect(&vtkPlusPhilips3DProbeVideoSource::StreamCallback, vtkPlusLogger::LOG_LEVEL_WARNING))
238  {
239  LOG_INFO("Philips iE33: Connection successfully re-established.");
240  LastValidTimestamp = vtkIGSIOAccurateTimer::GetSystemTime();
241  }
242  }
243 
244  return PLUS_SUCCESS;
245 }
246 
247 //-----------------------------------------------------------------------------
249 {
250  LOG_TRACE("vtkPlusPhilips3DProbeVideoSource::ReadConfiguration");
251  XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_READING(deviceConfig, rootConfigElement);
252 
253  XML_READ_SCALAR_ATTRIBUTE_REQUIRED(int, Port, deviceConfig);
254  XML_READ_CSTRING_ATTRIBUTE_REQUIRED(IPAddress, deviceConfig);
255 #ifdef _WIN32
256  struct in_addr address;
257  int result = InetPton(AF_INET, this->IPAddress, &address);
258 #else
259  struct sockaddr_in address;
260  int result = inet_pton(AF_INET, this->IPAddress, &(address.sin_addr));
261 #endif
262  if (result != 1)
263  {
264  LOG_ERROR("Improperly formatted IPAddress. Please confirm formatting in config file.");
265  return PLUS_FAIL;
266  }
267 
268  XML_READ_BOOL_ATTRIBUTE_OPTIONAL(ForceZQuantize, deviceConfig);
269  XML_READ_SCALAR_ATTRIBUTE_OPTIONAL(double, ResolutionFactor, deviceConfig);
270  XML_READ_BOOL_ATTRIBUTE_OPTIONAL(IntegerZ, deviceConfig);
271  XML_READ_BOOL_ATTRIBUTE_OPTIONAL(Isotropic, deviceConfig);
272  XML_READ_BOOL_ATTRIBUTE_OPTIONAL(QuantizeDim, deviceConfig);
273  XML_READ_SCALAR_ATTRIBUTE_OPTIONAL(int, ZDecimation, deviceConfig);
274  XML_READ_BOOL_ATTRIBUTE_OPTIONAL(Set4PtFIR, deviceConfig);
275  XML_READ_SCALAR_ATTRIBUTE_OPTIONAL(int, LatAndElevSmoothingIndex, deviceConfig);
276 
277  return PLUS_SUCCESS;
278 }
279 
280 //-----------------------------------------------------------------------------
282 {
283  LOG_TRACE("vtkPlusPhilips3DProbeVideoSource::WriteConfiguration");
284  XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_WRITING(deviceConfig, rootConfig);
285 
286  XML_WRITE_CSTRING_ATTRIBUTE_IF_NOT_NULL(IPAddress, deviceConfig);
287  deviceConfig->SetIntAttribute("Port", this->Port);
288 
289  deviceConfig->SetDoubleAttribute("ResolutionFactor", this->ResolutionFactor);
290  deviceConfig->SetIntAttribute("ZDecimation", this->ZDecimation);
291  deviceConfig->SetIntAttribute("LatAndElevSmoothingIndex", this->LatAndElevSmoothingIndex);
292  XML_WRITE_BOOL_ATTRIBUTE(ForceZQuantize, deviceConfig);
293  XML_WRITE_BOOL_ATTRIBUTE(IntegerZ, deviceConfig);
294  XML_WRITE_BOOL_ATTRIBUTE(Isotropic, deviceConfig);
295  XML_WRITE_BOOL_ATTRIBUTE(QuantizeDim, deviceConfig);
296  XML_WRITE_BOOL_ATTRIBUTE(Set4PtFIR, deviceConfig);
297  return PLUS_SUCCESS;
298 }
299 
300 //-----------------------------------------------------------------------------
302 {
303  if (this->OutputChannels.size() > 1)
304  {
305  LOG_WARNING("vtkPlusPhilips3DProbeVideoSource is expecting one output channel and there are " << this->OutputChannels.size() << " channels. First output channel will be used.");
306  this->SetCorrectlyConfigured(false);
307  return PLUS_FAIL;
308  }
309 
310  if (this->OutputChannels.empty())
311  {
312  LOG_ERROR("No output channels defined for vtkPlusPhilips3DProbeVideoSource. Cannot proceed.");
313  this->SetCorrectlyConfigured(false);
314  return PLUS_FAIL;
315  }
316 
317  vtkPlusDataSource* videoSource(NULL);
318  if (this->GetFirstVideoSource(videoSource) != PLUS_SUCCESS)
319  {
320  LOG_ERROR("Unable to find video source. Device needs a video buffer to put new frames into.");
321  this->SetCorrectlyConfigured(false);
322  return PLUS_FAIL;
323  }
324 
325  if (STRCASECMP(this->IPAddress, "") == 0 || this->Port <= 0)
326  {
327  this->SetCorrectlyConfigured(false);
328  return PLUS_FAIL;
329  }
330 
331  return PLUS_SUCCESS;
332 }
333 
334 //----------------------------------------------------------------------------
336 {
337  return false;
338 }
339 
340 //----------------------------------------------------------------------------
342 {
343  vtkPlusDataSource* videoSource(NULL);
344  if (this->GetFirstVideoSource(videoSource) != PLUS_SUCCESS)
345  {
346  LOG_ERROR("Unable to find video source. Cannot add new frame.");
347  return;
348  }
349 
350  std::future<void> addTask = std::async(std::launch::async, [ = ]()
351  {
352  if (videoSource->AddItem(imageData, videoSource->GetInputImageOrientation(), US_IMG_BRIGHTNESS, this->FrameNumber) != PLUS_SUCCESS)
353  {
354  LOG_ERROR("Unable to add item to buffer.");
355  return;
356  }
357  });
358  std::future<uint8_t> maxPixelTask = std::async(std::launch::async, [ = ]()
359  {
360  int extent[6];
361  imageData->GetExtent(extent);
362  uint8_t* pixelPtr = (uint8_t*)imageData->GetScalarPointer();
363  auto numComponents = imageData->GetNumberOfScalarComponents();
364 
365  uint8_t maxPixelValue(0);
366  for (int z = 0; z < extent[5] - extent[4]; ++z)
367  {
368  for (int y = 0; y < extent[3] - extent[2]; ++y)
369  {
370  for (int x = 0; x < extent[1] - extent[0]; ++x)
371  {
372  if (*pixelPtr > maxPixelValue)
373  {
374  maxPixelValue = *pixelPtr;
375  }
376  pixelPtr += numComponents;
377  }
378  }
379  }
380 
381  return maxPixelValue;
382  });
383 
384  addTask.wait();
385  unsigned int maxPixelValue = static_cast<unsigned int>(maxPixelTask.get());
386 
387  std::stringstream ss;
388  ss << maxPixelValue;
389  videoSource->ModifyBufferItemFrameField(videoSource->GetLatestItemUidInBuffer(), "MaximumPixelValue", ss.str());
390 
391  this->FrameNumber++;
392 }
virtual BufferItemUidType GetLatestItemUidInBuffer()
void SetPortNumber(unsigned int port)
virtual void PrintSelf(ostream &os, vtkIndent indent) VTK_OVERRIDE
virtual PlusStatus WriteConfiguration(vtkXMLDataElement *config)
#define XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_WRITING(deviceConfig, rootConfigElement)
static vtkPlusIEEListener * New()
const char int const char const char * src
Definition: phidget22.h:2458
igsioStatus PlusStatus
Definition: PlusCommon.h:40
vtkStandardNewMacro(vtkPlusPhilips3DProbeVideoSource)
virtual PlusStatus AddItem(vtkImageData *frame, US_IMAGE_ORIENTATION usImageOrientation, US_IMAGE_TYPE imageType, long frameNumber, double unfilteredTimestamp=UNDEFINED_TIMESTAMP, double filteredTimestamp=UNDEFINED_TIMESTAMP, const igsioFieldMapType *customFields=NULL)
double AcquisitionRate
#define PLUS_FAIL
Definition: PlusCommon.h:43
virtual PlusStatus ReadConfiguration(vtkXMLDataElement *config)
Class for providing VTK video input interface from Philips ie33 3D ultrasound probe.
PlusStatus GetFirstVideoSource(vtkPlusDataSource *&anImage)
virtual PlusStatus Disconnect()
virtual void SetCorrectlyConfigured(bool)
#define PLUS_SUCCESS
Definition: PlusCommon.h:44
virtual void PrintSelf(ostream &os, vtkIndent indent) VTK_OVERRIDE
const char * address
Definition: phidget22.h:2552
virtual void PrintSelf(ostream &os, vtkIndent indent) VTK_OVERRIDE
#define XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_READING(deviceConfig, rootConfigElement)
int x
Definition: phidget22.h:4265
virtual US_IMAGE_ORIENTATION GetInputImageOrientation()
bool StartThreadForInternalUpdates
PlusStatus Connect(CLIENT_POSTSCANCONVERT_CALLBACK callback, vtkPlusLogger::LogLevelType logType=vtkPlusLogger::LOG_LEVEL_ERROR)
virtual PlusStatus ModifyBufferItemFrameField(BufferItemUidType uid, const std::string &key, const std::string &value)
ChannelContainer OutputChannels
Direction vectors of rods y
Definition: algo3.m:15
void SetMachineName(const std::string &machineName)
Interface to a 3D positioning tool, video source, or generalized data stream.