]> Creatis software - gdcm.git/blob - vtk/vtkGdcmReader.cxx
*ENH: Update CMakeLists
[gdcm.git] / vtk / vtkGdcmReader.cxx
1 // $Header: /cvs/public/gdcm/vtk/vtkGdcmReader.cxx,v 1.27 2003/11/12 14:06:36 malaterre Exp $
2 // //////////////////////////////////////////////////////////////
3 // WARNING TODO CLENAME 
4 // Actual limitations of this code:
5 //
6 // /////// Redundant and unnecessary header parsing
7 // In it's current state this code actually parses three times the Dicom
8 // header of a file before the corresponding image gets loaded in the
9 // ad-hoc vtkData !
10 // Here is the process:
11 //  1/ First loading happens in ExecuteInformation which in order to
12 //     positionate the vtk extents calls CheckFileCoherence. The purpose
13 //     of CheckFileCoherence is to make sure all the images in the future
14 //     stack are "homogenous" (same size, same representation...). This
15 //     can only be achieved by parsing all the Dicom headers...
16 //  2/ ExecuteData is then responsible for the next two loadings:
17 //  2a/ ExecuteData calls AllocateOutputData that in turn seems to 
18 //      (indirectely call) ExecuteInformation which ends up in a second
19 //      header parsing
20 //  2b/ the core of ExecuteData then needs gdcmFile (which in turns
21 //      initialises gdcmHeader in the constructor) in order to access
22 //      the data-image.
23 //
24 // Possible solution:
25 // maintain a list of gdcmFiles (created by say ExecuteInformation) created
26 // once and for all accross the life of vtkGdcmHeader (it would only load
27 // new gdcmFile if the user changes the list). ExecuteData would then use 
28 // those gdcmFile and hence avoid calling the construtor:
29 //  - advantage: the header of the files would only be parser once.
30 //  - drawback: once execute information is called (i.e. on creation of
31 //              a vtkGdcmHeader) the gdcmFile structure is loaded in memory.
32 //              The average size of a gdcmHeader being of 100Ko, is one
33 //              loads 10 stacks of images with say 200 images each, you
34 //              end-up with a loss of 200Mo...
35 //
36 // /////// Never unallocated memory:
37 // ExecuteData allocates space for the pixel data [which will get pointed
38 // by the vtkPointData() through the call
39 // data->GetPointData()->GetScalars()->SetVoidArray(mem, StackNumPixels, 0);]
40 // This data is never "freed" neither in the destructor nor when the
41 // filename list is extended, ExecuteData is called a second (or third)
42 // time...
43 // //////////////////////////////////////////////////////////////
44
45 #include "gdcmFile.h"
46 #include "gdcmHeaderHelper.h"
47 #include "vtkGdcmReader.h"
48
49 //#include <stdio.h>
50 #include <vtkObjectFactory.h>
51 #include <vtkImageData.h>
52 #include <vtkPointData.h>
53 #include <vtkLookupTable.h>
54
55
56 vtkGdcmReader::vtkGdcmReader()
57 {
58   // Constructor
59   this->LookupTable = NULL;
60 }
61
62 //----------------------------------------------------------------------------
63 vtkGdcmReader::~vtkGdcmReader()
64
65   this->RemoveAllFileName();
66   this->InternalFileNameList.clear();
67   if(this->LookupTable) this->LookupTable->Delete();
68 }
69
70 //----------------------------------------------------------------------------
71 // Remove all files from the list of images to read.
72 void vtkGdcmReader::RemoveAllFileName(void)
73 {
74   this->FileNameList.clear();
75 }
76
77 //----------------------------------------------------------------------------
78 // Adds a file name to the list of images to read.
79 void vtkGdcmReader::AddFileName(const char* name)
80 {
81   // We need to bypass the const pointer [since list<>.push_bash() only
82   // takes a char* (but not a const char*)] by making a local copy:
83   char * LocalName = new char[strlen(name) + 1];
84   strcpy(LocalName, name);
85   this->FileNameList.push_back(LocalName);
86   this->Modified();
87   delete[] LocalName;
88 }
89
90 //----------------------------------------------------------------------------
91 // Sets up a filename to be read.
92 void vtkGdcmReader::SetFileName(const char *name) {
93   vtkImageReader2::SetFileName(name);
94   // Since we maintain a list of filenames, when building a volume,
95   // (see vtkGdcmReader::AddFileName), we additionaly need to purge
96   // this list when we manually positionate the filename.
97   this->FileNameList.clear();
98   this->Modified();
99 }
100
101 //----------------------------------------------------------------------------
102 // Adds a file name to the internal list of images to read.
103 void vtkGdcmReader::RemoveAllInternalFileName(void)
104 {
105   this->InternalFileNameList.clear();
106 }
107
108 //----------------------------------------------------------------------------
109 // Adds a file name to the internal list of images to read.
110 void vtkGdcmReader::AddInternalFileName(const char* name)
111 {
112   char * LocalName = new char[strlen(name) + 1];
113   strcpy(LocalName, name);
114   this->InternalFileNameList.push_back(LocalName);
115   delete[] LocalName;
116 }
117
118 //----------------------------------------------------------------------------
119 // vtkGdcmReader can have the file names specified through two ways:
120 // (1) by calling the vtkImageReader2::SetFileName(), SetFilePrefix() and
121 //     SetFilePattern()
122 // (2) By successive calls to vtkGdcmReader::AddFileName()
123 // When the first method was used by caller we need to update the local
124 // filename list
125 void vtkGdcmReader::BuildFileListFromPattern()
126 {
127    if ((! this->FileNameList.empty()) && this->FileName )
128      {
129      vtkErrorMacro("Both file patterns and AddFileName schemes were used");
130      vtkErrorMacro("Only the files specified with AddFileName shall be used");
131      return;
132      }
133
134    if (! this->FileNameList.empty()  )
135      {
136      vtkDebugMacro("Using the AddFileName specified files");
137           this->InternalFileNameList=this->FileNameList;
138      return;
139      }
140
141    if (!this->FileName && !this->FilePattern)
142      {
143      vtkErrorMacro("FileNames are not set. Either use AddFileName() or");
144      vtkErrorMacro("specify a FileName or FilePattern.");
145      return;
146      }
147
148    this->RemoveAllInternalFileName();
149    if( this->FileNameList.empty() )
150      {
151      //Multiframe case:
152      this->ComputeInternalFileName(this->DataExtent[4]);
153      vtkDebugMacro("Adding file " << this->InternalFileName);
154      this->AddInternalFileName(this->InternalFileName);
155      }
156    else
157      {
158      //stack of 2D dicom case:
159      for (int idx = this->DataExtent[4]; idx <= this->DataExtent[5]; ++idx)
160        {
161        this->ComputeInternalFileName(idx);
162        vtkDebugMacro("Adding file " << this->InternalFileName);
163        this->AddInternalFileName(this->InternalFileName);
164        }
165     }
166 }
167
168 //----------------------------------------------------------------------------
169 // When more than one filename is specified (i.e. we expect loading
170 // a stack or volume) we need to check that the corresponding images/volumes
171 // to be loaded are coherent i.e. to make sure:
172 //     - they all share the same X dimensions
173 //     - they all share the same Y dimensions
174 //     - they all share the same ImageType ( 8 bit signed, or unsigned...)
175 //
176 // Eventually, we emit a warning when all the files do NOT share the
177 // Z dimension, since we can still build a stack but the
178 // files are not coherent in Z, which is probably a source a trouble...
179 //   When files are not readable (either the file cannot be opened or
180 // because gdcm cannot parse it), they are flagged as "GDCM_UNREADABLE".  
181 //   This method returns the total number of planar images to be loaded
182 // (i.e. an image represents one plane, but a volume represents many planes)
183 int vtkGdcmReader::CheckFileCoherence()
184 {
185    int ReturnedTotalNumberOfPlanes = 0;   // The returned value.
186
187    this->BuildFileListFromPattern();
188    if (this->InternalFileNameList.empty())
189      {
190      vtkErrorMacro("FileNames are not set.");
191      return 0;
192      }
193
194    bool FoundReferenceFile = false;
195    int  ReferenceNZ = 0;
196
197    // Loop on the filenames:
198    // - check for their existence and gdcm "parsability"
199    // - get the coherence check done:
200    for (std::list<std::string>::iterator FileName  = InternalFileNameList.begin();
201                                         FileName != InternalFileNameList.end();
202                                       ++FileName)
203      {
204      // The file is always added in the number of planes
205      //  - If file doesn't exist, it will be replaced by a black plane in the 
206      //    ExecuteData method
207      //  - If file has more than 1 plane, other planes will be added later to
208      //    to the ReturnedTotalNumberOfPlanes variable counter
209      ReturnedTotalNumberOfPlanes += 1;
210
211      /////// Stage 0: check for file name:
212           if(*FileName==std::string("GDCM_UNREADABLE"))
213                   continue;
214
215      /////// Stage 1: check for file readability:
216      // Stage 1.1: check for file existence.
217      FILE *fp;
218      fp = fopen(FileName->c_str(),"rb");
219      if (!fp)
220        {
221        vtkErrorMacro("Unable to open file " << FileName->c_str());
222        vtkErrorMacro("Removing this file from readed files "
223                      << FileName->c_str());
224        *FileName = "GDCM_UNREADABLE";
225        continue;
226        }
227      fclose(fp);
228    
229      // Stage 1.2: check for Gdcm parsability
230      gdcmHeaderHelper GdcmHeader(FileName->c_str());
231      if (!GdcmHeader.IsReadable())
232        {
233        vtkErrorMacro("Gdcm cannot parse file " << FileName->c_str());
234        vtkErrorMacro("Removing this file from readed files "
235                      << FileName->c_str());
236        *FileName = "GDCM_UNREADABLE";
237        continue;
238        }
239
240      // Stage 1.3: further gdcm compatibility on PixelType
241      std::string type = GdcmHeader.GetPixelType();
242      if (   (type !=  "8U") && (type !=  "8S")
243          && (type != "16U") && (type != "16S")
244          && (type != "32U") && (type != "32S") )
245        {
246        vtkErrorMacro("Bad File Type for file" << FileName->c_str());
247        vtkErrorMacro("                      " << type.c_str());
248        vtkErrorMacro("Removing this file from readed files "
249                      << FileName->c_str());
250        *FileName = "GDCM_UNREADABLE";
251        continue;
252        }
253
254      /////// Stage 2: check coherence of the set of files
255      int NX = GdcmHeader.GetXSize();
256      int NY = GdcmHeader.GetYSize();
257      int NZ = GdcmHeader.GetZSize();
258      if (FoundReferenceFile) 
259        {
260         
261        // Stage 2.1: mandatory coherence stage:
262        if (   ( NX   != this->NumColumns )
263            || ( NY   != this->NumLines )
264            || ( type != this->ImageType ) ) 
265          {
266          vtkErrorMacro("This file is not coherent with previous ones"
267                        << FileName->c_str());
268          vtkErrorMacro("Removing this file from readed files "
269                        << FileName->c_str());
270          *FileName = "GDCM_UNREADABLE";
271          continue;
272          }
273
274        // Stage 2.2: optional coherence stage
275        if ( NZ != ReferenceNZ )
276          {
277          vtkErrorMacro("File is not coherent in Z with previous ones"
278                        << FileName->c_str());
279          }
280        else
281          {
282          vtkDebugMacro("File is coherent with previous ones"
283                        << FileName->c_str());
284          }
285
286        // Stage 2.3: when the file contains a volume (as opposed to an image),
287        // notify the caller.
288        if (NZ > 1)
289          {
290          vtkErrorMacro("This file contains multiple planes (images)"
291                        << FileName->c_str());
292          }
293
294        // Eventually, this file can be added on the stack. Update the
295        // full size of the stack
296        vtkDebugMacro("Number of planes added to the stack: " << NZ);
297        ReturnedTotalNumberOfPlanes += NZ - 1; // First plane already added
298        continue;
299
300        } else {
301        // We didn't have a workable reference file yet. Set this one
302        // as the reference.
303        FoundReferenceFile = true;
304        vtkDebugMacro("This file taken as coherence reference:"
305                      << FileName->c_str());
306        vtkDebugMacro("Image dimension of reference file as read from Gdcm:" <<
307                      NX << " " << NY << " " << NZ);
308        vtkDebugMacro("Number of planes added to the stack: " << NZ);
309        // Set aside the size of the image
310        this->NumColumns = NX;
311        this->NumLines   = NY;
312        ReferenceNZ      = NZ;
313        ReturnedTotalNumberOfPlanes += NZ - 1; // First plane already added
314        this->ImageType = type;
315        this->PixelSize = GdcmHeader.GetPixelSize();
316        
317        if( GdcmHeader.HasLUT() )
318          {
319          this->NumComponents = GdcmHeader.GetNumberOfScalarComponentsRaw();
320          }
321        else
322          {
323          this->NumComponents = GdcmHeader.GetNumberOfScalarComponents(); //rgb or mono
324          }
325        
326        //Set image spacing
327        this->DataSpacing[0] = GdcmHeader.GetXSpacing();
328        this->DataSpacing[1] = GdcmHeader.GetYSpacing();
329        this->DataSpacing[2] = GdcmHeader.GetZSpacing();
330
331        //Set image origin
332        this->DataOrigin[0] = GdcmHeader.GetXOrigin();
333        this->DataOrigin[1] = GdcmHeader.GetYOrigin();
334        this->DataOrigin[2] = GdcmHeader.GetZOrigin();
335        
336        }
337      } // End of loop on FileName
338
339    ///////// The files we CANNOT load are flaged. On debugging purposes
340    // count the loadable number of files and display their number:
341    int NumberCoherentFiles = 0;
342    for (std::list<std::string>::iterator Filename  = InternalFileNameList.begin();
343                                         Filename != InternalFileNameList.end();
344                                       ++Filename)
345      {
346      if (*Filename != "GDCM_UNREADABLE")
347         NumberCoherentFiles++;    
348      }
349    vtkDebugMacro("Number of coherent files: " << NumberCoherentFiles);
350
351    if (ReturnedTotalNumberOfPlanes == 0)
352      {
353      vtkErrorMacro("No loadable file.");
354      }
355
356    vtkDebugMacro("Total number of planes on the stack: "
357                  << ReturnedTotalNumberOfPlanes);
358    
359         return ReturnedTotalNumberOfPlanes;
360 }
361
362 //----------------------------------------------------------------------------
363 // Configure the output e.g. WholeExtent, spacing, origin, scalar type...
364 void vtkGdcmReader::ExecuteInformation()
365 {
366   this->TotalNumberOfPlanes = this->CheckFileCoherence();
367   if ( this->TotalNumberOfPlanes == 0)
368     {
369        vtkErrorMacro("File set is not coherent. Exiting...");
370        return;
371     }
372       
373   // if the user has not set the extent, but has set the VOI
374   // set the z axis extent to the VOI z axis
375   if (this->DataExtent[4]==0 && this->DataExtent[5] == 0 &&
376      (this->DataVOI[4] || this->DataVOI[5]))
377     {
378     this->DataExtent[4] = this->DataVOI[4];
379     this->DataExtent[5] = this->DataVOI[5];
380     }
381
382   // When the user has set the VOI, check it's coherence with the file content.
383   if (this->DataVOI[0] || this->DataVOI[1] || 
384       this->DataVOI[2] || this->DataVOI[3] ||
385       this->DataVOI[4] || this->DataVOI[5])
386     { 
387     if ((this->DataVOI[0] < 0) ||
388         (this->DataVOI[1] >= this->NumColumns) ||
389         (this->DataVOI[2] < 0) ||
390         (this->DataVOI[3] >= this->NumLines) ||
391         (this->DataVOI[4] < 0) ||
392         (this->DataVOI[5] >= this->TotalNumberOfPlanes ))
393       {
394       vtkWarningMacro("The requested VOI is larger than expected extent.");
395       this->DataVOI[0] = 0;
396       this->DataVOI[1] = this->NumColumns - 1;
397       this->DataVOI[2] = 0;
398       this->DataVOI[3] = this->NumLines - 1;
399       this->DataVOI[4] = 0;
400       this->DataVOI[5] = this->TotalNumberOfPlanes - 1;
401       }
402     }
403
404   // Positionate the Extent.
405   this->DataExtent[0] = 0;
406   this->DataExtent[1] = this->NumColumns - 1;
407   this->DataExtent[2] = 0;
408   this->DataExtent[3] = this->NumLines - 1;
409   this->DataExtent[4] = 0;
410   this->DataExtent[5] = this->TotalNumberOfPlanes - 1;
411   
412   // We don't need to positionate the Endian related stuff (by using
413   // this->SetDataByteOrderToBigEndian() or SetDataByteOrderToLittleEndian()
414   // since the reading of the file is done by gdcm.
415   // But we do need to set up the data type for downstream filters:
416   if      ( ImageType == "8U" )
417     {
418     vtkDebugMacro("8 bits unsigned image");
419     this->SetDataScalarTypeToUnsignedChar(); 
420     }
421   else if ( ImageType == "8S" )
422     {
423     vtkErrorMacro("Cannot handle 8 bit signed files");
424     return;
425     }
426   else if ( ImageType == "16U" )
427     {
428     vtkDebugMacro("16 bits unsigned image");
429     this->SetDataScalarTypeToUnsignedShort();
430     }
431   else if ( ImageType == "16S" )
432     {
433     vtkDebugMacro("16 bits signed image");
434     this->SetDataScalarTypeToShort();
435     //vtkErrorMacro("Cannot handle 16 bit signed files");
436     }
437   else if ( ImageType == "32U" )
438     {
439     vtkDebugMacro("32 bits unsigned image");
440     vtkDebugMacro("WARNING: forced to signed int !");
441     this->SetDataScalarTypeToInt();
442     }
443   else if ( ImageType == "32S" )
444     {
445     vtkDebugMacro("32 bits signed image");
446     this->SetDataScalarTypeToInt();
447     }
448
449   //Set number of scalar components:
450   this->SetNumberOfScalarComponents(this->NumComponents);
451
452   vtkImageReader::ExecuteInformation();
453 }
454
455 //----------------------------------------------------------------------------
456 // Loads the contents of the image/volume contained by Filename at
457 // the Dest memory address. Returns the size of the data loaded.
458 size_t vtkGdcmReader::LoadImageInMemory(
459              std::string FileName, 
460              unsigned char * Dest,
461              const unsigned long UpdateProgressTarget,
462              unsigned long & UpdateProgressCount)
463 {
464   vtkDebugMacro("Copying to memory image" << FileName.c_str());
465   gdcmFile GdcmFile(FileName.c_str());
466   size_t size;
467
468   // If the data structure of vtk for image/volume representation
469   // were straigthforwards the following would suffice:
470   //    GdcmFile.GetImageDataIntoVector((void*)Dest, size);
471   // But vtk chooses to invert the lines of an image, that is the last
472   // line comes first (for some axis related reasons?). Hence we need
473   // to load the image line by line, starting from the end.
474   int NumColumns = GdcmFile.GetXSize();
475   int NumLines   = GdcmFile.GetYSize();
476   int NumPlanes  = GdcmFile.GetZSize();
477   int LineSize   = NumComponents * NumColumns * GdcmFile.GetPixelSize();
478
479   unsigned char * Source;
480   if( GdcmFile.HasLUT() )
481     {
482     size               = GdcmFile.GetImageDataSizeRaw();
483     Source             = (unsigned char*) GdcmFile.GetImageDataRaw();
484     unsigned char *Lut = (unsigned char*) GdcmFile.GetLUTRGBA();
485   
486     if(!this->LookupTable) this->LookupTable = vtkLookupTable::New();
487     this->LookupTable->SetNumberOfTableValues(256);
488     for (int tmp=0; tmp<256; tmp++)
489       {
490       this->LookupTable->SetTableValue(tmp,
491         (float)Lut[4*tmp+0]/255.0,
492         (float)Lut[4*tmp+1]/255.0,
493         (float)Lut[4*tmp+2]/255.0,
494         1);
495       }
496     this->LookupTable->SetRange(0,255);
497     vtkDataSetAttributes *a=this->GetOutput()->GetPointData();
498     a->GetScalars()->SetLookupTable(this->LookupTable);
499     free(Lut);
500     }
501   else
502     {
503     size        = GdcmFile.GetImageDataSize();
504     Source      = (unsigned char*)GdcmFile.GetImageData();
505     }
506   unsigned char * pSource     = Source; //pointer for later deletion
507   unsigned char * Destination = Dest + size - LineSize;
508
509   for (int plane = 0; plane < NumPlanes; plane++)
510     {
511     for (int line = 0; line < NumLines; line++)
512       {
513       // Copy one line at proper destination:
514       memcpy((void*)Destination, (void*)Source, LineSize);
515       Source      += LineSize;
516       Destination -= LineSize;
517       // Update progress related:
518       if (!(UpdateProgressCount%UpdateProgressTarget))
519         {
520         this->UpdateProgress(UpdateProgressCount/(50.0*UpdateProgressTarget));
521         }
522       UpdateProgressCount++;
523       }
524     }
525   //GetImageData allocate a (void*)malloc, remove it:
526   free(pSource);
527
528   return size;
529 }
530
531 //----------------------------------------------------------------------------
532 // Update => ouput->Update => UpdateData => Execute => ExecuteData 
533 // (see vtkSource.cxx for last step).
534 // This function (redefinition of vtkImageReader::ExecuteData, see 
535 // VTK/IO/vtkImageReader.cxx) reads a data from a file. The datas
536 // extent/axes are assumed to be the same as the file extent/order.
537 void vtkGdcmReader::ExecuteData(vtkDataObject *output)
538 {
539   if (this->InternalFileNameList.empty())
540     {
541     vtkErrorMacro("A least a valid FileName must be specified.");
542     return;
543     }
544
545   // FIXME : extraneous parsing of header is made when allocating OuputData
546   vtkImageData *data = this->AllocateOutputData(output);
547   data->SetExtent(this->DataExtent);
548   data->GetPointData()->GetScalars()->SetName("DicomImage-Volume");
549
550   // Test if output has valid extent
551   // Prevent memory errors
552   if((this->DataExtent[1]-this->DataExtent[0]>=0) &&
553      (this->DataExtent[3]-this->DataExtent[2]>=0) &&
554      (this->DataExtent[5]-this->DataExtent[4]>=0))
555     {
556     // The memory size for a full stack of images of course depends
557     // on the number of planes and the size of each image:
558     size_t StackNumPixels = this->NumColumns * this->NumLines
559                           * this->TotalNumberOfPlanes * this->NumComponents;
560     size_t stack_size = StackNumPixels * this->PixelSize;
561     // Allocate pixel data space itself.
562     unsigned char *mem = new unsigned char [stack_size];
563
564     // Variables for the UpdateProgress. We shall use 50 steps to signify
565     // the advance of the process:
566     unsigned long UpdateProgressTarget = (unsigned long) ceil (this->NumLines
567                                        * this->TotalNumberOfPlanes
568                                        / 50.0);
569     // The actual advance measure:
570     unsigned long UpdateProgressCount = 0;
571
572     // Feeling the allocated memory space with each image/volume:
573     unsigned char * Dest = mem;
574     for (std::list<std::string>::iterator FileName  = InternalFileNameList.begin();
575                                           FileName != InternalFileNameList.end();
576                                         ++FileName)
577       { 
578       // Images that were tagged as unreadable in CheckFileCoherence()
579       // are substituted with a black image to let the caller visually
580       // notice something wrong is going on:
581       if (*FileName != "GDCM_UNREADABLE")
582         {
583         // Update progress related for good files is made in LoadImageInMemory
584         Dest += this->LoadImageInMemory(*FileName, Dest,
585                                         UpdateProgressTarget,
586                                         UpdateProgressCount);
587         } else {
588         // We insert a black image in the stack for the user to be aware that
589         // this image/volume couldn't be loaded. We simply skip one image
590         // size:
591         Dest += this->NumColumns * this->NumLines * this->PixelSize;
592
593         // Update progress related for bad files:
594         UpdateProgressCount += this->NumLines;
595         if (UpdateProgressTarget > 0)
596                       {
597           if (!(UpdateProgressCount%UpdateProgressTarget))
598             {
599                         this->UpdateProgress(UpdateProgressCount/(50.0*UpdateProgressTarget));
600             }
601                       }
602         } // Else, file not loadable
603       } // Loop on files
604
605     // The "size" of the vtkScalars data is expressed in number of points,
606     // and is not the memory size representing those points:
607     data->GetPointData()->GetScalars()->SetVoidArray(mem, StackNumPixels, 0);
608     //don't know why it's here, it's calling one more time ExecuteInformation:
609     //this->Modified();
610     }
611 }
612
613 //----------------------------------------------------------------------------
614 void vtkGdcmReader::PrintSelf(ostream& os, vtkIndent indent)
615 {
616   vtkImageReader::PrintSelf(os,indent);
617   os << indent << "Filenames  : " << endl;
618   vtkIndent nextIndent = indent.GetNextIndent();
619   for (std::list<std::string>::iterator FileName  = FileNameList.begin();
620                                         FileName != FileNameList.end();
621                                       ++FileName)
622     {
623     os << nextIndent << FileName->c_str() << endl ;
624     }
625 }