1 /*=========================================================================
4 Module: $RCSfile: gdcmFile.cxx,v $
6 Date: $Date: 2004/11/24 11:17:47 $
7 Version: $Revision: 1.161 $
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"
22 #include "gdcmBinEntry.h"
27 typedef std::pair<TagDocEntryHT::iterator,TagDocEntryHT::iterator> IterHT;
29 //-------------------------------------------------------------------------
30 // Constructor / Destructor
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
45 File::File(Header *header)
47 HeaderInternal = header;
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
66 File::File(std::string const & filename )
68 HeaderInternal = new Header( filename );
74 * \brief Factorization for various forms of constructors.
76 void File::Initialise()
78 WriteMode = WMODE_RGB;
79 WriteType = WTYPE_IMPL_VR;
81 PixelConverter = new PixelConvert;
82 Archive = new DocEntryArchive( HeaderInternal );
84 if ( HeaderInternal->IsReadable() )
86 ImageDataSizeRaw = ComputeDecompressedPixelDataSizeFromHeader();
87 if ( HeaderInternal->HasLUT() )
89 ImageDataSize = 3 * ImageDataSizeRaw;
93 ImageDataSize = ImageDataSizeRaw;
96 PixelConverter->GrabInformationsFromHeader( HeaderInternal );
103 * \brief canonical destructor
104 * \note If the Header was created by the File constructor,
105 * it is destroyed by the File
111 delete PixelConverter;
120 delete HeaderInternal;
126 * \brief Sets some initial values for the Constructor
127 * \warning not end user intended
129 void File::SaveInitialValues()
131 PixelRead = -1; // no ImageData read yet.
135 //-----------------------------------------------------------------------------
138 //-----------------------------------------------------------------------------
142 * \brief computes the length (in bytes) we must ALLOCATE to receive the
143 * image(s) pixels (multiframes taken into account)
144 * \warning : it is NOT the group 7FE0 length
145 * (no interest for compressed images).
147 int File::ComputeDecompressedPixelDataSizeFromHeader()
149 // see PS 3.3-2003 : C.7.6.3.2.1
159 // YBR_FULL_422 (no LUT, no Palette)
165 // ex : gdcm-US-ALOKA-16.dcm
166 // 0028|1221 [OW] [Segmented Red Palette Color Lookup Table Data]
167 // 0028|1222 [OW] [Segmented Green Palette Color Lookup Table Data]
168 // 0028|1223 [OW] [Segmented Blue Palette Color Lookup Table Data]
170 // ex : OT-PAL-8-face.dcm
171 // 0028|1201 [US] [Red Palette Color Lookup Table Data]
172 // 0028|1202 [US] [Green Palette Color Lookup Table Data]
173 // 0028|1203 [US] [Blue Palette Color Lookup Table Data]
175 int numberBitsAllocated = HeaderInternal->GetBitsAllocated();
176 // Number of "Bits Allocated" is fixed to 16 when:
177 // - it is not defined (i.e. it's value is 0)
178 // - it's 12, since we will expand the image to 16 bits (see
179 // PixelConvert::ConvertDecompress12BitsTo16Bits() )
180 if ( numberBitsAllocated == 0 || numberBitsAllocated == 12 )
182 numberBitsAllocated = 16;
185 int DecompressedSize = HeaderInternal->GetXSize()
186 * HeaderInternal->GetYSize()
187 * HeaderInternal->GetZSize()
188 * ( numberBitsAllocated / 8 )
189 * HeaderInternal->GetSamplesPerPixel();
191 return DecompressedSize;
195 * \brief - Allocates necessary memory,
196 * - Reads the pixels from disk (uncompress if necessary),
197 * - Transforms YBR pixels, if any, into RGB pixels
198 * - Transforms 3 planes R, G, B, if any, into a single RGB Plane
199 * - Transforms single Grey plane + 3 Palettes into a RGB Plane
200 * - Copies the pixel data (image[s]/volume[s]) to newly allocated zone.
201 * @return Pointer to newly allocated pixel data.
202 * NULL if alloc fails
204 uint8_t* File::GetImageData()
206 if ( ! GetDecompressed() )
208 // If the decompression failed nothing can be done.
212 if ( HeaderInternal->HasLUT() && PixelConverter->BuildRGBImage() )
214 return PixelConverter->GetRGB();
218 // When no LUT or LUT conversion fails, return the decompressed
219 return PixelConverter->GetDecompressed();
222 /* if ( ! GetDecompressed() )
224 // If the decompression failed nothing can be done.
229 if ( HeaderInternal->HasLUT() && PixelConverter->BuildRGBImage() )
231 pixelData = PixelConverter->GetRGB();
235 // When no LUT or LUT conversion fails, return the decompressed
236 pixelData = PixelConverter->GetDecompressed();
239 // PIXELCONVERT CLEANME
240 // Restore the header in a disk-consistent state
241 // (if user asks twice to get the pixels from disk)
242 if ( PixelRead != -1 ) // File was "read" before
244 RestoreInitialValues();
246 if ( PixelConverter->GetRGB() )
248 // now, it's an RGB image
249 // Lets's write it in the Header
250 std::string spp = "3"; // Samples Per Pixel
251 HeaderInternal->SetEntryByNumber(spp,0x0028,0x0002);
252 std::string rgb = "RGB "; // Photometric Interpretation
253 HeaderInternal->SetEntryByNumber(rgb,0x0028,0x0004);
254 std::string planConfig = "0"; // Planar Configuration
255 HeaderInternal->SetEntryByNumber(planConfig,0x0028,0x0006);
256 PixelRead = 0; // no PixelRaw
260 if ( HeaderInternal->HasLUT() )
262 // The LUT interpretation failed
263 std::string photometricInterpretation = Util::DicomString("MONOCHROME1");
264 HeaderInternal->SetEntryByNumber( photometricInterpretation,
266 PixelRead = 0; // no PixelRaw
270 if ( PixelConverter->IsDecompressedRGB() )
272 ///////////////////////////////////////////////////
273 // now, it's an RGB image
274 // Lets's write it in the Header
275 // Droping Palette Color out of the Header
276 // has been moved to the Write process.
277 // TODO : move 'values' modification to the write process
278 // : save also (in order to be able to restore)
279 // : 'high bit' -when not equal to 'bits stored' + 1
280 // : 'bits allocated', when it's equal to 12 ?!
281 std::string spp = "3"; // Samples Per Pixel
282 std::string photInt = "RGB "; // Photometric Interpretation
283 std::string planConfig = "0"; // Planar Configuration
284 HeaderInternal->SetEntryByNumber(spp,0x0028,0x0002);
285 HeaderInternal->SetEntryByNumber(photInt,0x0028,0x0004);
286 HeaderInternal->SetEntryByNumber(planConfig,0x0028,0x0006);
288 PixelRead = 1; // PixelRaw
292 // We say the value *is* loaded.
293 SetPixelData(pixelData);
294 // END PIXELCONVERT CLEANME
301 * Read the pixels from disk (uncompress if necessary),
302 * Transforms YBR pixels, if any, into RGB pixels
303 * Transforms 3 planes R, G, B, if any, into a single RGB Plane
304 * Transforms single Grey plane + 3 Palettes into a RGB Plane
305 * Copies at most MaxSize bytes of pixel data to caller allocated
307 * \warning This function allows people that want to build a volume
308 * from an image stack *not to* have, first to get the image pixels,
309 * and then move them to the volume area.
310 * It's absolutely useless for any VTK user since vtk chooses
311 * to invert the lines of an image, that is the last line comes first
312 * (for some axis related reasons?). Hence he will have
313 * to load the image line by line, starting from the end.
314 * VTK users have to call GetImageData
316 * @param destination Address (in caller's memory space) at which the
317 * pixel data should be copied
318 * @param maxSize Maximum number of bytes to be copied. When MaxSize
319 * is not sufficient to hold the pixel data the copy is not
320 * executed (i.e. no partial copy).
321 * @return On success, the number of bytes actually copied. Zero on
322 * failure e.g. MaxSize is lower than necessary.
324 size_t File::GetImageDataIntoVector (void* destination, size_t maxSize)
326 if ( ! GetDecompressed() )
328 // If the decompression failed nothing can be done.
332 if ( HeaderInternal->HasLUT() && PixelConverter->BuildRGBImage() )
334 if ( PixelConverter->GetRGBSize() > maxSize )
336 dbg.Verbose(0, "File::GetImageDataIntoVector: pixel data bigger"
337 "than caller's expected MaxSize");
341 (void*)PixelConverter->GetRGB(),
342 PixelConverter->GetRGBSize() );
343 return PixelConverter->GetRGBSize();
346 // Either no LUT conversion necessary or LUT conversion failed
347 if ( PixelConverter->GetDecompressedSize() > maxSize )
349 dbg.Verbose(0, "File::GetImageDataIntoVector: pixel data bigger"
350 "than caller's expected MaxSize");
354 (void*)PixelConverter->GetDecompressed(),
355 PixelConverter->GetDecompressedSize() );
356 return PixelConverter->GetDecompressedSize();
360 * \brief Allocates necessary memory,
361 * Transforms YBR pixels (if any) into RGB pixels
362 * Transforms 3 planes R, G, B (if any) into a single RGB Plane
363 * Copies the pixel data (image[s]/volume[s]) to newly allocated zone.
364 * DOES NOT transform Grey plane + 3 Palettes into a RGB Plane
365 * @return Pointer to newly allocated pixel data.
366 * \ NULL if alloc fails
368 uint8_t* File::GetImageDataRaw ()
370 return GetDecompressed();
371 /* uint8_t* decompressed = GetDecompressed();
372 if ( ! decompressed )
377 // PIXELCONVERT CLEANME
378 // Restore the header in a disk-consistent state
379 // (if user asks twice to get the pixels from disk)
380 if ( PixelRead != -1 ) // File was "read" before
382 RestoreInitialValues();
384 if ( PixelConverter->IsDecompressedRGB() )
386 ///////////////////////////////////////////////////
387 // now, it's an RGB image
388 // Lets's write it in the Header
389 // Droping Palette Color out of the Header
390 // has been moved to the Write process.
391 // TODO : move 'values' modification to the write process
392 // : save also (in order to be able to restore)
393 // : 'high bit' -when not equal to 'bits stored' + 1
394 // : 'bits allocated', when it's equal to 12 ?!
395 std::string spp = "3"; // Samples Per Pixel
396 std::string photInt = "RGB "; // Photometric Interpretation
397 std::string planConfig = "0"; // Planar Configuration
398 HeaderInternal->SetEntryByNumber(spp,0x0028,0x0002);
399 HeaderInternal->SetEntryByNumber(photInt,0x0028,0x0004);
400 HeaderInternal->SetEntryByNumber(planConfig,0x0028,0x0006);
403 // We say the value *is* loaded.
404 SetPixelData(decompressed);
406 PixelRead = 1; // PixelRaw
407 // END PIXELCONVERT CLEANME
409 return decompressed;*/
412 uint8_t* File::GetDecompressed()
414 uint8_t* decompressed = PixelConverter->GetDecompressed();
415 if ( ! decompressed )
417 // The decompressed image migth not be loaded yet:
418 std::ifstream* fp = HeaderInternal->OpenFile();
419 PixelConverter->ReadAndDecompressPixelData( fp );
420 if(fp) HeaderInternal->CloseFile();
421 decompressed = PixelConverter->GetDecompressed();
422 if ( ! decompressed )
424 dbg.Verbose(0, "File::GetDecompressed: read/decompress of "
425 "pixel data apparently went wrong.");
434 * \brief Points the internal Pixel_Data pointer to the callers inData
435 * image representation, BUT WITHOUT COPYING THE DATA.
436 * 'image' Pixels are presented as C-like 2D arrays : line per line.
437 * 'volume'Pixels are presented as C-like 3D arrays : plane per plane
438 * \warning Since the pixels are not copied, it is the caller's responsability
439 * not to deallocate it's data before gdcm uses them (e.g. with
440 * the Write() method.
441 * @param inData user supplied pixel area
442 * @param expectedSize total image size, in Bytes
446 bool File::SetImageData(uint8_t* inData, size_t expectedSize)
448 HeaderInternal->SetImageDataSize( expectedSize );
449 // FIXME : if already allocated, memory leak !
451 ImageDataSize = ImageDataSizeRaw = expectedSize;
453 // FIXME : 7fe0, 0010 IS NOT set ...
458 * \brief Writes on disk A SINGLE Dicom file
459 * NO test is performed on processor "Endiannity".
460 * It's up to the user to call his Reader properly
461 * @param fileName name of the file to be created
462 * (any already existing file is over written)
463 * @return false if write fails
466 bool File::WriteRawData(std::string const & fileName)
468 std::ofstream fp1(fileName.c_str(), std::ios::out | std::ios::binary );
471 dbg.Verbose(2, "Fail to open (write) file:", fileName.c_str());
474 fp1.write((char*)Pixel_Data, ImageDataSize);
481 * \brief Writes on disk A SINGLE Dicom file,
482 * using the Implicit Value Representation convention
483 * NO test is performed on processor "Endiannity".
484 * @param fileName name of the file to be created
485 * (any already existing file is overwritten)
486 * @return false if write fails
489 bool File::WriteDcmImplVR (std::string const & fileName)
491 SetWriteTypeToDcmImplVR();
492 return Write(fileName);
496 * \brief Writes on disk A SINGLE Dicom file,
497 * using the Explicit Value Representation convention
498 * NO test is performed on processor "Endiannity". * @param fileName name of the file to be created
499 * (any already existing file is overwritten)
500 * @return false if write fails
503 bool File::WriteDcmExplVR (std::string const & fileName)
505 SetWriteTypeToDcmExplVR();
506 return Write(fileName);
510 * \brief Writes on disk A SINGLE Dicom file,
511 * using the ACR-NEMA convention
512 * NO test is performed on processor "Endiannity".
513 * (a l'attention des logiciels cliniques
514 * qui ne prennent en entrée QUE des images ACR ...
515 * \warning if a DICOM_V3 header is supplied,
516 * groups < 0x0008 and shadow groups are ignored
517 * \warning NO TEST is performed on processor "Endiannity".
518 * @param fileName name of the file to be created
519 * (any already existing file is overwritten)
520 * @return false if write fails
523 bool File::WriteAcr (std::string const & fileName)
526 return Write(fileName);
529 bool File::Write(std::string const& fileName)
534 return WriteBase(fileName,ImplicitVR);
536 return WriteBase(fileName,ExplicitVR);
538 return WriteBase(fileName,ACR);
543 //-----------------------------------------------------------------------------
546 * \brief NOT a end user inteded function
547 * (used by WriteDcmExplVR, WriteDcmImplVR, WriteAcr, etc)
548 * @param fileName name of the file to be created
549 * (any already existing file is overwritten)
550 * @param type file type (ExplicitVR, ImplicitVR, ...)
551 * @return false if write fails
553 bool File::WriteBase (std::string const & fileName, FileType type)
555 /* if ( PixelRead == -1 && type != ExplicitVR)
560 std::ofstream* fp1 = new std::ofstream(fileName.c_str(),
561 std::ios::out | std::ios::binary);
564 dbg.Verbose(2, "Failed to open (write) File: " , fileName.c_str());
568 if ( type == ImplicitVR || type == ExplicitVR )
570 // writing Dicom File Preamble
571 char filePreamble[128];
572 memset(filePreamble, 0, 128);
573 fp1->write(filePreamble, 128);
574 fp1->write("DICM", 4);
582 case WMODE_DECOMPRESSED :
583 SetWriteToDecompressed();
590 // --------------------------------------------------------------
591 // Special Patch to allow gdcm to re-write ACR-LibIDO formated images
593 // if recognition code tells us we dealt with a LibIDO image
594 // we reproduce on disk the switch between lineNumber and columnNumber
595 // just before writting ...
597 /// \todo the best trick would be *change* the recognition code
598 /// but pb expected if user deals with, e.g. COMPLEX images
600 std::string rows, columns;
601 if ( HeaderInternal->GetFileType() == ACR_LIBIDO)
605 // ----------------- End of Special Patch ----------------
607 /* uint16_t grPixel = HeaderInternal->GetGrPixel();
608 uint16_t numPixel = HeaderInternal->GetNumPixel();;
610 DocEntry* PixelElement =
611 GetHeader()->GetDocEntryByNumber(grPixel, numPixel);
613 if ( PixelRead == 1 )
615 // we read pixel 'as is' (no tranformation LUT -> RGB)
616 PixelElement->SetLength( ImageDataSizeRaw );
618 else if ( PixelRead == 0 )
620 // we tranformed GrayLevel pixels + LUT into RGB Pixel
621 PixelElement->SetLength( ImageDataSize );
624 HeaderInternal->Write(fp1, type);
626 // --------------------------------------------------------------
627 // Special Patch to allow gdcm to re-write ACR-LibIDO formated images
629 // ...and we restore the Header to be Dicom Compliant again
630 // just after writting
632 if ( HeaderInternal->GetFileType() == ACR_LIBIDO )
634 RestoreWriteFromLibido();
636 // ----------------- End of Special Patch ----------------
648 * \brief Access to the underlying \ref PixelConverter RGBA LUT
650 uint8_t* File::GetLutRGBA()
652 return PixelConverter->GetLutRGBA();
655 //-----------------------------------------------------------------------------
658 * \brief Set the pixel datas in the good entry of the Header
660 void File::SetPixelData(uint8_t* data)
662 GetHeader()->SetEntryByNumber( GDCM_BINLOADED,
663 GetHeader()->GetGrPixel(), GetHeader()->GetNumPixel());
665 // Will be 7fe0, 0010 in standard case
666 DocEntry* currentEntry = GetHeader()->GetDocEntryByNumber(GetHeader()->GetGrPixel(), GetHeader()->GetNumPixel());
669 if ( BinEntry* binEntry = dynamic_cast<BinEntry *>(currentEntry) )
670 // Flag is to false because datas are kept in the gdcmPixelConvert
671 binEntry->SetBinArea( data, false );
675 void File::SetWriteToNative()
680 void File::SetWriteToDecompressed()
682 // if (( !HeaderInternal->HasLUT() ) || (!PixelConverter->BuildRGBImage()))
683 if(HeaderInternal->HasLUT() && PixelConverter->BuildRGBImage())
689 ValEntry* photInt = CopyValEntry(0x0028,0x0004);
690 photInt->SetValue("MONOCHROME1 ");
691 photInt->SetLength(12);
693 BinEntry* pixel = CopyBinEntry(GetHeader()->GetGrPixel(),GetHeader()->GetNumPixel());
694 pixel->SetValue(GDCM_BINLOADED);
695 pixel->SetBinArea(PixelConverter->GetDecompressed(),false);
696 pixel->SetLength(PixelConverter->GetDecompressedSize());
698 Archive->Push(photInt);
699 Archive->Push(pixel);
707 void File::SetWriteToRGB()
709 if(PixelConverter->BuildRGBImage())
711 ValEntry* spp = CopyValEntry(0x0028,0x0002);
715 ValEntry* photInt = CopyValEntry(0x0028,0x0004);
716 photInt->SetValue("RGB ");
717 photInt->SetLength(4);
719 ValEntry* planConfig = CopyValEntry(0x0028,0x0006);
720 planConfig->SetValue("0 ");
721 planConfig->SetLength(2);
723 BinEntry* pixel = CopyBinEntry(GetHeader()->GetGrPixel(),GetHeader()->GetNumPixel());
724 pixel->SetValue(GDCM_BINLOADED);
725 pixel->SetBinArea(PixelConverter->GetRGB(),false);
726 pixel->SetLength(PixelConverter->GetRGBSize());
729 Archive->Push(photInt);
730 Archive->Push(planConfig);
731 Archive->Push(pixel);
735 SetWriteToDecompressed();
739 void File::RestoreWrite()
741 Archive->Restore(0x0028,0x0002);
742 Archive->Restore(0x0028,0x0004);
743 Archive->Restore(0x0028,0x0006);
744 Archive->Restore(GetHeader()->GetGrPixel(),GetHeader()->GetNumPixel());
747 void File::SetWriteToLibido()
749 ValEntry *oldRow = dynamic_cast<ValEntry *>(HeaderInternal->GetDocEntryByNumber(0x0028, 0x0010));
750 ValEntry *oldCol = dynamic_cast<ValEntry *>(HeaderInternal->GetDocEntryByNumber(0x0028, 0x0011));
752 if( oldRow && oldCol )
754 std::string rows, columns;
756 ValEntry *newRow=new ValEntry(oldRow->GetDictEntry());
757 ValEntry *newCol=new ValEntry(oldCol->GetDictEntry());
759 newRow->Copy(oldCol);
760 newCol->Copy(oldRow);
762 newRow->SetValue(oldCol->GetValue());
763 newCol->SetValue(oldRow->GetValue());
765 Archive->Push(newRow);
766 Archive->Push(newCol);
770 void File::RestoreWriteFromLibido()
772 Archive->Restore(0x0028,0x0010);
773 Archive->Restore(0x0028,0x0011);
776 ValEntry* File::CopyValEntry(uint16_t group,uint16_t element)
778 DocEntry* oldE = HeaderInternal->GetDocEntryByNumber(group, element);
783 newE = new ValEntry(oldE->GetDictEntry());
788 newE = GetHeader()->NewValEntryByNumber(group,element);
794 BinEntry* File::CopyBinEntry(uint16_t group,uint16_t element)
796 DocEntry* oldE = HeaderInternal->GetDocEntryByNumber(group, element);
801 newE = new BinEntry(oldE->GetDictEntry());
806 newE = GetHeader()->NewBinEntryByNumber(group,element);
813 //-----------------------------------------------------------------------------
814 } // end namespace gdcm