PlusLib  2.9.0
Software library for tracked ultrasound image acquisition, calibration, and processing.
vtkPlusVirtualVolumeReconstructor.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 "igsioTrackedFrame.h"
9 #include "vtkObjectFactory.h"
10 #include "vtkPlusChannel.h"
11 #include "vtkPlusDataSource.h"
12 #include "vtkPlusSequenceIO.h"
13 #include "vtkIGSIOTrackedFrameList.h"
14 #include "vtkIGSIOTransformRepository.h"
17 #include "vtksys/SystemTools.hxx"
18 
19 //----------------------------------------------------------------------------
20 
22 
23 static const int MAX_ALLOWED_RECONSTRUCTION_LAG_SEC = 3.0; // if the reconstruction lags more than this then it'll skip frames to catch up
24 
25 //----------------------------------------------------------------------------
27  : vtkPlusDevice()
28  , m_LastAlreadyRecordedFrameTimestamp(UNDEFINED_TIMESTAMP)
29  , m_NextFrameToBeRecordedTimestamp(0.0)
30  , m_SamplingFrameRate(8)
31  , RequestedFrameRate(0.0)
32  , m_TimeWaited(0.0)
33  , m_LastUpdateTime(0.0)
34  , TotalFramesRecorded(0)
35  , EnableReconstruction(false)
36  , VolumeReconstructorAccessMutex(vtkSmartPointer<vtkIGSIORecursiveCriticalSection>::New())
37 {
38  // The data capture thread will be used to regularly read the frames and write to disk
39  this->StartThreadForInternalUpdates = true;
40 
41  this->VolumeReconstructor = vtkSmartPointer<vtkPlusVolumeReconstructor>::New();
42  this->TransformRepository = vtkSmartPointer<vtkIGSIOTransformRepository>::New();
43 }
44 
45 //----------------------------------------------------------------------------
47 {
48 }
49 
50 //----------------------------------------------------------------------------
51 void vtkPlusVirtualVolumeReconstructor::PrintSelf(ostream& os, vtkIndent indent)
52 {
53  this->Superclass::PrintSelf(os, indent);
54 }
55 
56 //----------------------------------------------------------------------------
58 {
59  XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_READING(deviceConfig, rootConfigElement);
60 
61  XML_READ_BOOL_ATTRIBUTE_OPTIONAL(EnableReconstruction, deviceConfig);
62  XML_READ_CSTRING_ATTRIBUTE_OPTIONAL(OutputVolFilename, deviceConfig);
63  XML_READ_CSTRING_ATTRIBUTE_OPTIONAL(OutputVolDeviceName, deviceConfig);
64 
65  igsioLockGuard<vtkIGSIORecursiveCriticalSection> writerLock(this->VolumeReconstructorAccessMutex);
66  this->VolumeReconstructor->ReadConfiguration(deviceConfig);
67 
68  return PLUS_SUCCESS;
69 }
70 
71 //----------------------------------------------------------------------------
73 {
74  XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_WRITING(deviceElement, rootConfig);
75 
76  deviceElement->SetAttribute("EnableReconstruction", this->EnableReconstruction ? "TRUE" : "FALSE");
77 
78  deviceElement->SetAttribute("OutputVolFilename", this->OutputVolFilename.c_str());
79  deviceElement->SetAttribute("OutputVolDeviceName", this->OutputVolDeviceName.c_str());
80 
81  igsioLockGuard<vtkIGSIORecursiveCriticalSection> writerLock(this->VolumeReconstructorAccessMutex);
82  this->VolumeReconstructor->WriteConfiguration(deviceElement);
83 
84  return PLUS_SUCCESS;
85 }
86 
87 //----------------------------------------------------------------------------
89 {
90  bool lowestRateKnown = false;
91  double lowestRate = 30; // just a usual value (FPS)
92  for (ChannelContainerConstIterator it = this->InputChannels.begin(); it != this->InputChannels.end(); ++it)
93  {
94  vtkPlusChannel* anInputStream = (*it);
95  if (anInputStream->GetOwnerDevice()->GetAcquisitionRate() < lowestRate || !lowestRateKnown)
96  {
97  lowestRate = anInputStream->GetOwnerDevice()->GetAcquisitionRate();
98  lowestRateKnown = true;
99  }
100  }
101  if (lowestRateKnown)
102  {
103  this->AcquisitionRate = lowestRate;
104  }
105  else
106  {
107  LOG_WARNING("vtkPlusVirtualVolumeReconstructor acquisition rate is not known");
108  }
109 
110  m_LastUpdateTime = vtkIGSIOAccurateTimer::GetSystemTime();
111 
112  return PLUS_SUCCESS;
113 }
114 
115 //----------------------------------------------------------------------------
117 {
119  return PLUS_SUCCESS;
120 }
121 
122 //----------------------------------------------------------------------------
124 {
125  if (!this->EnableReconstruction)
126  {
127  // Capturing is disabled
128  return PLUS_SUCCESS;
129  }
130 
131  if (m_LastUpdateTime == 0.0)
132  {
133  m_LastUpdateTime = vtkIGSIOAccurateTimer::GetSystemTime();
134  }
136  {
137  m_NextFrameToBeRecordedTimestamp = vtkIGSIOAccurateTimer::GetSystemTime();
138  }
139  double startTimeSec = vtkIGSIOAccurateTimer::GetSystemTime();
140 
141  m_TimeWaited += startTimeSec - m_LastUpdateTime;
142 
144  {
145  // Nothing to do yet
146  return PLUS_SUCCESS;
147  }
148 
149  m_TimeWaited = 0.0;
150 
151  double maxProcessingTimeSec = GetSamplingPeriodSec() * 2.0; // put a hard limit on the max processing time to make sure the application remains responsive during reconstruction
152  double requestedFramePeriodSec = 0.1;
153  double requestedFrameRate = this->RequestedFrameRate;
154  if (requestedFrameRate <= 0)
155  {
156  // no frame rate is specified, so use the same as the input's
157  requestedFrameRate = this->AcquisitionRate;
158  }
159  if (requestedFrameRate > 0)
160  {
161  requestedFramePeriodSec = 1.0 / requestedFrameRate;
162  }
163  else
164  {
165  LOG_WARNING("RequestedFrameRate is invalid, use default: " << 1 / requestedFramePeriodSec);
166  }
167 
168  igsioLockGuard<vtkIGSIORecursiveCriticalSection> writerLock(this->VolumeReconstructorAccessMutex);
169  if (!this->EnableReconstruction)
170  {
171  // While this thread was waiting for the unlock, capturing was disabled, so cancel the update now
172  return PLUS_SUCCESS;
173  }
174 
175  if (this->OutputChannels.empty())
176  {
177  LOG_ERROR("No output channels defined");
178  return PLUS_FAIL;
179  }
180  vtkPlusChannel* outputChannel = this->OutputChannels[0];
181 
182  vtkSmartPointer<vtkIGSIOTrackedFrameList> recordedFrames = vtkSmartPointer<vtkIGSIOTrackedFrameList>::New();
183  if (outputChannel->GetTrackedFrameListSampled(m_LastAlreadyRecordedFrameTimestamp, m_NextFrameToBeRecordedTimestamp, recordedFrames, requestedFramePeriodSec, maxProcessingTimeSec) != PLUS_SUCCESS)
184  {
185  LOG_ERROR("Error while getting tracked frame list from data collector during volume reconstruction. Last recorded timestamp: " << std::fixed << m_NextFrameToBeRecordedTimestamp);
186  }
187  int nbFramesRecorded = recordedFrames->GetNumberOfTrackedFrames();
188 
189  if (this->AddFrames(recordedFrames) != PLUS_SUCCESS)
190  {
191  LOG_ERROR(this->GetDeviceId() << ": Unable to add " << nbFramesRecorded << " frames for volume reconstruction");
192  return PLUS_FAIL;
193  }
194 
195  this->TotalFramesRecorded += nbFramesRecorded;
196 
197  // Check whether the reconstruction needed more time than the sampling interval
198  double recordingTimeSec = vtkIGSIOAccurateTimer::GetSystemTime() - startTimeSec;
199  if (recordingTimeSec > GetSamplingPeriodSec())
200  {
201  LOG_WARNING("Volume reconstruction of the acquired " << nbFramesRecorded << " frames takes too long time (" << recordingTimeSec << "sec instead of the allocated " << GetSamplingPeriodSec() << "sec). This can cause slow-down of the application and non-uniform sampling. Reduce the image acquisition rate, output size, or image clip rectangle size to resolve the problem.");
202  }
203  double recordingLagSec = vtkIGSIOAccurateTimer::GetSystemTime() - m_NextFrameToBeRecordedTimestamp;
204 
205  if (recordingLagSec > MAX_ALLOWED_RECONSTRUCTION_LAG_SEC)
206  {
207  LOG_ERROR("Volume reconstruction cannot keep up with the acquisition. Skip " << recordingLagSec << " seconds of the data stream to catch up.");
208  m_NextFrameToBeRecordedTimestamp = vtkIGSIOAccurateTimer::GetSystemTime();
209  }
210 
211  m_LastUpdateTime = vtkIGSIOAccurateTimer::GetSystemTime();
212 
213  return PLUS_SUCCESS;
214 }
215 
216 
217 //-----------------------------------------------------------------------------
219 {
220  if (!this->OutputChannels.empty())
221  {
222  LOG_WARNING("vtkPlusVirtualCapture is expecting no output channel(s) and there are " << this->OutputChannels.size() << " channels. Output channel information will be dropped.");
223  this->OutputChannels.clear();
224  }
225 
226  if (this->InputChannels.empty())
227  {
228  LOG_ERROR("No input channel sent to vtkPlusVirtualCapture. Unable to save anything.");
229  return PLUS_FAIL;
230  }
231  vtkPlusChannel* inputChannel = this->InputChannels[0];
232 
233  // GetTrackedFrame reads from the OutputChannels
234  // For now, place the input stream as an output stream so its data is read
235  this->OutputChannels.push_back(inputChannel);
236  inputChannel->Register(this); // this device uses this channel, too, se we need to update the reference count to avoid double delete in the destructor
237 
238  return PLUS_SUCCESS;
239 }
240 
241 //-----------------------------------------------------------------------------
243 {
244  if (this->EnableReconstruction == aValue)
245  {
246  // Reconstruction is already started/stopped, no change needed
247  return;
248  }
249 
250  if (aValue)
251  {
252  // starting/resuming...
253  // set the recording start time to add frames from now on
254  m_LastUpdateTime = 0.0;
255  m_TimeWaited = 0.0;
256  m_LastAlreadyRecordedFrameTimestamp = UNDEFINED_TIMESTAMP;
258  this->EnableReconstruction = true;
259  }
260  else
261  {
262  // stopping/suspending...
263  this->EnableReconstruction = aValue;
264  }
265 }
266 
267 //-----------------------------------------------------------------------------
269 {
270  igsioLockGuard<vtkIGSIORecursiveCriticalSection> writerLock(this->VolumeReconstructorAccessMutex);
271  this->VolumeReconstructor->Reset();
272  return PLUS_SUCCESS;
273 }
274 
275 //-----------------------------------------------------------------------------
277 {
278  if (this->InputChannels.size() <= 0)
279  {
281  }
282  return this->InputChannels[0]->GetOwnerDevice()->GetAcquisitionRate();
283 }
284 
285 //-----------------------------------------------------------------------------
287 {
288  // Even though we fake one output channel for easy GetTrackedFrame ability,
289  // we shouldn't return actual output channel size
290  return 0;
291 }
292 
293 //-----------------------------------------------------------------------------
295 {
296  // Do not write anything out, disc capture devices don't have output channels in the config
297 }
298 
299 //-----------------------------------------------------------------------------
300 PlusStatus vtkPlusVirtualVolumeReconstructor::GetReconstructedVolumeFromFile(const std::string& inputSeqFilename, vtkImageData* reconstructedVolume, std::string& errorMessage)
301 {
302  errorMessage.clear();
303 
304  // Read image sequence
305  if (inputSeqFilename.empty())
306  {
307  errorMessage = "Volume reconstruction failed, InputSeqFilename has not been defined";
308  LOG_INFO(errorMessage);
309  return PLUS_FAIL;
310  }
311  vtkSmartPointer<vtkIGSIOTrackedFrameList> trackedFrameList = vtkSmartPointer<vtkIGSIOTrackedFrameList>::New();
312  std::string inputImageSeqFileFullPath = vtkPlusConfig::GetInstance()->GetOutputPath(inputSeqFilename);
313  if (vtkPlusSequenceIO::Read(inputImageSeqFileFullPath, trackedFrameList) != PLUS_SUCCESS)
314  {
315  errorMessage = "Volume reconstruction failed, unable to open input file specified in InputSeqFilename: " + inputImageSeqFileFullPath;
316  LOG_INFO(errorMessage);
317  return PLUS_FAIL;
318  }
319 
320  igsioLockGuard<vtkIGSIORecursiveCriticalSection> writerLock(this->VolumeReconstructorAccessMutex);
321 
322  // Determine volume extents automatically
323  std::string errorDetail;
324  if (this->VolumeReconstructor->SetOutputExtentFromFrameList(trackedFrameList, this->TransformRepository, errorDetail) != PLUS_SUCCESS)
325  {
326  errorMessage = "vtkPlusReconstructVolumeCommand::Execute: failed, could not set up output volume - " + errorDetail;
327  LOG_INFO(errorMessage);
328  return PLUS_FAIL;
329  }
330  // Paste slices
331  if (AddFrames(trackedFrameList) != PLUS_SUCCESS)
332  {
333  errorMessage = "vtkPlusReconstructVolumeCommand::Execute: failed, add frames failed";
334  LOG_INFO(errorMessage);
335  return PLUS_FAIL;
336  }
337  // Get output
338  if (GetReconstructedVolume(reconstructedVolume, errorMessage) != PLUS_SUCCESS)
339  {
340  LOG_INFO(errorMessage);
341  return PLUS_FAIL;
342  }
343  return PLUS_SUCCESS;
344 }
345 
346 //-----------------------------------------------------------------------------
347 PlusStatus vtkPlusVirtualVolumeReconstructor::GetReconstructedVolume(vtkImageData* reconstructedVolume, std::string& outErrorMessage, bool applyHoleFilling/*=true*/)
348 {
349  outErrorMessage.clear();
350  igsioLockGuard<vtkIGSIORecursiveCriticalSection> writerLock(this->VolumeReconstructorAccessMutex);
351  bool oldFillHoles = this->VolumeReconstructor->GetFillHoles();
352  if (!applyHoleFilling)
353  {
354  this->VolumeReconstructor->SetFillHoles(false);
355  }
356  PlusStatus status = this->VolumeReconstructor->ExtractGrayLevels(reconstructedVolume);
357  if (!applyHoleFilling)
358  {
359  this->VolumeReconstructor->SetFillHoles(oldFillHoles);
360  }
361 
362  if (status != PLUS_SUCCESS)
363  {
364  outErrorMessage = "Extracting gray levels failed";
365  LOG_ERROR(outErrorMessage);
366  return PLUS_FAIL;
367  }
368  return PLUS_SUCCESS;
369 }
370 
371 //----------------------------------------------------------------------------
372 PlusStatus vtkPlusVirtualVolumeReconstructor::AddFrames(vtkIGSIOTrackedFrameList* trackedFrameList)
373 {
374  igsioLockGuard<vtkIGSIORecursiveCriticalSection> writerLock(this->VolumeReconstructorAccessMutex);
375 
376  PlusStatus status = PLUS_SUCCESS;
377  const int numberOfFrames = trackedFrameList->GetNumberOfTrackedFrames();
378  int numberOfFramesAddedToVolume = 0;
379  for (int frameIndex = 0; frameIndex < numberOfFrames; frameIndex += this->VolumeReconstructor->GetSkipInterval())
380  {
381  LOG_TRACE("Adding frame to volume reconstructor: " << frameIndex);
382  igsioTrackedFrame* frame = trackedFrameList->GetTrackedFrame(frameIndex);
383  if (this->TransformRepository->SetTransforms(*frame) != PLUS_SUCCESS)
384  {
385  LOG_ERROR("Failed to update transform repository with frame #" << frameIndex);
386  status = PLUS_FAIL;
387  continue;
388  }
389  // Insert slice for reconstruction
390  bool insertedIntoVolume = false;
391  bool isFirst = frameIndex == 0;
392  bool isLast = frameIndex + this->VolumeReconstructor->GetSkipInterval() >= numberOfFrames;
393  if (this->VolumeReconstructor->AddTrackedFrame(frame, this->TransformRepository, isFirst, isLast, &insertedIntoVolume) != PLUS_SUCCESS)
394  {
395  LOG_ERROR("Failed to add tracked frame to volume with frame #" << frameIndex);
396  status = PLUS_FAIL;
397  continue;
398  }
399  if (insertedIntoVolume)
400  {
401  numberOfFramesAddedToVolume++;
402  }
403  }
404  trackedFrameList->Clear();
405 
406  LOG_DEBUG("Number of frames added to the volume: " << numberOfFramesAddedToVolume << " out of " << numberOfFrames);
407 
408  return status;
409 }
410 
411 //-----------------------------------------------------------------------------
413 {
414  double samplingPeriodSec = 0.1;
415  if (m_SamplingFrameRate > 0)
416  {
417  samplingPeriodSec = 1.0 / m_SamplingFrameRate;
418  }
419  else
420  {
421  LOG_WARNING("m_SamplingFrameRate value is invalid " << m_SamplingFrameRate << ". Use default sampling period of " << samplingPeriodSec << " sec");
422  }
423  return samplingPeriodSec;
424 }
425 
426 //----------------------------------------------------------------------------
427 PlusStatus vtkPlusVirtualVolumeReconstructor::UpdateTransformRepository(vtkIGSIOTransformRepository* sharedTransformRepository)
428 {
429  if (sharedTransformRepository == NULL)
430  {
431  LOG_ERROR("vtkPlusVirtualVolumeReconstructor::UpdateTransformRepository: shared transform repository is invalid");
432  return PLUS_FAIL;
433  }
434  // Create a copy of the transform repository to allow using it for volume reconstruction while being also used in other threads
435  // TODO: protect transform repository with a mutex
436  this->TransformRepository->DeepCopy(sharedTransformRepository, false);
437  return PLUS_SUCCESS;
438 }
439 
440 //----------------------------------------------------------------------------
442 {
443  this->VolumeReconstructor->SetOutputOrigin(origin);
444 }
445 
446 //----------------------------------------------------------------------------
448 {
449  this->VolumeReconstructor->SetOutputSpacing(spacing);
450 }
451 
452 //----------------------------------------------------------------------------
454 {
455  this->VolumeReconstructor->SetOutputExtent(extent);
456 }
vtkSmartPointer< vtkPlusVolumeReconstructor > VolumeReconstructor
static const int VIRTUAL_DEVICE_FRAME_RATE
const char char * errorDetail
Definition: phidget22.h:1271
virtual void PrintSelf(ostream &os, vtkIndent indent) VTK_OVERRIDE
std::string GetOutputPath(const std::string &subPath)
Abstract interface for tracker and video devices.
Definition: vtkPlusDevice.h:60
PlusStatus AddFrames(vtkIGSIOTrackedFrameList *trackedFrameList)
#define XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_WRITING(deviceConfig, rootConfigElement)
igsioStatus PlusStatus
Definition: PlusCommon.h:40
ChannelContainer InputChannels
virtual std::string GetDeviceId() const
double AcquisitionRate
#define PLUS_FAIL
Definition: PlusCommon.h:43
static vtkPlusConfig * GetInstance()
virtual double GetAcquisitionRate() const
virtual PlusStatus GetReconstructedVolumeFromFile(const std::string &inputSeqFilename, vtkImageData *reconstructedVolume, std::string &errorMessage)
#define PLUS_SUCCESS
Definition: PlusCommon.h:44
vtkStandardNewMacro(vtkPlusVirtualVolumeReconstructor)
static const int MAX_ALLOWED_RECONSTRUCTION_LAG_SEC
ChannelContainer::const_iterator ChannelContainerConstIterator
Definition: vtkPlusDevice.h:35
virtual PlusStatus WriteConfiguration(vtkXMLDataElement *)
static igsioStatus Read(const std::string &filename, vtkIGSIOTrackedFrameList *frameList)
#define XML_FIND_DEVICE_ELEMENT_REQUIRED_FOR_READING(deviceConfig, rootConfigElement)
virtual PlusStatus GetTrackedFrameListSampled(double &aTimestampOfLastFrameAlreadyGot, double &aTimestampOfNextFrameToBeAdded, vtkIGSIOTrackedFrameList *aTrackedFrameList, double aSamplingPeriodSec, double maxTimeLimitSec=-1)
bool StartThreadForInternalUpdates
void PrintSelf(ostream &os, vtkIndent indent)
vtkSmartPointer< vtkIGSIORecursiveCriticalSection > VolumeReconstructorAccessMutex
Contains an optional timestamped circular buffer containing the video images and a number of timestam...
PlusStatus GetReconstructedVolume(vtkImageData *reconstructedVolume, std::string &outErrorMessage, bool applyHoleFilling=true)
ChannelContainer OutputChannels
virtual void InternalWriteOutputChannels(vtkXMLDataElement *rootXMLElement)
vtkSmartPointer< vtkIGSIOTransformRepository > TransformRepository
vtkPlusDevice * GetOwnerDevice() const
PlusStatus UpdateTransformRepository(vtkIGSIOTransformRepository *sharedTransformRepository)
virtual PlusStatus ReadConfiguration(vtkXMLDataElement *)