PlusLib  2.9.0
Software library for tracked ultrasound image acquisition, calibration, and processing.
vtkPlusStartStopRecordingCommand.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 "vtkPlusChannel.h"
11 #include "vtkPlusDataCollector.h"
13 #include "vtkPlusVirtualCapture.h"
14 #include "vtkPlusDeviceFactory.h"
15 
16 //----------------------------------------------------------------------------
17 
19 
20 //----------------------------------------------------------------------------
21 
22 namespace
23 {
24  static const std::string START_CMD = "StartRecording";
25  static const std::string SUSPEND_CMD = "SuspendRecording";
26  static const std::string RESUME_CMD = "ResumeRecording";
27  static const std::string STOP_CMD = "StopRecording";
28  static const std::string HEADER_CMD = "AddCustomHeaders";
29 }
30 
31 //----------------------------------------------------------------------------
33  : EnableCompression(false)
34  , CodecFourCC("")
35 {
36 }
37 
38 //----------------------------------------------------------------------------
40 {
41 }
42 
43 //----------------------------------------------------------------------------
49 
50 //----------------------------------------------------------------------------
51 void vtkPlusStartStopRecordingCommand::GetCommandNames(std::list<std::string>& cmdNames)
52 {
53  cmdNames.clear();
54  cmdNames.push_back(START_CMD);
55  cmdNames.push_back(SUSPEND_CMD);
56  cmdNames.push_back(RESUME_CMD);
57  cmdNames.push_back(STOP_CMD);
58  cmdNames.push_back(HEADER_CMD);
59 }
60 
61 //----------------------------------------------------------------------------
62 std::string vtkPlusStartStopRecordingCommand::GetDescription(const std::string& commandName)
63 {
64  std::string desc;
65  if (commandName.empty() || igsioCommon::IsEqualInsensitive(commandName, START_CMD))
66  {
67  desc += START_CMD;
68  desc += ": Start collecting data into file with a VirtualCapture device.";
69  desc += " Attributes: OutputFilename: name of the output file (optional if base file name is specified in config file).";
70  desc += " CaptureDeviceId: ID of the capture device, if not specified then the first VirtualCapture device will be started (optional)";
71  desc += " PauseOnStart: Whether to enable capture when opening file. (optional)";
72  }
73  if (commandName.empty() || igsioCommon::IsEqualInsensitive(commandName, SUSPEND_CMD))
74  {
75  desc += SUSPEND_CMD;
76  desc += ": Suspend data collection. Attributes: CaptureDeviceId: (optional)";
77  }
78  if (commandName.empty() || igsioCommon::IsEqualInsensitive(commandName, RESUME_CMD))
79  {
80  desc += RESUME_CMD;
81  desc += ": Resume suspended data collection. Attributes: CaptureDeviceId (optional)";
82  }
83  if (commandName.empty() || igsioCommon::IsEqualInsensitive(commandName, STOP_CMD))
84  {
85  desc += STOP_CMD;
86  desc += ": Stop collecting data into file with a VirtualCapture device. Attributes: OutputFilename: name of the output file (optional if base file name is specified in config file). CaptureDeviceId (optional)";
87  }
88  if (commandName.empty() || igsioCommon::IsEqualInsensitive(commandName, HEADER_CMD))
89  {
90  desc += HEADER_CMD;
91  desc += ": Add custom headers to the opened capture instance. Attributes: CaptureDeviceId (optional)";
92  }
93  return desc;
94 }
95 
96 //----------------------------------------------------------------------------
97 void vtkPlusStartStopRecordingCommand::PrintSelf(ostream& os, vtkIndent indent)
98 {
99  this->Superclass::PrintSelf(os, indent);
100 }
101 
102 //----------------------------------------------------------------------------
104 {
105  return New();
106 }
107 
108 //----------------------------------------------------------------------------
110 {
112  {
113  return PLUS_FAIL;
114  }
115 
116  // Common parameters
117  XML_READ_CSTRING_ATTRIBUTE_OPTIONAL(CaptureDeviceId, aConfig);
118  XML_READ_CSTRING_ATTRIBUTE_OPTIONAL(ChannelId, aConfig);
119 
120  if (CaptureDeviceId.empty() && ChannelId.empty())
121  {
122  LOG_ERROR("Neither CaptureDeviceId nor ChannelId specified. Aborting.");
123  return PLUS_FAIL;
124  }
125 
126  // Start/Stop Common parameters
127  XML_READ_CSTRING_ATTRIBUTE_OPTIONAL(OutputFilename, aConfig);
128 
129  // Start-only parameters
130  if (this->GetName() == START_CMD)
131  {
132  XML_READ_BOOL_ATTRIBUTE_OPTIONAL(EnableCompression, aConfig);
133  XML_READ_BOOL_ATTRIBUTE_OPTIONAL(PauseOnStart, aConfig);
134  XML_READ_STRING_ATTRIBUTE_OPTIONAL(CodecFourCC, aConfig);
135  }
136  else if (this->GetName() == HEADER_CMD)
137  {
138  this->RequestedCustomHeaders.clear();
139 
140  // Parse nested elements and store requested parameter changes
141  for (int elemIndex = 0; elemIndex < aConfig->GetNumberOfNestedElements(); ++elemIndex)
142  {
143  vtkXMLDataElement* currentElem = aConfig->GetNestedElement(elemIndex);
144  if (igsioCommon::IsEqualInsensitive(currentElem->GetName(), "Header"))
145  {
146  const char* headerName = currentElem->GetAttribute("Name");
147  const char* headerValue = currentElem->GetAttribute("Value");
148  if (!headerName || !headerValue)
149  {
150  LOG_ERROR("Unable to find required Name or Value attribute in " << (currentElem->GetName() ? currentElem->GetName() : "(undefined)") << " element is AddCustomHeaders command");
151  continue;
152  }
153 
154  this->RequestedCustomHeaders[headerName] = headerValue;
155  }
156  }
157  }
158 
159  return PLUS_SUCCESS;
160 }
161 
162 //----------------------------------------------------------------------------
164 {
166  {
167  return PLUS_FAIL;
168  }
169 
170  XML_WRITE_STRING_ATTRIBUTE_REMOVE_IF_EMPTY(CaptureDeviceId, aConfig);
171  XML_WRITE_STRING_ATTRIBUTE_REMOVE_IF_EMPTY(ChannelId, aConfig);
172  XML_WRITE_STRING_ATTRIBUTE_REMOVE_IF_EMPTY(OutputFilename, aConfig);
173 
174  if (this->GetName() == START_CMD)
175  {
176  XML_WRITE_BOOL_ATTRIBUTE(EnableCompression, aConfig);
177  }
178 
179  return PLUS_SUCCESS;
180 }
181 
182 //----------------------------------------------------------------------------
184 {
185  vtkPlusDataCollector* dataCollector = GetDataCollector();
186  if (dataCollector == NULL)
187  {
188  LOG_ERROR("Data collector is invalid");
189  return NULL;
190  }
191  vtkPlusVirtualCapture* captureDevice = NULL;
192  if (!captureDeviceId.empty())
193  {
194  // Capture device ID is specified
195  vtkPlusDevice* device = NULL;
196  if (dataCollector->GetDevice(device, captureDeviceId) != PLUS_SUCCESS)
197  {
198  LOG_ERROR("No VirtualCapture has been found by the name " << captureDeviceId);
199  return NULL;
200  }
201  // device found
202  captureDevice = vtkPlusVirtualCapture::SafeDownCast(device);
203  if (captureDevice == NULL)
204  {
205  // wrong type
206  LOG_ERROR("The specified device " << captureDeviceId << " is not a VirtualCapture device.");
207  return NULL;
208  }
209  }
210  else
211  {
212  // No capture device id is specified, auto-detect the first one and use that
213  for (DeviceCollectionConstIterator it = dataCollector->GetDeviceConstIteratorBegin(); it != dataCollector->GetDeviceConstIteratorEnd(); ++it)
214  {
215  captureDevice = vtkPlusVirtualCapture::SafeDownCast(*it);
216  if (captureDevice != NULL)
217  {
218  // found a recording device
219  break;
220  }
221  }
222  if (captureDevice == NULL)
223  {
224  LOG_ERROR("No VirtualCapture has been found");
225  return NULL;
226  }
227  }
228  return captureDevice;
229 }
230 
231 //----------------------------------------------------------------------------
233 {
234  vtkPlusDataCollector* dataCollector = GetDataCollector();
235  if (dataCollector == NULL)
236  {
237  LOG_ERROR("Data collector is invalid");
238  return nullptr;
239  }
240 
241  if (channelId.empty())
242  {
243  return nullptr;
244  }
245 
246  vtkPlusChannel* channel(nullptr);
247  if (dataCollector->GetChannel(channel, channelId) != PLUS_SUCCESS)
248  {
249  LOG_ERROR("Unable to locate channel. Aborting.");
250  return nullptr;
251  }
252 
253  vtkPlusVirtualCapture* foundDevice(nullptr);
254  for (auto iter = dataCollector->GetDeviceConstIteratorBegin(); iter != dataCollector->GetDeviceConstIteratorEnd(); ++iter)
255  {
256  if (dynamic_cast<vtkPlusVirtualCapture*>(*iter) != nullptr)
257  {
258  std::vector<vtkPlusDevice*> devices;
259  (*iter)->GetInputDevices(devices);
260  for (auto it = devices.begin(); it != devices.end(); ++it)
261  {
262  vtkPlusChannel* aChannel;
263  if ((*it)->GetOutputChannelByName(aChannel, channelId) == PLUS_SUCCESS)
264  {
265  foundDevice = dynamic_cast<vtkPlusVirtualCapture*>(*iter);
266  }
267  }
268  }
269  }
270 
271  if (foundDevice != nullptr)
272  {
273  return foundDevice;
274  }
275 
276  // Capture device for this input channel does not exist
277  vtkPlusDevice* device = channel->GetOwnerDevice();
278  assert(device != nullptr);
279 
280  vtkPlusDevice* newDevice(nullptr);
281  vtkSmartPointer<vtkPlusDeviceFactory> factory = vtkSmartPointer<vtkPlusDeviceFactory>::New();
282  if (factory->CreateInstance("VirtualCapture", newDevice, channelId + "_capture") != PLUS_SUCCESS)
283  {
284  LOG_ERROR("Unable to create capture device. Aborting.");
285  return nullptr;
286  }
287  auto capDevice = dynamic_cast<vtkPlusVirtualCapture*>(newDevice);
288  newDevice->SetDataCollector(dataCollector);
289  newDevice->AddInputChannel(channel);
290  capDevice->SetEnableFileCompression(this->EnableCompression);
291  if (!this->CodecFourCC.empty())
292  {
293  capDevice->SetEncodingFourCC(this->CodecFourCC);
294  }
295  capDevice->SetBaseFilename(channelId + "_capture.nrrd");
296 
297  return capDevice;
298 }
299 
300 //----------------------------------------------------------------------------
302 {
303  LOG_INFO("vtkPlusStartStopRecordingCommand::Execute:");
304 
305  if (this->Name.empty())
306  {
307  this->QueueCommandResponse(PLUS_FAIL, "Command failed. See error message.", "No command name specified");
308  return PLUS_FAIL;
309  }
310 
311  vtkPlusVirtualCapture* captureDevice(NULL);
312  if (!this->CaptureDeviceId.empty())
313  {
314  captureDevice = GetCaptureDevice(this->CaptureDeviceId);
315  }
316  else if (!this->ChannelId.empty())
317  {
318  captureDevice = GetOrCreateCaptureDevice(this->ChannelId);
319  }
320 
321  if (captureDevice == NULL)
322  {
323  this->QueueCommandResponse(PLUS_FAIL, "Command failed. See error message.", std::string("VirtualCapture has not been found (")
324  + (!this->CaptureDeviceId.empty() ? this->CaptureDeviceId : !this->ChannelId.empty() ? this->ChannelId : "auto-detect") + "), " + this->Name);
325  return PLUS_FAIL;
326  }
327 
328  std::string responseMessageBase = std::string("VirtualCapture (") + captureDevice->GetDeviceId() + ") " + this->Name + " ";
329  LOG_INFO("vtkPlusStartStopRecordingCommand::Execute: " << this->Name);
330 
331  if (igsioCommon::IsEqualInsensitive(this->Name, START_CMD))
332  {
333  if (captureDevice->GetEnableCapturing())
334  {
335  this->QueueCommandResponse(PLUS_FAIL, "Command failed. See error message.", responseMessageBase + std::string("Recording to file is already in progress."));
336  return PLUS_FAIL;
337  }
338  std::string outputFilename = captureDevice->GetBaseFilename();
339  if (!this->OutputFilename.empty())
340  {
341  outputFilename = this->OutputFilename;
342  }
343  // EnableFileCompression must be set before OpenFile is called so that it can be disabled for file types that
344  // don't support compression
345  captureDevice->SetEnableFileCompression(this->GetEnableCompression());
346  if (captureDevice->OpenFile(outputFilename.c_str()) != PLUS_SUCCESS)
347  {
348  this->QueueCommandResponse(PLUS_FAIL, "Command failed. See error message.", responseMessageBase + std::string("Failed to open file ") + (!this->OutputFilename.empty() ? this->OutputFilename : "(undefined)") + std::string("."));
349  return PLUS_FAIL;
350  }
351  captureDevice->SetEnableCapturing(!this->GetPauseOnStart());
352  this->QueueCommandResponse(PLUS_SUCCESS, responseMessageBase + "successful.");
353  return PLUS_SUCCESS;
354  }
355  else if (igsioCommon::IsEqualInsensitive(this->Name, SUSPEND_CMD))
356  {
357  if (!captureDevice->GetEnableCapturing())
358  {
359  this->QueueCommandResponse(PLUS_FAIL, "Command failed. See error message.", responseMessageBase + std::string("Suspend recording failed: recording to file is not in progress."));
360  return PLUS_FAIL;
361  }
362  captureDevice->SetEnableCapturing(false);
363  this->QueueCommandResponse(PLUS_SUCCESS, responseMessageBase + "successful.");
364  return PLUS_SUCCESS;
365  }
366  else if (igsioCommon::IsEqualInsensitive(this->Name, RESUME_CMD))
367  {
368  if (captureDevice->GetEnableCapturing())
369  {
370  this->QueueCommandResponse(PLUS_FAIL, "Command failed. See error message.", responseMessageBase + std::string("Resume recording failed: recording to file is already in progress."));
371  return PLUS_FAIL;
372  }
373  captureDevice->SetEnableCapturing(true);
374  this->QueueCommandResponse(PLUS_SUCCESS, responseMessageBase + "successful.");
375  return PLUS_SUCCESS;
376  }
377  else if (igsioCommon::IsEqualInsensitive(this->Name, STOP_CMD))
378  {
379  // Don't bother closing the file if there aren't any frames
380  if (captureDevice->GetTotalFramesRecorded() == 0)
381  {
382  this->QueueCommandResponse(PLUS_FAIL, "Command failed. See error message.", responseMessageBase + std::string("No frames to record."));
383  return PLUS_FAIL;
384  }
385 
386  captureDevice->SetEnableCapturing(false);
387 
388  // Once the file is closed, the filename is no longer valid, so we need to get the filename now
389  std::string resultFilename = captureDevice->GetOutputFileName();
390  // If we override the output filename then that will be the result filename
391  if (!this->OutputFilename.empty())
392  {
393  resultFilename = this->OutputFilename;
394  }
395 
396  long numberOfFramesRecorded = captureDevice->GetTotalFramesRecorded();
397  std::string actualOutputFilename;
398  if (captureDevice->CloseFile(resultFilename.c_str(), &actualOutputFilename) != PLUS_SUCCESS)
399  {
400  this->QueueCommandResponse(PLUS_FAIL, "Command failed. See error message.", responseMessageBase + "Failed to finalize file: " + resultFilename);
401  return PLUS_FAIL;
402  }
403  std::ostringstream ss;
404  ss << "Recording " << numberOfFramesRecorded << " frames successful to file " << actualOutputFilename;
405  this->QueueCommandResponse(PLUS_SUCCESS, responseMessageBase + ss.str());
406  return PLUS_SUCCESS;
407  }
408  else if (igsioCommon::IsEqualInsensitive(this->Name, HEADER_CMD))
409  {
410  std::map<std::string, std::string>::iterator headerIt;
411  for (headerIt = this->RequestedCustomHeaders.begin(); headerIt != this->RequestedCustomHeaders.end(); ++headerIt)
412  {
413  std::string headerName = headerIt->first;
414  std::string headerValue = headerIt->second;
415 
416  if (captureDevice->SetCustomHeaderField(headerName, headerValue) != PLUS_SUCCESS)
417  {
418  this->QueueCommandResponse(PLUS_FAIL, "Command failed. See error message.", responseMessageBase + "Failed to write header: " + headerName + ", " + headerValue);
419  return PLUS_FAIL;
420  }
421  }
422  this->QueueCommandResponse(PLUS_SUCCESS, responseMessageBase + std::string(" All headers written."));
423  return PLUS_SUCCESS;
424  }
425 
426  this->QueueCommandResponse(PLUS_FAIL, "Command failed. See error message.", responseMessageBase + "Unknown command: " + this->Name);
427  return PLUS_FAIL;
428 }
virtual PlusStatus ReadConfiguration(vtkXMLDataElement *aConfig)
PlusStatus AddInputChannel(vtkPlusChannel *aChannel)
int * channel
Definition: phidget22.h:1303
virtual void PrintSelf(ostream &os, vtkIndent indent)
Abstract interface for tracker and video devices.
Definition: vtkPlusDevice.h:60
std::vector< vtkPlusDevice * >::const_iterator DeviceCollectionConstIterator
Definition: vtkPlusDevice.h:48
virtual PlusStatus WriteConfiguration(vtkXMLDataElement *aConfig)
PlusStatus GetDevice(vtkPlusDevice *&aDevice, const std::string &aDeviceId) const
std::string Name
This is an abstract superclass for commands in the OpenIGTLink network interface for Plus.
igsioStatus PlusStatus
Definition: PlusCommon.h:40
virtual PlusStatus ReadConfiguration(vtkXMLDataElement *aConfig)
virtual std::string GetDeviceId() const
virtual std::string GetDescription(const std::string &commandName)
virtual long int GetTotalFramesRecorded()
static vtkPlusStartStopRecordingCommand * New()
#define PLUS_FAIL
Definition: PlusCommon.h:43
DeviceCollectionConstIterator GetDeviceConstIteratorBegin() const
virtual PlusStatus WriteConfiguration(vtkXMLDataElement *aConfig)
void SetEnableFileCompression(bool aFileCompression)
iter
Definition: algo3.m:29
DeviceCollectionConstIterator GetDeviceConstIteratorEnd() const
Manages devices that record image or positional data.
#define PLUS_SUCCESS
Definition: PlusCommon.h:44
virtual bool GetEnableCapturing()
virtual PlusStatus CloseFile(const char *aFilename=NULL, std::string *resultFilename=NULL)
virtual std::string GetBaseFilename()
virtual void PrintSelf(ostream &os, vtkIndent indent)
void QueueCommandResponse(PlusStatus status, const std::string &message, const std::string &error="", const igtl::MessageBase::MetaDataMap *metaData=nullptr)
virtual vtkPlusDataCollector * GetDataCollector()
void SetEnableCapturing(bool aValue)
virtual void SetDataCollector(vtkPlusDataCollector *_arg)
vtkStandardNewMacro(vtkPlusStartStopRecordingCommand)
Contains an optional timestamped circular buffer containing the video images and a number of timestam...
virtual PlusStatus OpenFile(const char *aFilename=NULL)
This command starts and stops capturing with a vtkPlusVirtualCapture capture on the server side.
virtual std::string GetOutputFileName()
vtkPlusVirtualCapture * GetOrCreateCaptureDevice(const std::string &channelId)
PlusStatus GetChannel(vtkPlusChannel *&aChannel, const std::string &aChannelId) const
static vtkPlusVirtualCapture * SafeDownCast(vtkObject *o)
virtual void GetCommandNames(std::list< std::string > &cmdNames)
vtkPlusVirtualCapture * GetCaptureDevice(const std::string &captureDeviceId)
virtual PlusStatus SetCustomHeaderField(const std::string &fieldName, const std::string &fieldValue)