PlusLib  2.9.0
Software library for tracked ultrasound image acquisition, calibration, and processing.
vtkLineSegmentationAlgoTest.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 
13 #include "PlusConfigure.h"
14 #include "igsioMath.h"
16 #include "vtkMath.h"
17 #include "vtkIGSIOSequenceIO.h"
18 #include "vtkIGSIOTrackedFrameList.h"
19 #include "vtkXMLDataElement.h"
20 #include "vtkXMLUtilities.h"
21 #include "vtksys/CommandLineArguments.hxx"
22 #include "vtksys/SystemTools.hxx"
23 
24 const double MAX_ORIGIN_DISTANCE_PIXEL = 10;
26 
27 //----------------------------------------------------------------------------
28 void WriteLineSegmentationResultsToFile( const std::string& resultSaveFilename, const std::vector<vtkPlusLineSegmentationAlgo::LineParameters>& lineParameters )
29 {
30  std::ofstream outFile;
31  outFile.open( resultSaveFilename.c_str() );
32  outFile << "<LineSegmentationResults>" << std::endl;
33  for ( unsigned int frameIndex = 0; frameIndex < lineParameters.size(); ++frameIndex )
34  {
35  outFile << " <Frame Index=\"" << frameIndex << "\" SegmentationStatus=\"";
36  if ( lineParameters[frameIndex].lineDetected )
37  {
38  outFile << "OK\" "
39  << std::fixed << std::setprecision( 8 )
40  << "LineOriginPointPx=\"" << lineParameters[frameIndex].lineOriginPoint_Image[0] << " " << lineParameters[frameIndex].lineOriginPoint_Image[1] << "\" "
41  << "LineDirectionVectorPx=\"" << lineParameters[frameIndex].lineDirectionVector_Image[0] << " " << lineParameters[frameIndex].lineDirectionVector_Image[1] << "\" ";
42  }
43  else
44  {
45  outFile << "Failed\"";
46  }
47  outFile << " />" << std::endl;
48  }
49  outFile << "</LineSegmentationResults>" << std::endl;
50  outFile.close();
51 }
52 
53 //----------------------------------------------------------------------------
54 PlusStatus ReadLineSegmentationResultsFromFile( const std::string& resultSaveFilename, std::vector<vtkPlusLineSegmentationAlgo::LineParameters>& lineParameters )
55 {
56  lineParameters.clear();
57  if ( resultSaveFilename.empty() )
58  {
59  LOG_ERROR( "Cannot read line segmentation results, filename is empty" );
60  return PLUS_FAIL;
61  }
62  vtkSmartPointer<vtkXMLDataElement> resultsElem = vtkSmartPointer<vtkXMLDataElement>::Take( vtkXMLUtilities::ReadElementFromFile( resultSaveFilename.c_str() ) );
63  if ( resultsElem == NULL )
64  {
65  LOG_ERROR( "Failed to read baseline file: " << resultSaveFilename );
66  return PLUS_FAIL;
67  }
68  if ( resultsElem->GetName() == NULL || STRCASECMP( resultsElem->GetName(), "LineSegmentationResults" ) != 0 )
69  {
70  LOG_ERROR( "Unable to find LineSegmentationResults XML data element in baseline: " << resultSaveFilename );
71  return PLUS_FAIL;
72  }
73  for ( int childElemIndex = 0; childElemIndex < resultsElem->GetNumberOfNestedElements(); ++childElemIndex )
74  {
75  vtkXMLDataElement* frameElem = resultsElem->GetNestedElement( childElemIndex );
76  if ( frameElem == NULL || frameElem->GetName() == NULL || STRCASECMP( frameElem->GetName(), "Frame" ) != 0 )
77  {
78  LOG_ERROR( "Invalid child element in LineSegmentationResults: #" << childElemIndex );
79  return PLUS_FAIL;
80  }
81  int frameIndex = 0;
82  if ( !frameElem->GetScalarAttribute( "Index", frameIndex ) )
83  {
84  LOG_ERROR( "Unable to find Index element in LineSegmentationResults: child index " << childElemIndex );
85  return PLUS_FAIL;
86  }
87  unsigned int frameIndexUint = static_cast<unsigned int>( frameIndex );
88 
90 
91  const char* baselineSegmentationStatusString = frameElem->GetAttribute( "SegmentationStatus" );
92  if ( baselineSegmentationStatusString == NULL )
93  {
94  LOG_ERROR( "SegmentationStatus is not available in the baseline for frame " << frameIndexUint );
95  return PLUS_FAIL;
96  }
97  currentLineParams.lineDetected = !STRCASECMP( baselineSegmentationStatusString, "OK" );
98 
99  if ( currentLineParams.lineDetected )
100  {
101  if ( !frameElem->GetVectorAttribute( "LineOriginPointPx", 2, currentLineParams.lineOriginPoint_Image ) )
102  {
103  LOG_ERROR( "Unable to find LineOriginPointPx element in LineSegmentationResults for frame " << frameIndexUint );
104  return PLUS_FAIL;
105  }
106  if ( !frameElem->GetVectorAttribute( "LineDirectionVectorPx", 2, currentLineParams.lineDirectionVector_Image ) )
107  {
108  LOG_ERROR( "Unable to find LineDirectionVectorPx element in LineSegmentationResults for frame " << frameIndexUint );
109  return PLUS_FAIL;
110  }
111  }
112  else
113  {
114  currentLineParams.lineOriginPoint_Image[0] = 0;
115  currentLineParams.lineOriginPoint_Image[1] = 0;
116  currentLineParams.lineDirectionVector_Image[0] = 0;
117  currentLineParams.lineDirectionVector_Image[1] = 1;
118  }
119 
120  if ( frameIndexUint >= lineParameters.size() )
121  {
122  // expand the results array to be able to store the results
123  vtkPlusLineSegmentationAlgo::LineParameters nonDetectedLineParams;
124  nonDetectedLineParams.lineDetected = false;
125  nonDetectedLineParams.lineOriginPoint_Image[0] = 0;
126  nonDetectedLineParams.lineOriginPoint_Image[1] = 0;
127  nonDetectedLineParams.lineDirectionVector_Image[0] = 0;
128  nonDetectedLineParams.lineDirectionVector_Image[1] = 1;
129  lineParameters.insert( lineParameters.end(), frameIndexUint - lineParameters.size() + 1, nonDetectedLineParams );
130  }
131  lineParameters[frameIndex] = currentLineParams;
132  }
133 
134  return PLUS_SUCCESS;
135 }
136 
137 //----------------------------------------------------------------------------
138 int CompareLineSegmentationResults( const std::vector<vtkPlusLineSegmentationAlgo::LineParameters>& lineParameters, const std::vector<vtkPlusLineSegmentationAlgo::LineParameters>& baselineLineParameters )
139 {
140  unsigned int numberOfFailures = 0;
141 
142  for ( unsigned int frameIndex = 0; frameIndex < lineParameters.size(); ++frameIndex )
143  {
144  LOG_DEBUG( "Comparing frame " << frameIndex );
145  if ( frameIndex >= baselineLineParameters.size() )
146  {
147  LOG_ERROR( "Unable to find frame " << frameIndex << " in LineSegmentationResults baseline" );
148  numberOfFailures++;
149  continue;
150  }
151 
152  vtkPlusLineSegmentationAlgo::LineParameters currentParam = lineParameters[frameIndex];
153  vtkPlusLineSegmentationAlgo::LineParameters baselineParam = baselineLineParameters[frameIndex];
154 
155  if ( currentParam.lineDetected != baselineParam.lineDetected )
156  {
157  LOG_ERROR( "SegmentationStatus mismatch in Frame #" << frameIndex << ": current=" << currentParam.lineDetected << ", baseline=" << baselineParam.lineDetected );
158  ++numberOfFailures;
159  continue;
160  }
161 
162  LOG_DEBUG( " Line detection status: " << ( currentParam.lineDetected ? "detected" : "not detected" ) );
163  if ( !currentParam.lineDetected )
164  {
165  // no segmentation data
166  continue;
167  }
168 
169  // Compare origin to baseline
170  double baselineOrigin3d[3] = {baselineParam.lineOriginPoint_Image[0], baselineParam.lineOriginPoint_Image[1], 0};
171  double currentLinePoint1[3] = {currentParam.lineOriginPoint_Image[0], currentParam.lineOriginPoint_Image[1], 0};
172  const double lineLen = 50; // pick a second point along the line at this distance, any positive number would do
173  double currentLinePoint2[3] = {currentLinePoint1[0] + currentParam.lineDirectionVector_Image[0]* lineLen,
174  currentLinePoint1[1] + currentParam.lineDirectionVector_Image[1]* lineLen, 0
175  };
176  double distanceOfBaselineOriginFromCurrentLinePx = igsioMath::ComputeDistanceLinePoint( currentLinePoint1, currentLinePoint2, baselineOrigin3d );
177  if ( distanceOfBaselineOriginFromCurrentLinePx > MAX_ORIGIN_DISTANCE_PIXEL )
178  {
179  LOG_ERROR( "Line position mismatch in Frame #" << frameIndex << ": baseline origin point distance from current line is " << distanceOfBaselineOriginFromCurrentLinePx << " pixels" );
180  numberOfFailures++;
181  }
182  else
183  {
184  LOG_DEBUG( " Line distance: " << distanceOfBaselineOriginFromCurrentLinePx << " pixels" );
185  }
186 
187  // Compare direction to baseline
188  double baselineVec3d[3] = {baselineParam.lineDirectionVector_Image[0], baselineParam.lineDirectionVector_Image[1], 0};
189  double currentVec3d[3] = {currentParam.lineDirectionVector_Image[0], currentParam.lineDirectionVector_Image[1], 0};
190  double angleDeg = acos( vtkMath::Dot( baselineVec3d, currentVec3d ) );
191  if ( angleDeg > MAX_LINE_ANGLE_DIFFERENCE_DEG )
192  {
193  LOG_ERROR( "Line angle mismatch in Frame #" << frameIndex << ": angle difference is " << angleDeg << " deg, vector coordinates: "
194  << "current=(" << std::fixed << currentParam.lineDirectionVector_Image[0] << ", " << currentParam.lineDirectionVector_Image[1] << ") "
195  << "baseline=(" << baselineParam.lineDirectionVector_Image[0] << ", " << baselineParam.lineDirectionVector_Image[1] << ")." );
196  numberOfFailures++;
197  }
198  else
199  {
200  LOG_DEBUG( " Line angle difference is " << angleDeg << " deg" );
201  }
202  }
203 
204  return numberOfFailures;
205 }
206 
207 //----------------------------------------------------------------------------
208 int main( int argc, char** argv )
209 {
210  int verboseLevel = vtkPlusLogger::LOG_LEVEL_UNDEFINED;
211 
212  bool printHelp = false;
213  vtksys::CommandLineArguments args;
214  args.Initialize( argc, argv );
215  std::string inputSequenceMetafile;
216  std::vector<int> clipRectOrigin;
217  std::vector<int> clipRectSize;
218  std::string inputBaselineFileName;
219  bool saveImages = false;
220 
221  args.AddArgument( "--help", vtksys::CommandLineArguments::NO_ARGUMENT, &printHelp, "Print this help." );
222  args.AddArgument( "--verbose", vtksys::CommandLineArguments::EQUAL_ARGUMENT, &verboseLevel, "Verbose level (1=error only, 2=warning, 3=info, 4=debug, 5=trace)" );
223  args.AddArgument( "--seq-file", vtksys::CommandLineArguments::EQUAL_ARGUMENT, &inputSequenceMetafile, "Input sequence metafile name with path" );
224  args.AddArgument( "--clip-rect-origin", vtksys::CommandLineArguments::MULTI_ARGUMENT, &clipRectOrigin, "Origin of the clipping rectangle" );
225  args.AddArgument( "--clip-rect-size", vtksys::CommandLineArguments::MULTI_ARGUMENT, &clipRectSize, "Size of the clipping rectangle" );
226  args.AddArgument( "--save-images", vtksys::CommandLineArguments::NO_ARGUMENT, &saveImages, "Save images with detected lines overlaid" );
227  args.AddArgument( "--baseline-file", vtksys::CommandLineArguments::EQUAL_ARGUMENT, &inputBaselineFileName, "Input xml baseline file name with path" );
228 
229  if ( !args.Parse() )
230  {
231  std::cerr << "Problem parsing arguments" << std::endl;
232  std::cout << "Help: " << args.GetHelp() << std::endl;
233  exit( EXIT_FAILURE );
234  }
235 
236  if ( printHelp )
237  {
238  std::cout << args.GetHelp() << std::endl;
239  exit( EXIT_SUCCESS );
240  }
241 
242  vtkPlusLogger::Instance()->SetLogLevel( verboseLevel );
243 
244  if ( inputSequenceMetafile.empty() )
245  {
246  std::cerr << "--seq-file argument is required" << std::endl;
247  std::cout << "Help: " << args.GetHelp() << std::endl;
248  exit( EXIT_FAILURE );
249  }
250 
251  LOG_DEBUG( "Read input sequence" );
252  vtkSmartPointer<vtkIGSIOTrackedFrameList> trackedFrameList = vtkSmartPointer<vtkIGSIOTrackedFrameList>::New();
253  if ( vtkIGSIOSequenceIO::Read( inputSequenceMetafile, trackedFrameList ) != PLUS_SUCCESS )
254  {
255  LOG_ERROR( "Failed to read sequence metafile: " << inputSequenceMetafile );
256  return EXIT_FAILURE;
257  }
258 
259  vtkSmartPointer<vtkPlusLineSegmentationAlgo> lineSegmenter = vtkSmartPointer<vtkPlusLineSegmentationAlgo>::New();
260 
261  if ( clipRectOrigin.size() > 0 || clipRectSize.size() > 0 )
262  {
263  // clip rectangle specified
264  if ( clipRectOrigin.size() != 2 || clipRectSize.size() != 2 )
265  {
266  LOG_ERROR( "--clip-rect-origin and --clip-rect-size arguments shall contain exactly two values each" );
267  exit( EXIT_FAILURE );
268  }
269  int origin[2] = {clipRectOrigin[0], clipRectOrigin[1]};
270  int size[2] = {clipRectSize[0], clipRectSize[1]};
271  lineSegmenter->SetClipRectangle( origin, size );
272  }
273 
274  lineSegmenter->SetTrackedFrameList( *trackedFrameList );
275  lineSegmenter->SetSaveIntermediateImages( saveImages );
276  lineSegmenter->SetIntermediateFilesOutputDirectory( vtkPlusConfig::GetInstance()->GetOutputDirectory() );
277 
278  LOG_DEBUG( "Segment lines" );
279  if ( lineSegmenter->Update() != PLUS_SUCCESS )
280  {
281  LOG_ERROR( "Failed to get line positions from video frames" );
282  return PLUS_FAIL;
283  }
284  std::vector<vtkPlusLineSegmentationAlgo::LineParameters> lineParameters;
285  lineSegmenter->GetDetectedLineParameters( lineParameters );
286 
287  // Save results to file
288  std::string resultSaveFilename = vtkPlusConfig::GetInstance()->GetOutputPath( "LineSegmentationResults.xml" );
289  LOG_INFO( "Save calibration results to XML file: " << resultSaveFilename );
290  WriteLineSegmentationResultsToFile( resultSaveFilename, lineParameters );
291 
292  // Compare result to baseline
293  if ( !inputBaselineFileName.empty() )
294  {
295  LOG_INFO( "Comparing result with baseline..." );
296  std::vector<vtkPlusLineSegmentationAlgo::LineParameters> baselineLineParameters;
297  if ( ReadLineSegmentationResultsFromFile( inputBaselineFileName, baselineLineParameters ) != PLUS_SUCCESS )
298  {
299  LOG_ERROR( "Failed to read baseline data file" );
300  exit( EXIT_FAILURE );
301  }
302  int numberOfFailures = CompareLineSegmentationResults( lineParameters, baselineLineParameters );
303  if ( numberOfFailures > 0 )
304  {
305  LOG_ERROR( "Number of differences compared to baseline: " << numberOfFailures << ". Test failed!" );
306  exit( EXIT_FAILURE );
307  }
308  }
309 
310  LOG_INFO( "Test finished successfully!" );
311  return EXIT_SUCCESS;
312 }
std::string GetOutputPath(const std::string &subPath)
void WriteLineSegmentationResultsToFile(const std::string &resultSaveFilename, const std::vector< vtkPlusLineSegmentationAlgo::LineParameters > &lineParameters)
igsioStatus PlusStatus
Definition: PlusCommon.h:40
#define PLUS_FAIL
Definition: PlusCommon.h:43
static vtkPlusConfig * GetInstance()
const double MAX_ORIGIN_DISTANCE_PIXEL
const double MAX_LINE_ANGLE_DIFFERENCE_DEG
PlusStatus ReadLineSegmentationResultsFromFile(const std::string &resultSaveFilename, std::vector< vtkPlusLineSegmentationAlgo::LineParameters > &lineParameters)
#define PLUS_SUCCESS
Definition: PlusCommon.h:44
static vtkIGSIOLogger * Instance()
int CompareLineSegmentationResults(const std::vector< vtkPlusLineSegmentationAlgo::LineParameters > &lineParameters, const std::vector< vtkPlusLineSegmentationAlgo::LineParameters > &baselineLineParameters)
int main(int argc, char **argv)