]> Creatis software - gdcm.git/blob - src/gdcmFile.cxx
60c86429471f044929d325fd96a35d72c336410d
[gdcm.git] / src / gdcmFile.cxx
1   /*=========================================================================
2                                                                                 
3   Program:   gdcm
4   Module:    $RCSfile: gdcmFile.cxx,v $
5   Language:  C++
6   Date:      $Date: 2004/11/25 10:24:34 $
7   Version:   $Revision: 1.163 $
8                                                                                 
9   Copyright (c) CREATIS (Centre de Recherche et d'Applications en Traitement de
10   l'Image). All rights reserved. See Doc/License.txt or
11   http://www.creatis.insa-lyon.fr/Public/Gdcm/License.html for details.
12                                                                                 
13      This software is distributed WITHOUT ANY WARRANTY; without even
14      the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15      PURPOSE.  See the above copyright notices for more information.
16                                                                                 
17 =========================================================================*/
18
19 #include "gdcmFile.h"
20 #include "gdcmDebug.h"
21 #include "gdcmUtil.h"
22 #include "gdcmBinEntry.h"
23 #include <fstream>
24
25 namespace gdcm 
26 {
27 typedef std::pair<TagDocEntryHT::iterator,TagDocEntryHT::iterator> IterHT;
28
29 //-------------------------------------------------------------------------
30 // Constructor / Destructor
31 /**
32  * \brief Constructor dedicated to deal with the *pixels* area of a ACR/DICOMV3
33  *        file (Header only deals with the ... header)
34  *        Opens (in read only and when possible) an existing file and checks
35  *        for DICOM compliance. Returns NULL on failure.
36  *        It will be up to the user to load the pixels into memory
37  *        (see GetImageData, GetImageDataRaw)
38  * \note  the in-memory representation of all available tags found in
39  *        the DICOM header is post-poned to first header information access.
40  *        This avoid a double parsing of public part of the header when
41  *        user sets an a posteriori shadow dictionary (efficiency can be
42  *        seen as a side effect).   
43  * @param header already built Header
44  */
45 File::File(Header *header)
46 {
47    HeaderInternal = header;
48    SelfHeader = false;
49    Initialise();
50 }
51
52 /**
53  * \brief Constructor dedicated to deal with the *pixels* area of a ACR/DICOMV3
54  *        file (Header only deals with the ... header)
55  *        Opens (in read only and when possible) an existing file and checks
56  *        for DICOM compliance. Returns NULL on failure.
57  *        It will be up to the user to load the pixels into memory
58  *        (see GetImageData, GetImageDataRaw)
59  * \note  the in-memory representation of all available tags found in
60  *        the DICOM header is post-poned to first header information access.
61  *        This avoid a double parsing of public part of the header when
62  *        one sets an a posteriori shadow dictionary (efficiency can be
63  *        seen as a side effect).   
64  * @param filename file to be opened for parsing
65  */
66 File::File(std::string const & filename )
67 {
68    HeaderInternal = new Header( filename );
69    SelfHeader = true;
70    Initialise();
71 }
72
73 /**
74  * \brief Factorization for various forms of constructors.
75  */
76 void File::Initialise()
77 {
78    WriteMode = WMODE_DECOMPRESSED;
79    WriteType = WTYPE_IMPL_VR;
80
81    PixelConverter = new PixelConvert;
82    Archive = new DocEntryArchive( HeaderInternal );
83
84    if ( HeaderInternal->IsReadable() )
85    {
86       PixelConverter->GrabInformationsFromHeader( HeaderInternal );
87    }
88
89    Pixel_Data = 0;
90    ImageDataSize = 0;
91 }
92
93 /**
94  * \brief canonical destructor
95  * \note  If the Header was created by the File constructor,
96  *        it is destroyed by the File
97  */
98 File::~File()
99
100    if( PixelConverter )
101    {
102       delete PixelConverter;
103    }
104    if( Archive )
105    {
106       delete Archive;
107    }
108
109    if( SelfHeader )
110    {
111       delete HeaderInternal;
112    }
113    HeaderInternal = 0;
114 }
115
116 //-----------------------------------------------------------------------------
117 // Print
118
119 //-----------------------------------------------------------------------------
120 // Public
121 /**
122  * \brief   Get the size of the image data
123  * 
124  *          If the image can be RGB (with a lut or by default), the size 
125  *          corresponds to the RGB image
126  * @return  The image size
127  */
128 size_t File::GetImageDataSize()
129 {
130    return PixelConverter->GetRGBSize();
131 }
132
133 /**
134  * \brief   Get the size of the image data
135  * 
136  *          If the image can be RGB by transformation in a LUT, this
137  *          transformation isn't considered
138  * @return  The raw image size
139  */
140 size_t File::GetImageDataRawSize()
141 {
142    return PixelConverter->GetDecompressedSize();
143 }
144
145 /**
146  * \brief   - Allocates necessary memory, 
147  *          - Reads the pixels from disk (uncompress if necessary),
148  *          - Transforms YBR pixels, if any, into RGB pixels
149  *          - Transforms 3 planes R, G, B, if any, into a single RGB Plane
150  *          - Transforms single Grey plane + 3 Palettes into a RGB Plane
151  *          - Copies the pixel data (image[s]/volume[s]) to newly allocated zone.
152  * @return  Pointer to newly allocated pixel data.
153  *          NULL if alloc fails 
154  */
155 uint8_t* File::GetImageData()
156 {
157    if ( ! GetDecompressed() )
158    {
159       // If the decompression failed nothing can be done.
160       return 0;
161    }
162
163    if ( HeaderInternal->HasLUT() && PixelConverter->BuildRGBImage() )
164    {
165       return PixelConverter->GetRGB();
166    }
167    else
168    {
169       // When no LUT or LUT conversion fails, return the decompressed
170       return PixelConverter->GetDecompressed();
171    }
172 }
173
174 /**
175  * \brief
176  *          Read the pixels from disk (uncompress if necessary),
177  *          Transforms YBR pixels, if any, into RGB pixels
178  *          Transforms 3 planes R, G, B, if any, into a single RGB Plane
179  *          Transforms single Grey plane + 3 Palettes into a RGB Plane   
180  *          Copies at most MaxSize bytes of pixel data to caller allocated
181  *          memory space.
182  * \warning This function allows people that want to build a volume
183  *          from an image stack *not to* have, first to get the image pixels, 
184  *          and then move them to the volume area.
185  *          It's absolutely useless for any VTK user since vtk chooses 
186  *          to invert the lines of an image, that is the last line comes first
187  *          (for some axis related reasons?). Hence he will have 
188  *          to load the image line by line, starting from the end.
189  *          VTK users have to call GetImageData
190  *     
191  * @param   destination Address (in caller's memory space) at which the
192  *          pixel data should be copied
193  * @param   maxSize Maximum number of bytes to be copied. When MaxSize
194  *          is not sufficient to hold the pixel data the copy is not
195  *          executed (i.e. no partial copy).
196  * @return  On success, the number of bytes actually copied. Zero on
197  *          failure e.g. MaxSize is lower than necessary.
198  */
199 size_t File::GetImageDataIntoVector (void* destination, size_t maxSize)
200 {
201    if ( ! GetDecompressed() )
202    {
203       // If the decompression failed nothing can be done.
204       return 0;
205    }
206
207    if ( HeaderInternal->HasLUT() && PixelConverter->BuildRGBImage() )
208    {
209       if ( PixelConverter->GetRGBSize() > maxSize )
210       {
211          dbg.Verbose(0, "File::GetImageDataIntoVector: pixel data bigger"
212                         "than caller's expected MaxSize");
213          return 0;
214       }
215       memcpy( destination,
216               (void*)PixelConverter->GetRGB(),
217               PixelConverter->GetRGBSize() );
218       return PixelConverter->GetRGBSize();
219    }
220
221    // Either no LUT conversion necessary or LUT conversion failed
222    if ( PixelConverter->GetDecompressedSize() > maxSize )
223    {
224       dbg.Verbose(0, "File::GetImageDataIntoVector: pixel data bigger"
225                      "than caller's expected MaxSize");
226       return 0;
227    }
228    memcpy( destination,
229            (void*)PixelConverter->GetDecompressed(),
230            PixelConverter->GetDecompressedSize() );
231    return PixelConverter->GetDecompressedSize();
232 }
233
234 /**
235  * \brief   Allocates necessary memory, 
236  *          Transforms YBR pixels (if any) into RGB pixels
237  *          Transforms 3 planes R, G, B  (if any) into a single RGB Plane
238  *          Copies the pixel data (image[s]/volume[s]) to newly allocated zone. 
239  *          DOES NOT transform Grey plane + 3 Palettes into a RGB Plane
240  * @return  Pointer to newly allocated pixel data.
241  * \        NULL if alloc fails 
242  */
243 uint8_t* File::GetImageDataRaw ()
244 {
245    return GetDecompressed();
246 }
247
248 uint8_t* File::GetDecompressed()
249 {
250    uint8_t* decompressed = PixelConverter->GetDecompressed();
251    if ( ! decompressed )
252    {
253       // The decompressed image migth not be loaded yet:
254       std::ifstream* fp = HeaderInternal->OpenFile();
255       PixelConverter->ReadAndDecompressPixelData( fp );
256       if(fp) 
257          HeaderInternal->CloseFile();
258
259       decompressed = PixelConverter->GetDecompressed();
260       if ( ! decompressed )
261       {
262          dbg.Verbose(0, "File::GetDecompressed: read/decompress of "
263                         "pixel data apparently went wrong.");
264          return 0;
265       }
266    }
267
268    return decompressed;
269 }
270
271 /**
272  * \brief   Points the internal Pixel_Data pointer to the callers inData
273  *          image representation, BUT WITHOUT COPYING THE DATA.
274  *          'image' Pixels are presented as C-like 2D arrays : line per line.
275  *          'volume'Pixels are presented as C-like 3D arrays : plane per plane 
276  * \warning Since the pixels are not copied, it is the caller's responsability
277  *          not to deallocate it's data before gdcm uses them (e.g. with
278  *          the Write() method.
279  * @param inData user supplied pixel area
280  * @param expectedSize total image size, in Bytes
281  *
282  * @return boolean
283  */
284 bool File::SetImageData(uint8_t* inData, size_t expectedSize)
285 {
286 // FIXME : if already allocated, memory leak !
287    Pixel_Data     = inData;
288    ImageDataSize = expectedSize;
289 // FIXME : 7fe0, 0010 IS NOT set ...
290    return true;
291 }
292
293 /**
294  * \brief Writes on disk A SINGLE Dicom file
295  *        NO test is performed on  processor "Endiannity".
296  *        It's up to the user to call his Reader properly
297  * @param fileName name of the file to be created
298  *                 (any already existing file is over written)
299  * @return false if write fails
300  */
301
302 bool File::WriteRawData(std::string const & fileName)
303 {
304   std::ofstream fp1(fileName.c_str(), std::ios::out | std::ios::binary );
305    if (!fp1)
306    {
307       dbg.Verbose(2, "Fail to open (write) file:", fileName.c_str());
308       return false;
309    }
310    fp1.write((char*)Pixel_Data, ImageDataSize);
311    fp1.close();
312
313    return true;
314 }
315
316 /**
317  * \brief Writes on disk A SINGLE Dicom file, 
318  *        using the Implicit Value Representation convention
319  *        NO test is performed on  processor "Endiannity".
320  * @param fileName name of the file to be created
321  *                 (any already existing file is overwritten)
322  * @return false if write fails
323  */
324
325 bool File::WriteDcmImplVR (std::string const & fileName)
326 {
327    SetWriteTypeToDcmImplVR();
328    return Write(fileName);
329 }
330
331 /**
332 * \brief Writes on disk A SINGLE Dicom file, 
333  *        using the Explicit Value Representation convention
334  *        NO test is performed on  processor "Endiannity". * @param fileName name of the file to be created
335  *                 (any already existing file is overwritten)
336  * @return false if write fails
337  */
338
339 bool File::WriteDcmExplVR (std::string const & fileName)
340 {
341    SetWriteTypeToDcmExplVR();
342    return Write(fileName);
343 }
344
345 /**
346  * \brief Writes on disk A SINGLE Dicom file, 
347  *        using the ACR-NEMA convention
348  *        NO test is performed on  processor "Endiannity".
349  *        (a l'attention des logiciels cliniques 
350  *        qui ne prennent en entrée QUE des images ACR ...
351  * \warning if a DICOM_V3 header is supplied,
352  *         groups < 0x0008 and shadow groups are ignored
353  * \warning NO TEST is performed on processor "Endiannity".
354  * @param fileName name of the file to be created
355  *                 (any already existing file is overwritten)
356  * @return false if write fails
357  */
358
359 bool File::WriteAcr (std::string const & fileName)
360 {
361    SetWriteTypeToAcr();
362    return Write(fileName);
363 }
364
365 bool File::Write(std::string const& fileName)
366 {
367    switch(WriteType)
368    {
369       case WTYPE_IMPL_VR:
370          return WriteBase(fileName,ImplicitVR);
371       case WTYPE_EXPL_VR:
372          return WriteBase(fileName,ExplicitVR);
373       case WTYPE_ACR:
374          return WriteBase(fileName,ACR);
375    }
376    return(false);
377 }
378
379 /**
380  * \brief Access to the underlying \ref PixelConverter RGBA LUT
381  */
382 uint8_t* File::GetLutRGBA()
383 {
384    return PixelConverter->GetLutRGBA();
385 }
386
387 //-----------------------------------------------------------------------------
388 // Protected
389 /**
390  * \brief NOT a end user inteded function
391  *        (used by WriteDcmExplVR, WriteDcmImplVR, WriteAcr, etc)
392  * @param fileName name of the file to be created
393  *                 (any already existing file is overwritten)
394  * @param  type file type (ExplicitVR, ImplicitVR, ...)
395  * @return false if write fails
396  */
397 bool File::WriteBase (std::string const & fileName, FileType type)
398 {
399    std::ofstream* fp1 = new std::ofstream(fileName.c_str(), 
400                               std::ios::out | std::ios::binary);
401    if (fp1 == NULL)
402    {
403       dbg.Verbose(2, "Failed to open (write) File: " , fileName.c_str());
404       return false;
405    }
406
407    switch(WriteMode)
408    {
409       case WMODE_NATIVE :
410          SetWriteToNative();
411          break;
412       case WMODE_DECOMPRESSED :
413          SetWriteToDecompressed();
414          break;
415       case WMODE_RGB :
416          SetWriteToRGB();
417          break;
418    }
419
420    // --------------------------------------------------------------
421    // Special Patch to allow gdcm to re-write ACR-LibIDO formated images
422    //
423    // if recognition code tells us we dealt with a LibIDO image
424    // we reproduce on disk the switch between lineNumber and columnNumber
425    // just before writting ...
426    /// \todo the best trick would be *change* the recognition code
427    ///       but pb expected if user deals with, e.g. COMPLEX images
428 /*   if ( HeaderInternal->GetFileType() == ACR_LIBIDO)
429    {
430       SetWriteToLibido();
431    }*/
432    // ----------------- End of Special Patch ----------------
433
434    bool check=CheckWriteIntegrity();
435    if(check)
436    {
437       HeaderInternal->Write(fp1,type);
438    }
439
440    // --------------------------------------------------------------
441    // Special Patch to allow gdcm to re-write ACR-LibIDO formated images
442    // 
443    // ...and we restore the Header to be Dicom Compliant again 
444    // just after writting
445 /*   if ( HeaderInternal->GetFileType() == ACR_LIBIDO )
446    {
447       RestoreWriteFromLibido();
448    }*/
449    // ----------------- End of Special Patch ----------------
450
451    RestoreWrite();
452
453    fp1->close();
454    delete fp1;
455
456    return check;
457 }
458
459 /**
460  * \brief Check the write integrity
461  *
462  * The tests made are :
463  *  - verify the size of the image to write with the possible write
464  *    when the user set an image data
465  * @return true if the check successfulls
466  */
467 bool File::CheckWriteIntegrity()
468 {
469    if(Pixel_Data)
470    {
471       switch(WriteMode)
472       {
473          case WMODE_NATIVE :
474             break;
475          case WMODE_DECOMPRESSED :
476             if(GetImageDataRawSize()!=ImageDataSize)
477             {
478                std::cerr<<"RAW : "<<GetImageDataRawSize()<<" / "<<ImageDataSize<<std::endl;
479                return false;
480             }
481             break;
482          case WMODE_RGB :
483             if(GetImageDataSize()!=ImageDataSize)
484             {
485                std::cerr<<"RGB : "<<GetImageDataSize()<<" / "<<ImageDataSize<<std::endl;
486                return false;
487             }
488             break;
489       }
490    }
491    
492    return true;
493 }
494
495 void File::SetWriteToNative()
496 {
497    if(Pixel_Data)
498    {
499       BinEntry* pixel = CopyBinEntry(GetHeader()->GetGrPixel(),GetHeader()->GetNumPixel());
500       pixel->SetValue(GDCM_BINLOADED);
501       pixel->SetBinArea(Pixel_Data,false);
502       pixel->SetLength(ImageDataSize);
503
504       Archive->Push(pixel);
505    }
506 }
507
508 void File::SetWriteToDecompressed()
509 {
510    if(HeaderInternal->GetNumberOfScalarComponents()==3 && !HeaderInternal->HasLUT())
511    {
512       SetWriteToRGB();
513    } 
514    else
515    {
516       ValEntry* photInt = CopyValEntry(0x0028,0x0004);
517       if(HeaderInternal->HasLUT())
518       {
519          photInt->SetValue("PALETTE COLOR ");
520          photInt->SetLength(14);
521       }
522       else
523       {
524          photInt->SetValue("MONOCHROME1 ");
525          photInt->SetLength(12);
526       }
527
528       BinEntry* pixel = CopyBinEntry(GetHeader()->GetGrPixel(),GetHeader()->GetNumPixel());
529       pixel->SetValue(GDCM_BINLOADED);
530       if(Pixel_Data)
531       {
532          pixel->SetBinArea(Pixel_Data,false);
533          pixel->SetLength(ImageDataSize);
534       }
535       else
536       {
537          pixel->SetBinArea(PixelConverter->GetDecompressed(),false);
538          pixel->SetLength(PixelConverter->GetDecompressedSize());
539       }
540
541       Archive->Push(photInt);
542       Archive->Push(pixel);
543    }
544 }
545
546 void File::SetWriteToRGB()
547 {
548    if(HeaderInternal->GetNumberOfScalarComponents()==3)
549    {
550       PixelConverter->BuildRGBImage();
551       
552       ValEntry* spp = CopyValEntry(0x0028,0x0002);
553       spp->SetValue("3 ");
554       spp->SetLength(2);
555
556       ValEntry* planConfig = CopyValEntry(0x0028,0x0006);
557       planConfig->SetValue("0 ");
558       planConfig->SetLength(2);
559
560       ValEntry* photInt = CopyValEntry(0x0028,0x0004);
561       photInt->SetValue("RGB ");
562       photInt->SetLength(4);
563
564       BinEntry* pixel = CopyBinEntry(GetHeader()->GetGrPixel(),GetHeader()->GetNumPixel());
565       pixel->SetValue(GDCM_BINLOADED);
566       if(Pixel_Data)
567       {
568          pixel->SetBinArea(Pixel_Data,false);
569          pixel->SetLength(ImageDataSize);
570       }
571       else if(PixelConverter->GetRGB())
572       {
573          pixel->SetBinArea(PixelConverter->GetRGB(),false);
574          pixel->SetLength(PixelConverter->GetRGBSize());
575       }
576       else // Decompressed data
577       {
578          pixel->SetBinArea(PixelConverter->GetDecompressed(),false);
579          pixel->SetLength(PixelConverter->GetDecompressedSize());
580       }
581
582       Archive->Push(spp);
583       Archive->Push(planConfig);
584       Archive->Push(photInt);
585       Archive->Push(pixel);
586
587       // Remove any LUT
588       Archive->Push(0x0028,0x1101);
589       Archive->Push(0x0028,0x1102);
590       Archive->Push(0x0028,0x1103);
591       Archive->Push(0x0028,0x1201);
592       Archive->Push(0x0028,0x1202);
593       Archive->Push(0x0028,0x1203);
594
595       // For old ACR-NEMA
596       // Thus, we have a RGB image and the bits allocated = 24 and 
597       // samples per pixels = 1 (in the read file)
598       if(HeaderInternal->GetBitsAllocated()==24) 
599       {
600          ValEntry* bitsAlloc = CopyValEntry(0x0028,0x0100);
601          bitsAlloc->SetValue("8 ");
602          bitsAlloc->SetLength(2);
603
604          ValEntry* bitsStored = CopyValEntry(0x0028,0x0101);
605          bitsStored->SetValue("8 ");
606          bitsStored->SetLength(2);
607
608          ValEntry* highBit = CopyValEntry(0x0028,0x0102);
609          highBit->SetValue("7 ");
610          highBit->SetLength(2);
611
612          Archive->Push(bitsAlloc);
613          Archive->Push(bitsStored);
614          Archive->Push(highBit);
615       }
616    }
617    else
618    {
619       SetWriteToDecompressed();
620    }
621 }
622
623 void File::RestoreWrite()
624 {
625    Archive->Restore(0x0028,0x0002);
626    Archive->Restore(0x0028,0x0004);
627    Archive->Restore(0x0028,0x0006);
628    Archive->Restore(GetHeader()->GetGrPixel(),GetHeader()->GetNumPixel());
629
630    // For old ACR-NEMA (24 bits problem)
631    Archive->Restore(0x0028,0x0100);
632    Archive->Restore(0x0028,0x0101);
633    Archive->Restore(0x0028,0x0102);
634
635    // For the LUT
636    Archive->Restore(0x0028,0x1101);
637    Archive->Restore(0x0028,0x1102);
638    Archive->Restore(0x0028,0x1103);
639    Archive->Restore(0x0028,0x1201);
640    Archive->Restore(0x0028,0x1202);
641    Archive->Restore(0x0028,0x1203);
642 }
643
644 void File::SetWriteToLibido()
645 {
646    ValEntry *oldRow = dynamic_cast<ValEntry *>(HeaderInternal->GetDocEntryByNumber(0x0028, 0x0010));
647    ValEntry *oldCol = dynamic_cast<ValEntry *>(HeaderInternal->GetDocEntryByNumber(0x0028, 0x0011));
648    
649    if( oldRow && oldCol )
650    {
651       std::string rows, columns; 
652
653       ValEntry *newRow=new ValEntry(oldRow->GetDictEntry());
654       ValEntry *newCol=new ValEntry(oldCol->GetDictEntry());
655
656       newRow->Copy(oldCol);
657       newCol->Copy(oldRow);
658
659       newRow->SetValue(oldCol->GetValue());
660       newCol->SetValue(oldRow->GetValue());
661
662       Archive->Push(newRow);
663       Archive->Push(newCol);
664    }
665 }
666
667 void File::RestoreWriteFromLibido()
668 {
669    Archive->Restore(0x0028,0x0010);
670    Archive->Restore(0x0028,0x0011);
671 }
672
673 ValEntry* File::CopyValEntry(uint16_t group,uint16_t element)
674 {
675    DocEntry* oldE = HeaderInternal->GetDocEntryByNumber(group, element);
676    ValEntry* newE;
677
678    if(oldE)
679    {
680       newE = new ValEntry(oldE->GetDictEntry());
681       newE->Copy(oldE);
682    }
683    else
684    {
685       newE = GetHeader()->NewValEntryByNumber(group,element);
686    }
687
688    return(newE);
689 }
690
691 BinEntry* File::CopyBinEntry(uint16_t group,uint16_t element)
692 {
693    DocEntry* oldE = HeaderInternal->GetDocEntryByNumber(group, element);
694    BinEntry* newE;
695
696    if(oldE)
697    {
698       newE = new BinEntry(oldE->GetDictEntry());
699       newE->Copy(oldE);
700    }
701    else
702    {
703       newE = GetHeader()->NewBinEntryByNumber(group,element);
704    }
705
706
707    return(newE);
708 }
709
710 //-----------------------------------------------------------------------------
711 // Private
712 /**
713  * \brief Set the pixel datas in the good entry of the Header
714  */
715 void File::SetPixelData(uint8_t* data)
716 {
717    GetHeader()->SetEntryByNumber( GDCM_BINLOADED,
718       GetHeader()->GetGrPixel(), GetHeader()->GetNumPixel());
719
720    // Will be 7fe0, 0010 in standard case
721    DocEntry* currentEntry = GetHeader()->GetDocEntryByNumber(GetHeader()->GetGrPixel(), GetHeader()->GetNumPixel());
722    if ( currentEntry )
723    {
724       if ( BinEntry* binEntry = dynamic_cast<BinEntry *>(currentEntry) )
725          // Flag is to false because datas are kept in the gdcmPixelConvert
726          binEntry->SetBinArea( data, false );
727    }
728 }
729
730 //-----------------------------------------------------------------------------
731 } // end namespace gdcm
732