1 /*# ---------------------------------------------------------------------
3 # Copyright (c) CREATIS (Centre de Recherche en Acquisition et Traitement de l'Image
5 # Authors : Eduardo Davila, Frederic Cervenansky, Claire Mouton
6 # Previous Authors : Laurent Guigues, Jean-Pierre Roux
7 # CreaTools website : www.creatis.insa-lyon.fr/site/fr/creatools_accueil
9 # This software is governed by the CeCILL-B license under French law and
10 # abiding by the rules of distribution of free software. You can use,
11 # modify and/ or redistribute the software under the terms of the CeCILL-B
12 # license as circulated by CEA, CNRS and INRIA at the following URL
13 # http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html
14 # or in the file LICENSE.txt.
16 # As a counterpart to the access to the source code and rights to copy,
17 # modify and redistribute granted by the license, users are provided only
18 # with a limited warranty and the software's author, the holder of the
19 # economic rights, and the successive licensors have only limited
22 # The fact that you are presently reading this means that you have had
23 # knowledge of the CeCILL-B license and that you accept its terms.
24 # ------------------------------------------------------------------------ */
26 /*=========================================================================
29 Module: $RCSfile: vtkImagePolyDataSeedConnectivity.cxx,v $
31 Date: $Date: 2012/11/15 14:15:18 $
32 Version: $Revision: 1.2 $
34 Copyright: (c) 2002, 2003
37 This software is distributed WITHOUT ANY WARRANTY; without even
38 the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
39 PURPOSE. See the above copyright notice for more information.
41 =========================================================================*/
42 #include "vtkImagePolyDataSeedConnectivity.h"
44 #include <vtkPolyData.h>
45 #include <vtkImageData.h>
48 #include <vtkSphere.h>
49 #include <vtkImplicitBoolean.h>
50 #include <vtkImplicitFunctionToImageStencil.h>
51 #include <vtkImageStencil.h>
52 #include <vtkObjectFactory.h>
53 #include <vtkImageSeedConnectivity.h>
54 #include <vtkImageIterator.h>
55 #include <vtkImageGaussianSmooth.h>
56 #include <vtkMarchingCubes.h>
57 #include <vtkPolyDataConnectivityFilter.h>
58 #include <vtkCleanPolyData.h>
59 #include <vtkTriangleFilter.h>
60 #include <vtkPointData.h>
61 #include <vtkImageContinuousDilate3D.h>
63 vtkCxxRevisionMacro(vtkImagePolyDataSeedConnectivity, "$Revision: 1.2 $");
64 vtkStandardNewMacro(vtkImagePolyDataSeedConnectivity);
65 vtkCxxSetObjectMacro(vtkImagePolyDataSeedConnectivity, Axis,vtkPolyData);
67 //----------------------------------------------------------------------------
68 vtkImagePolyDataSeedConnectivity::vtkImagePolyDataSeedConnectivity()
71 this->ClipImageData = vtkImageData::New();
72 this->OuterMold = vtkPolyData::New();
73 this->ThresholdRatio = 0.45;
75 //----------------------------------------------------------------------------
76 vtkImagePolyDataSeedConnectivity::~vtkImagePolyDataSeedConnectivity()
82 this->ClipImageData->Delete();
83 this->ClipImageData = NULL;
84 this->OuterMold->Delete();
85 this->OuterMold = NULL;
88 * Utilisation de static pour accel�rer l'execution ?
89 * cf: vtkDividingCubes.cxx
93 #define PSC_MIN(x,y) ((x)<(y) ? (x) : (y))
94 #define PSC_MAX(x,y) ((x)>(y) ? (x) : (y))
95 //----------------------------------------------------------------------------
96 void vtkImagePolyDataSeedConnectivity::Execute()
98 vtkImageData *input = this->GetInput();
99 vtkPolyData *output = this->GetOutput();
103 vtkDebugMacro(<< "Executing polydata seed connectivity...");
107 vtkErrorMacro(<<"Input is NULL");
111 // just deal with volumes
112 if ( input->GetDataDimension() != 3 )
114 vtkErrorMacro("Bad input: only treats 3D structured point datasets");
118 input->GetDimensions(dim);
120 //Allocate a temporary image that will be filled in b&w
121 //this is the base of the algothm, from a graysccale input + an axis
122 //we produce a b&w image the the marching cubes can contour:
124 vtkImageData *imagedata = vtkImageData::New();
125 imagedata->SetOrigin( input->GetOrigin() );
126 imagedata->SetSpacing( input->GetSpacing() );
127 imagedata->SetExtent( input->GetExtent() );
128 imagedata->SetScalarTypeToUnsignedChar (); //for connectityseed + reduce mem usage
129 imagedata->AllocateScalars();
132 Result: piece of crap !!
133 There was some leakage from another vessel that was near enough from the heart.
136 vtkImageSeedConnectivity *seed = vtkImageSeedConnectivity::New();
137 seed->SetInput( imagedata );
138 seed->SetInputConnectValue( 255 ); //If 0 -> inverse video, 255 -> rien !
140 // Before starting the loop we should make sure the image has been clipped
141 // with the polydata:
142 this->ClipImageWithAxis();
144 vtkPoints *points = this->Axis->GetPoints();
145 vtkDataArray* scalars = this->Axis->GetPointData()->GetScalars();
147 int numPts = points->GetNumberOfPoints();
148 double p1[3], p2[3], p3[3], tuple[2];
149 double radius, signalvalue;
151 int i_p2; //allow saving of p2 index
152 unsigned short* inSI, *inSIEnd;
153 unsigned char* outSI,*outSIEnd;
155 for(int i=0; i<numPts; i+=intervalle)
157 i_p2 = i+intervalle/2;
158 points->GetPoint(i, p1);
159 if(numPts <= i+2*intervalle)
162 points->GetPoint(numPts - 1, p3);
163 i = numPts; //no more iteration
167 points->GetPoint(i+intervalle, p3);
169 points->GetPoint(i_p2, p2);
171 //Now we can add p2 point to the see list:
172 //seed->AddSeed( 3, p2); //too bad :(
173 seed->AddSeed( (int)p2[0], (int)p2[1], (int)p2[2] );
175 //this parameter seems to be ok for most image
176 //furthermore it doesn't make any change to make it bigger.
177 scalars->GetTuple(i_p2, tuple);
179 signalvalue = tuple[1];
180 radius *= 4; //multiply for a factor 4 for aneurism case
182 region[0] = (int)PSC_MAX(p2[0] - radius, 0);
183 region[2] = (int)PSC_MAX(p2[1] - radius, 0);
184 region[4] = (int)PSC_MAX(p2[2] - radius, 0);
186 region[1] = (int)PSC_MIN(p2[0] + radius, dim[0] - 1);
187 region[3] = (int)PSC_MIN(p2[1] + radius, dim[1] - 1);
188 region[5] = (int)PSC_MIN(p2[2] + radius, dim[2] - 1);
190 // multiply signal by a factor this allow user to accuratley find the best
192 signalvalue *= ThresholdRatio;
195 * Instead of working on the real image, we might take advantage of
196 * class Stencil made by dgobbi
199 * - Create a fake disk (black) of radius ~ Optimale sphere and normal
200 * orientation = last point first eigen vector
201 * See: Disk-Stencil.py for more info
203 //Solution: this is too much a trouble: rather do it once on the entry (=first axis point)
205 vtkImageIterator<unsigned short> inIt(this->ClipImageData, region);//image_woheart
206 vtkImageIterator<unsigned char> outIt(imagedata, region);
207 // vtkImageProgressIterator should be nicer in conjonction with a wxGauge
209 // Loop through ouput pixels
210 while (!inIt.IsAtEnd())
212 inSI = inIt.BeginSpan(); inSIEnd = inIt.EndSpan();
213 outSI = outIt.BeginSpan(); outSIEnd = outIt.EndSpan();
216 while (inSI != inSIEnd)
217 *outSI++ = (signalvalue <= *inSI++) ? 255 : 0;
219 inIt.NextSpan(); outIt.NextSpan();
223 //NEW: introduced by Maciek/Marcela: use a vtkImageGaussian to get rid
224 //of the crenelage/steps introduced by MarchingCubes
226 vtkImageGaussianSmooth *gauss = vtkImageGaussianSmooth::New();
227 gauss->SetInput( seed->GetOutput() );
228 gauss->SetDimensionality(3);
229 //gauss->SetStandardDeviation(1.0);
230 //gauss->SetRadiusFactor(3.);
232 vtkMarchingCubes *contour = vtkMarchingCubes::New();
233 contour->SetInput ( gauss->GetOutput() );
234 contour->SetNumberOfContours( 1 );
235 contour->SetValue(0, 128);
236 contour->ComputeGradientsOn ();
237 contour->ComputeScalarsOn ();
239 vtkCleanPolyData *clean = vtkCleanPolyData::New();
240 clean->SetInput ( contour->GetOutput() );
242 vtkTriangleFilter *tf = vtkTriangleFilter::New();
243 tf->SetInput( clean->GetOutput() );
246 //Need to do a deep copy as we'll do a ShallowCopy afterwards
247 output->DeepCopy( tf->GetOutput() );
249 //output->Squeeze(); //usefull ?
251 // Now we do a dilatation of seed->GetOutput() in order to have inner (=output)
252 // and outer mold (=OuterMold):
254 * Internal function that take an input of a b&w or grayscale image (should be
255 * unsigned short/ unsigned char and apply a dilatation (see math morpho for
257 * NB: dilation o gaussian = gaussian o dilatation
260 //First apply a morpho math: dilatation of 2mm:
261 vtkImageContinuousDilate3D *dilate = vtkImageContinuousDilate3D ::New();
262 dilate->SetInput ( seed->GetOutput() );
263 //TODO: determine real kernel based on scale = cm/pixel
264 //See: http://public.kitware.com/pipermail/vtkusers/2000-September/004210.html
265 //[Spacing is x and y is the distance between the samples. The units of measure
266 //are your choice. Often medical images use millimeters. Let's say your medical
267 //data resolution is 512 x 512 and your study has a 250 mm field of view. Then
268 //the spacing in x and y would be 250/512 or .488 mm.]
269 //therefore we have here:
273 seed->GetOutput()->GetSpacing( spacing ); //beware input should be Update()
274 for(int j=0; j<3; j++)
276 //2mm thickness, we assume spacing units is in mm !!
277 kernelsize[j] = 3; //(int) (2. / spacing[i]);
278 //std::cout << "kernel=" << kernelsize[i] << ",et:" << (int)(2./0.3334) << ",";
279 //beware if spacing=0.3334 -> 2/0.3334 = 5 !!!! bad bad +/-0.5 to see
281 dilate->SetKernelSize (kernelsize[0], kernelsize[1], kernelsize[2]);
283 //VTK filters rules !!!
284 gauss->SetInput( dilate->GetOutput());
286 // shallow copy result into output.
287 // Could be re-written, as we know there is no vert neither strips...
288 OuterMold->ShallowCopy( tf->GetOutput() );
290 //OuterMold->Squeeze(); //usefull ?
293 imagedata -> Delete();
301 //TODO: if this filter is used many times when working on different image size
302 //I guess we need to find something brighter for this->ClipImageData
307 //----------------------------------------------------------------------------
308 //We need to add a function that from the raw data is able to clip with
309 //the begining and ending of polydata to produce sharp edge.
310 void vtkImagePolyDataSeedConnectivity::ClipImageWithAxis()
312 //no input need we'll work with member data:
313 //Now we only need the region growing part:
315 * Instead of working directly on leo we might remove the heart by a trick:
316 * If we could cut with a disk at place where the last(first) axis point is
317 * we could avoid the flood fill algo to spread in the heart...smart !
321 * First we construct a virtual disk (=half sphere for now)
322 * See: Disk-Stencil.py
328 * Get first point (p2) and second point (p1) : assume it exists !
329 * Then calculate vector p2->p1
331 * use it to define intersection plane between sphere and plane
333 this->Axis->Update();
334 vtkPoints *points = this->Axis->GetPoints();
335 int numPts = points->GetNumberOfPoints();
339 points->GetPoint( 0, p2 );
340 points->GetPoint( 1, p1 );
342 normal[0] = p1[0] - p2[0];
343 normal[1] = p1[1] - p2[1];
344 normal[2] = p1[2] - p2[2];
345 vtkMath::Normalize( normal );
347 vtkPlane *plane1 = vtkPlane::New();
348 plane1->SetOrigin( p2[ 0 ], p2[ 1 ], p2[ 2 ] );
349 plane1->SetNormal( normal[0], normal[1], normal[2]);
351 vtkDataArray* scalars = this->Axis->GetPointData()->GetScalars();
353 scalars->GetTuple(0, tuple);
354 double radius = tuple[0];
356 vtkSphere *sphere1 = vtkSphere::New();
357 sphere1->SetCenter( p2[ 0 ], p2[ 1 ], p2[ 2 ] );
358 sphere1->SetRadius( radius + 1);
360 vtkImplicitBoolean *disk1 = vtkImplicitBoolean::New();
361 disk1->SetOperationTypeToIntersection();
362 disk1->AddFunction(sphere1);
363 disk1->AddFunction(plane1);
367 * Get last point (p2) and before last point (p1) : assume it exists !
368 * Then calculate vector p2->p1
370 * use it to define intersection plane between sphere and plane
373 //ax->getControlPoint(numPts - 1, p2, p2+3);
374 points->GetPoint( numPts - 1, p2);
375 //ax->getControlPoint(numPts - 2, p1, p1+3); //assume it exists
376 points->GetPoint( numPts - 2, p1);
377 normal[0] = p1[0] - p2[0];
378 normal[1] = p1[1] - p2[1];
379 normal[2] = p1[2] - p2[2];
380 vtkMath::Normalize( normal );
382 vtkPlane *plane2 = vtkPlane::New();
383 plane2->SetOrigin( p2[ 0 ], p2[ 1 ], p2[ 2 ] );
384 plane2->SetNormal( normal[0], normal[1], normal[2]);
386 vtkSphere *sphere2 = vtkSphere::New();
387 sphere2->SetCenter( p2[ 0 ], p2[ 1 ], p2[ 2 ] );
388 sphere2->SetRadius( radius + 1 );
390 vtkImplicitBoolean *disk2 = vtkImplicitBoolean::New();
391 disk2->SetOperationTypeToIntersection();
392 disk2->AddFunction(sphere2);
393 disk2->AddFunction(plane2);
395 vtkImplicitBoolean *disk = vtkImplicitBoolean::New();
396 disk->SetOperationTypeToUnion();
397 disk->AddFunction(disk1);
398 disk->AddFunction(disk2);
401 * Now use dgobbi class: Stencil stuff to remove the 'bad' heart part
404 vtkImplicitFunctionToImageStencil *functionToStencil = vtkImplicitFunctionToImageStencil::New ();
405 functionToStencil->SetInput ( disk );
407 vtkImageStencil *stencil = vtkImageStencil::New();
408 stencil->SetInput( this->GetInput() );
409 stencil->SetStencil( functionToStencil->GetOutput() );
410 stencil->ReverseStencilOn();
411 stencil->SetBackgroundValue (0);
412 //don't forget to call ->Update before using an iterator !!!
415 this->ClipImageData->ShallowCopy( stencil->GetOutput() );
418 * Should I do my own vtkCappedCylinder ??
419 * solution: no , cause ... I can't remember why... :p
430 functionToStencil -> Delete();
433 //----------------------------------------------------------------------------
434 void vtkImagePolyDataSeedConnectivity::PrintSelf(ostream& os, vtkIndent indent)
436 this->Superclass::PrintSelf(os,indent);
439 os << indent << "Axis of " << this->Axis->GetNumberOfPoints()
440 << "points defined\n";
444 os << indent << "Axis not defined\n";