1 /*=========================================================================
4 Module: $RCSfile: gdcmFileHelper.cxx,v $
7 Date: $Date: 2006/04/20 16:12:11 $
8 Version: $Revision: 1.101 $
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)
274 return FileInternal->InsertEntryString(content, group, elem);
278 * \brief Modifies the value of a given DataEntry when it exists.
279 * Creates it with the given value when unexistant.
280 * A copy of the binArea is made to be kept in the Document.
281 * @param binArea (binary)value to be set
282 * @param lgth new value length
283 * @param group Group number of the Entry
284 * @param elem Element number of the Entry
285 * \return pointer to the modified/created DataEntry (NULL when creation
288 DataEntry *FileHelper::InsertEntryBinArea(uint8_t *binArea, int lgth,
289 uint16_t group, uint16_t elem)
291 return FileInternal->InsertEntryBinArea(binArea, lgth, group, elem);
295 * \brief Adds an empty SeqEntry
296 * (remove any existing entry with same group,elem)
297 * @param group Group number of the Entry
298 * @param elem Element number of the Entry
299 * \return pointer to the created SeqEntry (NULL when creation
302 SeqEntry *FileHelper::InsertSeqEntry(uint16_t group, uint16_t elem)
304 return FileInternal->InsertSeqEntry(group, elem);
308 * \brief Get the size of the image data
309 * If the image can be RGB (with a lut or by default), the size
310 * corresponds to the RGB image
311 * (use GetImageDataRawSize if you want to be sure to get *only*
312 * the size of the pixels)
313 * @return The image size
315 size_t FileHelper::GetImageDataSize()
317 if ( PixelWriteConverter->GetUserData() )
319 return PixelWriteConverter->GetUserDataSize();
321 return PixelReadConverter->GetRGBSize();
325 * \brief Get the size of the image data.
326 * If the image could be converted to RGB using a LUT,
327 * this transformation is not taken into account by GetImageDataRawSize
328 * (use GetImageDataSize if you wish)
329 * @return The raw image size
331 size_t FileHelper::GetImageDataRawSize()
333 if ( PixelWriteConverter->GetUserData() )
335 return PixelWriteConverter->GetUserDataSize();
337 return PixelReadConverter->GetRawSize();
341 * \brief brings pixels into memory :
342 * - Allocates necessary memory,
343 * - Reads the pixels from disk (uncompress if necessary),
344 * - Transforms YBR pixels, if any, into RGB pixels,
345 * - Transforms 3 planes R, G, B, if any, into a single RGB Plane
346 * - Transforms single Grey plane + 3 Palettes into a RGB Plane
347 * - Copies the pixel data (image[s]/volume[s]) to newly allocated zone.
348 * @return Pointer to newly allocated pixel data.
349 * (uint8_t is just for prototyping. feel free to cast)
350 * NULL if alloc fails
352 uint8_t *FileHelper::GetImageData()
354 if ( PixelWriteConverter->GetUserData() )
356 return PixelWriteConverter->GetUserData();
361 // If the decompression failed nothing can be done.
365 if ( FileInternal->HasLUT() && PixelReadConverter->BuildRGBImage() )
367 return PixelReadConverter->GetRGB();
371 // When no LUT or LUT conversion fails, return the Raw
372 return PixelReadConverter->GetRaw();
377 * \brief brings pixels into memory :
378 * - Allocates necessary memory,
379 * - Transforms YBR pixels (if any) into RGB pixels
380 * - Transforms 3 planes R, G, B (if any) into a single RGB Plane
381 * - Copies the pixel data (image[s]/volume[s]) to newly allocated zone.
382 * - DOES NOT transform Grey plane + 3 Palettes into a RGB Plane
383 * @return Pointer to newly allocated pixel data.
384 * (uint8_t is just for prototyping. feel free to cast)
385 * NULL if alloc fails
387 uint8_t *FileHelper::GetImageDataRaw ()
392 #ifndef GDCM_LEGACY_REMOVE
394 * \brief Useless function, since PixelReadConverter forces us
395 * copy the Pixels anyway.
396 * Reads the pixels from disk (uncompress if necessary),
397 * Transforms YBR pixels, if any, into RGB pixels
398 * Transforms 3 planes R, G, B, if any, into a single RGB Plane
399 * Transforms single Grey plane + 3 Palettes into a RGB Plane
400 * Copies at most MaxSize bytes of pixel data to caller allocated
402 * \warning This function allows people that want to build a volume
403 * from an image stack *not to* have, first to get the image pixels,
404 * and then move them to the volume area.
405 * It's absolutely useless for any VTK user since vtk chooses
406 * to invert the lines of an image, that is the last line comes first
407 * (for some axis related reasons?). Hence he will have
408 * to load the image line by line, starting from the end.
409 * VTK users have to call GetImageData
411 * @param destination Address (in caller's memory space) at which the
412 * pixel data should be copied
413 * @param maxSize Maximum number of bytes to be copied. When MaxSize
414 * is not sufficient to hold the pixel data the copy is not
415 * executed (i.e. no partial copy).
416 * @return On success, the number of bytes actually copied. Zero on
417 * failure e.g. MaxSize is lower than necessary.
419 size_t FileHelper::GetImageDataIntoVector (void *destination, size_t maxSize)
423 // If the decompression failed nothing can be done.
427 if ( FileInternal->HasLUT() && PixelReadConverter->BuildRGBImage() )
429 if ( PixelReadConverter->GetRGBSize() > maxSize )
431 gdcmWarningMacro( "Pixel data bigger than caller's expected MaxSize");
435 (void*)PixelReadConverter->GetRGB(),
436 PixelReadConverter->GetRGBSize() );
437 return PixelReadConverter->GetRGBSize();
440 // Either no LUT conversion necessary or LUT conversion failed
441 if ( PixelReadConverter->GetRawSize() > maxSize )
443 gdcmWarningMacro( "Pixel data bigger than caller's expected MaxSize");
447 (void *)PixelReadConverter->GetRaw(),
448 PixelReadConverter->GetRawSize() );
449 return PixelReadConverter->GetRawSize();
454 * \brief Points the internal pointer to the callers inData
455 * image representation, BUT WITHOUT COPYING THE DATA.
456 * 'image' Pixels are presented as C-like 2D arrays : line per line.
457 * 'volume'Pixels are presented as C-like 3D arrays : plane per plane
458 * \warning Since the pixels are not copied, it is the caller's responsability
459 * not to deallocate its data before gdcm uses them (e.g. with
460 * the Write() method )
461 * @param inData user supplied pixel area (uint8_t* is just for the compiler.
462 * user is allowed to pass any kind of pixelsn since the size is
464 * @param expectedSize total image size, *in Bytes*
466 void FileHelper::SetImageData(uint8_t *inData, size_t expectedSize)
468 SetUserData(inData, expectedSize);
472 * \brief Set the image data defined by the user
473 * \warning When writting the file, this data are get as default data to write
474 * @param inData user supplied pixel area (uint8_t* is just for the compiler.
475 * user is allowed to pass any kind of pixels since the size is
477 * @param expectedSize total image size, *in Bytes*
479 void FileHelper::SetUserData(uint8_t *inData, size_t expectedSize)
481 PixelWriteConverter->SetUserData(inData, expectedSize);
485 * \brief Get the image data defined by the user
486 * \warning When writting the file, this data are get as default data to write
488 uint8_t *FileHelper::GetUserData()
490 return PixelWriteConverter->GetUserData();
494 * \brief Get the image data size defined by the user
495 * \warning When writting the file, this data are get as default data to write
497 size_t FileHelper::GetUserDataSize()
499 return PixelWriteConverter->GetUserDataSize();
503 * \brief Get the image data from the file.
504 * If a LUT is found, the data are expanded to be RGB
506 uint8_t *FileHelper::GetRGBData()
508 return PixelReadConverter->GetRGB();
512 * \brief Get the image data size from the file.
513 * If a LUT is found, the data are expanded to be RGB
515 size_t FileHelper::GetRGBDataSize()
517 return PixelReadConverter->GetRGBSize();
521 * \brief Get the image data from the file.
522 * Even when a LUT is found, the data are not expanded to RGB!
524 uint8_t *FileHelper::GetRawData()
526 return PixelReadConverter->GetRaw();
530 * \brief Get the image data size from the file.
531 * Even when a LUT is found, the data are not expanded to RGB!
533 size_t FileHelper::GetRawDataSize()
535 return PixelReadConverter->GetRawSize();
539 * \brief Access to the underlying \ref PixelReadConverter RGBA LUT
541 uint8_t* FileHelper::GetLutRGBA()
543 if ( PixelReadConverter->GetLutRGBA() ==0 )
544 PixelReadConverter->BuildLUTRGBA();
545 return PixelReadConverter->GetLutRGBA();
549 * \brief Access to the underlying \ref PixelReadConverter RGBA LUT Item Number
551 int FileHelper::GetLutItemNumber()
553 return PixelReadConverter->GetLutItemNumber();
557 * \brief Access to the underlying \ref PixelReadConverter RGBA LUT Item Size
559 int FileHelper::GetLutItemSize()
561 return PixelReadConverter->GetLutItemSize();
565 * \brief Writes on disk A SINGLE Dicom file
566 * NO test is performed on processor "Endiannity".
567 * It's up to the user to call his Reader properly
568 * @param fileName name of the file to be created
569 * (any already existing file is over written)
570 * @return false if write fails
572 bool FileHelper::WriteRawData(std::string const &fileName)
574 std::ofstream fp1(fileName.c_str(), std::ios::out | std::ios::binary );
577 gdcmWarningMacro( "Fail to open (write) file:" << fileName.c_str());
581 if ( PixelWriteConverter->GetUserData() )
583 fp1.write( (char *)PixelWriteConverter->GetUserData(),
584 PixelWriteConverter->GetUserDataSize() );
586 else if ( PixelReadConverter->GetRGB() )
588 fp1.write( (char *)PixelReadConverter->GetRGB(),
589 PixelReadConverter->GetRGBSize());
591 else if ( PixelReadConverter->GetRaw() )
593 fp1.write( (char *)PixelReadConverter->GetRaw(),
594 PixelReadConverter->GetRawSize());
598 gdcmErrorMacro( "Nothing written." );
607 * \brief Writes on disk A SINGLE Dicom file,
608 * using the Implicit Value Representation convention
609 * NO test is performed on processor "Endianity".
610 * @param fileName name of the file to be created
611 * (any already existing file is overwritten)
612 * @return false if write fails
615 bool FileHelper::WriteDcmImplVR (std::string const &fileName)
617 SetWriteTypeToDcmImplVR();
618 return Write(fileName);
622 * \brief Writes on disk A SINGLE Dicom file,
623 * using the Explicit Value Representation convention
624 * NO test is performed on processor "Endiannity".
625 * @param fileName name of the file to be created
626 * (any already existing file is overwritten)
627 * @return false if write fails
630 bool FileHelper::WriteDcmExplVR (std::string const &fileName)
632 SetWriteTypeToDcmExplVR();
633 return Write(fileName);
637 * \brief Writes on disk A SINGLE Dicom file,
638 * using the ACR-NEMA convention
639 * NO test is performed on processor "Endiannity".
640 * (a l'attention des logiciels cliniques
641 * qui ne prennent en entrée QUE des images ACR ...
642 * \warning if a DICOM_V3 header is supplied,
643 * groups < 0x0008 and shadow groups are ignored
644 * \warning NO TEST is performed on processor "Endiannity".
645 * @param fileName name of the file to be created
646 * (any already existing file is overwritten)
647 * @return false if write fails
650 bool FileHelper::WriteAcr (std::string const &fileName)
653 return Write(fileName);
657 * \brief Writes on disk A SINGLE Dicom file,
658 * @param fileName name of the file to be created
659 * (any already existing file is overwritten)
660 * @return false if write fails
662 bool FileHelper::Write(std::string const &fileName)
665 CheckMandatoryElements(); //called once, here !
672 SetWriteFileTypeToImplicitVR();
675 case Unknown: // should never happen; ExplicitVR is the default value
678 // User should ask gdcm to write an image in Explicit VR mode
679 // only when he is sure *all* the VR of *all* the DataElements is known.
680 // i.e : when there are *only* Public Groups
681 // or *all* the Shadow Groups are fully described in the relevant Shadow
683 // Let's just *dream* about it; *never* trust a user !
684 // We turn to Implicit VR if at least the VR of one element is unknown.
687 e = FileInternal->GetFirstEntry();
690 if (e->GetVR() == " ")
692 SetWriteTypeToDcmImplVR();
693 SetWriteFileTypeToImplicitVR();
697 e = FileInternal->GetNextEntry();
702 SetWriteFileTypeToExplicitVR();
706 SetWriteFileTypeToExplicitVR(); // to see JPRx
710 // NOTHING is done here just for LibIDO.
711 // Just to avoid further trouble if user creates a file ex-nihilo,
712 // wants to write it as an ACR-NEMA file,
713 // and forgets to create any Entry belonging to group 0008
715 // We add Recognition Code (RET)
716 if ( ! FileInternal->GetDataEntry(0x0008, 0x0010) )
717 FileInternal->InsertEntryString("ACR-NEMA V1.0 ",
718 0x0008, 0x0010, "LO");
719 SetWriteFileTypeToACR();
720 // SetWriteFileTypeToImplicitVR(); // ACR IS implicit VR !
723 /// \todo FIXME : JPEG may be either ExplicitVR or ImplicitVR
725 SetWriteFileTypeToJPEG();
729 // --------------------------------------------------------------
730 // Special Patch to allow gdcm to re-write ACR-LibIDO formated images
732 // if recognition code tells us we dealt with a LibIDO image
733 // we reproduce on disk the switch between lineNumber and columnNumber
734 // just before writting ...
735 /// \todo the best trick would be *change* the recognition code
736 /// but pb expected if user deals with, e.g. COMPLEX images
738 if ( WriteType == ACR_LIBIDO )
744 SetWriteToNoLibido();
746 // ----------------- End of Special Patch ----------------
751 SetWriteToRaw(); // modifies and pushes to the archive, when necessary
754 SetWriteToRGB(); // modifies and pushes to the archive, when necessary
758 bool check = CheckWriteIntegrity(); // verifies length
759 if (WriteType == JPEG ) check = true;
762 check = FileInternal->Write(fileName,WriteType);
766 // RestoreWriteFileType();
767 // RestoreWriteMandatory();
770 // --------------------------------------------------------------
771 // Special Patch to allow gdcm to re-write ACR-LibIDO formated images
773 // ...and we restore the header to be Dicom Compliant again
774 // just after writting
775 RestoreWriteOfLibido();
776 // ----------------- End of Special Patch ----------------
781 //-----------------------------------------------------------------------------
784 * \brief Checks the write integrity
786 * The tests made are :
787 * - verify the size of the image to write with the possible write
788 * when the user set an image data
789 * @return true if check is successfull
791 bool FileHelper::CheckWriteIntegrity()
793 if ( PixelWriteConverter->GetUserData() )
795 int numberBitsAllocated = FileInternal->GetBitsAllocated();
796 if ( numberBitsAllocated == 0 || numberBitsAllocated == 12 )
798 gdcmWarningMacro( "numberBitsAllocated changed from "
799 << numberBitsAllocated << " to 16 "
800 << " for consistency purpose" );
801 numberBitsAllocated = 16;
804 size_t decSize = FileInternal->GetXSize()
805 * FileInternal->GetYSize()
806 * FileInternal->GetZSize()
807 * FileInternal->GetTSize()
808 * FileInternal->GetSamplesPerPixel()
809 * ( numberBitsAllocated / 8 );
810 size_t rgbSize = decSize;
811 if ( FileInternal->HasLUT() )
812 rgbSize = decSize * 3;
817 if ( decSize!=PixelWriteConverter->GetUserDataSize() )
819 gdcmWarningMacro( "Data size (Raw) is incorrect. Should be "
820 << decSize << " / Found :"
821 << PixelWriteConverter->GetUserDataSize() );
826 if ( rgbSize!=PixelWriteConverter->GetUserDataSize() )
828 gdcmWarningMacro( "Data size (RGB) is incorrect. Should be "
829 << decSize << " / Found "
830 << PixelWriteConverter->GetUserDataSize() );
841 * \brief Updates the File to write RAW data (as opposed to RGB data)
842 * (modifies, when necessary, photochromatic interpretation,
843 * bits allocated, Pixels element VR)
845 void FileHelper::SetWriteToRaw()
847 if ( FileInternal->GetNumberOfScalarComponents() == 3
848 && !FileInternal->HasLUT() )
854 DataEntry *photInt = CopyDataEntry(0x0028,0x0004,"CS");
855 if (FileInternal->HasLUT() )
857 photInt->SetString("PALETTE COLOR ");
861 photInt->SetString("MONOCHROME2 ");
864 PixelWriteConverter->SetReadData(PixelReadConverter->GetRaw(),
865 PixelReadConverter->GetRawSize());
867 std::string vr = "OB";
868 if ( FileInternal->GetBitsAllocated()>8 )
870 if ( FileInternal->GetBitsAllocated()==24 ) // For RGB ACR files
873 CopyDataEntry(GetFile()->GetGrPixel(),GetFile()->GetNumPixel(),vr);
874 pixel->SetFlag(DataEntry::FLAG_PIXELDATA);
875 pixel->SetBinArea(PixelWriteConverter->GetData(),false);
876 pixel->SetLength(PixelWriteConverter->GetDataSize());
878 Archive->Push(photInt);
879 Archive->Push(pixel);
887 * \brief Updates the File to write RGB data (as opposed to RAW data)
888 * (modifies, when necessary, photochromatic interpretation,
889 * samples per pixel, Planar configuration,
890 * bits allocated, bits stored, high bit -ACR 24 bits-
891 * Pixels element VR, pushes out the LUT, )
893 void FileHelper::SetWriteToRGB()
895 if ( FileInternal->GetNumberOfScalarComponents()==3 )
897 PixelReadConverter->BuildRGBImage();
899 DataEntry *spp = CopyDataEntry(0x0028,0x0002,"US");
900 spp->SetString("3 ");
902 DataEntry *planConfig = CopyDataEntry(0x0028,0x0006,"US");
903 planConfig->SetString("0 ");
905 DataEntry *photInt = CopyDataEntry(0x0028,0x0004,"CS");
906 photInt->SetString("RGB ");
908 if ( PixelReadConverter->GetRGB() )
910 PixelWriteConverter->SetReadData(PixelReadConverter->GetRGB(),
911 PixelReadConverter->GetRGBSize());
915 PixelWriteConverter->SetReadData(PixelReadConverter->GetRaw(),
916 PixelReadConverter->GetRawSize());
919 std::string vr = "OB";
920 if ( FileInternal->GetBitsAllocated()>8 )
922 if ( FileInternal->GetBitsAllocated()==24 ) // For RGB ACR files
925 CopyDataEntry(GetFile()->GetGrPixel(),GetFile()->GetNumPixel(),vr);
926 pixel->SetFlag(DataEntry::FLAG_PIXELDATA);
927 pixel->SetBinArea(PixelWriteConverter->GetData(),false);
928 pixel->SetLength(PixelWriteConverter->GetDataSize());
931 Archive->Push(planConfig);
932 Archive->Push(photInt);
933 Archive->Push(pixel);
936 planConfig->Delete();
941 Archive->Push(0x0028,0x1101);
942 Archive->Push(0x0028,0x1102);
943 Archive->Push(0x0028,0x1103);
944 Archive->Push(0x0028,0x1201);
945 Archive->Push(0x0028,0x1202);
946 Archive->Push(0x0028,0x1203);
948 // push out Palette Color Lookup Table UID, if any
949 Archive->Push(0x0028,0x1199);
951 // For old '24 Bits' ACR-NEMA
952 // Thus, we have a RGB image and the bits allocated = 24 and
953 // samples per pixels = 1 (in the read file)
954 if ( FileInternal->GetBitsAllocated()==24 )
956 DataEntry *bitsAlloc = CopyDataEntry(0x0028,0x0100,"US");
957 bitsAlloc->SetString("8 ");
959 DataEntry *bitsStored = CopyDataEntry(0x0028,0x0101,"US");
960 bitsStored->SetString("8 ");
962 DataEntry *highBit = CopyDataEntry(0x0028,0x0102,"US");
963 highBit->SetString("7 ");
965 Archive->Push(bitsAlloc);
966 Archive->Push(bitsStored);
967 Archive->Push(highBit);
970 bitsStored->Delete();
981 * \brief Restore the File write mode
983 void FileHelper::RestoreWrite()
986 Archive->Restore(0x0028,0x0002);
987 Archive->Restore(0x0028,0x0004);
989 Archive->Restore(0x0028,0x0006);
990 Archive->Restore(GetFile()->GetGrPixel(),GetFile()->GetNumPixel());
992 // For old ACR-NEMA (24 bits problem)
993 Archive->Restore(0x0028,0x0100);
994 Archive->Restore(0x0028,0x0101);
995 Archive->Restore(0x0028,0x0102);
998 Archive->Restore(0x0028,0x1101);
999 Archive->Restore(0x0028,0x1102);
1000 Archive->Restore(0x0028,0x1103);
1001 Archive->Restore(0x0028,0x1201);
1002 Archive->Restore(0x0028,0x1202);
1003 Archive->Restore(0x0028,0x1203);
1005 // For the Palette Color Lookup Table UID
1006 Archive->Restore(0x0028,0x1203);
1008 // group 0002 may be pushed out for ACR-NEMA writting purposes
1009 Archive->Restore(0x0002,0x0000);
1010 Archive->Restore(0x0002,0x0001);
1011 Archive->Restore(0x0002,0x0002);
1012 Archive->Restore(0x0002,0x0003);
1013 Archive->Restore(0x0002,0x0010);
1014 Archive->Restore(0x0002,0x0012);
1015 Archive->Restore(0x0002,0x0013);
1016 Archive->Restore(0x0002,0x0016);
1017 Archive->Restore(0x0002,0x0100);
1018 Archive->Restore(0x0002,0x0102);
1023 * \brief Pushes out the whole group 0002
1024 * FIXME : better, set a flag to tell the writer not to write it ...
1025 * FIXME : method should probably have an other name !
1026 * SetWriteFileTypeToACR is NOT opposed to
1027 * SetWriteFileTypeToExplicitVR and SetWriteFileTypeToImplicitVR
1029 void FileHelper::SetWriteFileTypeToACR()
1031 Archive->Push(0x0002,0x0000);
1032 Archive->Push(0x0002,0x0001);
1033 Archive->Push(0x0002,0x0002);
1034 Archive->Push(0x0002,0x0003);
1035 Archive->Push(0x0002,0x0010);
1036 Archive->Push(0x0002,0x0012);
1037 Archive->Push(0x0002,0x0013);
1038 Archive->Push(0x0002,0x0016);
1039 Archive->Push(0x0002,0x0100);
1040 Archive->Push(0x0002,0x0102);
1044 * \brief Sets in the File the TransferSyntax to 'JPEG'
1046 void FileHelper::SetWriteFileTypeToJPEG()
1048 std::string ts = Util::DicomString(
1049 Global::GetTS()->GetSpecialTransferSyntax(TS::JPEGBaselineProcess1) );
1051 DataEntry *tss = CopyDataEntry(0x0002,0x0010,"UI");
1059 * \brief Sets in the File the TransferSyntax to 'Explicit VR Little Endian"
1061 void FileHelper::SetWriteFileTypeToExplicitVR()
1063 std::string ts = Util::DicomString(
1064 Global::GetTS()->GetSpecialTransferSyntax(TS::ExplicitVRLittleEndian) );
1066 DataEntry *tss = CopyDataEntry(0x0002,0x0010,"UI");
1073 * \brief Sets in the File the TransferSyntax to 'Implicit VR Little Endian"
1075 void FileHelper::SetWriteFileTypeToImplicitVR()
1077 std::string ts = Util::DicomString(
1078 Global::GetTS()->GetSpecialTransferSyntax(TS::ImplicitVRLittleEndian) );
1080 DataEntry *tss = CopyDataEntry(0x0002,0x0010,"UI");
1088 * \brief Restore in the File the initial group 0002
1090 void FileHelper::RestoreWriteFileType()
1095 * \brief Set the Write not to Libido format
1097 void FileHelper::SetWriteToLibido()
1099 DataEntry *oldRow = FileInternal->GetDataEntry(0x0028, 0x0010);
1100 DataEntry *oldCol = FileInternal->GetDataEntry(0x0028, 0x0011);
1102 if ( oldRow && oldCol )
1104 std::string rows, columns;
1106 //DataEntry *newRow=DataEntry::New(oldRow->GetDictEntry());
1107 //DataEntry *newCol=DataEntry::New(oldCol->GetDictEntry());
1109 DataEntry *newRow=DataEntry::New(0x0028, 0x0010, "US");
1110 DataEntry *newCol=DataEntry::New(0x0028, 0x0011, "US");
1112 newRow->Copy(oldCol);
1113 newCol->Copy(oldRow);
1115 newRow->SetString(oldCol->GetString());
1116 newCol->SetString(oldRow->GetString());
1118 Archive->Push(newRow);
1119 Archive->Push(newCol);
1125 DataEntry *libidoCode = CopyDataEntry(0x0008,0x0010,"LO");
1126 libidoCode->SetString("ACRNEMA_LIBIDO_1.1");
1127 Archive->Push(libidoCode);
1128 libidoCode->Delete();
1132 * \brief Set the Write not to No Libido format
1134 void FileHelper::SetWriteToNoLibido()
1136 DataEntry *recCode = FileInternal->GetDataEntry(0x0008,0x0010);
1139 if ( recCode->GetString() == "ACRNEMA_LIBIDO_1.1" )
1141 DataEntry *libidoCode = CopyDataEntry(0x0008,0x0010,"LO");
1142 libidoCode->SetString("");
1143 Archive->Push(libidoCode);
1144 libidoCode->Delete();
1150 * \brief Restore the Write format
1152 void FileHelper::RestoreWriteOfLibido()
1154 Archive->Restore(0x0028,0x0010);
1155 Archive->Restore(0x0028,0x0011);
1156 Archive->Restore(0x0008,0x0010);
1158 // Restore 'LibIDO-special' entries, if any
1159 Archive->Restore(0x0028,0x0015);
1160 Archive->Restore(0x0028,0x0016);
1161 Archive->Restore(0x0028,0x0017);
1162 Archive->Restore(0x0028,0x00199);
1166 * \brief Duplicates a DataEntry or creates it.
1167 * @param group Group number of the Entry
1168 * @param elem Element number of the Entry
1169 * @param vr Value Representation of the Entry
1170 * \return pointer to the new Bin Entry (NULL when creation failed).
1172 DataEntry *FileHelper::CopyDataEntry(uint16_t group, uint16_t elem,
1175 DocEntry *oldE = FileInternal->GetDocEntry(group, elem);
1178 if ( oldE && vr != GDCM_VRUNKNOWN )
1179 if ( oldE->GetVR() != vr )
1184 //newE = DataEntry::New(oldE->GetDictEntry());
1185 newE = DataEntry::New(group, elem, vr);
1190 newE = GetFile()->NewDataEntry(group, elem, vr);
1197 * \brief This method is called automatically, just before writting
1198 * in order to produce a 'True Dicom V3' image.
1200 * We cannot know *how* the user made the File :
1201 * (reading an old ACR-NEMA file or a not very clean DICOM file ...)
1202 * Just before writting :
1203 * - we check the Entries
1204 * - we create the mandatory entries if they are missing
1205 * - we modify the values if necessary
1206 * - we push the sensitive entries to the Archive
1207 * The writing process will restore the entries as they where before
1208 * entering FileHelper::CheckMandatoryElements, so the user will always
1209 * see the entries just as they were before he decided to write.
1212 * - Entries whose type is 1 are mandatory, with a mandatory value
1213 * - Entries whose type is 1c are mandatory-inside-a-Sequence,
1214 * with a mandatory value
1215 * - Entries whose type is 2 are mandatory, with an optional value
1216 * - Entries whose type is 2c are mandatory-inside-a-Sequence,
1217 * with an optional value
1218 * - Entries whose type is 3 are optional
1221 * - warn the user if we had to add some entries :
1222 * even if a mandatory entry is missing, we add it, with a default value
1223 * (we don't want to give up the writting process if user forgot to
1224 * specify Lena's Patient ID, for instance ...)
1225 * - read the whole PS 3.3 Part of DICOM (890 pages)
1226 * and write a *full* checker (probably one method per Modality ...)
1227 * Any contribution is welcome.
1228 * - write a user callable full checker, to allow post reading
1229 * and/or pre writting image consistency check.
1232 /* -------------------------------------------------------------------------------------
1233 To be moved to User's guide / WIKI ?
1235 We have to deal with 4 *very* different cases :
1236 -1) user created ex nihilo his own image and wants to write it as a Dicom image.
1238 -2) user modified the pixels of an existing image.
1240 -3) user created a new image, using existing images (eg MIP, MPR, cartography image)
1242 -4) user modified/added some tags *without processing* the pixels (anonymization..
1243 UNMODIFIED_PIXELS_IMAGE
1245 gdcm::FileHelper::CheckMandatoryElements() deals automatically with these cases.
1248 0008 0012 Instance Creation Date
1249 0008 0013 Instance Creation Time
1250 0008 0018 SOP Instance UID
1251 are *always* created with the current values; user has *no* possible intervention on
1254 'Serie Instance UID'(0x0020,0x000e)
1255 'Study Instance UID'(0x0020,0x000d) are kept as is if already exist,
1256 created if it doesn't.
1257 The user is allowed to create his own Series/Studies,
1258 keeping the same 'Serie Instance UID' / 'Study Instance UID' for various images
1260 The user shouldn't add any image to a 'Manufacturer Serie'
1261 but there is no way no to allow him to do that
1263 None of the 'shadow elements' are droped out.
1267 'Conversion Type (0x0008,0x0064) is forced to 'SYN' (Synthetic Image).
1270 'Media Storage SOP Class UID' (0x0002,0x0002)
1271 'SOP Class UID' (0x0008,0x0016) are set to
1272 [Secondary Capture Image Storage]
1273 'Image Type' (0x0008,0x0008) is forced to "DERIVED\PRIMARY"
1274 Conversion Type (0x0008,0x0064) is forced to 'SYN' (Synthetic Image)
1277 If 'SOP Class UID' exists in the native image ('true DICOM' image)
1278 we create the 'Source Image Sequence' SeqEntry (0x0008, 0x2112)
1279 --> 'Referenced SOP Class UID' (0x0008, 0x1150)
1280 whose value is the original 'SOP Class UID'
1281 --> 'Referenced SOP Instance UID' (0x0008, 0x1155)
1282 whose value is the original 'SOP Class UID'
1284 3) TODO : find a trick to allow user to pass to the writter the list of the Dicom images
1285 or the Series, (or the Study ?) he used to created his image
1286 (MIP, MPR, cartography image, ...)
1287 These info should be stored (?)
1288 0008 1110 SQ 1 Referenced Study Sequence
1289 0008 1115 SQ 1 Referenced Series Sequence
1290 0008 1140 SQ 1 Referenced Image Sequence
1292 4) When user *knows* he didn't modified the pixels, we keep some informations unchanged :
1293 'Media Storage SOP Class UID' (0x0002,0x0002)
1294 'SOP Class UID' (0x0008,0x0016)
1295 'Image Type' (0x0008,0x0008)
1296 'Conversion Type' (0x0008,0x0064)
1299 Bellow follows the full description (hope so !) of the consistency checks performed
1300 by gdcm::FileHelper::CheckMandatoryElements()
1303 -->'Media Storage SOP Class UID' (0x0002,0x0002)
1304 -->'SOP Class UID' (0x0008,0x0016) are defaulted to
1305 [Secondary Capture Image Storage]
1306 --> 'Image Type' (0x0008,0x0008)
1307 is forced to "DERIVED\PRIMARY"
1308 (The written image is no longer an 'ORIGINAL' one)
1309 Except if user knows he didn't modify the image (e.g. : he just anonymized the file)
1311 --> Conversion Type (0x0008,0x0064)
1312 is defaulted to 'SYN' (Synthetic Image)
1313 when *he* knows he created his own image ex nihilo
1315 --> 'Modality' (0x0008,0x0060)
1316 is defaulted to "OT" (other) if missing.
1317 (a fully user created image belongs to *no* modality)
1319 --> 'Media Storage SOP Instance UID' (0x0002,0x0003)
1320 --> 'Implementation Class UID' (0x0002,0x0012)
1321 are automatically generated; no user intervention possible
1323 --> 'Serie Instance UID'(0x0020,0x000e)
1324 --> 'Study Instance UID'(0x0020,0x000d) are kept as is if already exist
1325 created if it doesn't.
1326 The user is allowed to create his own Series/Studies,
1327 keeping the same 'Serie Instance UID' / 'Study Instance UID'
1330 The user shouldn't add any image to a 'Manufacturer Serie'
1331 but there is no way no to allowed him to do that
1333 --> If 'SOP Class UID' exists in the native image ('true DICOM' image)
1334 we create the 'Source Image Sequence' SeqEntry (0x0008, 0x2112)
1336 --> 'Referenced SOP Class UID' (0x0008, 0x1150)
1337 whose value is the original 'SOP Class UID'
1338 --> 'Referenced SOP Instance UID' (0x0008, 0x1155)
1339 whose value is the original 'SOP Class UID'
1341 --> Bits Stored, Bits Allocated, Hight Bit Position are checked for consistency
1342 --> Pixel Spacing (0x0028,0x0030) is defaulted to "1.0\1.0"
1343 --> Samples Per Pixel (0x0028,0x0002) is defaulted to 1 (grayscale)
1345 --> Imager Pixel Spacing (0x0018,0x1164) : defaulted to Pixel Spacing value
1347 --> Instance Creation Date, Instance Creation Time are forced to current Date and Time
1349 --> Study Date, Study Time are defaulted to current Date and Time
1350 (they remain unchanged if they exist)
1352 --> Patient Orientation : (0x0020,0x0020), if not present, is deduced from
1353 Image Orientation (Patient) : (0020|0037) or from
1354 Image Orientation (RET) : (0020 0035)
1356 --> Study ID, Series Number, Instance Number, Patient Orientation (Type 2)
1357 are created, with empty value if there are missing.
1359 --> Manufacturer, Institution Name, Patient's Name, (Type 2)
1360 are defaulted with a 'gdcm' value.
1362 --> Patient ID, Patient's Birth Date, Patient's Sex, (Type 2)
1363 --> Referring Physician's Name (Type 2)
1364 are created, with empty value if there are missing.
1366 -------------------------------------------------------------------------------------*/
1368 void FileHelper::CheckMandatoryElements()
1370 std::string sop = Util::CreateUniqueUID();
1372 // --------------------- For Meta Elements ---------------------
1373 // just to remember : 'official' 0002 group
1374 if ( WriteType != ACR && WriteType != ACR_LIBIDO )
1376 // Group 000002 (Meta Elements) already pushed out
1378 //0002 0000 UL 1 Meta Group Length
1379 //0002 0001 OB 1 File Meta Information Version
1380 //0002 0002 UI 1 Media Stored SOP Class UID
1381 //0002 0003 UI 1 Media Stored SOP Instance UID
1382 //0002 0010 UI 1 Transfer Syntax UID
1383 //0002 0012 UI 1 Implementation Class UID
1384 //0002 0013 SH 1 Implementation Version Name
1385 //0002 0016 AE 1 Source Application Entity Title
1386 //0002 0100 UI 1 Private Information Creator
1387 //0002 0102 OB 1 Private Information
1389 // Create them if not found
1390 // Always modify the value
1391 // Push the entries to the archive.
1392 CopyMandatoryEntry(0x0002,0x0000,"0","UL");
1394 DataEntry *e_0002_0001 = CopyDataEntry(0x0002,0x0001, "OB");
1395 e_0002_0001->SetBinArea((uint8_t*)Util::GetFileMetaInformationVersion(),
1397 e_0002_0001->SetLength(2);
1398 Archive->Push(e_0002_0001);
1399 e_0002_0001->Delete();
1401 if ( ContentType == FILTERED_IMAGE || ContentType == UNMODIFIED_PIXELS_IMAGE)
1403 // we keep the original 'Media Storage SOP Class UID', we default it if missing
1404 CheckMandatoryEntry(0x0002,0x0002,"1.2.840.10008.5.1.4.1.1.7","UI");
1408 // It's *not* an image comming straight from a source. We force
1409 // 'Media Storage SOP Class UID' --> [Secondary Capture Image Storage]
1410 CopyMandatoryEntry(0x0002,0x0002,"1.2.840.10008.5.1.4.1.1.7","UI");
1413 // 'Media Storage SOP Instance UID'
1414 CopyMandatoryEntry(0x0002,0x0003,sop,"UI");
1416 // 'Implementation Class UID'
1417 // FIXME : in all examples we have, 0x0002,0x0012 is not so long :
1418 // seems to be Root UID + 4 digits (?)
1419 CopyMandatoryEntry(0x0002,0x0012,Util::CreateUniqueUID(),"UI");
1421 // 'Implementation Version Name'
1422 std::string version = "GDCM ";
1423 version += Util::GetVersion();
1424 CopyMandatoryEntry(0x0002,0x0013,version,"SH");
1427 // --------------------- For DataSet ---------------------
1429 if ( ContentType != USER_OWN_IMAGE) // when it's not a user made image
1432 gdcmDebugMacro( "USER_OWN_IMAGE (1)");
1433 // If 'SOP Class UID' exists ('true DICOM' image)
1434 // we create the 'Source Image Sequence' SeqEntry
1435 // to hold informations about the Source Image
1437 DataEntry *e_0008_0016 = FileInternal->GetDataEntry(0x0008, 0x0016);
1440 // Create 'Source Image Sequence' SeqEntry
1441 // SeqEntry *sis = SeqEntry::New (
1442 // Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x2112) );
1443 SeqEntry *sis = SeqEntry::New (0x0008, 0x2112);
1444 SQItem *sqi = SQItem::New(1);
1445 // (we assume 'SOP Instance UID' exists too)
1446 // create 'Referenced SOP Class UID'
1447 // DataEntry *e_0008_1150 = DataEntry::New(
1448 // Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x1150) );
1449 DataEntry *e_0008_1150 = DataEntry::New(0x0008, 0x1150, "UI");
1450 e_0008_1150->SetString( e_0008_0016->GetString());
1451 sqi->AddEntry(e_0008_1150);
1452 e_0008_1150->Delete();
1454 // create 'Referenced SOP Instance UID'
1455 DataEntry *e_0008_0018 = FileInternal->GetDataEntry(0x0008, 0x0018);
1456 // DataEntry *e_0008_1155 = DataEntry::New(
1457 // Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x1155) );
1458 DataEntry *e_0008_1155 = DataEntry::New(0x0008, 0x1155, "UI");
1459 e_0008_1155->SetString( e_0008_0018->GetString());
1460 sqi->AddEntry(e_0008_1155);
1461 e_0008_1155->Delete();
1463 sis->AddSQItem(sqi,1);
1466 // temporarily replaces any previous 'Source Image Sequence'
1470 // FIXME : is 'Image Type' *really* depending on the presence of 'SOP Class UID'?
1471 if ( ContentType == FILTERED_IMAGE)
1472 // the user *knows* he just modified the pixels
1473 // the image is no longer an 'Original' one
1474 CopyMandatoryEntry(0x0008,0x0008,"DERIVED\\PRIMARY","CS");
1478 if ( ContentType == FILTERED_IMAGE || ContentType == UNMODIFIED_PIXELS_IMAGE)
1480 // we keep the original 'Media Storage SOP Class UID', we default it if missing (it should be present !)
1481 CheckMandatoryEntry(0x0008,0x0016,"1.2.840.10008.5.1.4.1.1.7","UI");
1485 // It's *not* an image comming straight from a source. We force
1486 // 'Media Storage SOP Class UID' --> [Secondary Capture Image Storage]
1487 CopyMandatoryEntry(0x0008,0x0016,"1.2.840.10008.5.1.4.1.1.7", "UI");
1490 Archive->Push(0x0028,0x005); // [Image Dimensions (RET)
1491 // Push out 'LibIDO-special' entries, if any
1492 Archive->Push(0x0028,0x0015);
1493 Archive->Push(0x0028,0x0016);
1494 Archive->Push(0x0028,0x0017);
1495 Archive->Push(0x0028,0x0198); // very old versions
1496 Archive->Push(0x0028,0x0199);
1498 // Replace deprecated 0028 0012 US Planes
1499 // by new 0028 0008 IS Number of Frames
1500 DataEntry *e_0028_0012 = FileInternal->GetDataEntry(0x0028, 0x0012);
1503 CopyMandatoryEntry(0x0028, 0x0008,e_0028_0012->GetString(),"IS");
1504 Archive->Push(0x0028,0x0012);
1507 // Deal with the pb of (Bits Stored = 12)
1508 // - we're gonna write the image as Bits Stored = 16
1509 if ( FileInternal->GetEntryString(0x0028,0x0100) == "12")
1511 CopyMandatoryEntry(0x0028,0x0100,"16","US");
1514 // Check if user wasn't drunk ;-)
1516 std::ostringstream s;
1517 // check 'Bits Allocated' vs decent values
1518 int nbBitsAllocated = FileInternal->GetBitsAllocated();
1519 if ( nbBitsAllocated == 0 || nbBitsAllocated > 32)
1521 CopyMandatoryEntry(0x0028,0x0100,"16","US");
1522 gdcmWarningMacro("(0028,0100) changed from "
1523 << nbBitsAllocated << " to 16 for consistency purpose");
1524 nbBitsAllocated = 16;
1526 // check 'Bits Stored' vs 'Bits Allocated'
1527 int nbBitsStored = FileInternal->GetBitsStored();
1528 if ( nbBitsStored == 0 || nbBitsStored > nbBitsAllocated )
1531 s << nbBitsAllocated;
1532 CopyMandatoryEntry(0x0028,0x0101,s.str(),"US");
1533 gdcmWarningMacro("(0028,0101) changed from "
1534 << nbBitsStored << " to " << nbBitsAllocated
1535 << " for consistency purpose" );
1536 nbBitsStored = nbBitsAllocated;
1538 // check 'Hight Bit Position' vs 'Bits Allocated' and 'Bits Stored'
1539 int highBitPosition = FileInternal->GetHighBitPosition();
1540 if ( highBitPosition == 0 ||
1541 highBitPosition > nbBitsAllocated-1 ||
1542 highBitPosition < nbBitsStored-1 )
1545 s << nbBitsStored - 1;
1546 CopyMandatoryEntry(0x0028,0x0102,s.str(),"US");
1547 gdcmWarningMacro("(0028,0102) changed from "
1548 << highBitPosition << " to " << nbBitsAllocated-1
1549 << " for consistency purpose");
1552 std::string pixelSpacing = FileInternal->GetEntryString(0x0028,0x0030);
1553 if ( pixelSpacing == GDCM_UNFOUND )
1555 pixelSpacing = "1.0\\1.0";
1556 // if missing, Pixel Spacing forced to "1.0\1.0"
1557 CopyMandatoryEntry(0x0028,0x0030,pixelSpacing,"DS");
1560 // 'Imager Pixel Spacing' : defaulted to 'Pixel Spacing'
1561 // --> This one is the *legal* one !
1562 if ( ContentType != USER_OWN_IMAGE)
1563 // we write it only when we are *sure* the image comes from
1564 // an imager (see also 0008,0x0064)
1565 CheckMandatoryEntry(0x0018,0x1164,pixelSpacing,"DS");
1567 // Samples Per Pixel (type 1) : default to grayscale
1568 CheckMandatoryEntry(0x0028,0x0002,"1","US");
1570 // --- Check UID-related Entries ---
1572 // At the end, not to overwrite the original ones,
1573 // needed by 'Referenced SOP Instance UID', 'Referenced SOP Class UID'
1574 // 'SOP Instance UID'
1575 CopyMandatoryEntry(0x0008,0x0018,sop,"UI");
1577 if ( ContentType == USER_OWN_IMAGE)
1579 gdcmDebugMacro( "USER_OWN_IMAGE (2)");
1581 // Other possible values are :
1582 // See PS 3.3, Page 408
1584 // DV = Digitized Video
1585 // DI = Digital Interface
1586 // DF = Digitized Film
1587 // WSD = Workstation
1588 // SD = Scanned Document
1589 // SI = Scanned Image
1591 // SYN = Synthetic Image
1593 CheckMandatoryEntry(0x0008,0x0064,"SYN","CS"); // Why not?
1596 if ( ContentType == CREATED_IMAGE)
1598 /// \todo : find a trick to pass the Media Storage SOP Instance UID of the images used to create the current image
1603 // ---- The user will never have to take any action on the following ----
1605 // new value for 'SOP Instance UID'
1606 //SetMandatoryEntry(0x0008,0x0018,Util::CreateUniqueUID());
1608 // Instance Creation Date
1609 const std::string &date = Util::GetCurrentDate();
1610 CopyMandatoryEntry(0x0008,0x0012,date,"DA");
1612 // Instance Creation Time
1613 const std::string &time = Util::GetCurrentTime();
1614 CopyMandatoryEntry(0x0008,0x0013,time,"TM");
1617 CheckMandatoryEntry(0x0008,0x0020,date,"DA");
1619 CheckMandatoryEntry(0x0008,0x0030,time,"TM");
1622 //CopyMandatoryEntry(0x0008,0x0050,"");
1623 CheckMandatoryEntry(0x0008,0x0050,"","SH");
1626 // ----- Add Mandatory Entries if missing ---
1627 // Entries whose type is 1 are mandatory, with a mandatory value
1628 // Entries whose type is 1c are mandatory-inside-a-Sequence,
1629 // with a mandatory value
1630 // Entries whose type is 2 are mandatory, with an optional value
1631 // Entries whose type is 2c are mandatory-inside-a-Sequence,
1632 // with an optional value
1633 // Entries whose type is 3 are optional
1635 // 'Study Instance UID'
1636 // Keep the value if exists
1637 // The user is allowed to create his own Study,
1638 // keeping the same 'Study Instance UID' for various images
1639 // The user may add images to a 'Manufacturer Study',
1640 // adding new Series to an already existing Study
1641 CheckMandatoryEntry(0x0020,0x000d,Util::CreateUniqueUID(),"UI");
1643 // 'Serie Instance UID'
1644 // Keep the value if exists
1645 // The user is allowed to create his own Series,
1646 // keeping the same 'Serie Instance UID' for various images
1647 // The user shouldn't add any image to a 'Manufacturer Serie'
1648 // but there is no way no to prevent him for doing that
1649 CheckMandatoryEntry(0x0020,0x000e,Util::CreateUniqueUID(),"UI");
1652 CheckMandatoryEntry(0x0020,0x0010,"","SH");
1655 CheckMandatoryEntry(0x0020,0x0011,"","IS");
1658 CheckMandatoryEntry(0x0020,0x0013,"","IS");
1660 // Patient Orientation
1661 // Can be computed from (0020|0037) : Image Orientation (Patient)
1662 gdcm::Orientation *o = gdcm::Orientation::New();
1663 std::string ori = o->GetOrientation ( FileInternal );
1665 if (ori != "\\" && ori != GDCM_UNFOUND)
1666 CheckMandatoryEntry(0x0020,0x0020,ori,"CS");
1668 CheckMandatoryEntry(0x0020,0x0020,"","CS");
1670 // Default Patient Position to HFS
1671 CheckMandatoryEntry(0x0018,0x5100,"HFS","CS");
1673 // Modality : if missing we set it to 'OTher'
1674 CheckMandatoryEntry(0x0008,0x0060,"OT","CS");
1676 // Manufacturer : if missing we set it to 'GDCM Factory'
1677 CheckMandatoryEntry(0x0008,0x0070,"GDCM Factory","LO");
1679 // Institution Name : if missing we set it to 'GDCM Hospital'
1680 CheckMandatoryEntry(0x0008,0x0080,"GDCM Hospital","LO");
1682 // Patient's Name : if missing, we set it to 'GDCM^Patient'
1683 CheckMandatoryEntry(0x0010,0x0010,"GDCM^Patient","PN");
1685 // Patient ID : some clinical softwares *demand* it although it's a 'type 2' entry.
1686 CheckMandatoryEntry(0x0010,0x0020,"gdcm ID","LO");
1688 // Patient's Birth Date : 'type 2' entry -> must exist, value not mandatory
1689 CheckMandatoryEntry(0x0010,0x0030,"","DA");
1691 // Patient's Sex :'type 2' entry -> must exist, value not mandatory
1692 CheckMandatoryEntry(0x0010,0x0040,"","CS");
1694 // Referring Physician's Name :'type 2' entry -> must exist, value not mandatory
1695 CheckMandatoryEntry(0x0008,0x0090,"","PN");
1698 // Deal with element 0x0000 (group length) of each group.
1699 // First stage : get all the different Groups
1702 DocEntry *d = FileInternal->GetFirstEntry();
1705 grHT[d->GetGroup()] = 0;
1706 d=FileInternal->GetNextEntry();
1708 // Second stage : add the missing ones (if any)
1709 for (GroupHT::iterator it = grHT.begin(); it != grHT.end(); ++it)
1711 CheckMandatoryEntry(it->first, 0x0000, "0");
1713 // Third stage : update all 'zero level' groups length
1718 void FileHelper::CheckMandatoryEntry(uint16_t group,uint16_t elem,std::string value,const VRKey &vr )
1720 DataEntry *entry = FileInternal->GetDataEntry(group,elem);
1723 //entry = DataEntry::New(Global::GetDicts()->GetDefaultPubDict()->GetEntry(group,elem));
1724 entry = DataEntry::New(group,elem,vr);
1725 entry->SetString(value);
1726 Archive->Push(entry);
1731 /// \todo : what is it used for ? (FileHelper::SetMandatoryEntry)
1732 void FileHelper::SetMandatoryEntry(uint16_t group,uint16_t elem,std::string value,const VRKey &vr)
1734 //DataEntry *entry = DataEntry::New(Global::GetDicts()->GetDefaultPubDict()->GetEntry(group,elem));
1735 DataEntry *entry = DataEntry::New(group,elem,vr);
1736 entry->SetString(value);
1737 Archive->Push(entry);
1741 void FileHelper::CopyMandatoryEntry(uint16_t group,uint16_t elem,std::string value,const VRKey &vr)
1743 DataEntry *entry = CopyDataEntry(group,elem,vr);
1744 entry->SetString(value);
1745 Archive->Push(entry);
1750 * \brief Restore in the File the initial group 0002
1752 void FileHelper::RestoreWriteMandatory()
1754 // group 0002 may be pushed out for ACR-NEMA writting purposes
1755 Archive->Restore(0x0002,0x0000);
1756 Archive->Restore(0x0002,0x0001);
1757 Archive->Restore(0x0002,0x0002);
1758 Archive->Restore(0x0002,0x0003);
1759 Archive->Restore(0x0002,0x0010);
1760 Archive->Restore(0x0002,0x0012);
1761 Archive->Restore(0x0002,0x0013);
1762 Archive->Restore(0x0002,0x0016);
1763 Archive->Restore(0x0002,0x0100);
1764 Archive->Restore(0x0002,0x0102);
1766 // FIXME : Check if none is missing !
1768 Archive->Restore(0x0008,0x0012);
1769 Archive->Restore(0x0008,0x0013);
1770 Archive->Restore(0x0008,0x0016);
1771 Archive->Restore(0x0008,0x0018);
1772 Archive->Restore(0x0008,0x0060);
1773 Archive->Restore(0x0008,0x0070);
1774 Archive->Restore(0x0008,0x0080);
1775 Archive->Restore(0x0008,0x0090);
1776 Archive->Restore(0x0008,0x2112);
1778 Archive->Restore(0x0010,0x0010);
1779 Archive->Restore(0x0010,0x0030);
1780 Archive->Restore(0x0010,0x0040);
1782 Archive->Restore(0x0020,0x000d);
1783 Archive->Restore(0x0020,0x000e);
1788 * \brief CallStartMethod
1790 void FileHelper::CallStartMethod()
1794 CommandManager::ExecuteCommand(this,CMD_STARTPROGRESS);
1798 * \brief CallProgressMethod
1800 void FileHelper::CallProgressMethod()
1802 CommandManager::ExecuteCommand(this,CMD_PROGRESS);
1806 * \brief CallEndMethod
1808 void FileHelper::CallEndMethod()
1811 CommandManager::ExecuteCommand(this,CMD_ENDPROGRESS);
1814 //-----------------------------------------------------------------------------
1817 * \brief Factorization for various forms of constructors.
1819 void FileHelper::Initialize()
1822 ContentType = USER_OWN_IMAGE;
1824 WriteMode = WMODE_RAW;
1825 WriteType = ExplicitVR;
1827 PixelReadConverter = new PixelReadConvert;
1828 PixelWriteConverter = new PixelWriteConvert;
1829 Archive = new DocEntryArchive( FileInternal );
1833 * \brief Reads/[decompresses] the pixels,
1834 * *without* making RGB from Palette Colors
1835 * @return the pixels area, whatever its type
1836 * (uint8_t is just for prototyping : feel free to Cast it)
1838 uint8_t *FileHelper::GetRaw()
1840 PixelReadConverter->SetUserFunction( UserFunction );
1842 uint8_t *raw = PixelReadConverter->GetRaw();
1845 // The Raw image migth not be loaded yet:
1846 std::ifstream *fp = FileInternal->OpenFile();
1847 PixelReadConverter->ReadAndDecompressPixelData( fp );
1849 FileInternal->CloseFile();
1851 raw = PixelReadConverter->GetRaw();
1854 gdcmWarningMacro( "Read/decompress of pixel data apparently went wrong.");
1861 //-----------------------------------------------------------------------------
1863 * \brief Prints the common part of DataEntry, SeqEntry
1864 * @param os ostream we want to print in
1865 * @param indent (unused)
1867 void FileHelper::Print(std::ostream &os, std::string const &)
1869 FileInternal->SetPrintLevel(PrintLevel);
1870 FileInternal->Print(os);
1872 if ( FileInternal->IsReadable() )
1874 PixelReadConverter->SetPrintLevel(PrintLevel);
1875 PixelReadConverter->Print(os);
1879 //-----------------------------------------------------------------------------
1880 } // end namespace gdcm