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