1 /*=========================================================================
4 Module: $RCSfile: gdcmFileHelper.cxx,v $
7 Date: $Date: 2006/05/02 11:14:05 $
8 Version: $Revision: 1.102 $
10 Copyright (c) CREATIS (Centre de Recherche et d'Applications en Traitement de
11 l'Image). All rights reserved. See Doc/License.txt or
12 http://www.creatis.insa-lyon.fr/Public/Gdcm/License.html for details.
14 This software is distributed WITHOUT ANY WARRANTY; without even
15 the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
16 PURPOSE. See the above copyright notices for more information.
18 =========================================================================*/
20 #include "gdcmFileHelper.h"
21 #include "gdcmGlobal.h"
23 #include "gdcmDocument.h"
24 #include "gdcmDebug.h"
26 #include "gdcmSeqEntry.h"
27 #include "gdcmSQItem.h"
28 #include "gdcmDataEntry.h"
29 #include "gdcmDocEntry.h"
31 #include "gdcmPixelReadConvert.h"
32 #include "gdcmPixelWriteConvert.h"
33 #include "gdcmDocEntryArchive.h"
34 #include "gdcmDictSet.h"
35 #include "gdcmOrientation.h"
37 #if defined(__BORLANDC__)
38 #include <mem.h> // for memset
44 // ----------------------------- WARNING -------------------------
46 These lines will be moved to the document-to-be 'User's Guide'
48 // To read an image, user needs a gdcm::File
49 gdcm::File *f = new gdcm::File(fileName);
51 // user may also decide he doesn't want to load some parts of the header
52 gdcm::File *f = new gdcm::File();
53 f->SetFileName(fileName);
54 f->SetLoadMode(LD_NOSEQ); // or
55 f->SetLoadMode(LD_NOSHADOW); // or
56 f->SetLoadMode(LD_NOSEQ | LD_NOSHADOW); // or
57 f->SetLoadMode(LD_NOSHADOWSEQ);
60 // user can now check some values
61 std::string v = f->GetEntryValue(groupNb,ElementNb);
63 // to get the pixels, user needs a gdcm::FileHelper
64 gdcm::FileHelper *fh = new gdcm::FileHelper(f);
65 // user may ask not to convert Palette to RGB
66 uint8_t *pixels = fh->GetImageDataRaw();
67 int imageLength = fh->GetImageDataRawSize();
68 // He can now use the pixels, create a new image, ...
69 uint8_t *userPixels = ...
71 To re-write the image, user re-uses the gdcm::FileHelper
73 fh->SetImageData( userPixels, userPixelsLength);
74 fh->SetTypeToRaw(); // Even if it was possible to convert Palette to RGB
77 fh->SetWriteTypeToDcmExpl(); // he wants Explicit Value Representation
78 // Little Endian is the default
79 // no other value is allowed
80 (-->SetWriteType(ExplicitVR);)
81 -->WriteType = ExplicitVR;
82 fh->Write(newFileName); // overwrites the file, if any
85 fh->WriteDcmExplVR(newFileName);
88 // ----------------------------- WARNING -------------------------
90 These lines will be moved to the document-to-be 'Developer's Guide'
92 WriteMode : WMODE_RAW / WMODE_RGB
93 WriteType : ImplicitVR, ExplicitVR, ACR, ACR_LIBIDO
95 fh1->Write(newFileName);
96 SetWriteFileTypeToImplicitVR() / SetWriteFileTypeToExplicitVR();
97 (modifies TransferSyntax)
98 SetWriteToRaw(); / SetWriteToRGB();
99 (modifies, when necessary : photochromatic interpretation,
100 samples per pixel, Planar configuration,
101 bits allocated, bits stored, high bit -ACR 24 bits-
102 Pixels element VR, pushes out the LUT )
103 CheckWriteIntegrity();
104 (checks user given pixels length)
105 FileInternal->Write(fileName,WriteType)
106 fp = opens file(fileName);
107 ComputeGroup0002Length( );
109 RemoveEntry(palettes, etc)
110 Document::WriteContent(fp, writetype);
112 (moves back to the File all the archived elements)
113 RestoreWriteFileType();
114 (pushes back group 0002, with TransferSyntax)
122 typedef std::map<uint16_t, int> GroupHT; // Hash Table
123 //-------------------------------------------------------------------------
124 // Constructor / Destructor
126 * \brief Constructor dedicated to deal with the *pixels* area of a ACR/DICOMV3
127 * file (gdcm::File only deals with the ... header)
128 * Opens (in read only and when possible) an existing file and checks
129 * for DICOM compliance. Returns NULL on failure.
130 * It will be up to the user to load the pixels into memory
131 * ( GetImageDataSize() + GetImageData() methods)
132 * \note the in-memory representation of all available tags found in
133 * the DICOM header is post-poned to first header information access.
134 * This avoid a double parsing of public part of the header when
135 * one sets an a posteriori shadow dictionary (efficiency can be
136 * seen as a side effect).
138 FileHelper::FileHelper( )
140 FileInternal = File::New( );
145 * \brief Constructor dedicated to deal with the *pixels* area of a ACR/DICOMV3
146 * file (File only deals with the ... header)
147 * Opens (in read only and when possible) an existing file and checks
148 * for DICOM compliance. Returns NULL on failure.
149 * It will be up to the user to load the pixels into memory
150 * ( GetImageDataSize() + GetImageData() methods)
151 * \note the in-memory representation of all available tags found in
152 * the DICOM header is post-poned to first header information access.
153 * This avoid a double parsing of public part of the header when
154 * user sets an a posteriori shadow dictionary (efficiency can be
155 * seen as a side effect).
156 * @param header already built Header
158 FileHelper::FileHelper(File *header)
160 gdcmAssertMacro(header);
162 FileInternal = header;
163 FileInternal->Register();
165 if ( FileInternal->IsReadable() )
167 PixelReadConverter->GrabInformationsFromFile( FileInternal, this );
172 * \brief canonical destructor
173 * \note If the header (gdcm::File) was created by the FileHelper constructor,
174 * it is destroyed by the FileHelper
176 FileHelper::~FileHelper()
178 if ( PixelReadConverter )
180 delete PixelReadConverter;
182 if ( PixelWriteConverter )
184 delete PixelWriteConverter;
191 FileInternal->Unregister();
194 //-----------------------------------------------------------------------------
198 * \brief Sets the LoadMode of the internal gdcm::File as a boolean string.
199 * NO_SEQ, NO_SHADOW, NO_SHADOWSEQ ... (nothing more, right now)
200 * WARNING : before using NO_SHADOW, be sure *all* your files
201 * contain accurate values in the 0x0000 element (if any)
202 * of *each* Shadow Group. The parser will fail if the size is wrong !
203 * @param loadMode Load mode to be used
205 void FileHelper::SetLoadMode(int loadMode)
207 GetFile()->SetLoadMode( loadMode );
210 * \brief Sets the LoadMode of the internal gdcm::File
211 * @param fileName name of the file to be open
213 void FileHelper::SetFileName(std::string const &fileName)
215 FileInternal->SetFileName( fileName );
220 * @return false if file cannot be open or no swap info was found,
221 * or no tag was found.
223 bool FileHelper::Load()
225 if ( !FileInternal->Load() )
228 PixelReadConverter->GrabInformationsFromFile( FileInternal, this );
233 * \brief Accesses an existing DataEntry through it's (group, element)
234 * and modifies it's content with the given value.
235 * @param content new value (string) to substitute with
236 * @param group group number of the Dicom Element to modify
237 * @param elem element number of the Dicom Element to modify
238 * \return false if DataEntry not found
240 bool FileHelper::SetEntryString(std::string const &content,
241 uint16_t group, uint16_t elem)
243 return FileInternal->SetEntryString(content, group, elem);
248 * \brief Accesses an existing DataEntry through it's (group, element)
249 * and modifies it's content with the given value.
250 * @param content new value (void* -> uint8_t*) to substitute with
251 * @param lgth new value length
252 * @param group group number of the Dicom Element to modify
253 * @param elem element number of the Dicom Element to modify
254 * \return false if DataEntry not found
256 bool FileHelper::SetEntryBinArea(uint8_t *content, int lgth,
257 uint16_t group, uint16_t elem)
259 return FileInternal->SetEntryBinArea(content, lgth, group, elem);
263 * \brief Modifies the value of a given DataEntry when it exists.
264 * Creates it with the given value when unexistant.
265 * @param content (string) value to be set
266 * @param group Group number of the Entry
267 * @param elem Element number of the Entry
268 * \return pointer to the modified/created DataEntry (NULL when creation
271 DataEntry *FileHelper::InsertEntryString(std::string const &content,
272 uint16_t group, uint16_t elem,
275 return FileInternal->InsertEntryString(content, group, elem, vr);
279 * \brief Modifies the value of a given DataEntry when it exists.
280 * Creates it with the given value when unexistant.
281 * A copy of the binArea is made to be kept in the Document.
282 * @param binArea (binary)value to be set
283 * @param lgth new value length
284 * @param group Group number of the Entry
285 * @param elem Element number of the Entry
286 * \return pointer to the modified/created DataEntry (NULL when creation
289 DataEntry *FileHelper::InsertEntryBinArea(uint8_t *binArea, int lgth,
290 uint16_t group, uint16_t elem,
293 return FileInternal->InsertEntryBinArea(binArea, lgth, group, elem, vr);
297 * \brief Adds an empty SeqEntry
298 * (remove any existing entry with same group,elem)
299 * @param group Group number of the Entry
300 * @param elem Element number of the Entry
301 * \return pointer to the created SeqEntry (NULL when creation
304 SeqEntry *FileHelper::InsertSeqEntry(uint16_t group, uint16_t elem)
306 return FileInternal->InsertSeqEntry(group, elem);
310 * \brief Get the size of the image data
311 * If the image can be RGB (with a lut or by default), the size
312 * corresponds to the RGB image
313 * (use GetImageDataRawSize if you want to be sure to get *only*
314 * the size of the pixels)
315 * @return The image size
317 size_t FileHelper::GetImageDataSize()
319 if ( PixelWriteConverter->GetUserData() )
321 return PixelWriteConverter->GetUserDataSize();
323 return PixelReadConverter->GetRGBSize();
327 * \brief Get the size of the image data.
328 * If the image could be converted to RGB using a LUT,
329 * this transformation is not taken into account by GetImageDataRawSize
330 * (use GetImageDataSize if you wish)
331 * @return The raw image size
333 size_t FileHelper::GetImageDataRawSize()
335 if ( PixelWriteConverter->GetUserData() )
337 return PixelWriteConverter->GetUserDataSize();
339 return PixelReadConverter->GetRawSize();
343 * \brief brings pixels into memory :
344 * - Allocates necessary memory,
345 * - Reads the pixels from disk (uncompress if necessary),
346 * - Transforms YBR pixels, if any, into RGB pixels,
347 * - Transforms 3 planes R, G, B, if any, into a single RGB Plane
348 * - Transforms single Grey plane + 3 Palettes into a RGB Plane
349 * - Copies the pixel data (image[s]/volume[s]) to newly allocated zone.
350 * @return Pointer to newly allocated pixel data.
351 * (uint8_t is just for prototyping. feel free to cast)
352 * NULL if alloc fails
354 uint8_t *FileHelper::GetImageData()
356 if ( PixelWriteConverter->GetUserData() )
358 return PixelWriteConverter->GetUserData();
363 // If the decompression failed nothing can be done.
367 if ( FileInternal->HasLUT() && PixelReadConverter->BuildRGBImage() )
369 return PixelReadConverter->GetRGB();
373 // When no LUT or LUT conversion fails, return the Raw
374 return PixelReadConverter->GetRaw();
379 * \brief brings pixels into memory :
380 * - Allocates necessary memory,
381 * - Transforms YBR pixels (if any) into RGB pixels
382 * - Transforms 3 planes R, G, B (if any) into a single RGB Plane
383 * - Copies the pixel data (image[s]/volume[s]) to newly allocated zone.
384 * - DOES NOT transform Grey plane + 3 Palettes into a RGB Plane
385 * @return Pointer to newly allocated pixel data.
386 * (uint8_t is just for prototyping. feel free to cast)
387 * NULL if alloc fails
389 uint8_t *FileHelper::GetImageDataRaw ()
394 #ifndef GDCM_LEGACY_REMOVE
396 * \brief Useless function, since PixelReadConverter forces us
397 * copy the Pixels anyway.
398 * Reads the pixels from disk (uncompress if necessary),
399 * Transforms YBR pixels, if any, into RGB pixels
400 * Transforms 3 planes R, G, B, if any, into a single RGB Plane
401 * Transforms single Grey plane + 3 Palettes into a RGB Plane
402 * Copies at most MaxSize bytes of pixel data to caller allocated
404 * \warning This function allows people that want to build a volume
405 * from an image stack *not to* have, first to get the image pixels,
406 * and then move them to the volume area.
407 * It's absolutely useless for any VTK user since vtk chooses
408 * to invert the lines of an image, that is the last line comes first
409 * (for some axis related reasons?). Hence he will have
410 * to load the image line by line, starting from the end.
411 * VTK users have to call GetImageData
413 * @param destination Address (in caller's memory space) at which the
414 * pixel data should be copied
415 * @param maxSize Maximum number of bytes to be copied. When MaxSize
416 * is not sufficient to hold the pixel data the copy is not
417 * executed (i.e. no partial copy).
418 * @return On success, the number of bytes actually copied. Zero on
419 * failure e.g. MaxSize is lower than necessary.
421 size_t FileHelper::GetImageDataIntoVector (void *destination, size_t maxSize)
425 // If the decompression failed nothing can be done.
429 if ( FileInternal->HasLUT() && PixelReadConverter->BuildRGBImage() )
431 if ( PixelReadConverter->GetRGBSize() > maxSize )
433 gdcmWarningMacro( "Pixel data bigger than caller's expected MaxSize");
437 (void*)PixelReadConverter->GetRGB(),
438 PixelReadConverter->GetRGBSize() );
439 return PixelReadConverter->GetRGBSize();
442 // Either no LUT conversion necessary or LUT conversion failed
443 if ( PixelReadConverter->GetRawSize() > maxSize )
445 gdcmWarningMacro( "Pixel data bigger than caller's expected MaxSize");
449 (void *)PixelReadConverter->GetRaw(),
450 PixelReadConverter->GetRawSize() );
451 return PixelReadConverter->GetRawSize();
456 * \brief Points the internal pointer to the callers inData
457 * image representation, BUT WITHOUT COPYING THE DATA.
458 * 'image' Pixels are presented as C-like 2D arrays : line per line.
459 * 'volume'Pixels are presented as C-like 3D arrays : plane per plane
460 * \warning Since the pixels are not copied, it is the caller's responsability
461 * not to deallocate its data before gdcm uses them (e.g. with
462 * the Write() method )
463 * @param inData user supplied pixel area (uint8_t* is just for the compiler.
464 * user is allowed to pass any kind of pixelsn since the size is
466 * @param expectedSize total image size, *in Bytes*
468 void FileHelper::SetImageData(uint8_t *inData, size_t expectedSize)
470 SetUserData(inData, expectedSize);
474 * \brief Set the image data defined by the user
475 * \warning When writting the file, this data are get as default data to write
476 * @param inData user supplied pixel area (uint8_t* is just for the compiler.
477 * user is allowed to pass any kind of pixels since the size is
479 * @param expectedSize total image size, *in Bytes*
481 void FileHelper::SetUserData(uint8_t *inData, size_t expectedSize)
483 PixelWriteConverter->SetUserData(inData, expectedSize);
487 * \brief Get the image data defined by the user
488 * \warning When writting the file, this data are get as default data to write
490 uint8_t *FileHelper::GetUserData()
492 return PixelWriteConverter->GetUserData();
496 * \brief Get the image data size defined by the user
497 * \warning When writting the file, this data are get as default data to write
499 size_t FileHelper::GetUserDataSize()
501 return PixelWriteConverter->GetUserDataSize();
505 * \brief Get the image data from the file.
506 * If a LUT is found, the data are expanded to be RGB
508 uint8_t *FileHelper::GetRGBData()
510 return PixelReadConverter->GetRGB();
514 * \brief Get the image data size from the file.
515 * If a LUT is found, the data are expanded to be RGB
517 size_t FileHelper::GetRGBDataSize()
519 return PixelReadConverter->GetRGBSize();
523 * \brief Get the image data from the file.
524 * Even when a LUT is found, the data are not expanded to RGB!
526 uint8_t *FileHelper::GetRawData()
528 return PixelReadConverter->GetRaw();
532 * \brief Get the image data size from the file.
533 * Even when a LUT is found, the data are not expanded to RGB!
535 size_t FileHelper::GetRawDataSize()
537 return PixelReadConverter->GetRawSize();
541 * \brief Access to the underlying \ref PixelReadConverter RGBA LUT
543 uint8_t* FileHelper::GetLutRGBA()
545 if ( PixelReadConverter->GetLutRGBA() ==0 )
546 PixelReadConverter->BuildLUTRGBA();
547 return PixelReadConverter->GetLutRGBA();
551 * \brief Access to the underlying \ref PixelReadConverter RGBA LUT Item Number
553 int FileHelper::GetLutItemNumber()
555 return PixelReadConverter->GetLutItemNumber();
559 * \brief Access to the underlying \ref PixelReadConverter RGBA LUT Item Size
561 int FileHelper::GetLutItemSize()
563 return PixelReadConverter->GetLutItemSize();
567 * \brief Writes on disk A SINGLE Dicom file
568 * NO test is performed on processor "Endiannity".
569 * It's up to the user to call his Reader properly
570 * @param fileName name of the file to be created
571 * (any already existing file is over written)
572 * @return false if write fails
574 bool FileHelper::WriteRawData(std::string const &fileName)
576 std::ofstream fp1(fileName.c_str(), std::ios::out | std::ios::binary );
579 gdcmWarningMacro( "Fail to open (write) file:" << fileName.c_str());
583 if ( PixelWriteConverter->GetUserData() )
585 fp1.write( (char *)PixelWriteConverter->GetUserData(),
586 PixelWriteConverter->GetUserDataSize() );
588 else if ( PixelReadConverter->GetRGB() )
590 fp1.write( (char *)PixelReadConverter->GetRGB(),
591 PixelReadConverter->GetRGBSize());
593 else if ( PixelReadConverter->GetRaw() )
595 fp1.write( (char *)PixelReadConverter->GetRaw(),
596 PixelReadConverter->GetRawSize());
600 gdcmErrorMacro( "Nothing written." );
609 * \brief Writes on disk A SINGLE Dicom file,
610 * using the Implicit Value Representation convention
611 * NO test is performed on processor "Endianity".
612 * @param fileName name of the file to be created
613 * (any already existing file is overwritten)
614 * @return false if write fails
617 bool FileHelper::WriteDcmImplVR (std::string const &fileName)
619 SetWriteTypeToDcmImplVR();
620 return Write(fileName);
624 * \brief Writes on disk A SINGLE Dicom file,
625 * using the Explicit Value Representation convention
626 * NO test is performed on processor "Endiannity".
627 * @param fileName name of the file to be created
628 * (any already existing file is overwritten)
629 * @return false if write fails
632 bool FileHelper::WriteDcmExplVR (std::string const &fileName)
634 SetWriteTypeToDcmExplVR();
635 return Write(fileName);
639 * \brief Writes on disk A SINGLE Dicom file,
640 * using the ACR-NEMA convention
641 * NO test is performed on processor "Endiannity".
642 * (a l'attention des logiciels cliniques
643 * qui ne prennent en entrée QUE des images ACR ...
644 * \warning if a DICOM_V3 header is supplied,
645 * groups < 0x0008 and shadow groups are ignored
646 * \warning NO TEST is performed on processor "Endiannity".
647 * @param fileName name of the file to be created
648 * (any already existing file is overwritten)
649 * @return false if write fails
652 bool FileHelper::WriteAcr (std::string const &fileName)
655 return Write(fileName);
659 * \brief Writes on disk A SINGLE Dicom file,
660 * @param fileName name of the file to be created
661 * (any already existing file is overwritten)
662 * @return false if write fails
664 bool FileHelper::Write(std::string const &fileName)
667 CheckMandatoryElements(); //called once, here !
674 SetWriteFileTypeToImplicitVR();
677 case Unknown: // should never happen; ExplicitVR is the default value
680 // User should ask gdcm to write an image in Explicit VR mode
681 // only when he is sure *all* the VR of *all* the DataElements is known.
682 // i.e : when there are *only* Public Groups
683 // or *all* the Shadow Groups are fully described in the relevant Shadow
685 // Let's just *dream* about it; *never* trust a user !
686 // We turn to Implicit VR if at least the VR of one element is unknown.
689 e = FileInternal->GetFirstEntry();
692 if (e->GetVR() == " ")
694 SetWriteTypeToDcmImplVR();
695 SetWriteFileTypeToImplicitVR();
699 e = FileInternal->GetNextEntry();
704 SetWriteFileTypeToExplicitVR();
708 SetWriteFileTypeToExplicitVR(); // to see JPRx
712 // NOTHING is done here just for LibIDO.
713 // Just to avoid further trouble if user creates a file ex-nihilo,
714 // wants to write it as an ACR-NEMA file,
715 // and forgets to create any Entry belonging to group 0008
717 // We add Recognition Code (RET)
718 if ( ! FileInternal->GetDataEntry(0x0008, 0x0010) )
719 FileInternal->InsertEntryString("ACR-NEMA V1.0 ",
720 0x0008, 0x0010, "LO");
721 SetWriteFileTypeToACR();
722 // SetWriteFileTypeToImplicitVR(); // ACR IS implicit VR !
725 /// \todo FIXME : JPEG may be either ExplicitVR or ImplicitVR
727 SetWriteFileTypeToJPEG();
731 // --------------------------------------------------------------
732 // Special Patch to allow gdcm to re-write ACR-LibIDO formated images
734 // if recognition code tells us we dealt with a LibIDO image
735 // we reproduce on disk the switch between lineNumber and columnNumber
736 // just before writting ...
737 /// \todo the best trick would be *change* the recognition code
738 /// but pb expected if user deals with, e.g. COMPLEX images
740 if ( WriteType == ACR_LIBIDO )
746 SetWriteToNoLibido();
748 // ----------------- End of Special Patch ----------------
753 SetWriteToRaw(); // modifies and pushes to the archive, when necessary
756 SetWriteToRGB(); // modifies and pushes to the archive, when necessary
760 bool check = CheckWriteIntegrity(); // verifies length
761 if (WriteType == JPEG ) check = true;
764 check = FileInternal->Write(fileName,WriteType);
768 // RestoreWriteFileType();
769 // RestoreWriteMandatory();
772 // --------------------------------------------------------------
773 // Special Patch to allow gdcm to re-write ACR-LibIDO formated images
775 // ...and we restore the header to be Dicom Compliant again
776 // just after writting
777 RestoreWriteOfLibido();
778 // ----------------- End of Special Patch ----------------
783 //-----------------------------------------------------------------------------
786 * \brief Checks the write integrity
788 * The tests made are :
789 * - verify the size of the image to write with the possible write
790 * when the user set an image data
791 * @return true if check is successfull
793 bool FileHelper::CheckWriteIntegrity()
795 if ( PixelWriteConverter->GetUserData() )
797 int numberBitsAllocated = FileInternal->GetBitsAllocated();
798 if ( numberBitsAllocated == 0 || numberBitsAllocated == 12 )
800 gdcmWarningMacro( "numberBitsAllocated changed from "
801 << numberBitsAllocated << " to 16 "
802 << " for consistency purpose" );
803 numberBitsAllocated = 16;
806 size_t decSize = FileInternal->GetXSize()
807 * FileInternal->GetYSize()
808 * FileInternal->GetZSize()
809 * FileInternal->GetTSize()
810 * FileInternal->GetSamplesPerPixel()
811 * ( numberBitsAllocated / 8 );
812 size_t rgbSize = decSize;
813 if ( FileInternal->HasLUT() )
814 rgbSize = decSize * 3;
819 if ( decSize!=PixelWriteConverter->GetUserDataSize() )
821 gdcmWarningMacro( "Data size (Raw) is incorrect. Should be "
822 << decSize << " / Found :"
823 << PixelWriteConverter->GetUserDataSize() );
828 if ( rgbSize!=PixelWriteConverter->GetUserDataSize() )
830 gdcmWarningMacro( "Data size (RGB) is incorrect. Should be "
831 << decSize << " / Found "
832 << PixelWriteConverter->GetUserDataSize() );
843 * \brief Updates the File to write RAW data (as opposed to RGB data)
844 * (modifies, when necessary, photochromatic interpretation,
845 * bits allocated, Pixels element VR)
847 void FileHelper::SetWriteToRaw()
849 if ( FileInternal->GetNumberOfScalarComponents() == 3
850 && !FileInternal->HasLUT() )
856 DataEntry *photInt = CopyDataEntry(0x0028,0x0004,"CS");
857 if (FileInternal->HasLUT() )
859 photInt->SetString("PALETTE COLOR ");
863 photInt->SetString("MONOCHROME2 ");
866 PixelWriteConverter->SetReadData(PixelReadConverter->GetRaw(),
867 PixelReadConverter->GetRawSize());
869 std::string vr = "OB";
870 if ( FileInternal->GetBitsAllocated()>8 )
872 if ( FileInternal->GetBitsAllocated()==24 ) // For RGB ACR files
875 CopyDataEntry(GetFile()->GetGrPixel(),GetFile()->GetNumPixel(),vr);
876 pixel->SetFlag(DataEntry::FLAG_PIXELDATA);
877 pixel->SetBinArea(PixelWriteConverter->GetData(),false);
878 pixel->SetLength(PixelWriteConverter->GetDataSize());
880 Archive->Push(photInt);
881 Archive->Push(pixel);
889 * \brief Updates the File to write RGB data (as opposed to RAW data)
890 * (modifies, when necessary, photochromatic interpretation,
891 * samples per pixel, Planar configuration,
892 * bits allocated, bits stored, high bit -ACR 24 bits-
893 * Pixels element VR, pushes out the LUT, )
895 void FileHelper::SetWriteToRGB()
897 if ( FileInternal->GetNumberOfScalarComponents()==3 )
899 PixelReadConverter->BuildRGBImage();
901 DataEntry *spp = CopyDataEntry(0x0028,0x0002,"US");
902 spp->SetString("3 ");
904 DataEntry *planConfig = CopyDataEntry(0x0028,0x0006,"US");
905 planConfig->SetString("0 ");
907 DataEntry *photInt = CopyDataEntry(0x0028,0x0004,"CS");
908 photInt->SetString("RGB ");
910 if ( PixelReadConverter->GetRGB() )
912 PixelWriteConverter->SetReadData(PixelReadConverter->GetRGB(),
913 PixelReadConverter->GetRGBSize());
917 PixelWriteConverter->SetReadData(PixelReadConverter->GetRaw(),
918 PixelReadConverter->GetRawSize());
921 std::string vr = "OB";
922 if ( FileInternal->GetBitsAllocated()>8 )
924 if ( FileInternal->GetBitsAllocated()==24 ) // For RGB ACR files
927 CopyDataEntry(GetFile()->GetGrPixel(),GetFile()->GetNumPixel(),vr);
928 pixel->SetFlag(DataEntry::FLAG_PIXELDATA);
929 pixel->SetBinArea(PixelWriteConverter->GetData(),false);
930 pixel->SetLength(PixelWriteConverter->GetDataSize());
933 Archive->Push(planConfig);
934 Archive->Push(photInt);
935 Archive->Push(pixel);
938 planConfig->Delete();
943 Archive->Push(0x0028,0x1101);
944 Archive->Push(0x0028,0x1102);
945 Archive->Push(0x0028,0x1103);
946 Archive->Push(0x0028,0x1201);
947 Archive->Push(0x0028,0x1202);
948 Archive->Push(0x0028,0x1203);
950 // push out Palette Color Lookup Table UID, if any
951 Archive->Push(0x0028,0x1199);
953 // For old '24 Bits' ACR-NEMA
954 // Thus, we have a RGB image and the bits allocated = 24 and
955 // samples per pixels = 1 (in the read file)
956 if ( FileInternal->GetBitsAllocated()==24 )
958 DataEntry *bitsAlloc = CopyDataEntry(0x0028,0x0100,"US");
959 bitsAlloc->SetString("8 ");
961 DataEntry *bitsStored = CopyDataEntry(0x0028,0x0101,"US");
962 bitsStored->SetString("8 ");
964 DataEntry *highBit = CopyDataEntry(0x0028,0x0102,"US");
965 highBit->SetString("7 ");
967 Archive->Push(bitsAlloc);
968 Archive->Push(bitsStored);
969 Archive->Push(highBit);
972 bitsStored->Delete();
983 * \brief Restore the File write mode
985 void FileHelper::RestoreWrite()
988 Archive->Restore(0x0028,0x0002);
989 Archive->Restore(0x0028,0x0004);
991 Archive->Restore(0x0028,0x0006);
992 Archive->Restore(GetFile()->GetGrPixel(),GetFile()->GetNumPixel());
994 // For old ACR-NEMA (24 bits problem)
995 Archive->Restore(0x0028,0x0100);
996 Archive->Restore(0x0028,0x0101);
997 Archive->Restore(0x0028,0x0102);
1000 Archive->Restore(0x0028,0x1101);
1001 Archive->Restore(0x0028,0x1102);
1002 Archive->Restore(0x0028,0x1103);
1003 Archive->Restore(0x0028,0x1201);
1004 Archive->Restore(0x0028,0x1202);
1005 Archive->Restore(0x0028,0x1203);
1007 // For the Palette Color Lookup Table UID
1008 Archive->Restore(0x0028,0x1203);
1010 // group 0002 may be pushed out for ACR-NEMA writting purposes
1011 Archive->Restore(0x0002,0x0000);
1012 Archive->Restore(0x0002,0x0001);
1013 Archive->Restore(0x0002,0x0002);
1014 Archive->Restore(0x0002,0x0003);
1015 Archive->Restore(0x0002,0x0010);
1016 Archive->Restore(0x0002,0x0012);
1017 Archive->Restore(0x0002,0x0013);
1018 Archive->Restore(0x0002,0x0016);
1019 Archive->Restore(0x0002,0x0100);
1020 Archive->Restore(0x0002,0x0102);
1025 * \brief Pushes out the whole group 0002
1026 * FIXME : better, set a flag to tell the writer not to write it ...
1027 * FIXME : method should probably have an other name !
1028 * SetWriteFileTypeToACR is NOT opposed to
1029 * SetWriteFileTypeToExplicitVR and SetWriteFileTypeToImplicitVR
1031 void FileHelper::SetWriteFileTypeToACR()
1033 Archive->Push(0x0002,0x0000);
1034 Archive->Push(0x0002,0x0001);
1035 Archive->Push(0x0002,0x0002);
1036 Archive->Push(0x0002,0x0003);
1037 Archive->Push(0x0002,0x0010);
1038 Archive->Push(0x0002,0x0012);
1039 Archive->Push(0x0002,0x0013);
1040 Archive->Push(0x0002,0x0016);
1041 Archive->Push(0x0002,0x0100);
1042 Archive->Push(0x0002,0x0102);
1046 * \brief Sets in the File the TransferSyntax to 'JPEG'
1048 void FileHelper::SetWriteFileTypeToJPEG()
1050 std::string ts = Util::DicomString(
1051 Global::GetTS()->GetSpecialTransferSyntax(TS::JPEGBaselineProcess1) );
1053 DataEntry *tss = CopyDataEntry(0x0002,0x0010,"UI");
1061 * \brief Sets in the File the TransferSyntax to 'Explicit VR Little Endian"
1063 void FileHelper::SetWriteFileTypeToExplicitVR()
1065 std::string ts = Util::DicomString(
1066 Global::GetTS()->GetSpecialTransferSyntax(TS::ExplicitVRLittleEndian) );
1068 DataEntry *tss = CopyDataEntry(0x0002,0x0010,"UI");
1075 * \brief Sets in the File the TransferSyntax to 'Implicit VR Little Endian"
1077 void FileHelper::SetWriteFileTypeToImplicitVR()
1079 std::string ts = Util::DicomString(
1080 Global::GetTS()->GetSpecialTransferSyntax(TS::ImplicitVRLittleEndian) );
1082 DataEntry *tss = CopyDataEntry(0x0002,0x0010,"UI");
1090 * \brief Restore in the File the initial group 0002
1092 void FileHelper::RestoreWriteFileType()
1097 * \brief Set the Write not to Libido format
1099 void FileHelper::SetWriteToLibido()
1101 DataEntry *oldRow = FileInternal->GetDataEntry(0x0028, 0x0010);
1102 DataEntry *oldCol = FileInternal->GetDataEntry(0x0028, 0x0011);
1104 if ( oldRow && oldCol )
1106 std::string rows, columns;
1108 //DataEntry *newRow=DataEntry::New(oldRow->GetDictEntry());
1109 //DataEntry *newCol=DataEntry::New(oldCol->GetDictEntry());
1111 DataEntry *newRow=DataEntry::New(0x0028, 0x0010, "US");
1112 DataEntry *newCol=DataEntry::New(0x0028, 0x0011, "US");
1114 newRow->Copy(oldCol);
1115 newCol->Copy(oldRow);
1117 newRow->SetString(oldCol->GetString());
1118 newCol->SetString(oldRow->GetString());
1120 Archive->Push(newRow);
1121 Archive->Push(newCol);
1127 DataEntry *libidoCode = CopyDataEntry(0x0008,0x0010,"LO");
1128 libidoCode->SetString("ACRNEMA_LIBIDO_1.1");
1129 Archive->Push(libidoCode);
1130 libidoCode->Delete();
1134 * \brief Set the Write not to No Libido format
1136 void FileHelper::SetWriteToNoLibido()
1138 DataEntry *recCode = FileInternal->GetDataEntry(0x0008,0x0010);
1141 if ( recCode->GetString() == "ACRNEMA_LIBIDO_1.1" )
1143 DataEntry *libidoCode = CopyDataEntry(0x0008,0x0010,"LO");
1144 libidoCode->SetString("");
1145 Archive->Push(libidoCode);
1146 libidoCode->Delete();
1152 * \brief Restore the Write format
1154 void FileHelper::RestoreWriteOfLibido()
1156 Archive->Restore(0x0028,0x0010);
1157 Archive->Restore(0x0028,0x0011);
1158 Archive->Restore(0x0008,0x0010);
1160 // Restore 'LibIDO-special' entries, if any
1161 Archive->Restore(0x0028,0x0015);
1162 Archive->Restore(0x0028,0x0016);
1163 Archive->Restore(0x0028,0x0017);
1164 Archive->Restore(0x0028,0x00199);
1168 * \brief Duplicates a DataEntry or creates it.
1169 * @param group Group number of the Entry
1170 * @param elem Element number of the Entry
1171 * @param vr Value Representation of the Entry
1172 * \return pointer to the new Bin Entry (NULL when creation failed).
1174 DataEntry *FileHelper::CopyDataEntry(uint16_t group, uint16_t elem,
1177 DocEntry *oldE = FileInternal->GetDocEntry(group, elem);
1180 if ( oldE && vr != GDCM_VRUNKNOWN )
1181 if ( oldE->GetVR() != vr )
1186 //newE = DataEntry::New(oldE->GetDictEntry());
1187 newE = DataEntry::New(group, elem, vr);
1192 newE = GetFile()->NewDataEntry(group, elem, vr);
1199 * \brief This method is called automatically, just before writting
1200 * in order to produce a 'True Dicom V3' image.
1202 * We cannot know *how* the user made the File :
1203 * (reading an old ACR-NEMA file or a not very clean DICOM file ...)
1204 * Just before writting :
1205 * - we check the Entries
1206 * - we create the mandatory entries if they are missing
1207 * - we modify the values if necessary
1208 * - we push the sensitive entries to the Archive
1209 * The writing process will restore the entries as they where before
1210 * entering FileHelper::CheckMandatoryElements, so the user will always
1211 * see the entries just as they were before he decided to write.
1214 * - Entries whose type is 1 are mandatory, with a mandatory value
1215 * - Entries whose type is 1c are mandatory-inside-a-Sequence,
1216 * with a mandatory value
1217 * - Entries whose type is 2 are mandatory, with an optional value
1218 * - Entries whose type is 2c are mandatory-inside-a-Sequence,
1219 * with an optional value
1220 * - Entries whose type is 3 are optional
1223 * - warn the user if we had to add some entries :
1224 * even if a mandatory entry is missing, we add it, with a default value
1225 * (we don't want to give up the writting process if user forgot to
1226 * specify Lena's Patient ID, for instance ...)
1227 * - read the whole PS 3.3 Part of DICOM (890 pages)
1228 * and write a *full* checker (probably one method per Modality ...)
1229 * Any contribution is welcome.
1230 * - write a user callable full checker, to allow post reading
1231 * and/or pre writting image consistency check.
1234 /* -------------------------------------------------------------------------------------
1235 To be moved to User's guide / WIKI ?
1237 We have to deal with 4 *very* different cases :
1238 -1) user created ex nihilo his own image and wants to write it as a Dicom image.
1240 -2) user modified the pixels of an existing image.
1242 -3) user created a new image, using existing images (eg MIP, MPR, cartography image)
1244 -4) user modified/added some tags *without processing* the pixels (anonymization..
1245 UNMODIFIED_PIXELS_IMAGE
1247 gdcm::FileHelper::CheckMandatoryElements() deals automatically with these cases.
1250 0008 0012 Instance Creation Date
1251 0008 0013 Instance Creation Time
1252 0008 0018 SOP Instance UID
1253 are *always* created with the current values; user has *no* possible intervention on
1256 'Serie Instance UID'(0x0020,0x000e)
1257 'Study Instance UID'(0x0020,0x000d) are kept as is if already exist,
1258 created if it doesn't.
1259 The user is allowed to create his own Series/Studies,
1260 keeping the same 'Serie Instance UID' / 'Study Instance UID' for various images
1262 The user shouldn't add any image to a 'Manufacturer Serie'
1263 but there is no way no to allow him to do that
1265 None of the 'shadow elements' are droped out.
1269 'Conversion Type (0x0008,0x0064) is forced to 'SYN' (Synthetic Image).
1272 'Media Storage SOP Class UID' (0x0002,0x0002)
1273 'SOP Class UID' (0x0008,0x0016) are set to
1274 [Secondary Capture Image Storage]
1275 'Image Type' (0x0008,0x0008) is forced to "DERIVED\PRIMARY"
1276 Conversion Type (0x0008,0x0064) is forced to 'SYN' (Synthetic Image)
1279 If 'SOP Class UID' exists in the native image ('true DICOM' image)
1280 we create the 'Source Image Sequence' SeqEntry (0x0008, 0x2112)
1281 --> 'Referenced SOP Class UID' (0x0008, 0x1150)
1282 whose value is the original 'SOP Class UID'
1283 --> 'Referenced SOP Instance UID' (0x0008, 0x1155)
1284 whose value is the original 'SOP Class UID'
1286 3) TODO : find a trick to allow user to pass to the writter the list of the Dicom images
1287 or the Series, (or the Study ?) he used to created his image
1288 (MIP, MPR, cartography image, ...)
1289 These info should be stored (?)
1290 0008 1110 SQ 1 Referenced Study Sequence
1291 0008 1115 SQ 1 Referenced Series Sequence
1292 0008 1140 SQ 1 Referenced Image Sequence
1294 4) When user *knows* he didn't modified the pixels, we keep some informations unchanged :
1295 'Media Storage SOP Class UID' (0x0002,0x0002)
1296 'SOP Class UID' (0x0008,0x0016)
1297 'Image Type' (0x0008,0x0008)
1298 'Conversion Type' (0x0008,0x0064)
1301 Bellow follows the full description (hope so !) of the consistency checks performed
1302 by gdcm::FileHelper::CheckMandatoryElements()
1305 -->'Media Storage SOP Class UID' (0x0002,0x0002)
1306 -->'SOP Class UID' (0x0008,0x0016) are defaulted to
1307 [Secondary Capture Image Storage]
1308 --> 'Image Type' (0x0008,0x0008)
1309 is forced to "DERIVED\PRIMARY"
1310 (The written image is no longer an 'ORIGINAL' one)
1311 Except if user knows he didn't modify the image (e.g. : he just anonymized the file)
1313 --> Conversion Type (0x0008,0x0064)
1314 is defaulted to 'SYN' (Synthetic Image)
1315 when *he* knows he created his own image ex nihilo
1317 --> 'Modality' (0x0008,0x0060)
1318 is defaulted to "OT" (other) if missing.
1319 (a fully user created image belongs to *no* modality)
1321 --> 'Media Storage SOP Instance UID' (0x0002,0x0003)
1322 --> 'Implementation Class UID' (0x0002,0x0012)
1323 are automatically generated; no user intervention possible
1325 --> 'Serie Instance UID'(0x0020,0x000e)
1326 --> 'Study Instance UID'(0x0020,0x000d) are kept as is if already exist
1327 created if it doesn't.
1328 The user is allowed to create his own Series/Studies,
1329 keeping the same 'Serie Instance UID' / 'Study Instance UID'
1332 The user shouldn't add any image to a 'Manufacturer Serie'
1333 but there is no way no to allowed him to do that
1335 --> If 'SOP Class UID' exists in the native image ('true DICOM' image)
1336 we create the 'Source Image Sequence' SeqEntry (0x0008, 0x2112)
1338 --> 'Referenced SOP Class UID' (0x0008, 0x1150)
1339 whose value is the original 'SOP Class UID'
1340 --> 'Referenced SOP Instance UID' (0x0008, 0x1155)
1341 whose value is the original 'SOP Class UID'
1343 --> Bits Stored, Bits Allocated, Hight Bit Position are checked for consistency
1344 --> Pixel Spacing (0x0028,0x0030) is defaulted to "1.0\1.0"
1345 --> Samples Per Pixel (0x0028,0x0002) is defaulted to 1 (grayscale)
1347 --> Imager Pixel Spacing (0x0018,0x1164) : defaulted to Pixel Spacing value
1349 --> Instance Creation Date, Instance Creation Time are forced to current Date and Time
1351 --> Study Date, Study Time are defaulted to current Date and Time
1352 (they remain unchanged if they exist)
1354 --> Patient Orientation : (0x0020,0x0020), if not present, is deduced from
1355 Image Orientation (Patient) : (0020|0037) or from
1356 Image Orientation (RET) : (0020 0035)
1358 --> Study ID, Series Number, Instance Number, Patient Orientation (Type 2)
1359 are created, with empty value if there are missing.
1361 --> Manufacturer, Institution Name, Patient's Name, (Type 2)
1362 are defaulted with a 'gdcm' value.
1364 --> Patient ID, Patient's Birth Date, Patient's Sex, (Type 2)
1365 --> Referring Physician's Name (Type 2)
1366 are created, with empty value if there are missing.
1368 -------------------------------------------------------------------------------------*/
1370 void FileHelper::CheckMandatoryElements()
1372 std::string sop = Util::CreateUniqueUID();
1374 // --------------------- For Meta Elements ---------------------
1375 // just to remember : 'official' 0002 group
1376 if ( WriteType != ACR && WriteType != ACR_LIBIDO )
1378 // Group 000002 (Meta Elements) already pushed out
1380 //0002 0000 UL 1 Meta Group Length
1381 //0002 0001 OB 1 File Meta Information Version
1382 //0002 0002 UI 1 Media Stored SOP Class UID
1383 //0002 0003 UI 1 Media Stored SOP Instance UID
1384 //0002 0010 UI 1 Transfer Syntax UID
1385 //0002 0012 UI 1 Implementation Class UID
1386 //0002 0013 SH 1 Implementation Version Name
1387 //0002 0016 AE 1 Source Application Entity Title
1388 //0002 0100 UI 1 Private Information Creator
1389 //0002 0102 OB 1 Private Information
1391 // Create them if not found
1392 // Always modify the value
1393 // Push the entries to the archive.
1394 CopyMandatoryEntry(0x0002,0x0000,"0","UL");
1396 DataEntry *e_0002_0001 = CopyDataEntry(0x0002,0x0001, "OB");
1397 e_0002_0001->SetBinArea((uint8_t*)Util::GetFileMetaInformationVersion(),
1399 e_0002_0001->SetLength(2);
1400 Archive->Push(e_0002_0001);
1401 e_0002_0001->Delete();
1403 if ( ContentType == FILTERED_IMAGE || ContentType == UNMODIFIED_PIXELS_IMAGE)
1405 // we keep the original 'Media Storage SOP Class UID', we default it if missing
1406 CheckMandatoryEntry(0x0002,0x0002,"1.2.840.10008.5.1.4.1.1.7","UI");
1410 // It's *not* an image comming straight from a source. We force
1411 // 'Media Storage SOP Class UID' --> [Secondary Capture Image Storage]
1412 CopyMandatoryEntry(0x0002,0x0002,"1.2.840.10008.5.1.4.1.1.7","UI");
1415 // 'Media Storage SOP Instance UID'
1416 CopyMandatoryEntry(0x0002,0x0003,sop,"UI");
1418 // 'Implementation Class UID'
1419 // FIXME : in all examples we have, 0x0002,0x0012 is not so long :
1420 // seems to be Root UID + 4 digits (?)
1421 CopyMandatoryEntry(0x0002,0x0012,Util::CreateUniqueUID(),"UI");
1423 // 'Implementation Version Name'
1424 std::string version = "GDCM ";
1425 version += Util::GetVersion();
1426 CopyMandatoryEntry(0x0002,0x0013,version,"SH");
1429 // --------------------- For DataSet ---------------------
1431 if ( ContentType != USER_OWN_IMAGE) // when it's not a user made image
1434 gdcmDebugMacro( "USER_OWN_IMAGE (1)");
1435 // If 'SOP Class UID' exists ('true DICOM' image)
1436 // we create the 'Source Image Sequence' SeqEntry
1437 // to hold informations about the Source Image
1439 DataEntry *e_0008_0016 = FileInternal->GetDataEntry(0x0008, 0x0016);
1442 // Create 'Source Image Sequence' SeqEntry
1443 // SeqEntry *sis = SeqEntry::New (
1444 // Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x2112) );
1445 SeqEntry *sis = SeqEntry::New (0x0008, 0x2112);
1446 SQItem *sqi = SQItem::New(1);
1447 // (we assume 'SOP Instance UID' exists too)
1448 // create 'Referenced SOP Class UID'
1449 // DataEntry *e_0008_1150 = DataEntry::New(
1450 // Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x1150) );
1451 DataEntry *e_0008_1150 = DataEntry::New(0x0008, 0x1150, "UI");
1452 e_0008_1150->SetString( e_0008_0016->GetString());
1453 sqi->AddEntry(e_0008_1150);
1454 e_0008_1150->Delete();
1456 // create 'Referenced SOP Instance UID'
1457 DataEntry *e_0008_0018 = FileInternal->GetDataEntry(0x0008, 0x0018);
1458 // DataEntry *e_0008_1155 = DataEntry::New(
1459 // Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x1155) );
1460 DataEntry *e_0008_1155 = DataEntry::New(0x0008, 0x1155, "UI");
1461 e_0008_1155->SetString( e_0008_0018->GetString());
1462 sqi->AddEntry(e_0008_1155);
1463 e_0008_1155->Delete();
1465 sis->AddSQItem(sqi,1);
1468 // temporarily replaces any previous 'Source Image Sequence'
1472 // FIXME : is 'Image Type' *really* depending on the presence of 'SOP Class UID'?
1473 if ( ContentType == FILTERED_IMAGE)
1474 // the user *knows* he just modified the pixels
1475 // the image is no longer an 'Original' one
1476 CopyMandatoryEntry(0x0008,0x0008,"DERIVED\\PRIMARY","CS");
1480 if ( ContentType == FILTERED_IMAGE || ContentType == UNMODIFIED_PIXELS_IMAGE)
1482 // we keep the original 'Media Storage SOP Class UID', we default it if missing (it should be present !)
1483 CheckMandatoryEntry(0x0008,0x0016,"1.2.840.10008.5.1.4.1.1.7","UI");
1487 // It's *not* an image comming straight from a source. We force
1488 // 'Media Storage SOP Class UID' --> [Secondary Capture Image Storage]
1489 CopyMandatoryEntry(0x0008,0x0016,"1.2.840.10008.5.1.4.1.1.7", "UI");
1492 Archive->Push(0x0028,0x005); // [Image Dimensions (RET)
1493 // Push out 'LibIDO-special' entries, if any
1494 Archive->Push(0x0028,0x0015);
1495 Archive->Push(0x0028,0x0016);
1496 Archive->Push(0x0028,0x0017);
1497 Archive->Push(0x0028,0x0198); // very old versions
1498 Archive->Push(0x0028,0x0199);
1500 // Replace deprecated 0028 0012 US Planes
1501 // by new 0028 0008 IS Number of Frames
1502 DataEntry *e_0028_0012 = FileInternal->GetDataEntry(0x0028, 0x0012);
1505 CopyMandatoryEntry(0x0028, 0x0008,e_0028_0012->GetString(),"IS");
1506 Archive->Push(0x0028,0x0012);
1509 // Deal with the pb of (Bits Stored = 12)
1510 // - we're gonna write the image as Bits Stored = 16
1511 if ( FileInternal->GetEntryString(0x0028,0x0100) == "12")
1513 CopyMandatoryEntry(0x0028,0x0100,"16","US");
1516 // Check if user wasn't drunk ;-)
1518 std::ostringstream s;
1519 // check 'Bits Allocated' vs decent values
1520 int nbBitsAllocated = FileInternal->GetBitsAllocated();
1521 if ( nbBitsAllocated == 0 || nbBitsAllocated > 32)
1523 CopyMandatoryEntry(0x0028,0x0100,"16","US");
1524 gdcmWarningMacro("(0028,0100) changed from "
1525 << nbBitsAllocated << " to 16 for consistency purpose");
1526 nbBitsAllocated = 16;
1528 // check 'Bits Stored' vs 'Bits Allocated'
1529 int nbBitsStored = FileInternal->GetBitsStored();
1530 if ( nbBitsStored == 0 || nbBitsStored > nbBitsAllocated )
1533 s << nbBitsAllocated;
1534 CopyMandatoryEntry(0x0028,0x0101,s.str(),"US");
1535 gdcmWarningMacro("(0028,0101) changed from "
1536 << nbBitsStored << " to " << nbBitsAllocated
1537 << " for consistency purpose" );
1538 nbBitsStored = nbBitsAllocated;
1540 // check 'Hight Bit Position' vs 'Bits Allocated' and 'Bits Stored'
1541 int highBitPosition = FileInternal->GetHighBitPosition();
1542 if ( highBitPosition == 0 ||
1543 highBitPosition > nbBitsAllocated-1 ||
1544 highBitPosition < nbBitsStored-1 )
1547 s << nbBitsStored - 1;
1548 CopyMandatoryEntry(0x0028,0x0102,s.str(),"US");
1549 gdcmWarningMacro("(0028,0102) changed from "
1550 << highBitPosition << " to " << nbBitsAllocated-1
1551 << " for consistency purpose");
1554 std::string pixelSpacing = FileInternal->GetEntryString(0x0028,0x0030);
1555 if ( pixelSpacing == GDCM_UNFOUND )
1557 pixelSpacing = "1.0\\1.0";
1558 // if missing, Pixel Spacing forced to "1.0\1.0"
1559 CopyMandatoryEntry(0x0028,0x0030,pixelSpacing,"DS");
1562 // 'Imager Pixel Spacing' : defaulted to 'Pixel Spacing'
1563 // --> This one is the *legal* one !
1564 if ( ContentType != USER_OWN_IMAGE)
1565 // we write it only when we are *sure* the image comes from
1566 // an imager (see also 0008,0x0064)
1567 CheckMandatoryEntry(0x0018,0x1164,pixelSpacing,"DS");
1569 // Samples Per Pixel (type 1) : default to grayscale
1570 CheckMandatoryEntry(0x0028,0x0002,"1","US");
1572 // --- Check UID-related Entries ---
1574 // At the end, not to overwrite the original ones,
1575 // needed by 'Referenced SOP Instance UID', 'Referenced SOP Class UID'
1576 // 'SOP Instance UID'
1577 CopyMandatoryEntry(0x0008,0x0018,sop,"UI");
1579 if ( ContentType == USER_OWN_IMAGE)
1581 gdcmDebugMacro( "USER_OWN_IMAGE (2)");
1583 // Other possible values are :
1584 // See PS 3.3, Page 408
1586 // DV = Digitized Video
1587 // DI = Digital Interface
1588 // DF = Digitized Film
1589 // WSD = Workstation
1590 // SD = Scanned Document
1591 // SI = Scanned Image
1593 // SYN = Synthetic Image
1595 CheckMandatoryEntry(0x0008,0x0064,"SYN","CS"); // Why not?
1598 if ( ContentType == CREATED_IMAGE)
1600 /// \todo : find a trick to pass the Media Storage SOP Instance UID of the images used to create the current image
1605 // ---- The user will never have to take any action on the following ----
1607 // new value for 'SOP Instance UID'
1608 //SetMandatoryEntry(0x0008,0x0018,Util::CreateUniqueUID());
1610 // Instance Creation Date
1611 const std::string &date = Util::GetCurrentDate();
1612 CopyMandatoryEntry(0x0008,0x0012,date,"DA");
1614 // Instance Creation Time
1615 const std::string &time = Util::GetCurrentTime();
1616 CopyMandatoryEntry(0x0008,0x0013,time,"TM");
1619 CheckMandatoryEntry(0x0008,0x0020,date,"DA");
1621 CheckMandatoryEntry(0x0008,0x0030,time,"TM");
1624 //CopyMandatoryEntry(0x0008,0x0050,"");
1625 CheckMandatoryEntry(0x0008,0x0050,"","SH");
1628 // ----- Add Mandatory Entries if missing ---
1629 // Entries whose type is 1 are mandatory, with a mandatory value
1630 // Entries whose type is 1c are mandatory-inside-a-Sequence,
1631 // with a mandatory value
1632 // Entries whose type is 2 are mandatory, with an optional value
1633 // Entries whose type is 2c are mandatory-inside-a-Sequence,
1634 // with an optional value
1635 // Entries whose type is 3 are optional
1637 // 'Study Instance UID'
1638 // Keep the value if exists
1639 // The user is allowed to create his own Study,
1640 // keeping the same 'Study Instance UID' for various images
1641 // The user may add images to a 'Manufacturer Study',
1642 // adding new Series to an already existing Study
1643 CheckMandatoryEntry(0x0020,0x000d,Util::CreateUniqueUID(),"UI");
1645 // 'Serie Instance UID'
1646 // Keep the value if exists
1647 // The user is allowed to create his own Series,
1648 // keeping the same 'Serie Instance UID' for various images
1649 // The user shouldn't add any image to a 'Manufacturer Serie'
1650 // but there is no way no to prevent him for doing that
1651 CheckMandatoryEntry(0x0020,0x000e,Util::CreateUniqueUID(),"UI");
1654 CheckMandatoryEntry(0x0020,0x0010,"","SH");
1657 CheckMandatoryEntry(0x0020,0x0011,"","IS");
1660 CheckMandatoryEntry(0x0020,0x0013,"","IS");
1662 // Patient Orientation
1663 // Can be computed from (0020|0037) : Image Orientation (Patient)
1664 gdcm::Orientation *o = gdcm::Orientation::New();
1665 std::string ori = o->GetOrientation ( FileInternal );
1667 if (ori != "\\" && ori != GDCM_UNFOUND)
1668 CheckMandatoryEntry(0x0020,0x0020,ori,"CS");
1670 CheckMandatoryEntry(0x0020,0x0020,"","CS");
1672 // Default Patient Position to HFS
1673 CheckMandatoryEntry(0x0018,0x5100,"HFS","CS");
1675 // Modality : if missing we set it to 'OTher'
1676 CheckMandatoryEntry(0x0008,0x0060,"OT","CS");
1678 // Manufacturer : if missing we set it to 'GDCM Factory'
1679 CheckMandatoryEntry(0x0008,0x0070,"GDCM Factory","LO");
1681 // Institution Name : if missing we set it to 'GDCM Hospital'
1682 CheckMandatoryEntry(0x0008,0x0080,"GDCM Hospital","LO");
1684 // Patient's Name : if missing, we set it to 'GDCM^Patient'
1685 CheckMandatoryEntry(0x0010,0x0010,"GDCM^Patient","PN");
1687 // Patient ID : some clinical softwares *demand* it although it's a 'type 2' entry.
1688 CheckMandatoryEntry(0x0010,0x0020,"gdcm ID","LO");
1690 // Patient's Birth Date : 'type 2' entry -> must exist, value not mandatory
1691 CheckMandatoryEntry(0x0010,0x0030,"","DA");
1693 // Patient's Sex :'type 2' entry -> must exist, value not mandatory
1694 CheckMandatoryEntry(0x0010,0x0040,"","CS");
1696 // Referring Physician's Name :'type 2' entry -> must exist, value not mandatory
1697 CheckMandatoryEntry(0x0008,0x0090,"","PN");
1700 // Deal with element 0x0000 (group length) of each group.
1701 // First stage : get all the different Groups
1704 DocEntry *d = FileInternal->GetFirstEntry();
1707 grHT[d->GetGroup()] = 0;
1708 d=FileInternal->GetNextEntry();
1710 // Second stage : add the missing ones (if any)
1711 for (GroupHT::iterator it = grHT.begin(); it != grHT.end(); ++it)
1713 CheckMandatoryEntry(it->first, 0x0000, "0");
1715 // Third stage : update all 'zero level' groups length
1720 void FileHelper::CheckMandatoryEntry(uint16_t group,uint16_t elem,std::string value,const VRKey &vr )
1722 DataEntry *entry = FileInternal->GetDataEntry(group,elem);
1725 //entry = DataEntry::New(Global::GetDicts()->GetDefaultPubDict()->GetEntry(group,elem));
1726 entry = DataEntry::New(group,elem,vr);
1727 entry->SetString(value);
1728 Archive->Push(entry);
1733 /// \todo : what is it used for ? (FileHelper::SetMandatoryEntry)
1734 void FileHelper::SetMandatoryEntry(uint16_t group,uint16_t elem,std::string value,const VRKey &vr)
1736 //DataEntry *entry = DataEntry::New(Global::GetDicts()->GetDefaultPubDict()->GetEntry(group,elem));
1737 DataEntry *entry = DataEntry::New(group,elem,vr);
1738 entry->SetString(value);
1739 Archive->Push(entry);
1743 void FileHelper::CopyMandatoryEntry(uint16_t group,uint16_t elem,std::string value,const VRKey &vr)
1745 DataEntry *entry = CopyDataEntry(group,elem,vr);
1746 entry->SetString(value);
1747 Archive->Push(entry);
1752 * \brief Restore in the File the initial group 0002
1754 void FileHelper::RestoreWriteMandatory()
1756 // group 0002 may be pushed out for ACR-NEMA writting purposes
1757 Archive->Restore(0x0002,0x0000);
1758 Archive->Restore(0x0002,0x0001);
1759 Archive->Restore(0x0002,0x0002);
1760 Archive->Restore(0x0002,0x0003);
1761 Archive->Restore(0x0002,0x0010);
1762 Archive->Restore(0x0002,0x0012);
1763 Archive->Restore(0x0002,0x0013);
1764 Archive->Restore(0x0002,0x0016);
1765 Archive->Restore(0x0002,0x0100);
1766 Archive->Restore(0x0002,0x0102);
1768 // FIXME : Check if none is missing !
1770 Archive->Restore(0x0008,0x0012);
1771 Archive->Restore(0x0008,0x0013);
1772 Archive->Restore(0x0008,0x0016);
1773 Archive->Restore(0x0008,0x0018);
1774 Archive->Restore(0x0008,0x0060);
1775 Archive->Restore(0x0008,0x0070);
1776 Archive->Restore(0x0008,0x0080);
1777 Archive->Restore(0x0008,0x0090);
1778 Archive->Restore(0x0008,0x2112);
1780 Archive->Restore(0x0010,0x0010);
1781 Archive->Restore(0x0010,0x0030);
1782 Archive->Restore(0x0010,0x0040);
1784 Archive->Restore(0x0020,0x000d);
1785 Archive->Restore(0x0020,0x000e);
1790 * \brief CallStartMethod
1792 void FileHelper::CallStartMethod()
1796 CommandManager::ExecuteCommand(this,CMD_STARTPROGRESS);
1800 * \brief CallProgressMethod
1802 void FileHelper::CallProgressMethod()
1804 CommandManager::ExecuteCommand(this,CMD_PROGRESS);
1808 * \brief CallEndMethod
1810 void FileHelper::CallEndMethod()
1813 CommandManager::ExecuteCommand(this,CMD_ENDPROGRESS);
1816 //-----------------------------------------------------------------------------
1819 * \brief Factorization for various forms of constructors.
1821 void FileHelper::Initialize()
1824 ContentType = USER_OWN_IMAGE;
1826 WriteMode = WMODE_RAW;
1827 WriteType = ExplicitVR;
1829 PixelReadConverter = new PixelReadConvert;
1830 PixelWriteConverter = new PixelWriteConvert;
1831 Archive = new DocEntryArchive( FileInternal );
1835 * \brief Reads/[decompresses] the pixels,
1836 * *without* making RGB from Palette Colors
1837 * @return the pixels area, whatever its type
1838 * (uint8_t is just for prototyping : feel free to Cast it)
1840 uint8_t *FileHelper::GetRaw()
1842 PixelReadConverter->SetUserFunction( UserFunction );
1844 uint8_t *raw = PixelReadConverter->GetRaw();
1847 // The Raw image migth not be loaded yet:
1848 std::ifstream *fp = FileInternal->OpenFile();
1849 PixelReadConverter->ReadAndDecompressPixelData( fp );
1851 FileInternal->CloseFile();
1853 raw = PixelReadConverter->GetRaw();
1856 gdcmWarningMacro( "Read/decompress of pixel data apparently went wrong.");
1863 //-----------------------------------------------------------------------------
1865 * \brief Prints the common part of DataEntry, SeqEntry
1866 * @param os ostream we want to print in
1867 * @param indent (unused)
1869 void FileHelper::Print(std::ostream &os, std::string const &)
1871 FileInternal->SetPrintLevel(PrintLevel);
1872 FileInternal->Print(os);
1874 if ( FileInternal->IsReadable() )
1876 PixelReadConverter->SetPrintLevel(PrintLevel);
1877 PixelReadConverter->Print(os);
1881 //-----------------------------------------------------------------------------
1882 } // end namespace gdcm