]> Creatis software - FrontAlgorithms.git/blob - appli/CTArteries/CTArteries.cxx
345b94e556908fbba347da01832464232fd02982
[FrontAlgorithms.git] / appli / CTArteries / CTArteries.cxx
1 // =========================================================================
2 // @author Leonardo Florez-Valencia (florez-l@javeriana.edu.co)
3 // =========================================================================
4
5 #include "CTArteries.h"
6 #include "ui_CTArteries.h"
7 #include "ui_Parameters.h"
8 #include "FileDialog.h"
9
10 #include <set>
11
12 #include <itkImageFileReader.h>
13 #include <itkImageSeriesReader.h>
14 #include <itkQuadEdgeMesh.h>
15
16 #include <vtkMarchingCubes.h>
17 #include <vtkMarchingSquares.h>
18 #include <vtkPolyDataConnectivityFilter.h>
19 #include <vtkPolyDataMapper.h>
20 #include <vtkProperty.h>
21 #include <vtkRenderer.h>
22 #include <vtkRenderWindow.h>
23 #include <vtkSplineWidget.h>
24
25 #include <QtGui>
26
27 #include <ivq/ITK/CPRImageFilter.h>
28 #include <ivq/VTK/ImageActor.h>
29 #include <ivq/VTK/ImageViewer.h>
30 #include <ivq/VTK/MPRViewers.h>
31 #include <ivq/VTK/PolyDataActor.h>
32 #include <ivq/VTK/SeedWidgetOverImageActor.h>
33 #include <ivq/Qt/DicomSeriesSelectorDialog.h>
34 #include <ivq/Qt/RendererWidget.h>
35
36 #include "algorithms/RandomWalkSegmentation.h"
37
38 // -------------------------------------------------------------------------
39 #define QT_ACTION_CONN( x )                                             \
40   this->connect(                                                        \
41     this->m_UI->a##x, SIGNAL( triggered( ) ), this, SLOT( _s##x( ) )    \
42     )
43
44 // -------------------------------------------------------------------------
45 class ContourCallback
46   : public vtkCommand
47 {
48 public:
49   static ContourCallback* New( )
50     {
51       return( new ContourCallback( ) );
52     }
53   virtual void Execute(
54     vtkObject* caller, unsigned long eId, void* data
55     ) override
56     {
57       ivq::VTK::ImageActor* actor =
58         ivq::VTK::ImageActor::SafeDownCast( caller );
59       if( eId == vtkCommand::InteractionEvent && actor != NULL )
60       {
61         ivq::VTK::ImageViewer* viewer = this->Window->m_UI->Slice->GetViewer( );
62         if( this->Window->m_ContourActor != NULL )
63           viewer->GetRenderer( )->RemoveViewProp( this->Window->m_ContourActor->GetActor( ) );
64
65         double abnds[ 6 ];
66         actor->GetActorBounds( abnds );
67         unsigned int nSlice = actor->GetSliceNumber( );
68         for( unsigned int i = 0; i < 16; ++i )
69         {
70           double w = double( 6.28 ) * double( i ) / double( 16 );
71           CTArteries::TFourier::TPoint v = this->Window->m_Fourier[ nSlice ]( w );
72           this->Window->m_Spline->SetHandlePosition(
73             i, v[ 0 ], v[ 1 ], abnds[ 4 ]
74             );
75
76         } // rof
77         this->Window->m_Spline->SetProjectionPosition(
78           (
79             actor->GetBounds( )[ 4 ] +
80             actor->GetBounds( )[ 5 ]
81             ) / double( 2 )
82           );
83
84
85         this->Window->m_ContourActor =
86           this->Window->m_ContoursActors[ actor->GetSliceNumber( ) ];
87         viewer->GetRenderer( )->AddViewProp( this->Window->m_ContourActor->GetActor( ) );
88         viewer->Render( );
89
90       } // fi
91     }
92
93 protected:
94   ContourCallback( )
95     : vtkCommand( )
96     {
97     }
98   virtual ~ContourCallback( )
99     {
100     }
101
102 private:
103   // Purposely not implemented
104   ContourCallback( const ContourCallback& other );
105   ContourCallback& operator=( const ContourCallback& other );
106 public:
107   CTArteries* Window;
108 };
109
110 // -------------------------------------------------------------------------
111 CTArteries::
112 CTArteries( int argc, char* argv[], QWidget* parent )
113   : Superclass( parent ),
114     m_UI( new Ui::CTArteries ),
115     m_UIParameters( new Ui::Parameters )
116 {
117   this->m_UI->setupUi( this );
118   this->m_DlgParameters = new QDialog( );
119   this->m_UIParameters->setupUi( this->m_DlgParameters );
120
121   // Connect signals with slots
122   QT_ACTION_CONN( Open );
123   QT_ACTION_CONN( Config );
124   QT_ACTION_CONN( Process );
125 }
126
127 // -------------------------------------------------------------------------
128 CTArteries::
129 ~CTArteries( )
130 {
131   delete this->m_UI;
132   delete this->m_UIParameters;
133   delete this->m_DlgParameters;
134 }
135
136 // -------------------------------------------------------------------------
137 template< class _TStrings >
138 void CTArteries::
139 _openImage( const _TStrings& fnames )
140 {
141   // Create appropriate reader
142   itk::ImageSource< TImage >::Pointer reader;
143   if( fnames.size( ) == 1 )
144   {
145     itk::ImageFileReader< TImage >::Pointer r =
146       itk::ImageFileReader< TImage >::New( );
147     r->SetFileName( *( fnames.begin( ) ) );
148     reader = r;
149   }
150   else if( fnames.size( ) > 1 )
151   {
152     itk::ImageSeriesReader< TImage >::Pointer r =
153       itk::ImageSeriesReader< TImage >::New( );
154     typename _TStrings::const_iterator i;
155     for( i = fnames.begin( ); i != fnames.end( ); ++i )
156       r->AddFileName( *i );
157     reader = r;
158
159   } // fi
160
161   // Execute reader
162   if( reader.IsNull( ) )
163     return;
164   try
165   {
166     reader->Update( );
167     this->m_Image = reader->GetOutput( );
168     this->m_Image->DisconnectPipeline( );
169   }
170   catch( std::exception& err )
171   {
172     this->m_Image = NULL;
173     QMessageBox::critical( this, "Error opening image", err.what( ) );
174
175   } // yrt
176   this->_showInputImage( );
177 }
178
179 // -------------------------------------------------------------------------
180 void CTArteries::
181 _openDicom( const std::string& dirname )
182 {
183   ivq::Qt::DicomSeriesSelectorDialog dlg( this );
184   dlg.setStartDir( dirname );
185   if( dlg.exec( ) == QDialog::Accepted )
186     this->_openImage( *( dlg.selectedFilenames( ) ) );
187 }
188
189 // -------------------------------------------------------------------------
190 void CTArteries::
191 _showInputImage( )
192 {
193   if( this->m_Image.IsNotNull( ) )
194   {
195     this->m_VTKImage = TVTKImage::New( );
196     this->m_VTKImage->SetInput( this->m_Image );
197     this->m_VTKImage->Update( );
198
199     ivq::VTK::MPRViewers* viewers = this->m_UI->MPR->GetViewers( );
200     viewers->SetInputData( this->m_VTKImage->GetOutput( ) );
201     viewers->SetColorWindow( 1000 );
202     viewers->SetColorLevel( 100 );
203
204     ivq::VTK::ImageViewer* view = viewers->GetView( 2 );
205     this->m_Seeds =
206       vtkSmartPointer< ivq::VTK::SeedWidgetOverImageActor >::New( );
207     this->m_Seeds->SetActor( view->GetImageActor( ) );
208     this->m_Seeds->SetInteractor( view->GetRenderWindow( )->GetInteractor( ) );
209
210     viewers->Render( );
211     viewers->ResetCameras( );
212     this->m_Seeds->EnabledOn( );
213     viewers->Render( );
214   }
215   else
216   {
217     this->m_VTKImage = NULL;
218
219   } // fi
220 }
221
222 // -------------------------------------------------------------------------
223 void CTArteries::
224 _process( )
225 {
226   // Get seeds
227   typedef ivq::VTK::SeedWidgetOverImageActor::TSeeds _TSeeds;
228   if( this->m_Image.IsNull( ) || this->m_Seeds.GetPointer( ) == NULL )
229   {
230     QMessageBox::critical( this, "Error processing", "No valid input image." );
231     return;
232
233   } // fi
234   std::vector< TImage::PointType > seeds;
235   for( _TSeeds::value_type sValue: this->m_Seeds->GetSeeds( ) )
236   {
237     for( unsigned int i = 0; i < sValue.second.size( ); i += 3 )
238     {
239       TImage::PointType pnt;
240       pnt[ 0 ] = sValue.second[ i + 0 ];
241       pnt[ 1 ] = sValue.second[ i + 1 ];
242       pnt[ 2 ] = sValue.second[ i + 2 ];
243       seeds.push_back( pnt );
244
245     } // rof
246
247   } // rof
248   if( seeds.size( ) < 2 )
249   {
250     QMessageBox::critical( this, "Error processing", "Not enough seeds." );
251     return;
252
253   } // fi
254
255   // Create algorithm
256   typedef RandomWalkSegmentation< TImage, TScalarImage > _TSegmentation;
257   _TSegmentation::Pointer seg = _TSegmentation::New( );
258   seg->SetInput( this->m_Image );
259   seg->SetBeta( this->m_UIParameters->Beta->value( ) );
260   seg->SetSigma( this->m_UIParameters->Sigma->value( ) );
261   seg->SetRadius( this->m_UIParameters->Radius->value( ) );
262   seg->SetStartSeed( seeds[ 0 ] );
263   seg->SetEndSeed( seeds[ 1 ] );
264   for( TImage::PointType seed: seeds )
265     seg->AddSeed( seed );
266   try
267   {
268     seg->Update( );
269   }
270   catch( std::exception& err )
271   {
272     QMessageBox::critical(
273       this,
274       QMessageBox::tr( "Error reading image" ),
275       QMessageBox::tr( err.what( ) )
276       );
277     return;
278
279   } // yrt
280   this->m_Seeds->EnabledOff( );
281
282   this->m_Segmentation = seg->GetOutput( );
283   this->m_Axis = seg->GetOutputAxis( );
284   this->m_Segmentation->DisconnectPipeline( );
285   this->m_Axis->DisconnectPipeline( );
286 }
287
288 // -------------------------------------------------------------------------
289 void CTArteries::
290 _prepareQuantification( )
291 {
292   this->m_Contours.clear( );
293   this->m_ContoursActors.clear( );
294   this->m_Fourier.clear( );
295
296   TImage::RegionType ext_region = this->m_CPRImage->GetRequestedRegion( );
297   for( long z = 0; z < ext_region.GetSize( )[ 2 ]; ++z )
298   {
299     TImage::IndexType slc_idx = ext_region.GetIndex( );
300     TImage::SizeType slc_size = ext_region.GetSize( );
301     slc_idx[ 2 ] += z;
302     slc_size[ 2 ] = 1;
303     TImage::RegionType slc_region;
304     slc_region.SetIndex( slc_idx );
305     slc_region.SetSize( slc_size );
306
307     typedef itk::RegionOfInterestImageFilter< TScalarImage, TScalarImage > _TROI;
308     _TROI::Pointer roi = _TROI::New( );
309     roi->SetInput( this->m_CPRSegmentation );
310     roi->SetRegionOfInterest( slc_region );
311
312     /* TODO
313        slc_idx[ 2 ] = 1;
314        slc_size[ 2 ] = 0;
315        slc_region.SetIndex( slc_idx );
316        slc_region.SetSize( slc_size );
317        typedef itk::Image< TBinPixel, 2 > _TBinSlice;
318        typedef itk::ExtractImageFilter< TBinImage, _TBinSlice > _TCollapse;
319        _TCollapse::Pointer collapse = _TCollapse::New( );
320        collapse->SetInput( roi->GetOutput( ) );
321        collapse->SetDirectionCollapseToIdentity( );
322        collapse->SetExtractionRegion( slc_region );
323        collapse->UpdateLargestPossibleRegion( );
324     */
325     typedef TScalarImage _TScalarSlice;
326
327     typedef itk::ImageToVTKImageFilter< _TScalarSlice > _TVTKScalarSlice;
328     _TVTKScalarSlice::Pointer vtk_slice = _TVTKScalarSlice::New( );
329     vtk_slice->SetInput( roi->GetOutput( ) );
330     vtk_slice->Update( );
331
332     double r[ 2 ];
333     vtk_slice->GetOutput( )->GetScalarRange( r );
334
335     vtkSmartPointer< vtkMarchingSquares > ms =
336       vtkSmartPointer< vtkMarchingSquares >::New( );
337     ms->SetInputData( vtk_slice->GetOutput( ) );
338     ms->SetValue( 0, 0 );
339
340     vtkSmartPointer< vtkPolyDataConnectivityFilter > conn =
341       vtkSmartPointer< vtkPolyDataConnectivityFilter >::New( );
342     conn->SetInputConnection( ms->GetOutputPort( ) );
343     conn->SetExtractionModeToClosestPointRegion( );
344     conn->SetClosestPoint( 0, 0, 0 );
345     conn->Update( );
346
347     vtkSmartPointer< vtkPolyData > cnt = vtkSmartPointer< vtkPolyData >::New( );
348     cnt->DeepCopy( conn->GetOutput( ) );
349     this->m_Contours.push_back( cnt );
350
351     typedef itk::QuadEdgeMesh< double, 2 > _TQEMesh;
352     typedef _TQEMesh::PointType _T2DPoint;
353     typedef std::vector< _T2DPoint > _T2DPoints;
354     _T2DPoints points;
355     _TQEMesh::Pointer mesh = _TQEMesh::New( );
356     for( unsigned int i = 0; i < cnt->GetNumberOfPoints( ); ++i )
357     {
358       _T2DPoint pnt;
359       double p[ 3 ];
360       cnt->GetPoint( i, p );
361       pnt[ 0 ] = p[ 0 ];
362       pnt[ 1 ] = p[ 1 ];
363       mesh->AddPoint( pnt );
364
365     } // rof
366     vtkCellArray* lines = cnt->GetLines( );
367     lines->InitTraversal( );
368     vtkIdType* ids = new vtkIdType[ 2 ];
369     vtkIdType npts;
370     while( lines->GetNextCell( npts, ids ) != 0 )
371       mesh->AddEdge( ids[ 0 ], ids[ 1 ] );
372     delete ids;
373     mesh->AddFace( mesh->GetEdge( ) );
374     _TQEMesh::QEPrimal* edge = mesh->GetEdge( );
375     for( auto eIt = edge->BeginGeomLnext( ); eIt != edge->EndGeomLnext( ); ++eIt )
376       points.push_back( mesh->GetPoint( *eIt ) );
377
378     TFourier f( points.begin( ), points.end( ), 3 );
379     f.SetOrderingToCounterClockWise( );
380     this->m_Fourier.push_back( f );
381
382     vtkSmartPointer< ivq::VTK::PolyDataActor > actor =
383       vtkSmartPointer< ivq::VTK::PolyDataActor >::New( );
384     actor->SetInputData( cnt );
385     actor->GetMapper( )->ScalarVisibilityOff( );
386     actor->Update( );
387     actor->GetProperty( )->SetColor( 1, 0, 0 );
388     actor->GetProperty( )->SetLineWidth( 2 );
389     this->m_ContoursActors.push_back( actor );
390
391   } // rof
392
393   ivq::VTK::ImageViewer* slice_view = this->m_UI->Slice->GetViewer( );
394   vtkSmartPointer< ContourCallback > cbk =
395     vtkSmartPointer< ContourCallback >::New( );
396   cbk->Window = this;
397   slice_view->GetImageActor( )->AddObserver( vtkCommand::InteractionEvent, cbk );
398   this->m_ContourActor = NULL;
399
400   this->m_Spline = vtkSmartPointer< vtkSplineWidget >::New( );
401   this->m_Spline->SetCurrentRenderer( slice_view->GetRenderer( ) );
402   this->m_Spline->SetDefaultRenderer( slice_view->GetRenderer( ) );
403   this->m_Spline->SetInputData( slice_view->GetInput( ) );
404   this->m_Spline->SetProp3D( slice_view->GetImageActor( ) );
405   this->m_Spline->SetInteractor( slice_view->GetRenderWindow( )->GetInteractor( ) );
406   double bnds[ 6 ];
407   slice_view->GetInput( )->GetBounds( bnds );
408   this->m_Spline->PlaceWidget(
409     bnds[ 0 ], bnds[ 1 ],
410     bnds[ 2 ], bnds[ 3 ],
411     bnds[ 4 ], bnds[ 5 ]
412     );
413   this->m_Spline->ProjectToPlaneOn( );
414   this->m_Spline->SetProjectionNormalToZAxes( );
415   this->m_Spline->SetProjectionPosition(
416     (
417       slice_view->GetImageActor( )->GetBounds( )[ 4 ] +
418       slice_view->GetImageActor( )->GetBounds( )[ 5 ]
419       ) / double( 2 )
420     );
421   this->m_Spline->SetHandleSize( 0.005 );
422   this->m_Spline->ClosedOn( );
423   this->m_Spline->SetNumberOfHandles( 16 );
424   this->m_Spline->EnabledOn( );
425
426   QVector< double > xdata, dmindata, dmaxdata, areadata;
427   double data[ 3 ];
428   for( unsigned int i = 0; i < this->m_Fourier.size( ); ++i )
429   {
430     double a, b, t, p;
431     this->m_Fourier[ i ].GetEllipse( 1, a, b, t, p );
432     double area = this->m_Fourier[ i ].GetArea( );
433
434     xdata.push_back( double( i ) );
435     dmindata.push_back( b );
436     dmaxdata.push_back( a );
437     areadata.push_back( area );
438
439   } // rof
440   this->m_UI->Plot->addGraph( this->m_UI->Plot->xAxis, this->m_UI->Plot->yAxis );
441   this->m_UI->Plot->graph( 0 )->setPen( QPen( QColor( 255, 0, 0 ) ) );
442   this->m_UI->Plot->graph( 0 )->setName( "Minimum diameter (mm)" );
443   this->m_UI->Plot->graph( 0 )->setData( xdata, dmindata );
444
445   this->m_UI->Plot->addGraph( this->m_UI->Plot->xAxis, this->m_UI->Plot->yAxis );
446   this->m_UI->Plot->graph( 1 )->setPen( QPen( QColor( 0, 255, 0 ) ) );
447   this->m_UI->Plot->graph( 1 )->setName( "Maximum diameter (mm)" );
448   this->m_UI->Plot->graph( 1 )->setData( xdata, dmaxdata );
449
450   this->m_UI->Plot->addGraph( this->m_UI->Plot->xAxis, this->m_UI->Plot->yAxis2 );
451   this->m_UI->Plot->graph( 2 )->setPen( QPen( QColor( 0, 0, 255 ) ) );
452   this->m_UI->Plot->graph( 2 )->setName( "Area (mm2)" );
453   this->m_UI->Plot->graph( 2 )->setData( xdata, areadata );
454
455   this->m_UI->Plot->xAxis->setLabel( "Position" );
456   this->m_UI->Plot->yAxis->setLabel( "Diameters (mm)" );
457   this->m_UI->Plot->yAxis2->setLabel( "Areas (mm2)" );
458
459   this->m_UI->Plot->legend->setVisible( true );
460   this->m_UI->Plot->xAxis->setVisible( true );
461   this->m_UI->Plot->yAxis->setVisible( true );
462   this->m_UI->Plot->yAxis2->setVisible( true );
463
464   this->m_UI->Plot->rescaleAxes( );
465   this->m_UI->Plot->replot( );
466 }
467
468 // -------------------------------------------------------------------------
469 void CTArteries::
470 _showProcessResults( )
471 {
472   if( this->m_Segmentation.IsNull( ) || this->m_Axis.IsNull( ) )
473     return;
474
475   this->m_VTKSegmentation = TVTKScalarImage::New( );
476   this->m_VTKSegmentation->SetInput( this->m_Segmentation );
477   this->m_VTKSegmentation->Update( );
478
479   // Show surface
480   vtkImageData* seg = this->m_VTKSegmentation->GetOutput( );
481   double r[ 2 ];
482   seg->GetScalarRange( r );
483   vtkSmartPointer< vtkMarchingCubes > mc =
484     vtkSmartPointer< vtkMarchingCubes >::New( );
485   mc->SetInputData( seg );
486   mc->SetValue( 0, 0 );
487   mc->Update( );
488   this->m_Surface = vtkSmartPointer< ivq::VTK::PolyDataActor >::New( );
489   this->m_Surface->SetInputConnection( mc->GetOutputPort( ) );
490   this->m_Surface->Update( );
491
492   // Prepare curve
493   this->m_Curve = TCurve::New( );
494   for( unsigned int i = 0; i < this->m_Axis->GetSize( ); ++i )
495     this->m_Curve->AddPoint( this->m_Axis->GetPoint( i ) );
496   this->m_Curve->Smooth( 2 );
497
498   // Compute CPRs
499   this->_CPR(
500     this->m_CPRImage, this->m_VTKCPRImage,
501     this->m_Image, this->m_Curve
502     );
503   this->_CPR(
504     this->m_CPRSegmentation, this->m_VTKCPRSegmentation,
505     this->m_Segmentation, this->m_Curve
506     );
507
508   // Show all data
509   vtkRenderer* renderer =
510     this->m_UI->MPR->GetRendererWidget( )->GetRenderer( );
511   renderer->AddViewProp( this->m_Surface->GetActor( ) );
512   renderer->Render( );
513   renderer->ResetCamera( );
514   this->m_UI->MPR->GetRendererWidget( )->GetRenderWindow( )->Render( );
515
516   ivq::VTK::ImageViewer* cpr_view = this->m_UI->CPR->GetViewer( );
517   ivq::VTK::ImageViewer* slice_view = this->m_UI->Slice->GetViewer( );
518
519   cpr_view->SetInputData( this->m_VTKCPRImage->GetOutput( ) );
520   cpr_view->SetColorWindow( 1000 );
521   cpr_view->SetColorLevel( 100 );
522   cpr_view->SetSliceOrientationToXZ( );
523   cpr_view->Render( );
524
525   slice_view->SetInputData( this->m_VTKCPRImage->GetOutput( ) );
526   slice_view->SetColorWindow( 1000 );
527   slice_view->SetColorLevel( 100 );
528   slice_view->SetSliceOrientationToXY( );
529   slice_view->Render( );
530
531   this->_prepareQuantification( );
532 }
533
534 // -------------------------------------------------------------------------
535 void CTArteries::
536 _sOpen( )
537 {
538   FileDialog dlg( this );
539   dlg.setNameFilter(
540     "Medical images (*.mhd *.dcm);;Other images (*.png *.jpg *.bmp);;\
541 All files (*)"
542     );
543   if( dlg.exec( ) == QDialog::Accepted )
544   {
545     std::set< std::string > filenames, dirnames;
546     QStringList names = dlg.selectedFiles( );
547     for( QString name: names )
548     {
549       QFileInfo info( name );
550       if( info.isDir( ) )
551         dirnames.insert( name.toStdString( ) );
552       else
553         filenames.insert( name.toStdString( ) );
554
555     } // rof
556
557     if( dirnames.size( ) == 1 && filenames.size( ) == 0 )
558       this->_openDicom( *( dirnames.begin( ) ) );
559     else if( dirnames.size( ) == 0 && filenames.size( ) > 0 )
560       this->_openImage( filenames );
561     else
562       QMessageBox::critical(
563         this,
564         "Error opening image",
565         "Directory - files mixed up, don't know what to do."
566         );
567
568   } // fi
569 }
570
571 // -------------------------------------------------------------------------
572 void CTArteries::
573 _sConfig( )
574 {
575   this->m_DlgParameters->exec( );
576 }
577
578 // -------------------------------------------------------------------------
579 void CTArteries::
580 _sProcess( )
581 {
582   this->_process( );
583   this->_showProcessResults( );
584 }
585
586 // -------------------------------------------------------------------------
587 template< class _TImagePtr, class _TVTKImagePtr, class _TCurvePtr >
588 void CTArteries::
589 _CPR(
590   _TImagePtr& output, _TVTKImagePtr& vtk_output,
591   const _TImagePtr& input, const _TCurvePtr& curve
592   )
593 {
594   typedef typename _TImagePtr::ObjectType _TImage;
595   typedef typename _TCurvePtr::ObjectType _TCurve;
596   typedef ivq::ITK::CPRImageFilter< _TImage, _TCurve > _TFilter;
597
598   typename _TFilter::Pointer cpr = _TFilter::New( );
599   cpr->SetInput( input );
600   cpr->SetCurve( curve );
601   cpr->SetSliceRadius( 40 );
602   cpr->Update( );
603   output = cpr->GetOutput( );
604   output->DisconnectPipeline( );
605
606   vtk_output = _TVTKImagePtr::ObjectType::New( );
607   vtk_output->SetInput( output );
608   vtk_output->Update( );
609 }
610
611 // -------------------------------------------------------------------------
612 #include <QApplication>
613
614 int main( int argc, char* argv[] )
615 {
616   QApplication a( argc, argv );
617   CTArteries w( argc, argv );
618   w.show( );
619   return( a.exec( ) );
620 }
621
622 // eof - $RCSfile$