1 /*=========================================================================
4 Module: $RCSfile: gdcmFile.cxx,v $
6 Date: $Date: 2004/10/20 22:31:52 $
7 Version: $Revision: 1.147 $
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.
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.
17 =========================================================================*/
20 #include "gdcmDebug.h"
24 typedef std::pair<TagDocEntryHT::iterator,TagDocEntryHT::iterator> IterHT;
26 //-------------------------------------------------------------------------
27 // Constructor / Destructor
29 * \brief Constructor dedicated to deal with the *pixels* area of a ACR/DICOMV3
30 * file (Header only deals with the ... header)
31 * Opens (in read only and when possible) an existing file and checks
32 * for DICOM compliance. Returns NULL on failure.
33 * It will be up to the user to load the pixels into memory
34 * (see GetImageData, GetImageDataRaw)
35 * \note the in-memory representation of all available tags found in
36 * the DICOM header is post-poned to first header information access.
37 * This avoid a double parsing of public part of the header when
38 * user sets an a posteriori shadow dictionary (efficiency can be
39 * seen as a side effect).
40 * @param header already built Header
42 File::File(Header *header)
44 HeaderInternal = header;
50 * \brief Constructor dedicated to deal with the *pixels* area of a ACR/DICOMV3
51 * file (Header only deals with the ... header)
52 * Opens (in read only and when possible) an existing file and checks
53 * for DICOM compliance. Returns NULL on failure.
54 * It will be up to the user to load the pixels into memory
55 * (see GetImageData, GetImageDataRaw)
56 * \note the in-memory representation of all available tags found in
57 * the DICOM header is post-poned to first header information access.
58 * This avoid a double parsing of public part of the header when
59 * one sets an a posteriori shadow dictionary (efficiency can be
60 * seen as a side effect).
61 * @param filename file to be opened for parsing
63 File::File(std::string const & filename )
65 HeaderInternal = new Header( filename );
71 * \brief Factorization for various forms of constructors.
73 void File::Initialise()
75 if ( HeaderInternal->IsReadable() )
77 ImageDataSizeRaw = ComputeDecompressedPixelDataSizeFromHeader();
78 if ( HeaderInternal->HasLUT() )
80 ImageDataSize = 3 * ImageDataSizeRaw;
84 ImageDataSize = ImageDataSizeRaw;
87 PixelConverter = new PixelConvert;
88 PixelConverter->GrabInformationsFromHeader( HeaderInternal );
94 * \brief canonical destructor
95 * \note If the Header was created by the File constructor,
96 * it is destroyed by the File
102 delete HeaderInternal;
106 DeleteInitialValues();
110 * \brief Sets some initial values for the Constructor
111 * \warning not end user intended
113 void File::SaveInitialValues()
116 PixelRead = -1; // no ImageData read yet.
117 LastAllocatedPixelDataLength = 0;
122 InitialPlanConfig = "";
123 InitialBitsAllocated = "";
126 InitialRedLUTDescr = 0;
127 InitialGreenLUTDescr = 0;
128 InitialBlueLUTDescr = 0;
129 InitialRedLUTData = 0;
130 InitialGreenLUTData = 0;
131 InitialBlueLUTData = 0;
133 if ( HeaderInternal->IsReadable() )
135 // the following values *may* be modified
136 // by File::GetImageDataIntoVectorRaw
137 // we save their initial value.
138 InitialSpp = HeaderInternal->GetEntryByNumber(0x0028,0x0002);
139 InitialPhotInt = HeaderInternal->GetEntryByNumber(0x0028,0x0004);
140 InitialPlanConfig = HeaderInternal->GetEntryByNumber(0x0028,0x0006);
142 InitialBitsAllocated = HeaderInternal->GetEntryByNumber(0x0028,0x0100);
143 InitialHighBit = HeaderInternal->GetEntryByNumber(0x0028,0x0102);
145 // the following entries *may* be removed from the H table
146 // (NOT deleted ...) by File::GetImageDataIntoVectorRaw
147 // we keep a pointer on them.
148 InitialRedLUTDescr = HeaderInternal->GetDocEntryByNumber(0x0028,0x1101);
149 InitialGreenLUTDescr = HeaderInternal->GetDocEntryByNumber(0x0028,0x1102);
150 InitialBlueLUTDescr = HeaderInternal->GetDocEntryByNumber(0x0028,0x1103);
152 InitialRedLUTData = HeaderInternal->GetDocEntryByNumber(0x0028,0x1201);
153 InitialGreenLUTData = HeaderInternal->GetDocEntryByNumber(0x0028,0x1202);
154 InitialBlueLUTData = HeaderInternal->GetDocEntryByNumber(0x0028,0x1203);
159 * \brief restores some initial values
160 * \warning not end user intended
162 void File::RestoreInitialValues()
164 if ( HeaderInternal->IsReadable() )
166 // the following values *may* have been modified
167 // by File::GetImageDataIntoVectorRaw
168 // we restore their initial value.
169 if ( InitialSpp != "")
170 HeaderInternal->SetEntryByNumber(InitialSpp,0x0028,0x0002);
171 if ( InitialPhotInt != "")
172 HeaderInternal->SetEntryByNumber(InitialPhotInt,0x0028,0x0004);
173 if ( InitialPlanConfig != "")
175 HeaderInternal->SetEntryByNumber(InitialPlanConfig,0x0028,0x0006);
176 if ( InitialBitsAllocated != "")
177 HeaderInternal->SetEntryByNumber(InitialBitsAllocated,0x0028,0x0100);
178 if ( InitialHighBit != "")
179 HeaderInternal->SetEntryByNumber(InitialHighBit,0x0028,0x0102);
181 // the following entries *may* be have been removed from the H table
182 // (NOT deleted ...) by File::GetImageDataIntoVectorRaw
185 if (InitialRedLUTDescr)
186 HeaderInternal->AddEntry(InitialRedLUTDescr);
187 if (InitialGreenLUTDescr)
188 HeaderInternal->AddEntry(InitialGreenLUTDescr);
189 if (InitialBlueLUTDescr)
190 HeaderInternal->AddEntry(InitialBlueLUTDescr);
192 if (InitialRedLUTData)
193 HeaderInternal->AddEntry(InitialBlueLUTDescr);
194 if (InitialGreenLUTData)
195 HeaderInternal->AddEntry(InitialGreenLUTData);
196 if (InitialBlueLUTData)
197 HeaderInternal->AddEntry(InitialBlueLUTData);
202 * \brief delete initial values (il they were saved)
203 * of InitialLutDescriptors and InitialLutData
205 void File::DeleteInitialValues()
208 // InitialLutDescriptors and InitialLutData
209 // will have to be deleted if the don't belong any longer
210 // to the Header H table when the header is deleted...
212 if ( InitialRedLUTDescr )
213 delete InitialRedLUTDescr;
215 if ( InitialGreenLUTDescr )
216 delete InitialGreenLUTDescr;
218 if ( InitialBlueLUTDescr )
219 delete InitialBlueLUTDescr;
221 if ( InitialRedLUTData )
222 delete InitialRedLUTData;
224 if ( InitialGreenLUTData != NULL)
225 delete InitialGreenLUTData;
227 if ( InitialBlueLUTData != NULL)
228 delete InitialBlueLUTData;
231 //-----------------------------------------------------------------------------
234 //-----------------------------------------------------------------------------
238 * \brief computes the length (in bytes) we must ALLOCATE to receive the
239 * image(s) pixels (multiframes taken into account)
240 * \warning : it is NOT the group 7FE0 length
241 * (no interest for compressed images).
243 int File::ComputeDecompressedPixelDataSizeFromHeader()
245 // see PS 3.3-2003 : C.7.6.3.2.1
255 // YBR_FULL_422 (no LUT, no Palette)
261 // ex : gdcm-US-ALOKA-16.dcm
262 // 0028|1221 [OW] [Segmented Red Palette Color Lookup Table Data]
263 // 0028|1222 [OW] [Segmented Green Palette Color Lookup Table Data]
264 // 0028|1223 [OW] [Segmented Blue Palette Color Lookup Table Data]
266 // ex : OT-PAL-8-face.dcm
267 // 0028|1201 [US] [Red Palette Color Lookup Table Data]
268 // 0028|1202 [US] [Green Palette Color Lookup Table Data]
269 // 0028|1203 [US] [Blue Palette Color Lookup Table Data]
271 int numberBitsAllocated = HeaderInternal->GetBitsAllocated();
272 // Number of "Bits Allocated" is fixed to 16 when:
273 // - it is not defined (i.e. it's value is 0)
274 // - it's 12, since we will expand the image to 16 bits (see
275 // PixelConvert::ConvertDecompress12BitsTo16Bits() )
276 if ( ( numberBitsAllocated == 0 ) || ( numberBitsAllocated == 12 ) )
278 numberBitsAllocated = 16;
281 int DecompressedSize = HeaderInternal->GetXSize()
282 * HeaderInternal->GetYSize()
283 * HeaderInternal->GetZSize()
284 * ( numberBitsAllocated / 8 )
285 * HeaderInternal->GetSamplesPerPixel();
287 return DecompressedSize;
291 * \brief - Allocates necessary memory,
292 * - Reads the pixels from disk (uncompress if necessary),
293 * - Transforms YBR pixels, if any, into RGB pixels
294 * - Transforms 3 planes R, G, B, if any, into a single RGB Plane
295 * - Transforms single Grey plane + 3 Palettes into a RGB Plane
296 * - Copies the pixel data (image[s]/volume[s]) to newly allocated zone.
297 * @return Pointer to newly allocated pixel data.
298 * NULL if alloc fails
300 uint8_t* File::GetImageData()
303 // I need to deallocate Pixel_Data before doing any allocation:
306 if ( LastAllocatedPixelDataLength != ImageDataSize )
309 Pixel_Data = new uint8_t[ImageDataSize];
313 LastAllocatedPixelDataLength = ImageDataSize;
315 // we load the pixels (and transform grey level + LUT into RGB)
316 GetImageDataIntoVector(Pixel_Data, ImageDataSize);
318 // We say the value *is* loaded.
319 GetHeader()->SetEntryByNumber( GDCM_BINLOADED,
320 GetHeader()->GetGrPixel(), GetHeader()->GetNumPixel());
322 // Will be 7fe0, 0010 in standard case
323 GetHeader()->SetEntryBinAreaByNumber( Pixel_Data,
324 GetHeader()->GetGrPixel(), GetHeader()->GetNumPixel());
326 PixelRead = 0; // no PixelRaw
333 * Read the pixels from disk (uncompress if necessary),
334 * Transforms YBR pixels, if any, into RGB pixels
335 * Transforms 3 planes R, G, B, if any, into a single RGB Plane
336 * Transforms single Grey plane + 3 Palettes into a RGB Plane
337 * Copies at most MaxSize bytes of pixel data to caller allocated
339 * \warning This function allows people that want to build a volume
340 * from an image stack *not to* have, first to get the image pixels,
341 * and then move them to the volume area.
342 * It's absolutely useless for any VTK user since vtk chooses
343 * to invert the lines of an image, that is the last line comes first
344 * (for some axis related reasons?). Hence he will have
345 * to load the image line by line, starting from the end.
346 * VTK users have to call GetImageData
348 * @param destination Address (in caller's memory space) at which the
349 * pixel data should be copied
350 * @param maxSize Maximum number of bytes to be copied. When MaxSize
351 * is not sufficient to hold the pixel data the copy is not
352 * executed (i.e. no partial copy).
353 * @return On success, the number of bytes actually copied. Zero on
354 * failure e.g. MaxSize is lower than necessary.
356 size_t File::GetImageDataIntoVector (void* destination, size_t maxSize)
358 GetImageDataIntoVectorRaw (destination, maxSize);
359 PixelRead = 0 ; // =0 : no ImageDataRaw
360 if ( !HeaderInternal->HasLUT() )
362 return ImageDataSize;
365 if ( PixelConverter->BuildRGBImage() )
367 memmove( destination,
368 (void*)PixelConverter->GetRGB(),
369 PixelConverter->GetRGBSize() );
371 // now, it's an RGB image
372 // Lets's write it in the Header
374 // FIXME : Better use CreateOrReplaceIfExist ?
376 std::string spp = "3"; // Samples Per Pixel
377 HeaderInternal->SetEntryByNumber(spp,0x0028,0x0002);
378 std::string rgb = "RGB "; // Photometric Interpretation
379 HeaderInternal->SetEntryByNumber(rgb,0x0028,0x0004);
380 std::string planConfig = "0"; // Planar Configuration
381 HeaderInternal->SetEntryByNumber(planConfig,0x0028,0x0006);
386 // PixelConverter->BuildRGBImage() failed probably because
387 // PixelConverter->GetLUTRGBA() failed:
388 // (gdcm-US-ALOKA-16.dcm), contains Segmented xxx Palette Color
389 // that are *more* than 65535 long ?!?
390 // No idea how to manage such an image !
391 // Need to make RGB Pixels (?) from grey Pixels (?!) and Gray Lut (!?!)
392 // It seems that *no Dicom Viewer* has any idea :-(
394 std::string photomInterp = "MONOCHROME1 "; // Photometric Interpretation
395 HeaderInternal->SetEntryByNumber(photomInterp,0x0028,0x0004);
398 /// \todo Drop Palette Color out of the Header?
399 return ImageDataSize;
403 * \brief Allocates necessary memory,
404 * Transforms YBR pixels (if any) into RGB pixels
405 * Transforms 3 planes R, G, B (if any) into a single RGB Plane
406 * Copies the pixel data (image[s]/volume[s]) to newly allocated zone.
407 * DOES NOT transform Grey plane + 3 Palettes into a RGB Plane
408 * @return Pointer to newly allocated pixel data.
409 * \ NULL if alloc fails
411 uint8_t* File::GetImageDataRaw ()
413 uint8_t* decompressed = PixelConverter->GetDecompressed();
414 if ( ! decompressed )
416 // The decompressed image migth not be loaded yet:
417 FILE* fp = HeaderInternal->OpenFile();
418 PixelConverter->ReadAndDecompressPixelData( fp );
419 HeaderInternal->CloseFile();
420 if ( ! decompressed )
422 dbg.Verbose(0, "File::GetImageDataRaw: read/decompress of "
423 "pixel data apparently went wrong.");
428 // PIXELCONVERT CLEANME
429 // Restore the header in a disk-consistent state
430 // (if user asks twice to get the pixels from disk)
431 if ( PixelRead != -1 ) // File was "read" before
433 RestoreInitialValues();
435 if ( PixelConverter->IsDecompressedRGB() )
437 ///////////////////////////////////////////////////
438 // now, it's an RGB image
439 // Lets's write it in the Header
440 // Droping Palette Color out of the Header
441 // has been moved to the Write process.
442 // TODO : move 'values' modification to the write process
443 // : save also (in order to be able to restore)
444 // : 'high bit' -when not equal to 'bits stored' + 1
445 // : 'bits allocated', when it's equal to 12 ?!
446 std::string spp = "3"; // Samples Per Pixel
447 std::string photInt = "RGB "; // Photometric Interpretation
448 std::string planConfig = "0"; // Planar Configuration
449 HeaderInternal->SetEntryByNumber(spp,0x0028,0x0002);
450 HeaderInternal->SetEntryByNumber(photInt,0x0028,0x0004);
451 HeaderInternal->SetEntryByNumber(planConfig,0x0028,0x0006);
454 // We say the value *is* loaded.
455 GetHeader()->SetEntryByNumber( GDCM_BINLOADED,
456 GetHeader()->GetGrPixel(), GetHeader()->GetNumPixel());
458 // will be 7fe0, 0010 in standard cases
459 GetHeader()->SetEntryBinAreaByNumber( decompressed,
460 GetHeader()->GetGrPixel(), GetHeader()->GetNumPixel());
462 PixelRead = 1; // PixelRaw
463 // END PIXELCONVERT CLEANME
469 * \brief Copies at most MaxSize bytes of pixel data to caller's
471 * \warning This function was designed to avoid people that want to build
472 * a volume from an image stack to need first to get the image pixels
473 * and then move them to the volume area.
474 * It's absolutely useless for any VTK user since vtk chooses
475 * to invert the lines of an image, that is the last line comes first
476 * (for some axis related reasons?). Hence he will have
477 * to load the image line by line, starting from the end.
478 * VTK users hace to call GetImageData
479 * \warning DOES NOT transform the Grey Plane + Palette Color (if any)
480 * into a single RGB Pixels Plane
481 * the (VTK) user will manage the palettes
483 * @param destination Address (in caller's memory space) at which the
484 * pixel data should be copied
485 * @param maxSize Maximum number of bytes to be copied. When MaxSize
486 * is not sufficient to hold the pixel data the copy is not
487 * executed (i.e. no partial copy).
488 * @return On success, the number of bytes actually copied. Zero on
489 * failure e.g. MaxSize is lower than necessary.
491 void File::GetImageDataIntoVectorRaw (void* destination, size_t maxSize)
493 // we save the initial values of the following
494 // in order to be able to restore the header in a disk-consistent state
495 // (if user asks twice to get the pixels from disk)
497 if ( PixelRead != -1 ) // File was "read" before
499 RestoreInitialValues();
502 PixelRead = 1 ; // PixelRaw
504 if ( ImageDataSize > maxSize )
506 dbg.Verbose(0, "File::GetImageDataIntoVector: pixel data bigger"
507 "than caller's expected MaxSize");
511 FILE* fp = HeaderInternal->OpenFile();
512 PixelConverter->ReadAndDecompressPixelData( fp );
513 HeaderInternal->CloseFile();
514 memmove( destination,
515 (void*)PixelConverter->GetDecompressed(),
516 PixelConverter->GetDecompressedSize() );
518 if ( ! PixelConverter->IsDecompressedRGB() )
523 ///////////////////////////////////////////////////
524 // now, it's an RGB image
525 // Lets's write it in the Header
527 // Droping Palette Color out of the Header
528 // has been moved to the Write process.
530 // TODO : move 'values' modification to the write process
531 // : save also (in order to be able to restore)
532 // : 'high bit' -when not equal to 'bits stored' + 1
533 // : 'bits allocated', when it's equal to 12 ?!
535 std::string spp = "3"; // Samples Per Pixel
536 std::string photInt = "RGB "; // Photometric Interpretation
537 std::string planConfig = "0"; // Planar Configuration
539 HeaderInternal->SetEntryByNumber(spp,0x0028,0x0002);
540 HeaderInternal->SetEntryByNumber(photInt,0x0028,0x0004);
541 HeaderInternal->SetEntryByNumber(planConfig,0x0028,0x0006);
547 * \brief Points the internal Pixel_Data pointer to the callers inData
548 * image representation, BUT WITHOUT COPYING THE DATA.
549 * 'image' Pixels are presented as C-like 2D arrays : line per line.
550 * 'volume'Pixels are presented as C-like 3D arrays : plane per plane
551 * \warning Since the pixels are not copied, it is the caller's responsability
552 * not to deallocate it's data before gdcm uses them (e.g. with
553 * the Write() method.
554 * @param inData user supplied pixel area
555 * @param expectedSize total image size, in Bytes
559 bool File::SetImageData(uint8_t* inData, size_t expectedSize)
561 HeaderInternal->SetImageDataSize( expectedSize );
562 // FIXME : if already allocated, memory leak !
564 ImageDataSize = ImageDataSizeRaw = expectedSize;
566 // FIXME : 7fe0, 0010 IS NOT set ...
571 * \brief Writes on disk A SINGLE Dicom file
572 * NO test is performed on processor "Endiannity".
573 * It's up to the user to call his Reader properly
574 * @param fileName name of the file to be created
575 * (any already existing file is over written)
576 * @return false if write fails
579 bool File::WriteRawData(std::string const & fileName)
581 FILE* fp1 = fopen(fileName.c_str(), "wb");
584 printf("Fail to open (write) file [%s] \n", fileName.c_str());
587 fwrite (Pixel_Data, ImageDataSize, 1, fp1);
594 * \brief Writes on disk A SINGLE Dicom file,
595 * using the Implicit Value Representation convention
596 * NO test is performed on processor "Endiannity".
597 * @param fileName name of the file to be created
598 * (any already existing file is overwritten)
599 * @return false if write fails
602 bool File::WriteDcmImplVR (std::string const & fileName)
604 return WriteBase(fileName, ImplicitVR);
608 * \brief Writes on disk A SINGLE Dicom file,
609 * using the Explicit Value Representation convention
610 * NO test is performed on processor "Endiannity". * @param fileName name of the file to be created
611 * (any already existing file is overwritten)
612 * @return false if write fails
615 bool File::WriteDcmExplVR (std::string const & fileName)
617 return WriteBase(fileName, ExplicitVR);
621 * \brief Writes on disk A SINGLE Dicom file,
622 * using the ACR-NEMA convention
623 * NO test is performed on processor "Endiannity".
624 * (a l'attention des logiciels cliniques
625 * qui ne prennent en entrée QUE des images ACR ...
626 * \warning if a DICOM_V3 header is supplied,
627 * groups < 0x0008 and shadow groups are ignored
628 * \warning NO TEST is performed on processor "Endiannity".
629 * @param fileName name of the file to be created
630 * (any already existing file is overwritten)
631 * @return false if write fails
634 bool File::WriteAcr (std::string const & fileName)
636 return WriteBase(fileName, ACR);
639 //-----------------------------------------------------------------------------
642 * \brief NOT a end user inteded function
643 * (used by WriteDcmExplVR, WriteDcmImplVR, WriteAcr, etc)
644 * @param fileName name of the file to be created
645 * (any already existing file is overwritten)
646 * @param type file type (ExplicitVR, ImplicitVR, ...)
647 * @return false if write fails
649 bool File::WriteBase (std::string const & fileName, FileType type)
651 if ( PixelRead == -1 && type != ExplicitVR)
656 FILE* fp1 = fopen(fileName.c_str(), "wb");
659 printf("Failed to open (write) File [%s] \n", fileName.c_str());
663 if ( type == ImplicitVR || type == ExplicitVR )
665 // writing Dicom File Preamble
666 uint8_t* filePreamble = new uint8_t[128];
667 memset(filePreamble, 0, 128);
668 fwrite(filePreamble, 128, 1, fp1);
669 fwrite("DICM", 4, 1, fp1);
671 delete[] filePreamble;
674 // --------------------------------------------------------------
675 // Special Patch to allow gdcm to re-write ACR-LibIDO formated images
677 // if recognition code tells us we dealt with a LibIDO image
678 // we reproduce on disk the switch between lineNumber and columnNumber
679 // just before writting ...
681 /// \todo the best trick would be *change* the recognition code
682 /// but pb expected if user deals with, e.g. COMPLEX images
684 std::string rows, columns;
685 if ( HeaderInternal->GetFileType() == ACR_LIBIDO)
687 rows = HeaderInternal->GetEntryByNumber(0x0028, 0x0010);
688 columns = HeaderInternal->GetEntryByNumber(0x0028, 0x0011);
690 HeaderInternal->SetEntryByNumber(columns, 0x0028, 0x0010);
691 HeaderInternal->SetEntryByNumber(rows , 0x0028, 0x0011);
693 // ----------------- End of Special Patch ----------------
695 uint16_t grPixel = HeaderInternal->GetGrPixel();
696 uint16_t numPixel = HeaderInternal->GetNumPixel();;
698 DocEntry* PixelElement =
699 GetHeader()->GetDocEntryByNumber(grPixel, numPixel);
701 if ( PixelRead == 1 )
703 // we read pixel 'as is' (no tranformation LUT -> RGB)
704 PixelElement->SetLength( ImageDataSizeRaw );
706 else if ( PixelRead == 0 )
708 // we tranformed GrayLevel pixels + LUT into RGB Pixel
709 PixelElement->SetLength( ImageDataSize );
712 HeaderInternal->Write(fp1, type);
714 // --------------------------------------------------------------
715 // Special Patch to allow gdcm to re-write ACR-LibIDO formated images
717 // ...and we restore the Header to be Dicom Compliant again
718 // just after writting
720 if ( HeaderInternal->GetFileType() == ACR_LIBIDO )
722 HeaderInternal->SetEntryByNumber(rows , 0x0028, 0x0010);
723 HeaderInternal->SetEntryByNumber(columns, 0x0028, 0x0011);
725 // ----------------- End of Special Patch ----------------
727 // fwrite(Pixel_Data, ImageDataSize, 1, fp1); // should be useless, now
734 * \brief Access to the underlying \ref PixelConverter RGBA LUT
736 uint8_t* File::GetLutRGBA()
738 return PixelConverter->GetLutRGBA();
741 } // end namespace gdcm