PlusLib  2.9.0
Software library for tracked ultrasound image acquisition, calibration, and processing.
vtkPlusEpiphanVideoSource.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 "PixelCodec.h"
10 #include "vtkPlusChannel.h"
11 #include "vtkPlusDataSource.h"
13 
14 // Epiphan includes
15 #include <frmgrab.h>
16 
17 // vtk includes
18 #include <vtkImageData.h>
19 #include <vtkObjectFactory.h>
20 #include <vtksys/SystemTools.hxx>
21 
22 //----------------------------------------------------------------------------
23 
25 
26 //----------------------------------------------------------------------------
28  : GrabberLocation("")
29  , CropRectangle(nullptr)
30  , ScaleMode("")
31  , RotationMode("")
32  , Rotation(V2URotationNone)
33  , Scale(V2UScaleNone)
34 {
35  this->ClipRectangleOrigin[0] = igsioCommon::NO_CLIP;
36  this->ClipRectangleOrigin[1] = igsioCommon::NO_CLIP;
37  this->ClipRectangleOrigin[2] = igsioCommon::NO_CLIP;
38  this->ClipRectangleSize[0] = igsioCommon::NO_CLIP;
39  this->ClipRectangleSize[1] = igsioCommon::NO_CLIP;
40  this->ClipRectangleSize[2] = igsioCommon::NO_CLIP;
41 
42  this->FrameSize[0] = 0;
43  this->FrameSize[1] = 0;
44  this->FrameSize[2] = 1;
45 
47 
48  // No callback function provided by the device, so the data capture thread will be used to poll the hardware and add new items to the buffer
49  this->StartThreadForInternalUpdates = true;
50  this->AcquisitionRate = 30;
51 }
52 
53 //----------------------------------------------------------------------------
55 {
56  if (!this->Connected)
57  {
58  this->Disconnect();
59  }
60 
61  if (this->FrameGrabber != nullptr)
62  {
64  this->FrameGrabber = nullptr;
65  }
66 
67  if (this->CropRectangle != nullptr)
68  {
69  delete this->CropRectangle;
70  this->CropRectangle = nullptr;
71  }
72 }
73 
74 //----------------------------------------------------------------------------
75 void vtkPlusEpiphanVideoSource::PrintSelf(ostream& os, vtkIndent indent)
76 {
77  this->Superclass::PrintSelf(os, indent);
78 }
79 
80 //----------------------------------------------------------------------------
82 {
83  return false;
84 }
85 
86 //----------------------------------------------------------------------------
88 {
89  LOG_TRACE("vtkPlusEpiphanVideoSource::InternalConnect");
90 
91  // Initialize frmgrab library
93 
94  if (!this->GrabberLocation.empty())
95  {
96  if ((this->FrameGrabber = FrmGrab_Open(this->GrabberLocation.c_str())) == NULL)
97  {
98  if ((this->FrameGrabber = FrmGrabLocal_Open()) == NULL)
99  {
100  LOG_ERROR("Epiphan Device found");
101  return PLUS_FAIL;
102  }
103  const char UNKNOWN_DEVICE[] = "UNKNOWN";
104  const char* connectedTo = FrmGrab_GetLocation((FrmGrabber*)this->FrameGrabber);
105  if (connectedTo == NULL)
106  {
107  connectedTo = UNKNOWN_DEVICE;
108  }
109 
110  LOG_WARNING("Epiphan Device with the requested location '" << this->GrabberLocation << "' not found. Connected to " << connectedTo << " device instead.");
111  }
112  }
113  else
114  {
115  LOG_DEBUG("Serial Number not specified. Looking for any available device");
116  if ((this->FrameGrabber = FrmGrabLocal_Open()) == NULL)
117  {
118  LOG_ERROR("Epiphan Device Not found");
119  return PLUS_FAIL;
120  }
121  }
122 
123  V2U_VideoMode vm;
125  {
126  LOG_ERROR("No signal detected");
127  return PLUS_FAIL;
128  }
129 
130  double maxPossibleAcquisitionRate = vm.vfreq / 1000;
131  if (this->GetAcquisitionRate() > maxPossibleAcquisitionRate)
132  {
133  this->SetAcquisitionRate(maxPossibleAcquisitionRate);
134  }
135  if (vm.width <= 0 || vm.height <= 0)
136  {
137  LOG_ERROR("No valid signal detected. Invalid frame size is received from the framegrabber: " << vm.width << "x" << vm.height);
138  return PLUS_FAIL;
139  }
140  this->FrameSize[0] = static_cast<unsigned int>(vm.width);
141  this->FrameSize[1] = static_cast<unsigned int>(vm.height);
142 
143  if ((this->ClipRectangleSize[0] > 0) && (this->ClipRectangleSize[1] > 0))
144  {
145  if (this->ClipRectangleSize[0] % 4 != 0)
146  {
147  LOG_WARNING("ClipRectangleSize[0] is not a multiple of 4. Acquired image may be skewed.");
148  }
149  if (this->ClipRectangleOrigin[0] + this->ClipRectangleSize[0] > vm.width || this->ClipRectangleOrigin[1] + this->ClipRectangleSize[1] > vm.height)
150  {
151  LOG_ERROR("Invalid clip rectangle: rectangle does not fit into the image. Clip rectangle origin: (" << this->ClipRectangleOrigin[0] << "," << this->ClipRectangleOrigin[1]
152  << "), size: " << this->ClipRectangleSize[0] << "," << this->ClipRectangleSize[1] << ". Image size: " << vm.width << "x" << vm.height);
153  return PLUS_FAIL;
154  }
155  this->FrameSize[0] = static_cast<unsigned int>(this->ClipRectangleSize[0]);
156  this->FrameSize[1] = static_cast<unsigned int>(this->ClipRectangleSize[1]);
157  }
158 
159  if (this->GetNumberOfVideoSources() == 1)
160  {
161  vtkPlusDataSource* aSource(NULL);
162  if (this->GetFirstVideoSource(aSource) != PLUS_SUCCESS)
163  {
164  LOG_ERROR("Unable to retrieve the video source in the Epiphan device on channel " << (*this->OutputChannels.begin())->GetChannelId());
165  return PLUS_FAIL;
166  }
167  else
168  {
169  US_IMAGE_TYPE imageType = aSource->GetImageType();
170  aSource->SetPixelType(VTK_UNSIGNED_CHAR);
171  aSource->SetNumberOfScalarComponents(imageType == US_IMG_RGB_COLOR ? 3 : 1);
172  aSource->SetInputFrameSize(this->FrameSize);
173  }
174  }
175  else
176  {
177  // Can only be 1 or 2, so we must have two video sources, most likely for biplane configuration
178  vtkPlusDataSource* aSource(NULL);
179  for (int i = 0; i < this->GetNumberOfVideoSources(); ++i)
180  {
181  if (this->GetVideoSourceByIndex(i, aSource) != PLUS_SUCCESS)
182  {
183  LOG_ERROR("Unable to retrieve the video source in the Epiphan device on channel " << (*this->OutputChannels.begin())->GetChannelId());
184  return PLUS_FAIL;
185  }
186 
187  US_IMAGE_TYPE imageType = aSource->GetImageType();
188  aSource->SetPixelType(VTK_UNSIGNED_CHAR);
189  aSource->SetNumberOfScalarComponents(imageType == US_IMG_RGB_COLOR ? 3 : 1);
190  aSource->SetInputFrameSize(this->FrameSize);
191  }
192  }
193 
194  return PLUS_SUCCESS;
195 }
196 
197 //----------------------------------------------------------------------------
199 {
200  LOG_DEBUG("vtkPlusEpiphanVideoSource::InternalDisconnect");
201 
202  if (this->Recording)
203  {
204  if (this->StopRecording() != PLUS_SUCCESS)
205  {
206  LOG_WARNING(this->GetDeviceId() << ": Unable to stop recording.");
207  }
208  }
209 
210  if (this->FrameGrabber != NULL)
211  {
213  }
214  this->FrameGrabber = NULL;
215 
216  return PLUS_SUCCESS;
217 }
218 
219 //----------------------------------------------------------------------------
221 {
222  if (!FrmGrab_Start((FrmGrabber*)this->FrameGrabber))
223  {
224  LOG_ERROR(this->GetDeviceId() << ": Unable to frame grabber.");
225  return PLUS_FAIL;
226  }
227 
228  return PLUS_SUCCESS;
229 }
230 
231 //----------------------------------------------------------------------------
233 {
235  return PLUS_SUCCESS;
236 }
237 
238 //----------------------------------------------------------------------------
240 {
241  if (!this->Recording)
242  {
243  // drop the frame, we are not recording data now
244  return PLUS_SUCCESS;
245  }
246 
247  V2U_GrabFrame2* frame = NULL;
248 
249  // If someone ever wants RGB8 or YUY2 (etc...) this line will have to be changed
250  // to support any future video format choices
251  // ReadConfiguration will probably need a new flag to tell this line what to do
252  V2U_UINT32 videoFormat = (this->CaptureImageType == US_IMG_RGB_COLOR ? V2U_GRABFRAME_FORMAT_RGB24 : V2U_GRABFRAME_FORMAT_Y8);
253  videoFormat |= V2U_SCALE_MODE_TO_FLAGS(Scale);
254  videoFormat |= V2U_ROTATION_MODE_TO_FLAGS(Rotation);
255 
256  frame = FrmGrab_Frame((FrmGrabber*)this->FrameGrabber, videoFormat, this->CropRectangle);
257 
258  if (frame == NULL)
259  {
260  LOG_WARNING("Frame not captured for video format: " << videoFormat);
261  return PLUS_FAIL;
262  }
263 
264  if (frame->crop.width < 0 || frame->crop.height < 0)
265  {
266  LOG_ERROR("Negative cropping values passed in from epiphan.");
267  return PLUS_FAIL;
268  }
269 
270  if (static_cast<unsigned int>(frame->crop.width) != this->FrameSize[0] || static_cast<unsigned int>(frame->crop.height) != this->FrameSize[1])
271  {
272  LOG_ERROR("Image size received from Epiphan (" << frame->crop.width << "x" << frame->crop.height << ") does not match the clip rectangle size (" <<
273  this->FrameSize[0] << "x" << this->FrameSize[1] << ")");
274  FrmGrab_Release((FrmGrabber*)this->FrameGrabber, frame);
275  return PLUS_FAIL;
276  }
277 
278  vtkPlusDataSource* aSource(NULL);
279  for (int i = 0; i < this->GetNumberOfVideoSources(); ++i)
280  {
281  if (this->GetVideoSourceByIndex(i, aSource) != PLUS_SUCCESS)
282  {
283  LOG_ERROR("Unable to retrieve the video source in the Epiphan device on channel " << (*this->OutputChannels.begin())->GetChannelId());
284  return PLUS_FAIL;
285  }
286  int numberOfScalarComponents(1);
287  if (aSource->GetImageType() == US_IMG_RGB_COLOR)
288  {
289  numberOfScalarComponents = 3;
290  }
291  if (aSource->AddItem(frame->pixbuf, aSource->GetInputImageOrientation(), this->FrameSize, VTK_UNSIGNED_CHAR, numberOfScalarComponents, aSource->GetImageType(), 0, this->FrameNumber) != PLUS_SUCCESS)
292  {
293  LOG_ERROR("Error adding item to video source " << aSource->GetId() << " on channel " << (*this->OutputChannels.begin())->GetChannelId());
294  return PLUS_FAIL;
295  }
296  else
297  {
298  this->Modified();
299  }
300  }
301 
302  FrmGrab_Release((FrmGrabber*)this->FrameGrabber, frame);
303 
304  this->FrameNumber++;
305 
306  return PLUS_SUCCESS;
307 }
308 
309 //-----------------------------------------------------------------------------
310 PlusStatus vtkPlusEpiphanVideoSource::ReadConfiguration(vtkXMLDataElement* rootConfigElement)
311 {
312  XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_READING(deviceConfig, rootConfigElement);
313 
314  // SerialNumber is kept for backward compatibility only. Serial number or other address should be specified in the
315  // GrabberLocation attribute.
316  const char* grabberLocation = deviceConfig->GetAttribute("GrabberLocation");
317  const char* serialNumber = deviceConfig->GetAttribute("SerialNumber");
318  if (grabberLocation != NULL)
319  {
320  SetGrabberLocation(grabberLocation);
321  }
322  else if (serialNumber != NULL)
323  {
324  std::string grabberLocationString = std::string("sn:") + serialNumber;
325  SetGrabberLocation(grabberLocationString);
326  LOG_WARNING("Epiphan SerialNumber is specified. This attribute is deprecated, please use GrabberLocation=\"sn:SERIAL\" attribute instead.");
327  }
328  else
329  {
330  LOG_DEBUG("Epiphan device location is not specified in the configuration");
331  }
332 
333  XML_READ_STRING_ATTRIBUTE_OPTIONAL(RotationMode, deviceConfig);
334  if (!this->RotationMode.empty())
335  {
336  if (igsioCommon::IsEqualInsensitive(this->RotationMode, "Left90") == 0)
337  {
338  this->Rotation = V2URotationLeft90;
339  }
340  else if (igsioCommon::IsEqualInsensitive(this->RotationMode, "Right90") == 0)
341  {
343  }
344  else if (igsioCommon::IsEqualInsensitive(this->RotationMode, "180") == 0)
345  {
346  this->Rotation = V2URotation180;
347  }
348  }
349 
350  XML_READ_STRING_ATTRIBUTE_OPTIONAL(ScaleMode, deviceConfig);
351  if (!this->ScaleMode.empty())
352  {
353  if (igsioCommon::IsEqualInsensitive(this->ScaleMode, "NearestNeighbor") == 0)
354  {
356  }
357  else if (igsioCommon::IsEqualInsensitive(this->ScaleMode, "WeightedAverage") == 0)
358  {
360  }
361  else if (igsioCommon::IsEqualInsensitive(this->ScaleMode, "FastBilinear") == 0)
362  {
364  }
365  else if (igsioCommon::IsEqualInsensitive(this->ScaleMode, "Bilinear") == 0)
366  {
367  this->Scale = V2UScaleModeBilinear;
368  }
369  else if (igsioCommon::IsEqualInsensitive(this->ScaleMode, "Bicubic") == 0)
370  {
371  this->Scale = V2UScaleModeBicubic;
372  }
373  else if (igsioCommon::IsEqualInsensitive(this->ScaleMode, "Experimental") == 0)
374  {
376  }
377  else if (igsioCommon::IsEqualInsensitive(this->ScaleMode, "Point") == 0)
378  {
379  this->Scale = V2UScaleModePoint;
380  }
381  else if (igsioCommon::IsEqualInsensitive(this->ScaleMode, "Area") == 0)
382  {
383  this->Scale = V2UScaleModeArea;
384  }
385  else if (igsioCommon::IsEqualInsensitive(this->ScaleMode, "BicubLin") == 0)
386  {
387  this->Scale = V2UScaleModeBicubLin;
388  }
389  else if (igsioCommon::IsEqualInsensitive(this->ScaleMode, "Sinc") == 0)
390  {
391  this->Scale = V2UScaleModeSinc;
392  }
393  else if (igsioCommon::IsEqualInsensitive(this->ScaleMode, "Lanczos") == 0)
394  {
395  this->Scale = V2UScaleModeLanczos;
396  }
397  else if (igsioCommon::IsEqualInsensitive(this->ScaleMode, "Spline") == 0)
398  {
399  this->Scale = V2UScaleModeSpline;
400  }
401  else if (igsioCommon::IsEqualInsensitive(this->ScaleMode, "Hardware") == 0)
402  {
403  this->Scale = V2UScaleModeHardware;
404  }
405  }
406 
407  // Epiphan hardware clipping parameters
408  XML_READ_STD_ARRAY_ATTRIBUTE_OPTIONAL(int, 2, ClipRectangleOrigin, deviceConfig);
409  XML_READ_STD_ARRAY_ATTRIBUTE_OPTIONAL(int, 2, ClipRectangleSize, deviceConfig);
410 
411  return PLUS_SUCCESS;
412 }
413 
414 //-----------------------------------------------------------------------------
415 PlusStatus vtkPlusEpiphanVideoSource::WriteConfiguration(vtkXMLDataElement* rootConfigElement)
416 {
417  XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_WRITING(imageAcquisitionConfig, rootConfigElement);
418 
419  XML_WRITE_STRING_ATTRIBUTE_REMOVE_IF_EMPTY(GrabberLocation, imageAcquisitionConfig);
420 
421  // SerialNumber is an obsolete attribute, the information is stored now in GrabberLocation
422  XML_REMOVE_ATTRIBUTE("SerialNumber", imageAcquisitionConfig);
423 
424  // Epiphan hardware clipping parameters
425  imageAcquisitionConfig->SetVectorAttribute("ClipRectangleOrigin", 2, this->GetClipRectangleOrigin().data());
426  imageAcquisitionConfig->SetVectorAttribute("ClipRectangleSize", 2, this->GetClipRectangleSize().data());
427 
428  return PLUS_SUCCESS;
429 }
430 
431 //----------------------------------------------------------------------------
432 void vtkPlusEpiphanVideoSource::SetClipRectangleSize(const std::array<int, 3>& size)
433 {
434  this->ClipRectangleSize = size;
435 }
436 
437 //----------------------------------------------------------------------------
439 {
440  return this->ClipRectangleSize;
441 }
442 
443 //----------------------------------------------------------------------------
444 void vtkPlusEpiphanVideoSource::SetClipRectangleOrigin(const std::array<int, 3>& origin)
445 {
446  this->ClipRectangleOrigin = origin;
447 }
448 
449 //----------------------------------------------------------------------------
451 {
452  return this->ClipRectangleOrigin;
453 }
454 
455 //-----------------------------------------------------------------------------
457 {
458  vtkPlusDataSource* videoSource(NULL);
459  if (this->OutputChannels.size() == 1 && this->GetFirstVideoSource(videoSource) != PLUS_SUCCESS)
460  {
461  LOG_ERROR("Epiphan is incorrectly configured. Unable to access video data source.");
462  return PLUS_FAIL;
463  }
464  else if (this->OutputChannels.size() == 2 && this->GetVideoSourceByIndex(0, videoSource) != PLUS_SUCCESS && this->GetVideoSourceByIndex(1, videoSource) != PLUS_SUCCESS)
465  {
466  LOG_ERROR("Epiphan is incorrectly configured. Unable to access video data sources.");
467  return PLUS_FAIL;
468  }
469 
470  this->CaptureImageType = videoSource->GetImageType();
471  for (int i = 0; i < this->GetNumberOfVideoSources(); ++i)
472  {
473  if (this->GetVideoSourceByIndex(i, videoSource) != PLUS_SUCCESS)
474  {
475  LOG_ERROR("Unable to retrieve the video source in the Epiphan device on channel " << (*this->OutputChannels.begin())->GetChannelId());
476  return PLUS_FAIL;
477  }
478  if (videoSource->GetImageType() != this->CaptureImageType)
479  {
480  LOG_ERROR("Conflicting image types in data sources. Please confirm matching image types.");
481  return PLUS_FAIL;
482  }
483  }
484 
485  if (this->ClipRectangleSize[0] > 0 && this->ClipRectangleSize[1] > 0)
486  {
487  this->CropRectangle = new V2URect;
488  this->CropRectangle->x = this->ClipRectangleOrigin[0];
489  this->CropRectangle->y = this->ClipRectangleOrigin[1];
490  this->CropRectangle->width = this->ClipRectangleSize[0];
491  this->CropRectangle->height = this->ClipRectangleSize[1];
492  }
493 
494  return PLUS_SUCCESS;
495 }
void FrmGrab_Close(FrmGrabber *fg)
const uint32_t * data
Definition: phidget22.h:3971
virtual void PrintSelf(ostream &os, vtkIndent indent) VTK_OVERRIDE
std::array< int, 3 > GetClipRectangleSize() const
struct v2u_rect V2URect
US_IMAGE_TYPE GetImageType()
virtual int GetNumberOfVideoSources() const
#define XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_WRITING(deviceConfig, rootConfigElement)
void FrmGrab_Stop(FrmGrabber *fg)
std::array< int, 3 > ClipRectangleOrigin
V2U_INT32 x
Definition: v2u_defs.h:87
igsioStatus PlusStatus
Definition: PlusCommon.h:40
std::array< int, 3 > ClipRectangleSize
PlusStatus GetVideoSourceByIndex(const unsigned int index, vtkPlusDataSource *&aVideoSource)
virtual PlusStatus InternalStopRecording()
virtual std::string GetDeviceId() const
PlusStatus SetInputFrameSize(unsigned int x, unsigned int y, unsigned int z)
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)
bool RequireImageOrientationInConfiguration
for i
double AcquisitionRate
V2U_INT32 vfreq
Definition: v2u_defs.h:112
PlusStatus SetAcquisitionRate(double aRate)
struct _FrmGrabber FrmGrabber
Definition: frmgrab.h:20
#define PLUS_FAIL
Definition: PlusCommon.h:43
PlusStatus SetPixelType(igsioCommon::VTKScalarPixelType pixelType)
FrmGrabber * FrmGrabLocal_Open(void)
virtual double GetAcquisitionRate() const
PlusStatus GetFirstVideoSource(vtkPlusDataSource *&anImage)
const char * FrmGrab_GetLocation(FrmGrabber *fg)
uint32_t V2U_UINT32
Definition: v2u_defs.h:64
virtual PlusStatus Disconnect()
unsigned long FrameNumber
#define PLUS_SUCCESS
Definition: PlusCommon.h:44
void FrmGrab_Deinit(void)
V2U_INT32 height
Definition: v2u_defs.h:111
std::array< int, 3 > GetClipRectangleOrigin() const
V2U_BOOL FrmGrab_Start(FrmGrabber *fg)
V2U_INT32 width
Definition: v2u_defs.h:89
#define V2U_SCALE_MODE_TO_FLAGS(_m)
Definition: v2u_defs.h:264
virtual PlusStatus StopRecording()
vtkStandardNewMacro(vtkPlusEpiphanVideoSource)
V2U_BOOL FrmGrab_DetectVideoMode(FrmGrabber *fg, V2U_VideoMode *vm)
#define V2U_ROTATION_MODE_TO_FLAGS(_m)
Definition: v2u_defs.h:279
#define XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_READING(deviceConfig, rootConfigElement)
void FrmGrabNet_Init(void)
virtual US_IMAGE_ORIENTATION GetInputImageOrientation()
PlusStatus CropRectangle(vtkIGSIOTrackedFrameList *trackedFrameList, igsioVideoFrame::FlipInfoType &flipInfo, const std::vector< int > &cropRectOrigin, const std::vector< int > &cropRectSize)
V2U_INT32 width
Definition: v2u_defs.h:110
bool StartThreadForInternalUpdates
virtual PlusStatus ReadConfiguration(vtkXMLDataElement *config)
void SetClipRectangleSize(const std::array< int, 3 > &size)
V2U_GrabFrame2 * FrmGrab_Frame(FrmGrabber *fg, V2U_UINT32 format, const V2URect *crop)
ChannelContainer OutputChannels
PlusStatus SetNumberOfScalarComponents(unsigned int numberOfScalarComponents)
#define V2U_GRABFRAME_FORMAT_RGB24
Definition: v2u_defs.h:756
#define V2U_GRABFRAME_FORMAT_Y8
Definition: v2u_defs.h:761
Class for providing video input interfaces between VTK and Epiphan frame grabber device.
void FrmGrab_Release(FrmGrabber *fg, V2U_GrabFrame2 *frame)
V2U_INT32 y
Definition: v2u_defs.h:88
virtual PlusStatus InternalStartRecording()
FrmGrabber * FrmGrab_Open(const char *location)
V2U_INT32 height
Definition: v2u_defs.h:90
void SetClipRectangleOrigin(const std::array< int, 3 > &origin)
virtual void PrintSelf(ostream &os, vtkIndent indent) VTK_OVERRIDE
virtual PlusStatus WriteConfiguration(vtkXMLDataElement *config)
Interface to a 3D positioning tool, video source, or generalized data stream.