1 /*=========================================================================
4 Module: $RCSfile: gdcmFileHelper.cxx,v $
7 Date: $Date: 2006/05/30 08:14:50 $
8 Version: $Revision: 1.104 $
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.
422 size_t FileHelper::GetImageDataIntoVector (void *destination, size_t maxSize)
426 // If the decompression failed nothing can be done.
430 if ( FileInternal->HasLUT() && PixelReadConverter->BuildRGBImage() )
432 if ( PixelReadConverter->GetRGBSize() > maxSize )
434 gdcmWarningMacro( "Pixel data bigger than caller's expected MaxSize");
438 (void*)PixelReadConverter->GetRGB(),
439 PixelReadConverter->GetRGBSize() );
440 return PixelReadConverter->GetRGBSize();
443 // Either no LUT conversion necessary or LUT conversion failed
444 if ( PixelReadConverter->GetRawSize() > maxSize )
446 gdcmWarningMacro( "Pixel data bigger than caller's expected MaxSize");
450 (void *)PixelReadConverter->GetRaw(),
451 PixelReadConverter->GetRawSize() );
452 return PixelReadConverter->GetRawSize();
458 * \brief Points the internal pointer to the callers inData
459 * image representation, BUT WITHOUT COPYING THE DATA.
460 * 'image' Pixels are presented as C-like 2D arrays : line per line.
461 * 'volume'Pixels are presented as C-like 3D arrays : plane per plane
462 * \warning Since the pixels are not copied, it is the caller's responsability
463 * not to deallocate its data before gdcm uses them (e.g. with
464 * the Write() method )
465 * @param inData user supplied pixel area (uint8_t* is just for the compiler.
466 * user is allowed to pass any kind of pixelsn since the size is
468 * @param expectedSize total image size, *in Bytes*
470 void FileHelper::SetImageData(uint8_t *inData, size_t expectedSize)
472 SetUserData(inData, expectedSize);
476 * \brief Set the image data defined by the user
477 * \warning When writting the file, this data are get as default data to write
478 * @param inData user supplied pixel area (uint8_t* is just for the compiler.
479 * user is allowed to pass any kind of pixels since the size is
481 * @param expectedSize total image size, *in Bytes*
483 void FileHelper::SetUserData(uint8_t *inData, size_t expectedSize)
485 PixelWriteConverter->SetUserData(inData, expectedSize);
489 * \brief Get the image data defined by the user
490 * \warning When writting the file, this data are get as default data to write
492 uint8_t *FileHelper::GetUserData()
494 return PixelWriteConverter->GetUserData();
498 * \brief Get the image data size defined by the user
499 * \warning When writting the file, this data are get as default data to write
501 size_t FileHelper::GetUserDataSize()
503 return PixelWriteConverter->GetUserDataSize();
507 * \brief Get the image data from the file.
508 * If a LUT is found, the data are expanded to be RGB
510 uint8_t *FileHelper::GetRGBData()
512 return PixelReadConverter->GetRGB();
516 * \brief Get the image data size from the file.
517 * If a LUT is found, the data are expanded to be RGB
519 size_t FileHelper::GetRGBDataSize()
521 return PixelReadConverter->GetRGBSize();
525 * \brief Get the image data from the file.
526 * Even when a LUT is found, the data are not expanded to RGB!
528 uint8_t *FileHelper::GetRawData()
530 return PixelReadConverter->GetRaw();
534 * \brief Get the image data size from the file.
535 * Even when a LUT is found, the data are not expanded to RGB!
537 size_t FileHelper::GetRawDataSize()
539 return PixelReadConverter->GetRawSize();
543 * \brief Access to the underlying \ref PixelReadConverter RGBA LUT
545 uint8_t* FileHelper::GetLutRGBA()
547 if ( PixelReadConverter->GetLutRGBA() ==0 )
548 PixelReadConverter->BuildLUTRGBA();
549 return PixelReadConverter->GetLutRGBA();
553 * \brief Access to the underlying \ref PixelReadConverter RGBA LUT Item Number
555 int FileHelper::GetLutItemNumber()
557 return PixelReadConverter->GetLutItemNumber();
561 * \brief Access to the underlying \ref PixelReadConverter RGBA LUT Item Size
563 int FileHelper::GetLutItemSize()
565 return PixelReadConverter->GetLutItemSize();
569 * \brief Writes on disk A SINGLE Dicom file
570 * NO test is performed on processor "Endiannity".
571 * It's up to the user to call his Reader properly
572 * @param fileName name of the file to be created
573 * (any already existing file is over written)
574 * @return false if write fails
576 bool FileHelper::WriteRawData(std::string const &fileName)
578 std::ofstream fp1(fileName.c_str(), std::ios::out | std::ios::binary );
581 gdcmWarningMacro( "Fail to open (write) file:" << fileName.c_str());
585 if ( PixelWriteConverter->GetUserData() )
587 fp1.write( (char *)PixelWriteConverter->GetUserData(),
588 PixelWriteConverter->GetUserDataSize() );
590 else if ( PixelReadConverter->GetRGB() )
592 fp1.write( (char *)PixelReadConverter->GetRGB(),
593 PixelReadConverter->GetRGBSize());
595 else if ( PixelReadConverter->GetRaw() )
597 fp1.write( (char *)PixelReadConverter->GetRaw(),
598 PixelReadConverter->GetRawSize());
602 gdcmErrorMacro( "Nothing written." );
611 * \brief Writes on disk A SINGLE Dicom file,
612 * using the Implicit Value Representation convention
613 * NO test is performed on processor "Endianity".
614 * @param fileName name of the file to be created
615 * (any already existing file is overwritten)
616 * @return false if write fails
619 bool FileHelper::WriteDcmImplVR (std::string const &fileName)
621 SetWriteTypeToDcmImplVR();
622 return Write(fileName);
626 * \brief Writes on disk A SINGLE Dicom file,
627 * using the Explicit Value Representation convention
628 * NO test is performed on processor "Endiannity".
629 * @param fileName name of the file to be created
630 * (any already existing file is overwritten)
631 * @return false if write fails
634 bool FileHelper::WriteDcmExplVR (std::string const &fileName)
636 SetWriteTypeToDcmExplVR();
637 return Write(fileName);
641 * \brief Writes on disk A SINGLE Dicom file,
642 * using the ACR-NEMA convention
643 * NO test is performed on processor "Endiannity".
644 * (a l'attention des logiciels cliniques
645 * qui ne prennent en entrée QUE des images ACR ...
646 * \warning if a DICOM_V3 header is supplied,
647 * groups < 0x0008 and shadow groups are ignored
648 * \warning NO TEST is performed on processor "Endiannity".
649 * @param fileName name of the file to be created
650 * (any already existing file is overwritten)
651 * @return false if write fails
654 bool FileHelper::WriteAcr (std::string const &fileName)
657 return Write(fileName);
661 * \brief Writes on disk A SINGLE Dicom file,
662 * @param fileName name of the file to be created
663 * (any already existing file is overwritten)
664 * @return false if write fails
666 bool FileHelper::Write(std::string const &fileName)
669 CheckMandatoryElements(); //called once, here !
676 SetWriteFileTypeToImplicitVR();
679 case Unknown: // should never happen; ExplicitVR is the default value
682 // User should ask gdcm to write an image in Explicit VR mode
683 // only when he is sure *all* the VR of *all* the DataElements is known.
684 // i.e : when there are *only* Public Groups
685 // or *all* the Shadow Groups are fully described in the relevant Shadow
687 // Let's just *dream* about it; *never* trust a user !
688 // We turn to Implicit VR if at least the VR of one element is unknown.
691 e = FileInternal->GetFirstEntry();
694 if (e->GetVR() == " ")
696 SetWriteTypeToDcmImplVR();
697 SetWriteFileTypeToImplicitVR();
701 e = FileInternal->GetNextEntry();
706 SetWriteFileTypeToExplicitVR();
710 SetWriteFileTypeToExplicitVR(); // to see JPRx
714 // NOTHING is done here just for LibIDO.
715 // Just to avoid further trouble if user creates a file ex-nihilo,
716 // wants to write it as an ACR-NEMA file,
717 // and forgets to create any Entry belonging to group 0008
719 // We add Recognition Code (RET)
720 if ( ! FileInternal->GetDataEntry(0x0008, 0x0010) )
721 FileInternal->InsertEntryString("ACR-NEMA V1.0 ",
722 0x0008, 0x0010, "LO");
723 SetWriteFileTypeToACR();
724 // SetWriteFileTypeToImplicitVR(); // ACR IS implicit VR !
727 /// \todo FIXME : JPEG may be either ExplicitVR or ImplicitVR
729 SetWriteFileTypeToJPEG();
733 // --------------------------------------------------------------
734 // Special Patch to allow gdcm to re-write ACR-LibIDO formated images
736 // if recognition code tells us we dealt with a LibIDO image
737 // we reproduce on disk the switch between lineNumber and columnNumber
738 // just before writting ...
739 /// \todo the best trick would be *change* the recognition code
740 /// but pb expected if user deals with, e.g. COMPLEX images
742 if ( WriteType == ACR_LIBIDO )
748 SetWriteToNoLibido();
750 // ----------------- End of Special Patch ----------------
755 SetWriteToRaw(); // modifies and pushes to the archive, when necessary
758 SetWriteToRGB(); // modifies and pushes to the archive, when necessary
762 bool check = CheckWriteIntegrity(); // verifies length
763 if (WriteType == JPEG ) check = true;
766 check = FileInternal->Write(fileName,WriteType);
770 // RestoreWriteFileType();
771 // RestoreWriteMandatory();
774 // --------------------------------------------------------------
775 // Special Patch to allow gdcm to re-write ACR-LibIDO formated images
777 // ...and we restore the header to be Dicom Compliant again
778 // just after writting
779 RestoreWriteOfLibido();
780 // ----------------- End of Special Patch ----------------
785 //-----------------------------------------------------------------------------
788 * \brief Checks the write integrity
790 * The tests made are :
791 * - verify the size of the image to write with the possible write
792 * when the user set an image data
793 * @return true if check is successfull
795 bool FileHelper::CheckWriteIntegrity()
797 if ( PixelWriteConverter->GetUserData() )
799 int numberBitsAllocated = FileInternal->GetBitsAllocated();
800 if ( numberBitsAllocated == 0 || numberBitsAllocated == 12 )
802 gdcmWarningMacro( "numberBitsAllocated changed from "
803 << numberBitsAllocated << " to 16 "
804 << " for consistency purpose" );
805 numberBitsAllocated = 16;
808 size_t decSize = FileInternal->GetXSize()
809 * FileInternal->GetYSize()
810 * FileInternal->GetZSize()
811 * FileInternal->GetTSize()
812 * FileInternal->GetSamplesPerPixel()
813 * ( numberBitsAllocated / 8 );
814 size_t rgbSize = decSize;
815 if ( FileInternal->HasLUT() )
816 rgbSize = decSize * 3;
821 if ( decSize!=PixelWriteConverter->GetUserDataSize() )
823 gdcmWarningMacro( "Data size (Raw) is incorrect. Should be "
824 << decSize << " / Found :"
825 << PixelWriteConverter->GetUserDataSize() );
830 if ( rgbSize!=PixelWriteConverter->GetUserDataSize() )
832 gdcmWarningMacro( "Data size (RGB) is incorrect. Should be "
833 << decSize << " / Found "
834 << PixelWriteConverter->GetUserDataSize() );
845 * \brief Updates the File to write RAW data (as opposed to RGB data)
846 * (modifies, when necessary, photochromatic interpretation,
847 * bits allocated, Pixels element VR)
849 void FileHelper::SetWriteToRaw()
851 if ( FileInternal->GetNumberOfScalarComponents() == 3
852 && !FileInternal->HasLUT() )
858 DataEntry *photInt = CopyDataEntry(0x0028,0x0004,"CS");
859 if (FileInternal->HasLUT() )
861 photInt->SetString("PALETTE COLOR ");
865 photInt->SetString("MONOCHROME2 ");
868 PixelWriteConverter->SetReadData(PixelReadConverter->GetRaw(),
869 PixelReadConverter->GetRawSize());
871 std::string vr = "OB";
872 if ( FileInternal->GetBitsAllocated()>8 )
874 if ( FileInternal->GetBitsAllocated()==24 ) // For RGB ACR files
877 CopyDataEntry(GetFile()->GetGrPixel(),GetFile()->GetNumPixel(),vr);
878 pixel->SetFlag(DataEntry::FLAG_PIXELDATA);
879 pixel->SetBinArea(PixelWriteConverter->GetData(),false);
880 pixel->SetLength(PixelWriteConverter->GetDataSize());
882 Archive->Push(photInt);
883 Archive->Push(pixel);
891 * \brief Updates the File to write RGB data (as opposed to RAW data)
892 * (modifies, when necessary, photochromatic interpretation,
893 * samples per pixel, Planar configuration,
894 * bits allocated, bits stored, high bit -ACR 24 bits-
895 * Pixels element VR, pushes out the LUT, )
897 void FileHelper::SetWriteToRGB()
899 if ( FileInternal->GetNumberOfScalarComponents()==3 )
901 PixelReadConverter->BuildRGBImage();
903 DataEntry *spp = CopyDataEntry(0x0028,0x0002,"US");
904 spp->SetString("3 ");
906 DataEntry *planConfig = CopyDataEntry(0x0028,0x0006,"US");
907 planConfig->SetString("0 ");
909 DataEntry *photInt = CopyDataEntry(0x0028,0x0004,"CS");
910 photInt->SetString("RGB ");
912 if ( PixelReadConverter->GetRGB() )
914 PixelWriteConverter->SetReadData(PixelReadConverter->GetRGB(),
915 PixelReadConverter->GetRGBSize());
919 PixelWriteConverter->SetReadData(PixelReadConverter->GetRaw(),
920 PixelReadConverter->GetRawSize());
923 std::string vr = "OB";
924 if ( FileInternal->GetBitsAllocated()>8 )
926 if ( FileInternal->GetBitsAllocated()==24 ) // For RGB ACR files
929 CopyDataEntry(GetFile()->GetGrPixel(),GetFile()->GetNumPixel(),vr);
930 pixel->SetFlag(DataEntry::FLAG_PIXELDATA);
931 pixel->SetBinArea(PixelWriteConverter->GetData(),false);
932 pixel->SetLength(PixelWriteConverter->GetDataSize());
935 Archive->Push(planConfig);
936 Archive->Push(photInt);
937 Archive->Push(pixel);
940 planConfig->Delete();
945 Archive->Push(0x0028,0x1101);
946 Archive->Push(0x0028,0x1102);
947 Archive->Push(0x0028,0x1103);
948 Archive->Push(0x0028,0x1201);
949 Archive->Push(0x0028,0x1202);
950 Archive->Push(0x0028,0x1203);
952 // push out Palette Color Lookup Table UID, if any
953 Archive->Push(0x0028,0x1199);
955 // For old '24 Bits' ACR-NEMA
956 // Thus, we have a RGB image and the bits allocated = 24 and
957 // samples per pixels = 1 (in the read file)
958 if ( FileInternal->GetBitsAllocated()==24 )
960 DataEntry *bitsAlloc = CopyDataEntry(0x0028,0x0100,"US");
961 bitsAlloc->SetString("8 ");
963 DataEntry *bitsStored = CopyDataEntry(0x0028,0x0101,"US");
964 bitsStored->SetString("8 ");
966 DataEntry *highBit = CopyDataEntry(0x0028,0x0102,"US");
967 highBit->SetString("7 ");
969 Archive->Push(bitsAlloc);
970 Archive->Push(bitsStored);
971 Archive->Push(highBit);
974 bitsStored->Delete();
985 * \brief Restore the File write mode
987 void FileHelper::RestoreWrite()
990 Archive->Restore(0x0028,0x0002);
991 Archive->Restore(0x0028,0x0004);
993 Archive->Restore(0x0028,0x0006);
994 Archive->Restore(GetFile()->GetGrPixel(),GetFile()->GetNumPixel());
996 // For old ACR-NEMA (24 bits problem)
997 Archive->Restore(0x0028,0x0100);
998 Archive->Restore(0x0028,0x0101);
999 Archive->Restore(0x0028,0x0102);
1002 Archive->Restore(0x0028,0x1101);
1003 Archive->Restore(0x0028,0x1102);
1004 Archive->Restore(0x0028,0x1103);
1005 Archive->Restore(0x0028,0x1201);
1006 Archive->Restore(0x0028,0x1202);
1007 Archive->Restore(0x0028,0x1203);
1009 // For the Palette Color Lookup Table UID
1010 Archive->Restore(0x0028,0x1203);
1012 // group 0002 may be pushed out for ACR-NEMA writting purposes
1013 Archive->Restore(0x0002,0x0000);
1014 Archive->Restore(0x0002,0x0001);
1015 Archive->Restore(0x0002,0x0002);
1016 Archive->Restore(0x0002,0x0003);
1017 Archive->Restore(0x0002,0x0010);
1018 Archive->Restore(0x0002,0x0012);
1019 Archive->Restore(0x0002,0x0013);
1020 Archive->Restore(0x0002,0x0016);
1021 Archive->Restore(0x0002,0x0100);
1022 Archive->Restore(0x0002,0x0102);
1027 * \brief Pushes out the whole group 0002
1028 * FIXME : better, set a flag to tell the writer not to write it ...
1029 * FIXME : method should probably have an other name !
1030 * SetWriteFileTypeToACR is NOT opposed to
1031 * SetWriteFileTypeToExplicitVR and SetWriteFileTypeToImplicitVR
1033 void FileHelper::SetWriteFileTypeToACR()
1035 Archive->Push(0x0002,0x0000);
1036 Archive->Push(0x0002,0x0001);
1037 Archive->Push(0x0002,0x0002);
1038 Archive->Push(0x0002,0x0003);
1039 Archive->Push(0x0002,0x0010);
1040 Archive->Push(0x0002,0x0012);
1041 Archive->Push(0x0002,0x0013);
1042 Archive->Push(0x0002,0x0016);
1043 Archive->Push(0x0002,0x0100);
1044 Archive->Push(0x0002,0x0102);
1048 * \brief Sets in the File the TransferSyntax to 'JPEG'
1050 void FileHelper::SetWriteFileTypeToJPEG()
1052 std::string ts = Util::DicomString(
1053 Global::GetTS()->GetSpecialTransferSyntax(TS::JPEGBaselineProcess1) );
1055 DataEntry *tss = CopyDataEntry(0x0002,0x0010,"UI");
1063 * \brief Sets in the File the TransferSyntax to 'Explicit VR Little Endian"
1065 void FileHelper::SetWriteFileTypeToExplicitVR()
1067 std::string ts = Util::DicomString(
1068 Global::GetTS()->GetSpecialTransferSyntax(TS::ExplicitVRLittleEndian) );
1070 DataEntry *tss = CopyDataEntry(0x0002,0x0010,"UI");
1077 * \brief Sets in the File the TransferSyntax to 'Implicit VR Little Endian"
1079 void FileHelper::SetWriteFileTypeToImplicitVR()
1081 std::string ts = Util::DicomString(
1082 Global::GetTS()->GetSpecialTransferSyntax(TS::ImplicitVRLittleEndian) );
1084 DataEntry *tss = CopyDataEntry(0x0002,0x0010,"UI");
1092 * \brief Restore in the File the initial group 0002
1094 void FileHelper::RestoreWriteFileType()
1099 * \brief Set the Write not to Libido format
1101 void FileHelper::SetWriteToLibido()
1103 DataEntry *oldRow = FileInternal->GetDataEntry(0x0028, 0x0010);
1104 DataEntry *oldCol = FileInternal->GetDataEntry(0x0028, 0x0011);
1106 if ( oldRow && oldCol )
1108 std::string rows, columns;
1110 //DataEntry *newRow=DataEntry::New(oldRow->GetDictEntry());
1111 //DataEntry *newCol=DataEntry::New(oldCol->GetDictEntry());
1113 DataEntry *newRow=DataEntry::New(0x0028, 0x0010, "US");
1114 DataEntry *newCol=DataEntry::New(0x0028, 0x0011, "US");
1116 newRow->Copy(oldCol);
1117 newCol->Copy(oldRow);
1119 newRow->SetString(oldCol->GetString());
1120 newCol->SetString(oldRow->GetString());
1122 Archive->Push(newRow);
1123 Archive->Push(newCol);
1129 DataEntry *libidoCode = CopyDataEntry(0x0008,0x0010,"LO");
1130 libidoCode->SetString("ACRNEMA_LIBIDO_1.1");
1131 Archive->Push(libidoCode);
1132 libidoCode->Delete();
1136 * \brief Set the Write not to No Libido format
1138 void FileHelper::SetWriteToNoLibido()
1140 DataEntry *recCode = FileInternal->GetDataEntry(0x0008,0x0010);
1143 if ( recCode->GetString() == "ACRNEMA_LIBIDO_1.1" )
1145 DataEntry *libidoCode = CopyDataEntry(0x0008,0x0010,"LO");
1146 libidoCode->SetString("");
1147 Archive->Push(libidoCode);
1148 libidoCode->Delete();
1154 * \brief Restore the Write format
1156 void FileHelper::RestoreWriteOfLibido()
1158 Archive->Restore(0x0028,0x0010);
1159 Archive->Restore(0x0028,0x0011);
1160 Archive->Restore(0x0008,0x0010);
1162 // Restore 'LibIDO-special' entries, if any
1163 Archive->Restore(0x0028,0x0015);
1164 Archive->Restore(0x0028,0x0016);
1165 Archive->Restore(0x0028,0x0017);
1166 Archive->Restore(0x0028,0x00199);
1170 * \brief Duplicates a DataEntry or creates it.
1171 * @param group Group number of the Entry
1172 * @param elem Element number of the Entry
1173 * @param vr Value Representation of the Entry
1174 * \return pointer to the new Bin Entry (NULL when creation failed).
1176 DataEntry *FileHelper::CopyDataEntry(uint16_t group, uint16_t elem,
1179 DocEntry *oldE = FileInternal->GetDocEntry(group, elem);
1182 if ( oldE && vr != GDCM_VRUNKNOWN )
1183 if ( oldE->GetVR() != vr )
1188 //newE = DataEntry::New(oldE->GetDictEntry());
1189 newE = DataEntry::New(group, elem, vr);
1194 newE = GetFile()->NewDataEntry(group, elem, vr);
1201 * \brief This method is called automatically, just before writting
1202 * in order to produce a 'True Dicom V3' image.
1204 * We cannot know *how* the user made the File :
1205 * (reading an old ACR-NEMA file or a not very clean DICOM file ...)
1206 * Just before writting :
1207 * - we check the Entries
1208 * - we create the mandatory entries if they are missing
1209 * - we modify the values if necessary
1210 * - we push the sensitive entries to the Archive
1211 * The writing process will restore the entries as they where before
1212 * entering FileHelper::CheckMandatoryElements, so the user will always
1213 * see the entries just as they were before he decided to write.
1216 * - Entries whose type is 1 are mandatory, with a mandatory value
1217 * - Entries whose type is 1c are mandatory-inside-a-Sequence,
1218 * with a mandatory value
1219 * - Entries whose type is 2 are mandatory, with an optional value
1220 * - Entries whose type is 2c are mandatory-inside-a-Sequence,
1221 * with an optional value
1222 * - Entries whose type is 3 are optional
1225 * - warn the user if we had to add some entries :
1226 * even if a mandatory entry is missing, we add it, with a default value
1227 * (we don't want to give up the writting process if user forgot to
1228 * specify Lena's Patient ID, for instance ...)
1229 * - read the whole PS 3.3 Part of DICOM (890 pages)
1230 * and write a *full* checker (probably one method per Modality ...)
1231 * Any contribution is welcome.
1232 * - write a user callable full checker, to allow post reading
1233 * and/or pre writting image consistency check.
1236 /* -------------------------------------------------------------------------------------
1237 To be moved to User's guide / WIKI ?
1239 We have to deal with 4 *very* different cases :
1240 -1) user created ex nihilo his own image and wants to write it as a Dicom image.
1242 -2) user modified the pixels of an existing image.
1244 -3) user created a new image, using a set of existing images (eg MIP, MPR, cartography image)
1246 -4) user modified/added some tags *without processing* the pixels (anonymization..
1247 UNMODIFIED_PIXELS_IMAGE
1248 -Probabely some more to be added
1250 gdcm::FileHelper::CheckMandatoryElements() deals automatically with these cases.
1253 0008 0012 Instance Creation Date
1254 0008 0013 Instance Creation Time
1255 0008 0018 SOP Instance UID
1256 are *always* created with the current values; user has *no* possible intervention on
1259 'Serie Instance UID'(0x0020,0x000e)
1260 'Study Instance UID'(0x0020,0x000d) are kept as is if already exist,
1261 created if it doesn't.
1262 The user is allowed to create his own Series/Studies,
1263 keeping the same 'Serie Instance UID' / 'Study Instance UID' for various images
1265 The user shouldn't add any image to a 'Manufacturer Serie'
1266 but there is no way no to allow him to do that
1268 None of the 'shadow elements' are droped out.
1272 'Conversion Type (0x0008,0x0064) is forced to 'SYN' (Synthetic Image).
1275 'Media Storage SOP Class UID' (0x0002,0x0002)
1276 'SOP Class UID' (0x0008,0x0016) are set to
1277 [Secondary Capture Image Storage]
1278 'Image Type' (0x0008,0x0008) is forced to "DERIVED\PRIMARY"
1279 Conversion Type (0x0008,0x0064) is forced to 'SYN' (Synthetic Image)
1282 If 'SOP Class UID' exists in the native image ('true DICOM' image)
1283 we create the 'Source Image Sequence' SeqEntry (0x0008, 0x2112)
1284 --> 'Referenced SOP Class UID' (0x0008, 0x1150)
1285 whose value is the original 'SOP Class UID'
1286 --> 'Referenced SOP Instance UID' (0x0008, 0x1155)
1287 whose value is the original 'SOP Class UID'
1289 3) TODO : find a trick to allow user to pass to the writter the list of the Dicom images
1290 or the Series, (or the Study ?) he used to created his image
1291 (MIP, MPR, cartography image, ...)
1292 These info should be stored (?)
1293 0008 1110 SQ 1 Referenced Study Sequence
1294 0008 1115 SQ 1 Referenced Series Sequence
1295 0008 1140 SQ 1 Referenced Image Sequence
1297 4) When user *knows* he didn't modified the pixels, we keep some informations unchanged :
1298 'Media Storage SOP Class UID' (0x0002,0x0002)
1299 'SOP Class UID' (0x0008,0x0016)
1300 'Image Type' (0x0008,0x0008)
1301 'Conversion Type' (0x0008,0x0064)
1304 Bellow follows the full description (hope so !) of the consistency checks performed
1305 by gdcm::FileHelper::CheckMandatoryElements()
1308 -->'Media Storage SOP Class UID' (0x0002,0x0002)
1309 -->'SOP Class UID' (0x0008,0x0016) are defaulted to
1310 [Secondary Capture Image Storage]
1311 --> 'Image Type' (0x0008,0x0008)
1312 is forced to "DERIVED\PRIMARY"
1313 (The written image is no longer an 'ORIGINAL' one)
1314 Except if user knows he didn't modify the image (e.g. : he just anonymized the file)
1316 --> Conversion Type (0x0008,0x0064)
1317 is defaulted to 'SYN' (Synthetic Image)
1318 when *he* knows he created his own image ex nihilo
1320 --> 'Modality' (0x0008,0x0060)
1321 is defaulted to "OT" (other) if missing.
1322 (a fully user created image belongs to *no* modality)
1324 --> 'Media Storage SOP Instance UID' (0x0002,0x0003)
1325 --> 'Implementation Class UID' (0x0002,0x0012)
1326 are automatically generated; no user intervention possible
1328 --> 'Serie Instance UID'(0x0020,0x000e)
1329 --> 'Study Instance UID'(0x0020,0x000d) are kept as is if already exist
1330 created if it doesn't.
1331 The user is allowed to create his own Series/Studies,
1332 keeping the same 'Serie Instance UID' / 'Study Instance UID'
1335 The user shouldn't add any image to a 'Manufacturer Serie'
1336 but there is no way no to allowed him to do that
1338 --> If 'SOP Class UID' exists in the native image ('true DICOM' image)
1339 we create the 'Source Image Sequence' SeqEntry (0x0008, 0x2112)
1341 --> 'Referenced SOP Class UID' (0x0008, 0x1150)
1342 whose value is the original 'SOP Class UID'
1343 --> 'Referenced SOP Instance UID' (0x0008, 0x1155)
1344 whose value is the original 'SOP Class UID'
1346 --> Bits Stored, Bits Allocated, Hight Bit Position are checked for consistency
1347 --> Pixel Spacing (0x0028,0x0030) is defaulted to "1.0\1.0"
1348 --> Samples Per Pixel (0x0028,0x0002) is defaulted to 1 (grayscale)
1350 --> Imager Pixel Spacing (0x0018,0x1164) : defaulted to Pixel Spacing value
1352 --> Instance Creation Date, Instance Creation Time are forced to current Date and Time
1354 --> Study Date, Study Time are defaulted to current Date and Time
1355 (they remain unchanged if they exist)
1357 --> Patient Orientation : (0x0020,0x0020), if not present, is deduced from
1358 Image Orientation (Patient) : (0020|0037) or from
1359 Image Orientation (RET) : (0020 0035)
1361 --> Study ID, Series Number, Instance Number, Patient Orientation (Type 2)
1362 are created, with empty value if there are missing.
1364 --> Manufacturer, Institution Name, Patient's Name, (Type 2)
1365 are defaulted with a 'gdcm' value.
1367 --> Patient ID, Patient's Birth Date, Patient's Sex, (Type 2)
1368 --> Referring Physician's Name (Type 2)
1369 are created, with empty value if there are missing.
1371 -------------------------------------------------------------------------------------*/
1373 void FileHelper::CheckMandatoryElements()
1375 std::string sop = Util::CreateUniqueUID();
1377 // --------------------- For Meta Elements ---------------------
1378 // just to remember : 'official' 0002 group
1379 if ( WriteType != ACR && WriteType != ACR_LIBIDO )
1381 // Group 000002 (Meta Elements) already pushed out
1383 //0002 0000 UL 1 Meta Group Length
1384 //0002 0001 OB 1 File Meta Information Version
1385 //0002 0002 UI 1 Media Stored SOP Class UID
1386 //0002 0003 UI 1 Media Stored SOP Instance UID
1387 //0002 0010 UI 1 Transfer Syntax UID
1388 //0002 0012 UI 1 Implementation Class UID
1389 //0002 0013 SH 1 Implementation Version Name
1390 //0002 0016 AE 1 Source Application Entity Title
1391 //0002 0100 UI 1 Private Information Creator
1392 //0002 0102 OB 1 Private Information
1394 // Create them if not found
1395 // Always modify the value
1396 // Push the entries to the archive.
1397 CopyMandatoryEntry(0x0002,0x0000,"0","UL");
1399 DataEntry *e_0002_0001 = CopyDataEntry(0x0002,0x0001, "OB");
1400 e_0002_0001->SetBinArea((uint8_t*)Util::GetFileMetaInformationVersion(),
1402 e_0002_0001->SetLength(2);
1403 Archive->Push(e_0002_0001);
1404 e_0002_0001->Delete();
1406 if ( ContentType == FILTERED_IMAGE || ContentType == UNMODIFIED_PIXELS_IMAGE)
1408 // we keep the original 'Media Storage SOP Class UID', we default it if missing
1409 CheckMandatoryEntry(0x0002,0x0002,"1.2.840.10008.5.1.4.1.1.7","UI");
1413 // It's *not* an image comming straight from a source. We force
1414 // 'Media Storage SOP Class UID' --> [Secondary Capture Image Storage]
1415 CopyMandatoryEntry(0x0002,0x0002,"1.2.840.10008.5.1.4.1.1.7","UI");
1418 // 'Media Storage SOP Instance UID'
1419 CopyMandatoryEntry(0x0002,0x0003,sop,"UI");
1421 // 'Implementation Class UID'
1422 // FIXME : in all examples we have, 0x0002,0x0012 is not so long :
1423 // seems to be Root UID + 4 digits (?)
1424 CopyMandatoryEntry(0x0002,0x0012,Util::CreateUniqueUID(),"UI");
1426 // 'Implementation Version Name'
1427 std::string version = "GDCM ";
1428 version += Util::GetVersion();
1429 CopyMandatoryEntry(0x0002,0x0013,version,"SH");
1432 // --------------------- For DataSet ---------------------
1434 if ( ContentType != USER_OWN_IMAGE) // when it's not a user made image
1437 gdcmDebugMacro( "USER_OWN_IMAGE (1)");
1438 // If 'SOP Class UID' exists ('true DICOM' image)
1439 // we create the 'Source Image Sequence' SeqEntry
1440 // to hold informations about the Source Image
1442 DataEntry *e_0008_0016 = FileInternal->GetDataEntry(0x0008, 0x0016);
1445 // Create 'Source Image Sequence' SeqEntry
1446 // SeqEntry *sis = SeqEntry::New (
1447 // Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x2112) );
1448 SeqEntry *sis = SeqEntry::New (0x0008, 0x2112);
1449 SQItem *sqi = SQItem::New(1);
1450 // (we assume 'SOP Instance UID' exists too)
1451 // create 'Referenced SOP Class UID'
1452 // DataEntry *e_0008_1150 = DataEntry::New(
1453 // Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x1150) );
1454 DataEntry *e_0008_1150 = DataEntry::New(0x0008, 0x1150, "UI");
1455 e_0008_1150->SetString( e_0008_0016->GetString());
1456 sqi->AddEntry(e_0008_1150);
1457 e_0008_1150->Delete();
1459 // create 'Referenced SOP Instance UID'
1460 DataEntry *e_0008_0018 = FileInternal->GetDataEntry(0x0008, 0x0018);
1461 // DataEntry *e_0008_1155 = DataEntry::New(
1462 // Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x1155) );
1463 DataEntry *e_0008_1155 = DataEntry::New(0x0008, 0x1155, "UI");
1464 e_0008_1155->SetString( e_0008_0018->GetString());
1465 sqi->AddEntry(e_0008_1155);
1466 e_0008_1155->Delete();
1468 sis->AddSQItem(sqi,1);
1471 // temporarily replaces any previous 'Source Image Sequence'
1475 // FIXME : is 'Image Type' *really* depending on the presence of 'SOP Class UID'?
1476 if ( ContentType == FILTERED_IMAGE)
1477 // the user *knows* he just modified the pixels
1478 // the image is no longer an 'Original' one
1479 CopyMandatoryEntry(0x0008,0x0008,"DERIVED\\PRIMARY","CS");
1483 if ( ContentType == FILTERED_IMAGE || ContentType == UNMODIFIED_PIXELS_IMAGE)
1485 // we keep the original 'Media Storage SOP Class UID', we default it if missing (it should be present !)
1486 CheckMandatoryEntry(0x0008,0x0016,"1.2.840.10008.5.1.4.1.1.7","UI");
1490 // It's *not* an image comming straight from a source. We force
1491 // 'Media Storage SOP Class UID' --> [Secondary Capture Image Storage]
1492 CopyMandatoryEntry(0x0008,0x0016,"1.2.840.10008.5.1.4.1.1.7", "UI");
1495 Archive->Push(0x0028,0x005); // [Image Dimensions (RET)
1496 // Push out 'LibIDO-special' entries, if any
1497 Archive->Push(0x0028,0x0015);
1498 Archive->Push(0x0028,0x0016);
1499 Archive->Push(0x0028,0x0017);
1500 Archive->Push(0x0028,0x0198); // very old versions
1501 Archive->Push(0x0028,0x0199);
1503 // Replace deprecated 0028 0012 US Planes
1504 // by new 0028 0008 IS Number of Frames
1506 ///\todo : find if there is a rule!
1507 DataEntry *e_0028_0012 = FileInternal->GetDataEntry(0x0028, 0x0012);
1510 CopyMandatoryEntry(0x0028, 0x0008,e_0028_0012->GetString(),"IS");
1511 Archive->Push(0x0028,0x0012);
1514 // Deal with the pb of (Bits Stored = 12)
1515 // - we're gonna write the image as Bits Stored = 16
1516 if ( FileInternal->GetEntryString(0x0028,0x0100) == "12")
1518 CopyMandatoryEntry(0x0028,0x0100,"16","US");
1521 // Check if user wasn't drunk ;-)
1523 std::ostringstream s;
1524 // check 'Bits Allocated' vs decent values
1525 int nbBitsAllocated = FileInternal->GetBitsAllocated();
1526 if ( nbBitsAllocated == 0 || nbBitsAllocated > 32)
1528 CopyMandatoryEntry(0x0028,0x0100,"16","US");
1529 gdcmWarningMacro("(0028,0100) changed from "
1530 << nbBitsAllocated << " to 16 for consistency purpose");
1531 nbBitsAllocated = 16;
1533 // check 'Bits Stored' vs 'Bits Allocated'
1534 int nbBitsStored = FileInternal->GetBitsStored();
1535 if ( nbBitsStored == 0 || nbBitsStored > nbBitsAllocated )
1538 s << nbBitsAllocated;
1539 CopyMandatoryEntry(0x0028,0x0101,s.str(),"US");
1540 gdcmWarningMacro("(0028,0101) changed from "
1541 << nbBitsStored << " to " << nbBitsAllocated
1542 << " for consistency purpose" );
1543 nbBitsStored = nbBitsAllocated;
1545 // check 'Hight Bit Position' vs 'Bits Allocated' and 'Bits Stored'
1546 int highBitPosition = FileInternal->GetHighBitPosition();
1547 if ( highBitPosition == 0 ||
1548 highBitPosition > nbBitsAllocated-1 ||
1549 highBitPosition < nbBitsStored-1 )
1552 s << nbBitsStored - 1;
1553 CopyMandatoryEntry(0x0028,0x0102,s.str(),"US");
1554 gdcmWarningMacro("(0028,0102) changed from "
1555 << highBitPosition << " to " << nbBitsAllocated-1
1556 << " for consistency purpose");
1559 std::string pixelSpacing = FileInternal->GetEntryString(0x0028,0x0030);
1560 if ( pixelSpacing == GDCM_UNFOUND )
1562 pixelSpacing = "1.0\\1.0";
1563 // if missing, Pixel Spacing forced to "1.0\1.0"
1564 CopyMandatoryEntry(0x0028,0x0030,pixelSpacing,"DS");
1567 // 'Imager Pixel Spacing' : defaulted to 'Pixel Spacing'
1568 // --> This one is the *legal* one !
1569 if ( ContentType != USER_OWN_IMAGE)
1570 // we write it only when we are *sure* the image comes from
1571 // an imager (see also 0008,0x0064)
1572 CheckMandatoryEntry(0x0018,0x1164,pixelSpacing,"DS");
1577 ///Exact meaning of RETired fields
1579 // See page 73 of ACR-NEMA_300-1988.pdf !
1581 // 0020,0020 : Patient Orientation :
1582 Patient direction of the first row and
1583 column of the images. The first entry id the direction of the raws, given by the
1584 direction of the last pixel in the first row from the first pixel in tha row.
1585 the second entry is the direction of the columns, given by the direction of the
1586 last pixel in the first column from the first pixel in that column.
1587 L : Left, F : Feet, A : Anterior, P : Posterior.
1588 Up to 3 letters can be used in combination to indicate oblique planes.
1590 //0020,0030 Image Position (RET)
1591 x,y,z coordinates im mm of the first pixel in the image
1593 // 0020,0035 Image Orientation (RET)
1594 Direction cosines of the R axis of the image system with respect to the
1595 equipment coordinate axes x,y,z, followed by direction cosines of the C axis of
1596 the image system with respect to the same axes
1598 //0020,0050 Location
1599 An image location reference, standard for the modality (such as CT bed
1600 position), used to indicate position. Calculation of position for other purposes
1601 is only from (0020,0030) and (0020,0035)
1605 // if imagePositionPatient not found, default it with imagePositionRet, if any
1606 // if imageOrientationPatient not found, default it with imageOrientationRet, if any
1608 std::string imagePositionRet = FileInternal->GetEntryString(0x0020,0x0030);
1609 std::string imageOrientationRet = FileInternal->GetEntryString(0x0020,0x0035);
1610 std::string imagePositionPatient = FileInternal->GetEntryString(0x0020,0x0032);
1611 std::string imageOrientationPatient = FileInternal->GetEntryString(0x0020,0x0037);
1613 if( imagePositionPatient == GDCM_UNFOUND && imageOrientationPatient == GDCM_UNFOUND
1614 && imagePositionRet != GDCM_UNFOUND && imageOrientationRet != GDCM_UNFOUND)
1616 CopyMandatoryEntry(0x0020, 0x0032,imagePositionRet,"DS");
1617 Archive->Push(0x0020,0x0030);
1618 CopyMandatoryEntry(0x0020, 0x0037,imageOrientationRet,"DS");
1619 Archive->Push(0x0020,0x0035);
1623 // Samples Per Pixel (type 1) : default to grayscale
1624 CheckMandatoryEntry(0x0028,0x0002,"1","US");
1626 // --- Check UID-related Entries ---
1628 // At the end, not to overwrite the original ones,
1629 // needed by 'Referenced SOP Instance UID', 'Referenced SOP Class UID'
1630 // 'SOP Instance UID'
1631 CopyMandatoryEntry(0x0008,0x0018,sop,"UI");
1633 if ( ContentType == USER_OWN_IMAGE)
1635 gdcmDebugMacro( "USER_OWN_IMAGE (2)");
1637 // Other possible values are :
1638 // See PS 3.3, Page 408
1640 // DV = Digitized Video
1641 // DI = Digital Interface
1642 // DF = Digitized Film
1643 // WSD = Workstation
1644 // SD = Scanned Document
1645 // SI = Scanned Image
1647 // SYN = Synthetic Image
1649 CheckMandatoryEntry(0x0008,0x0064,"SYN","CS"); // Why not?
1652 if ( ContentType == CREATED_IMAGE)
1654 /// \todo : find a trick to pass the Media Storage SOP Instance UID of the images used to create the current image
1659 // ---- The user will never have to take any action on the following ----
1661 // new value for 'SOP Instance UID'
1662 //SetMandatoryEntry(0x0008,0x0018,Util::CreateUniqueUID());
1664 // Instance Creation Date
1665 const std::string &date = Util::GetCurrentDate();
1666 CopyMandatoryEntry(0x0008,0x0012,date,"DA");
1668 // Instance Creation Time
1669 const std::string &time = Util::GetCurrentTime();
1670 CopyMandatoryEntry(0x0008,0x0013,time,"TM");
1673 CheckMandatoryEntry(0x0008,0x0020,date,"DA");
1675 CheckMandatoryEntry(0x0008,0x0030,time,"TM");
1678 //CopyMandatoryEntry(0x0008,0x0050,"");
1679 CheckMandatoryEntry(0x0008,0x0050,"","SH");
1682 // ----- Add Mandatory Entries if missing ---
1683 // Entries whose type is 1 are mandatory, with a mandatory value
1684 // Entries whose type is 1c are mandatory-inside-a-Sequence,
1685 // with a mandatory value
1686 // Entries whose type is 2 are mandatory, with an optional value
1687 // Entries whose type is 2c are mandatory-inside-a-Sequence,
1688 // with an optional value
1689 // Entries whose type is 3 are optional
1691 // 'Study Instance UID'
1692 // Keep the value if exists
1693 // The user is allowed to create his own Study,
1694 // keeping the same 'Study Instance UID' for various images
1695 // The user may add images to a 'Manufacturer Study',
1696 // adding new Series to an already existing Study
1697 CheckMandatoryEntry(0x0020,0x000d,Util::CreateUniqueUID(),"UI");
1699 // 'Serie Instance UID'
1700 // Keep the value if exists
1701 // The user is allowed to create his own Series,
1702 // keeping the same 'Serie Instance UID' for various images
1703 // The user shouldn't add any image to a 'Manufacturer Serie'
1704 // but there is no way no to prevent him for doing that
1705 CheckMandatoryEntry(0x0020,0x000e,Util::CreateUniqueUID(),"UI");
1708 CheckMandatoryEntry(0x0020,0x0010,"","SH");
1711 CheckMandatoryEntry(0x0020,0x0011,"","IS");
1714 CheckMandatoryEntry(0x0020,0x0013,"","IS");
1716 // Patient Orientation
1717 // Can be computed from (0020|0037) : Image Orientation (Patient)
1718 gdcm::Orientation *o = gdcm::Orientation::New();
1719 std::string ori = o->GetOrientation ( FileInternal );
1721 if (ori != "\\" && ori != GDCM_UNFOUND)
1722 CheckMandatoryEntry(0x0020,0x0020,ori,"CS");
1724 CheckMandatoryEntry(0x0020,0x0020,"","CS");
1726 // Default Patient Position to HFS
1727 CheckMandatoryEntry(0x0018,0x5100,"HFS","CS");
1729 // Modality : if missing we set it to 'OTher'
1730 CheckMandatoryEntry(0x0008,0x0060,"OT","CS");
1732 // Manufacturer : if missing we set it to 'GDCM Factory'
1733 CheckMandatoryEntry(0x0008,0x0070,"GDCM Factory","LO");
1735 // Institution Name : if missing we set it to 'GDCM Hospital'
1736 CheckMandatoryEntry(0x0008,0x0080,"GDCM Hospital","LO");
1738 // Patient's Name : if missing, we set it to 'GDCM^Patient'
1739 CheckMandatoryEntry(0x0010,0x0010,"GDCM^Patient","PN");
1741 // Patient ID : some clinical softwares *demand* it although it's a 'type 2' entry.
1742 CheckMandatoryEntry(0x0010,0x0020,"gdcm ID","LO");
1744 // Patient's Birth Date : 'type 2' entry -> must exist, value not mandatory
1745 CheckMandatoryEntry(0x0010,0x0030,"","DA");
1747 // Patient's Sex :'type 2' entry -> must exist, value not mandatory
1748 CheckMandatoryEntry(0x0010,0x0040,"","CS");
1750 // Referring Physician's Name :'type 2' entry -> must exist, value not mandatory
1751 CheckMandatoryEntry(0x0008,0x0090,"","PN");
1754 // Deal with element 0x0000 (group length) of each group.
1755 // First stage : get all the different Groups
1758 DocEntry *d = FileInternal->GetFirstEntry();
1761 grHT[d->GetGroup()] = 0;
1762 d=FileInternal->GetNextEntry();
1764 // Second stage : add the missing ones (if any)
1765 for (GroupHT::iterator it = grHT.begin(); it != grHT.end(); ++it)
1767 CheckMandatoryEntry(it->first, 0x0000, "0");
1769 // Third stage : update all 'zero level' groups length
1774 void FileHelper::CheckMandatoryEntry(uint16_t group,uint16_t elem,std::string value,const VRKey &vr )
1776 DataEntry *entry = FileInternal->GetDataEntry(group,elem);
1779 //entry = DataEntry::New(Global::GetDicts()->GetDefaultPubDict()->GetEntry(group,elem));
1780 entry = DataEntry::New(group,elem,vr);
1781 entry->SetString(value);
1782 Archive->Push(entry);
1787 /// \todo : what is it used for ? (FileHelper::SetMandatoryEntry)
1788 void FileHelper::SetMandatoryEntry(uint16_t group,uint16_t elem,std::string value,const VRKey &vr)
1790 //DataEntry *entry = DataEntry::New(Global::GetDicts()->GetDefaultPubDict()->GetEntry(group,elem));
1791 DataEntry *entry = DataEntry::New(group,elem,vr);
1792 entry->SetString(value);
1793 Archive->Push(entry);
1797 void FileHelper::CopyMandatoryEntry(uint16_t group,uint16_t elem,std::string value,const VRKey &vr)
1799 DataEntry *entry = CopyDataEntry(group,elem,vr);
1800 entry->SetString(value);
1801 Archive->Push(entry);
1806 * \brief Restore in the File the initial group 0002
1808 void FileHelper::RestoreWriteMandatory()
1810 // group 0002 may be pushed out for ACR-NEMA writting purposes
1811 Archive->Restore(0x0002,0x0000);
1812 Archive->Restore(0x0002,0x0001);
1813 Archive->Restore(0x0002,0x0002);
1814 Archive->Restore(0x0002,0x0003);
1815 Archive->Restore(0x0002,0x0010);
1816 Archive->Restore(0x0002,0x0012);
1817 Archive->Restore(0x0002,0x0013);
1818 Archive->Restore(0x0002,0x0016);
1819 Archive->Restore(0x0002,0x0100);
1820 Archive->Restore(0x0002,0x0102);
1822 // FIXME : Check if none is missing !
1824 Archive->Restore(0x0008,0x0012);
1825 Archive->Restore(0x0008,0x0013);
1826 Archive->Restore(0x0008,0x0016);
1827 Archive->Restore(0x0008,0x0018);
1828 Archive->Restore(0x0008,0x0060);
1829 Archive->Restore(0x0008,0x0070);
1830 Archive->Restore(0x0008,0x0080);
1831 Archive->Restore(0x0008,0x0090);
1832 Archive->Restore(0x0008,0x2112);
1834 Archive->Restore(0x0010,0x0010);
1835 Archive->Restore(0x0010,0x0030);
1836 Archive->Restore(0x0010,0x0040);
1838 Archive->Restore(0x0020,0x000d);
1839 Archive->Restore(0x0020,0x000e);
1844 * \brief CallStartMethod
1846 void FileHelper::CallStartMethod()
1850 CommandManager::ExecuteCommand(this,CMD_STARTPROGRESS);
1854 * \brief CallProgressMethod
1856 void FileHelper::CallProgressMethod()
1858 CommandManager::ExecuteCommand(this,CMD_PROGRESS);
1862 * \brief CallEndMethod
1864 void FileHelper::CallEndMethod()
1867 CommandManager::ExecuteCommand(this,CMD_ENDPROGRESS);
1870 //-----------------------------------------------------------------------------
1873 * \brief Factorization for various forms of constructors.
1875 void FileHelper::Initialize()
1878 ContentType = USER_OWN_IMAGE;
1880 WriteMode = WMODE_RAW;
1881 WriteType = ExplicitVR;
1883 PixelReadConverter = new PixelReadConvert;
1884 PixelWriteConverter = new PixelWriteConvert;
1885 Archive = new DocEntryArchive( FileInternal );
1889 * \brief Reads/[decompresses] the pixels,
1890 * *without* making RGB from Palette Colors
1891 * @return the pixels area, whatever its type
1892 * (uint8_t is just for prototyping : feel free to Cast it)
1894 uint8_t *FileHelper::GetRaw()
1896 PixelReadConverter->SetUserFunction( UserFunction );
1898 uint8_t *raw = PixelReadConverter->GetRaw();
1901 // The Raw image migth not be loaded yet:
1902 std::ifstream *fp = FileInternal->OpenFile();
1903 PixelReadConverter->ReadAndDecompressPixelData( fp );
1905 FileInternal->CloseFile();
1907 raw = PixelReadConverter->GetRaw();
1910 gdcmWarningMacro( "Read/decompress of pixel data apparently went wrong.");
1917 //-----------------------------------------------------------------------------
1919 * \brief Prints the common part of DataEntry, SeqEntry
1920 * @param os ostream we want to print in
1921 * @param indent (unused)
1923 void FileHelper::Print(std::ostream &os, std::string const &)
1925 FileInternal->SetPrintLevel(PrintLevel);
1926 FileInternal->Print(os);
1928 if ( FileInternal->IsReadable() )
1930 PixelReadConverter->SetPrintLevel(PrintLevel);
1931 PixelReadConverter->Print(os);
1935 //-----------------------------------------------------------------------------
1936 } // end namespace gdcm