PlusLib  2.9.0
Software library for tracked ultrasound image acquisition, calibration, and processing.
vtkPlusImageProcessorVideoSource.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 #include "igsioTrackedFrame.h"
10 #include "vtkPlusBoneEnhancer.h"
12 #include "vtkObjectFactory.h"
13 #include "vtkPlusChannel.h"
14 #include "vtkPlusDataSource.h"
15 #include "vtkIGSIOTrackedFrameList.h"
17 #include "vtkIGSIOTransformRepository.h"
18 #include "vtksys/SystemTools.hxx"
19 
20 //----------------------------------------------------------------------------
21 
23 
24 //----------------------------------------------------------------------------
26  : vtkPlusDevice()
27  , LastProcessedInputDataTimestamp(0)
28  , EnableProcessing(true)
29  , ProcessingAlgorithmAccessMutex(vtkSmartPointer<vtkIGSIORecursiveCriticalSection>::New())
30  , GracePeriodLogLevel(vtkPlusLogger::LOG_LEVEL_DEBUG)
31  , ProcessorAlgorithm(NULL)
32 {
33  this->MissingInputGracePeriodSec = 2.0;
34 
35  // Create transform repository
36  this->TransformRepository = vtkIGSIOTransformRepository::New();
37 
38  // The data capture thread will be used to regularly read the frames and process them
39  this->StartThreadForInternalUpdates = true;
40 }
41 
42 //----------------------------------------------------------------------------
44 {
45  if (this->TransformRepository)
46  {
47  this->TransformRepository->Delete();
48  this->TransformRepository = NULL;
49  }
50  if (this->ProcessorAlgorithm)
51  {
52  this->ProcessorAlgorithm->Delete();
53  this->ProcessorAlgorithm = NULL;
54  }
55 }
56 
57 //----------------------------------------------------------------------------
58 void vtkPlusImageProcessorVideoSource::PrintSelf(ostream& os, vtkIndent indent)
59 {
60  this->Superclass::PrintSelf(os, indent);
61 }
62 
63 //----------------------------------------------------------------------------
65 {
66  XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_READING(deviceConfig, rootConfigElement);
67  XML_READ_BOOL_ATTRIBUTE_OPTIONAL(EnableProcessing, deviceConfig);
68 
69  // Read transform repository configuration
70  if (this->TransformRepository->ReadConfiguration(rootConfigElement) != PLUS_SUCCESS)
71  {
72  LOG_ERROR("Failed to read transform repository configuration");
73  return PLUS_FAIL;
74  }
75 
76  // Instantiate processor(s)
77  if (this->ProcessorAlgorithm)
78  {
79  this->ProcessorAlgorithm->Delete();
80  this->ProcessorAlgorithm = NULL;
81  }
82  int numberOfNestedElements = deviceConfig->GetNumberOfNestedElements();
83  for (int nestedElemIndex = 0; nestedElemIndex < numberOfNestedElements; ++nestedElemIndex)
84  {
85  vtkXMLDataElement* processorElement = deviceConfig->GetNestedElement(nestedElemIndex);
86 
87  if ((processorElement == NULL) || (STRCASECMP(vtkPlusTrackedFrameProcessor::GetTagName(), processorElement->GetName())))
88  {
89  // not a processor element, ignore it
90  continue;
91  }
92 
93  if (this->ProcessorAlgorithm != NULL)
94  {
95  LOG_WARNING("Multiple " << processorElement->GetName() << " elements found in ImageProcessor configuration. Only the first one is used, all others are ignored");
96  break;
97  }
98 
99  // Verify type
100  const char* processorType = processorElement->GetAttribute("Type");
101  if (processorType == NULL)
102  {
103  LOG_ERROR("Type attribute of Processor element is missing");
104  return PLUS_FAIL;
105  }
106 
107  // Instantiate processor corresponding to the specified type
108  vtkSmartPointer<vtkPlusBoneEnhancer> boneEnhancer = vtkSmartPointer<vtkPlusBoneEnhancer>::New();
109  vtkSmartPointer<vtkPlusTransverseProcessEnhancer> TransverseProcessEnhancer = vtkSmartPointer<vtkPlusTransverseProcessEnhancer>::New();
110  if (!(STRCASECMP(boneEnhancer->GetProcessorTypeName(), processorType)))
111  {
112  boneEnhancer->SetTransformRepository(this->TransformRepository);
113  boneEnhancer->ReadConfiguration(processorElement);
114  this->ProcessorAlgorithm = boneEnhancer;
115  this->ProcessorAlgorithm->Register(this);
116  break; // If only one processor is allowed per ImageProcessor class, we can break out when we find it.
117  }
118  else if (!(STRCASECMP(TransverseProcessEnhancer->GetProcessorTypeName(), processorType)))
119  {
120  TransverseProcessEnhancer->SetTransformRepository(this->TransformRepository);
121  TransverseProcessEnhancer->ReadConfiguration(processorElement);
122  this->ProcessorAlgorithm = TransverseProcessEnhancer;
123  this->ProcessorAlgorithm->Register(this);
124  break;
125  }
126  else
127  {
128  LOG_ERROR("Unknown processor type: " << processorType);
129  return PLUS_FAIL;
130  }
131  }
132 
133  return PLUS_SUCCESS;
134 }
135 
136 //----------------------------------------------------------------------------
138 {
139  XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_WRITING(deviceElement, rootConfig);
140  deviceElement->SetAttribute("EnableCapturing", this->EnableProcessing ? "TRUE" : "FALSE");
141 
142  // Write processor elements
143  if (this->ProcessorAlgorithm != NULL)
144  {
145  vtkXMLDataElement* processorElement = igsioXmlUtils::GetNestedElementWithName(deviceElement, vtkPlusTrackedFrameProcessor::GetTagName());
146  if (processorElement == NULL)
147  {
148  LOG_ERROR("Cannot find " << vtkPlusTrackedFrameProcessor::GetTagName() << " element in XML tree!");
149  return PLUS_FAIL;
150  }
151  this->ProcessorAlgorithm->WriteConfiguration(processorElement);
152  }
153  else
154  {
155  // Remove processor elements
156  vtkXMLDataElement* processorElement = NULL;
157  while ((processorElement = deviceElement->FindNestedElementWithName(vtkPlusTrackedFrameProcessor::GetTagName())))
158  {
159  deviceElement->RemoveNestedElement(processorElement);
160  }
161  }
162 
163  return PLUS_SUCCESS;
164 }
165 
166 //----------------------------------------------------------------------------
168 {
169  bool lowestRateKnown = false;
170  double lowestRate = 30; // just a usual value (FPS)
171  for (ChannelContainerConstIterator it = this->InputChannels.begin(); it != this->InputChannels.end(); ++it)
172  {
173  vtkPlusChannel* anInputStream = (*it);
174  if (anInputStream->GetOwnerDevice()->GetAcquisitionRate() < lowestRate || !lowestRateKnown)
175  {
176  lowestRate = anInputStream->GetOwnerDevice()->GetAcquisitionRate();
177  lowestRateKnown = true;
178  }
179  }
180  if (lowestRateKnown)
181  {
182  this->AcquisitionRate = lowestRate;
183  }
184  else
185  {
186  LOG_WARNING("vtkPlusImageProcessorVideoSource acquisition rate is not known");
187  }
188 
190 
191  return PLUS_SUCCESS;
192 }
193 
194 //----------------------------------------------------------------------------
196 {
197  igsioLockGuard<vtkIGSIORecursiveCriticalSection> writerLock(this->ProcessingAlgorithmAccessMutex);
198  this->EnableProcessing = false;
199  return PLUS_SUCCESS;
200 }
201 
202 //----------------------------------------------------------------------------
204 {
205  if (!this->EnableProcessing)
206  {
207  // Capturing is disabled
208  return PLUS_SUCCESS;
209  }
210 
211  if (this->InputChannels.size() != 1)
212  {
213  LOG_ERROR("ImageProcessor device requires exactly 1 input stream (that contains video data). Check configuration.");
214  return PLUS_FAIL;
215  }
216 
217  if (this->HasGracePeriodExpired())
218  {
219  this->GracePeriodLogLevel = vtkPlusLogger::LOG_LEVEL_WARNING;
220  }
221 
222  // Get image to tracker transform from the tracker (only request 1 frame, the latest)
223  if (!this->InputChannels[0]->GetVideoDataAvailable())
224  {
225  LOG_DYNAMIC("Processed data is not generated, as no video data is available yet. Device ID: " << this->GetDeviceId(), this->GracePeriodLogLevel);
226  return PLUS_SUCCESS;
227  }
228  double oldestTrackingTimestamp(0);
229  if (this->InputChannels[0]->GetOldestTimestamp(oldestTrackingTimestamp) == PLUS_SUCCESS)
230  {
231  if (this->LastProcessedInputDataTimestamp < oldestTrackingTimestamp)
232  {
233  LOG_INFO("Processed image generation started. No tracking data was available between " << this->LastProcessedInputDataTimestamp << "-" << oldestTrackingTimestamp <<
234  "sec, therefore no processed images were generated during this time period.");
235  this->LastProcessedInputDataTimestamp = oldestTrackingTimestamp;
236  }
237  }
238  igsioTrackedFrame trackedFrame;
239  if (this->InputChannels[0]->GetTrackedFrame(trackedFrame) != PLUS_SUCCESS)
240  {
241  LOG_ERROR("Error while getting latest tracked frame. Last recorded timestamp: " << std::fixed << this->LastProcessedInputDataTimestamp << ". Device ID: " << this->GetDeviceId());
242  this->LastProcessedInputDataTimestamp = vtkIGSIOAccurateTimer::GetSystemTime(); // forget about the past, try to add frames that are acquired from now on
243  return PLUS_FAIL;
244  }
245 
246  LOG_TRACE("Image to be processed: timestamp=" << trackedFrame.GetTimestamp());
247 
248  if (this->OutputChannels.empty())
249  {
250  LOG_ERROR("No output channels defined");
251  return PLUS_FAIL;
252  }
253  vtkPlusChannel* outputChannel = this->OutputChannels[0];
254  double latestFrameAlreadyAddedTimestamp = 0;
255  outputChannel->GetMostRecentTimestamp(latestFrameAlreadyAddedTimestamp);
256 
257  double frameTimestamp = trackedFrame.GetTimestamp();
258  if (latestFrameAlreadyAddedTimestamp >= frameTimestamp)
259  {
260  // processed data has been already generated for this timestamp
261  return PLUS_SUCCESS;
262  }
263 
264  vtkSmartPointer<vtkIGSIOTrackedFrameList> trackingFrames = vtkSmartPointer<vtkIGSIOTrackedFrameList>::New();
265  trackingFrames->AddTrackedFrame(&trackedFrame);
266  this->ProcessorAlgorithm->SetInputFrames(trackingFrames);
267  if (this->ProcessorAlgorithm->Update() != PLUS_SUCCESS)
268  {
269  return PLUS_FAIL;
270  }
271 
272  vtkPlusDataSource* aSource(NULL);
273  if (outputChannel->GetVideoSource(aSource) != PLUS_SUCCESS)
274  {
275  LOG_ERROR("Unable to retrieve the video source in the image processor device.");
276  return PLUS_FAIL;
277  }
278 
279  PlusStatus status = PLUS_SUCCESS;
280 
281  vtkIGSIOTrackedFrameList* processedFrames = this->ProcessorAlgorithm->GetOutputFrames();
282  if (processedFrames == NULL || processedFrames->GetNumberOfTrackedFrames() < 1)
283  {
284  LOG_ERROR("Failed to retrieve processed frame");
285  return PLUS_FAIL;
286  }
287 
288  igsioTrackedFrame* processedTrackedFrame = processedFrames->GetTrackedFrame(0);
289  // Generate unique frame number (not used for filtering, so the actual increment value does not matter)
290  this->FrameNumber++;
291 
292  // If the buffer is empty, set the pixel type and frame size to the first received properties
293  if (aSource->GetNumberOfItems() == 0)
294  {
295  igsioVideoFrame* videoFrame = processedTrackedFrame->GetImageData();
296  if (videoFrame == NULL)
297  {
298  LOG_ERROR("Invalid video frame received, cannot use it to initialize the video buffer");
299  return PLUS_FAIL;
300  }
301  aSource->SetPixelType(videoFrame->GetVTKScalarPixelType());
302  unsigned int numberOfScalarComponents(1);
303  if (videoFrame->GetNumberOfScalarComponents(numberOfScalarComponents) != PLUS_SUCCESS)
304  {
305  LOG_ERROR("Unable to retrieve number of scalar components.");
306  return PLUS_FAIL;
307  }
308  aSource->SetNumberOfScalarComponents(numberOfScalarComponents);
309  aSource->SetImageType(videoFrame->GetImageType());
310  aSource->SetInputFrameSize(processedTrackedFrame->GetFrameSize());
311  }
312 
313  igsioFieldMapType customFields = processedTrackedFrame->GetCustomFields();
314  if (aSource->AddItem(processedTrackedFrame->GetImageData(), this->FrameNumber, frameTimestamp, frameTimestamp, &customFields) != PLUS_SUCCESS)
315  {
316  status = PLUS_FAIL;
317  }
318 
319  this->Modified();
320  return status;
321 }
322 
323 //-----------------------------------------------------------------------------
325 {
326  if (this->OutputChannels.empty())
327  {
328  LOG_ERROR("No output channels defined for ImageProcessor");
329  this->SetCorrectlyConfigured(false);
330  return PLUS_FAIL;
331  }
332 
333  if (this->OutputChannels.size() > 1)
334  {
335  LOG_WARNING("ImageProcessor is expecting one output channel and there are " << this->OutputChannels.size() << " channels. First output channel will be used.");
336  }
337 
338  if (this->InputChannels.empty())
339  {
340  LOG_ERROR("No input channel is set for ImageProcessor");
341  return PLUS_FAIL;
342  }
343 
344  return PLUS_SUCCESS;
345 }
346 
347 //-----------------------------------------------------------------------------
349 {
350  bool processingStartsNow = (!this->EnableProcessing && aValue);
351  this->EnableProcessing = aValue;
352 
353  if (processingStartsNow)
354  {
356  this->RecordingStartTime = vtkIGSIOAccurateTimer::GetSystemTime(); // reset the starting time for the grace period
357  }
358 }
virtual void PrintSelf(ostream &os, vtkIndent indent) VTK_OVERRIDE
virtual vtkIGSIOTrackedFrameList * GetOutputFrames()
vtkStandardNewMacro(vtkPlusImageProcessorVideoSource)
Abstract interface for tracker and video devices.
Definition: vtkPlusDevice.h:60
#define XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_WRITING(deviceConfig, rootConfigElement)
Virtual device that performs real-time image processing on the input channel.
Class to abstract away specific sequence file read/write details.
Definition: vtkPlusLogger.h:21
igsioStatus PlusStatus
Definition: PlusCommon.h:40
ChannelContainer InputChannels
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)
virtual void PrintSelf(ostream &os, vtkIndent indent) VTK_OVERRIDE
PlusStatus SetImageType(US_IMAGE_TYPE imageType)
double AcquisitionRate
#define PLUS_FAIL
Definition: PlusCommon.h:43
PlusStatus SetPixelType(igsioCommon::VTKScalarPixelType pixelType)
virtual double GetAcquisitionRate() const
double MissingInputGracePeriodSec
virtual void SetCorrectlyConfigured(bool)
unsigned long FrameNumber
#define PLUS_SUCCESS
Definition: PlusCommon.h:44
double RecordingStartTime
virtual PlusStatus GetMostRecentTimestamp(double &ts)
ChannelContainer::const_iterator ChannelContainerConstIterator
Definition: vtkPlusDevice.h:35
#define XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_READING(deviceConfig, rootConfigElement)
vtkPlusTrackedFrameProcessor * ProcessorAlgorithm
vtkSmartPointer< vtkIGSIORecursiveCriticalSection > ProcessingAlgorithmAccessMutex
bool StartThreadForInternalUpdates
bool HasGracePeriodExpired()
Contains an optional timestamped circular buffer containing the video images and a number of timestam...
virtual void SetInputFrames(vtkIGSIOTrackedFrameList *inputFrames)
ChannelContainer OutputChannels
PlusStatus SetNumberOfScalarComponents(unsigned int numberOfScalarComponents)
virtual PlusStatus WriteConfiguration(vtkXMLDataElement *)
virtual PlusStatus WriteConfiguration(vtkXMLDataElement *processingElement)
PlusStatus GetVideoSource(vtkPlusDataSource *&aVideoSource) const
virtual PlusStatus ReadConfiguration(vtkXMLDataElement *)
virtual void SetTransformRepository(vtkIGSIOTransformRepository *transformRepository)
vtkPlusDevice * GetOwnerDevice() const
vtkIGSIOTransformRepository * TransformRepository
virtual int GetNumberOfItems()
Interface to a 3D positioning tool, video source, or generalized data stream.