1 /*=========================================================================
4 Module: $RCSfile: gdcmFileHelper.cxx,v $
7 Date: $Date: 2006/07/06 12:38:06 $
8 Version: $Revision: 1.108 $
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 * @param vr Value Represenation of the DataElement to be inserted
287 * \return pointer to the modified/created DataEntry (NULL when creation
290 DataEntry *FileHelper::InsertEntryBinArea(uint8_t *binArea, int lgth,
291 uint16_t group, uint16_t elem,
294 return FileInternal->InsertEntryBinArea(binArea, lgth, group, elem, vr);
298 * \brief Adds an empty SeqEntry
299 * (remove any existing entry with same group,elem)
300 * @param group Group number of the Entry
301 * @param elem Element number of the Entry
302 * \return pointer to the created SeqEntry (NULL when creation
305 SeqEntry *FileHelper::InsertSeqEntry(uint16_t group, uint16_t elem)
307 return FileInternal->InsertSeqEntry(group, elem);
311 * \brief Get the size of the image data
312 * If the image can be RGB (with a lut or by default), the size
313 * corresponds to the RGB image
314 * (use GetImageDataRawSize if you want to be sure to get *only*
315 * the size of the pixels)
316 * @return The image size
318 size_t FileHelper::GetImageDataSize()
320 if ( PixelWriteConverter->GetUserData() )
322 return PixelWriteConverter->GetUserDataSize();
324 return PixelReadConverter->GetRGBSize();
328 * \brief Get the size of the image data.
329 * If the image could be converted to RGB using a LUT,
330 * this transformation is not taken into account by GetImageDataRawSize
331 * (use GetImageDataSize if you wish)
332 * @return The raw image size
334 size_t FileHelper::GetImageDataRawSize()
336 if ( PixelWriteConverter->GetUserData() )
338 return PixelWriteConverter->GetUserDataSize();
340 return PixelReadConverter->GetRawSize();
344 * \brief brings pixels into memory :
345 * - Allocates necessary memory,
346 * - Reads the pixels from disk (uncompress if necessary),
347 * - Transforms YBR pixels, if any, into RGB pixels,
348 * - Transforms 3 planes R, G, B, if any, into a single RGB Plane
349 * - Transforms single Grey plane + 3 Palettes into a RGB Plane
350 * - Copies the pixel data (image[s]/volume[s]) to newly allocated zone.
351 * @return Pointer to newly allocated pixel data.
352 * (uint8_t is just for prototyping. feel free to cast)
353 * NULL if alloc fails
355 uint8_t *FileHelper::GetImageData()
357 if ( PixelWriteConverter->GetUserData() )
359 return PixelWriteConverter->GetUserData();
364 // If the decompression failed nothing can be done.
368 if ( FileInternal->HasLUT() && PixelReadConverter->BuildRGBImage() )
370 return PixelReadConverter->GetRGB();
374 // When no LUT or LUT conversion fails, return the Raw
375 return PixelReadConverter->GetRaw();
380 * \brief brings pixels into memory :
381 * - Allocates necessary memory,
382 * - Transforms YBR pixels (if any) into RGB pixels
383 * - Transforms 3 planes R, G, B (if any) into a single RGB Plane
384 * - Copies the pixel data (image[s]/volume[s]) to newly allocated zone.
385 * - DOES NOT transform Grey plane + 3 Palettes into a RGB Plane
386 * @return Pointer to newly allocated pixel data.
387 * (uint8_t is just for prototyping. feel free to cast)
388 * NULL if alloc fails
390 uint8_t *FileHelper::GetImageDataRaw ()
395 //#ifndef GDCM_LEGACY_REMOVE
397 * \brief Useless function, since PixelReadConverter forces us
398 * copy the Pixels anyway.
399 * Reads the pixels from disk (uncompress if necessary),
400 * Transforms YBR pixels, if any, into RGB pixels
401 * Transforms 3 planes R, G, B, if any, into a single RGB Plane
402 * Transforms single Grey plane + 3 Palettes into a RGB Plane
403 * Copies at most MaxSize bytes of pixel data to caller allocated
405 * \warning This function allows people that want to build a volume
406 * from an image stack *not to* have, first to get the image pixels,
407 * and then move them to the volume area.
408 * It's absolutely useless for any VTK user since vtk chooses
409 * to invert the lines of an image, that is the last line comes first
410 * (for some axis related reasons?). Hence he will have
411 * to load the image line by line, starting from the end.
412 * VTK users have to call GetImageData
414 * @param destination Address (in caller's memory space) at which the
415 * pixel data should be copied
416 * @param maxSize Maximum number of bytes to be copied. When MaxSize
417 * is not sufficient to hold the pixel data the copy is not
418 * executed (i.e. no partial copy).
419 * @return On success, the number of bytes actually copied. Zero on
420 * failure e.g. MaxSize is lower than necessary.
423 size_t FileHelper::GetImageDataIntoVector (void *destination, size_t maxSize)
427 // If the decompression failed nothing can be done.
431 if ( FileInternal->HasLUT() && PixelReadConverter->BuildRGBImage() )
433 if ( PixelReadConverter->GetRGBSize() > maxSize )
435 gdcmWarningMacro( "Pixel data bigger than caller's expected MaxSize");
439 (void*)PixelReadConverter->GetRGB(),
440 PixelReadConverter->GetRGBSize() );
441 return PixelReadConverter->GetRGBSize();
444 // Either no LUT conversion necessary or LUT conversion failed
445 if ( PixelReadConverter->GetRawSize() > maxSize )
447 gdcmWarningMacro( "Pixel data bigger than caller's expected MaxSize");
451 (void *)PixelReadConverter->GetRaw(),
452 PixelReadConverter->GetRawSize() );
453 return PixelReadConverter->GetRawSize();
459 * \brief Points the internal pointer to the callers inData
460 * image representation, BUT WITHOUT COPYING THE DATA.
461 * 'image' Pixels are presented as C-like 2D arrays : line per line.
462 * 'volume'Pixels are presented as C-like 3D arrays : plane per plane
463 * \warning Since the pixels are not copied, it is the caller's responsability
464 * not to deallocate its data before gdcm uses them (e.g. with
465 * the Write() method )
466 * @param inData user supplied pixel area (uint8_t* is just for the compiler.
467 * user is allowed to pass any kind of pixelsn since the size is
469 * @param expectedSize total image size, *in Bytes*
471 void FileHelper::SetImageData(uint8_t *inData, size_t expectedSize)
473 SetUserData(inData, expectedSize);
477 * \brief Set the image data defined by the user
478 * \warning When writting the file, this data are get as default data to write
479 * @param inData user supplied pixel area (uint8_t* is just for the compiler.
480 * user is allowed to pass any kind of pixels since the size is
482 * @param expectedSize total image size, *in Bytes*
484 void FileHelper::SetUserData(uint8_t *inData, size_t expectedSize)
486 PixelWriteConverter->SetUserData(inData, expectedSize);
490 * \brief Get the image data defined by the user
491 * \warning When writting the file, this data are get as default data to write
493 uint8_t *FileHelper::GetUserData()
495 return PixelWriteConverter->GetUserData();
499 * \brief Get the image data size defined by the user
500 * \warning When writting the file, this data are get as default data to write
502 size_t FileHelper::GetUserDataSize()
504 return PixelWriteConverter->GetUserDataSize();
508 * \brief Get the image data from the file.
509 * If a LUT is found, the data are expanded to be RGB
511 uint8_t *FileHelper::GetRGBData()
513 return PixelReadConverter->GetRGB();
517 * \brief Get the image data size from the file.
518 * If a LUT is found, the data are expanded to be RGB
520 size_t FileHelper::GetRGBDataSize()
522 return PixelReadConverter->GetRGBSize();
526 * \brief Get the image data from the file.
527 * Even when a LUT is found, the data are not expanded to RGB!
529 uint8_t *FileHelper::GetRawData()
531 return PixelReadConverter->GetRaw();
535 * \brief Get the image data size from the file.
536 * Even when a LUT is found, the data are not expanded to RGB!
538 size_t FileHelper::GetRawDataSize()
540 return PixelReadConverter->GetRawSize();
544 * \brief Access to the underlying \ref PixelReadConverter RGBA LUT
546 uint8_t* FileHelper::GetLutRGBA()
548 if ( PixelReadConverter->GetLutRGBA() ==0 )
549 PixelReadConverter->BuildLUTRGBA();
550 return PixelReadConverter->GetLutRGBA();
554 * \brief Access to the underlying \ref PixelReadConverter RGBA LUT Item Number
556 int FileHelper::GetLutItemNumber()
558 return PixelReadConverter->GetLutItemNumber();
562 * \brief Access to the underlying \ref PixelReadConverter RGBA LUT Item Size
564 int FileHelper::GetLutItemSize()
566 return PixelReadConverter->GetLutItemSize();
570 * \brief Writes on disk A SINGLE Dicom file
571 * NO test is performed on processor "Endiannity".
572 * It's up to the user to call his Reader properly
573 * @param fileName name of the file to be created
574 * (any already existing file is over written)
575 * @return false if write fails
577 bool FileHelper::WriteRawData(std::string const &fileName)
579 std::ofstream fp1(fileName.c_str(), std::ios::out | std::ios::binary );
582 gdcmWarningMacro( "Fail to open (write) file:" << fileName.c_str());
586 if ( PixelWriteConverter->GetUserData() )
588 fp1.write( (char *)PixelWriteConverter->GetUserData(),
589 PixelWriteConverter->GetUserDataSize() );
591 else if ( PixelReadConverter->GetRGB() )
593 fp1.write( (char *)PixelReadConverter->GetRGB(),
594 PixelReadConverter->GetRGBSize());
596 else if ( PixelReadConverter->GetRaw() )
598 fp1.write( (char *)PixelReadConverter->GetRaw(),
599 PixelReadConverter->GetRawSize());
603 gdcmErrorMacro( "Nothing written." );
612 * \brief Writes on disk A SINGLE Dicom file,
613 * using the Implicit Value Representation convention
614 * NO test is performed on processor "Endianity".
615 * @param fileName name of the file to be created
616 * (any already existing file is overwritten)
617 * @return false if write fails
620 bool FileHelper::WriteDcmImplVR (std::string const &fileName)
622 SetWriteTypeToDcmImplVR();
623 return Write(fileName);
627 * \brief Writes on disk A SINGLE Dicom file,
628 * using the Explicit Value Representation convention
629 * NO test is performed on processor "Endiannity".
630 * @param fileName name of the file to be created
631 * (any already existing file is overwritten)
632 * @return false if write fails
635 bool FileHelper::WriteDcmExplVR (std::string const &fileName)
637 SetWriteTypeToDcmExplVR();
638 return Write(fileName);
642 * \brief Writes on disk A SINGLE Dicom file,
643 * using the ACR-NEMA convention
644 * NO test is performed on processor "Endiannity".
645 * (a l'attention des logiciels cliniques
646 * qui ne prennent en entrée QUE des images ACR ...
647 * \warning if a DICOM_V3 header is supplied,
648 * groups < 0x0008 and shadow groups are ignored
649 * \warning NO TEST is performed on processor "Endiannity".
650 * @param fileName name of the file to be created
651 * (any already existing file is overwritten)
652 * @return false if write fails
655 bool FileHelper::WriteAcr (std::string const &fileName)
658 return Write(fileName);
662 * \brief Writes on disk A SINGLE Dicom file,
663 * @param fileName name of the file to be created
664 * (any already existing file is overwritten)
665 * @return false if write fails
667 bool FileHelper::Write(std::string const &fileName)
670 CheckMandatoryElements(); //called once, here !
677 SetWriteFileTypeToImplicitVR();
680 case Unknown: // should never happen; ExplicitVR is the default value
683 // User should ask gdcm to write an image in Explicit VR mode
684 // only when he is sure *all* the VR of *all* the DataElements is known.
685 // i.e : when there are *only* Public Groups
686 // or *all* the Shadow Groups are fully described in the relevant Shadow
688 // Let's just *dream* about it; *never* trust a user !
689 // We turn to Implicit VR if at least the VR of one element is unknown.
692 e = FileInternal->GetFirstEntry();
695 if (e->GetVR() == " ")
697 SetWriteTypeToDcmImplVR();
698 SetWriteFileTypeToImplicitVR();
702 e = FileInternal->GetNextEntry();
707 SetWriteFileTypeToExplicitVR();
711 SetWriteFileTypeToExplicitVR(); // to see JPRx
715 // NOTHING is done here just for LibIDO.
716 // Just to avoid further trouble if user creates a file ex-nihilo,
717 // wants to write it as an ACR-NEMA file,
718 // and forgets to create any Entry belonging to group 0008
720 // We add Recognition Code (RET)
721 if ( ! FileInternal->GetDataEntry(0x0008, 0x0010) )
722 FileInternal->InsertEntryString("ACR-NEMA V1.0 ",
723 0x0008, 0x0010, "LO");
724 SetWriteFileTypeToACR();
725 // SetWriteFileTypeToImplicitVR(); // ACR IS implicit VR !
728 /// \todo FIXME : JPEG may be either ExplicitVR or ImplicitVR
730 SetWriteFileTypeToJPEG();
734 SetWriteFileTypeToJPEG2000();
738 // --------------------------------------------------------------
739 // Special Patch to allow gdcm to re-write ACR-LibIDO formated images
741 // if recognition code tells us we dealt with a LibIDO image
742 // we reproduce on disk the switch between lineNumber and columnNumber
743 // just before writting ...
744 /// \todo the best trick would be *change* the recognition code
745 /// but pb expected if user deals with, e.g. COMPLEX images
747 if ( WriteType == ACR_LIBIDO )
753 SetWriteToNoLibido();
755 // ----------------- End of Special Patch ----------------
760 SetWriteToRaw(); // modifies and pushes to the archive, when necessary
763 SetWriteToRGB(); // modifies and pushes to the archive, when necessary
767 bool check = CheckWriteIntegrity(); // verifies length
768 if (WriteType == JPEG || WriteType == JPEG2000) check = true;
771 check = FileInternal->Write(fileName,WriteType);
775 // RestoreWriteFileType();
776 // RestoreWriteMandatory();
779 // --------------------------------------------------------------
780 // Special Patch to allow gdcm to re-write ACR-LibIDO formated images
782 // ...and we restore the header to be Dicom Compliant again
783 // just after writting
784 RestoreWriteOfLibido();
785 // ----------------- End of Special Patch ----------------
790 //-----------------------------------------------------------------------------
793 * \brief Checks the write integrity
795 * The tests made are :
796 * - verify the size of the image to write with the possible write
797 * when the user set an image data
798 * @return true if check is successfull
800 bool FileHelper::CheckWriteIntegrity()
802 if ( PixelWriteConverter->GetUserData() )
804 int numberBitsAllocated = FileInternal->GetBitsAllocated();
805 if ( numberBitsAllocated == 0 || numberBitsAllocated == 12 )
807 gdcmWarningMacro( "numberBitsAllocated changed from "
808 << numberBitsAllocated << " to 16 "
809 << " for consistency purpose" );
810 numberBitsAllocated = 16;
813 size_t decSize = FileInternal->GetXSize()
814 * FileInternal->GetYSize()
815 * FileInternal->GetZSize()
816 * FileInternal->GetTSize()
817 * FileInternal->GetSamplesPerPixel()
818 * ( numberBitsAllocated / 8 );
819 size_t rgbSize = decSize;
820 if ( FileInternal->HasLUT() )
821 rgbSize = decSize * 3;
826 if ( decSize!=PixelWriteConverter->GetUserDataSize() )
828 gdcmWarningMacro( "Data size (Raw) is incorrect. Should be "
829 << decSize << " / Found :"
830 << PixelWriteConverter->GetUserDataSize() );
835 if ( rgbSize!=PixelWriteConverter->GetUserDataSize() )
837 gdcmWarningMacro( "Data size (RGB) is incorrect. Should be "
838 << decSize << " / Found "
839 << PixelWriteConverter->GetUserDataSize() );
850 * \brief Updates the File to write RAW data (as opposed to RGB data)
851 * (modifies, when necessary, photochromatic interpretation,
852 * bits allocated, Pixels element VR)
854 void FileHelper::SetWriteToRaw()
856 if ( FileInternal->GetNumberOfScalarComponents() == 3
857 && !FileInternal->HasLUT() )
863 DataEntry *photInt = CopyDataEntry(0x0028,0x0004,"CS");
864 if (FileInternal->HasLUT() )
866 photInt->SetString("PALETTE COLOR ");
870 photInt->SetString("MONOCHROME2 ");
873 PixelWriteConverter->SetReadData(PixelReadConverter->GetRaw(),
874 PixelReadConverter->GetRawSize());
876 std::string vr = "OB";
877 if ( FileInternal->GetBitsAllocated()>8 )
879 if ( FileInternal->GetBitsAllocated()==24 ) // For RGB ACR files
881 // For non RAW data. Mainly JPEG
882 if( WriteType == JPEG || WriteType == JPEG2000)
887 CopyDataEntry(GetFile()->GetGrPixel(),GetFile()->GetNumPixel(),vr);
888 pixel->SetFlag(DataEntry::FLAG_PIXELDATA);
889 pixel->SetBinArea(PixelWriteConverter->GetData(),false);
890 pixel->SetLength(PixelWriteConverter->GetDataSize());
892 Archive->Push(photInt);
893 Archive->Push(pixel);
901 * \brief Updates the File to write RGB data (as opposed to RAW data)
902 * (modifies, when necessary, photochromatic interpretation,
903 * samples per pixel, Planar configuration,
904 * bits allocated, bits stored, high bit -ACR 24 bits-
905 * Pixels element VR, pushes out the LUT, )
907 void FileHelper::SetWriteToRGB()
909 if ( FileInternal->GetNumberOfScalarComponents()==3 )
911 PixelReadConverter->BuildRGBImage();
913 DataEntry *spp = CopyDataEntry(0x0028,0x0002,"US");
914 spp->SetString("3 ");
916 DataEntry *planConfig = CopyDataEntry(0x0028,0x0006,"US");
917 planConfig->SetString("0 ");
919 DataEntry *photInt = CopyDataEntry(0x0028,0x0004,"CS");
920 photInt->SetString("RGB ");
922 if ( PixelReadConverter->GetRGB() )
924 PixelWriteConverter->SetReadData(PixelReadConverter->GetRGB(),
925 PixelReadConverter->GetRGBSize());
929 PixelWriteConverter->SetReadData(PixelReadConverter->GetRaw(),
930 PixelReadConverter->GetRawSize());
933 std::string vr = "OB";
934 if ( FileInternal->GetBitsAllocated()>8 )
936 if ( FileInternal->GetBitsAllocated()==24 ) // For RGB ACR files
939 CopyDataEntry(GetFile()->GetGrPixel(),GetFile()->GetNumPixel(),vr);
940 pixel->SetFlag(DataEntry::FLAG_PIXELDATA);
941 pixel->SetBinArea(PixelWriteConverter->GetData(),false);
942 pixel->SetLength(PixelWriteConverter->GetDataSize());
945 Archive->Push(planConfig);
946 Archive->Push(photInt);
947 Archive->Push(pixel);
950 planConfig->Delete();
955 Archive->Push(0x0028,0x1101);
956 Archive->Push(0x0028,0x1102);
957 Archive->Push(0x0028,0x1103);
958 Archive->Push(0x0028,0x1201);
959 Archive->Push(0x0028,0x1202);
960 Archive->Push(0x0028,0x1203);
962 // push out Palette Color Lookup Table UID, if any
963 Archive->Push(0x0028,0x1199);
965 // For old '24 Bits' ACR-NEMA
966 // Thus, we have a RGB image and the bits allocated = 24 and
967 // samples per pixels = 1 (in the read file)
968 if ( FileInternal->GetBitsAllocated()==24 )
970 DataEntry *bitsAlloc = CopyDataEntry(0x0028,0x0100,"US");
971 bitsAlloc->SetString("8 ");
973 DataEntry *bitsStored = CopyDataEntry(0x0028,0x0101,"US");
974 bitsStored->SetString("8 ");
976 DataEntry *highBit = CopyDataEntry(0x0028,0x0102,"US");
977 highBit->SetString("7 ");
979 Archive->Push(bitsAlloc);
980 Archive->Push(bitsStored);
981 Archive->Push(highBit);
984 bitsStored->Delete();
995 * \brief Restore the File write mode
997 void FileHelper::RestoreWrite()
1000 Archive->Restore(0x0028,0x0002);
1001 Archive->Restore(0x0028,0x0004);
1003 Archive->Restore(0x0028,0x0006);
1004 Archive->Restore(GetFile()->GetGrPixel(),GetFile()->GetNumPixel());
1006 // For old ACR-NEMA (24 bits problem)
1007 Archive->Restore(0x0028,0x0100);
1008 Archive->Restore(0x0028,0x0101);
1009 Archive->Restore(0x0028,0x0102);
1012 Archive->Restore(0x0028,0x1101);
1013 Archive->Restore(0x0028,0x1102);
1014 Archive->Restore(0x0028,0x1103);
1015 Archive->Restore(0x0028,0x1201);
1016 Archive->Restore(0x0028,0x1202);
1017 Archive->Restore(0x0028,0x1203);
1019 // For the Palette Color Lookup Table UID
1020 Archive->Restore(0x0028,0x1203);
1022 // group 0002 may be pushed out for ACR-NEMA writting purposes
1023 Archive->Restore(0x0002,0x0000);
1024 Archive->Restore(0x0002,0x0001);
1025 Archive->Restore(0x0002,0x0002);
1026 Archive->Restore(0x0002,0x0003);
1027 Archive->Restore(0x0002,0x0010);
1028 Archive->Restore(0x0002,0x0012);
1029 Archive->Restore(0x0002,0x0013);
1030 Archive->Restore(0x0002,0x0016);
1031 Archive->Restore(0x0002,0x0100);
1032 Archive->Restore(0x0002,0x0102);
1037 * \brief Pushes out the whole group 0002
1038 * FIXME : better, set a flag to tell the writer not to write it ...
1039 * FIXME : method should probably have an other name !
1040 * SetWriteFileTypeToACR is NOT opposed to
1041 * SetWriteFileTypeToExplicitVR and SetWriteFileTypeToImplicitVR
1043 void FileHelper::SetWriteFileTypeToACR()
1045 Archive->Push(0x0002,0x0000);
1046 Archive->Push(0x0002,0x0001);
1047 Archive->Push(0x0002,0x0002);
1048 Archive->Push(0x0002,0x0003);
1049 Archive->Push(0x0002,0x0010);
1050 Archive->Push(0x0002,0x0012);
1051 Archive->Push(0x0002,0x0013);
1052 Archive->Push(0x0002,0x0016);
1053 Archive->Push(0x0002,0x0100);
1054 Archive->Push(0x0002,0x0102);
1058 * \brief Sets in the File the TransferSyntax to 'JPEG2000'
1060 void FileHelper::SetWriteFileTypeToJPEG2000()
1062 std::string ts = Util::DicomString(
1063 Global::GetTS()->GetSpecialTransferSyntax(TS::JPEG2000Lossless) );
1065 DataEntry *tss = CopyDataEntry(0x0002,0x0010,"UI");
1073 * \brief Sets in the File the TransferSyntax to 'JPEG'
1075 void FileHelper::SetWriteFileTypeToJPEG()
1077 std::string ts = Util::DicomString(
1078 Global::GetTS()->GetSpecialTransferSyntax(TS::JPEGBaselineProcess1) );
1080 DataEntry *tss = CopyDataEntry(0x0002,0x0010,"UI");
1088 * \brief Sets in the File the TransferSyntax to 'Explicit VR Little Endian"
1090 void FileHelper::SetWriteFileTypeToExplicitVR()
1092 std::string ts = Util::DicomString(
1093 Global::GetTS()->GetSpecialTransferSyntax(TS::ExplicitVRLittleEndian) );
1095 DataEntry *tss = CopyDataEntry(0x0002,0x0010,"UI");
1102 * \brief Sets in the File the TransferSyntax to 'Implicit VR Little Endian"
1104 void FileHelper::SetWriteFileTypeToImplicitVR()
1106 std::string ts = Util::DicomString(
1107 Global::GetTS()->GetSpecialTransferSyntax(TS::ImplicitVRLittleEndian) );
1109 DataEntry *tss = CopyDataEntry(0x0002,0x0010,"UI");
1117 * \brief Restore in the File the initial group 0002
1119 void FileHelper::RestoreWriteFileType()
1124 * \brief Set the Write not to Libido format
1126 void FileHelper::SetWriteToLibido()
1128 DataEntry *oldRow = FileInternal->GetDataEntry(0x0028, 0x0010);
1129 DataEntry *oldCol = FileInternal->GetDataEntry(0x0028, 0x0011);
1131 if ( oldRow && oldCol )
1133 std::string rows, columns;
1135 //DataEntry *newRow=DataEntry::New(oldRow->GetDictEntry());
1136 //DataEntry *newCol=DataEntry::New(oldCol->GetDictEntry());
1138 DataEntry *newRow=DataEntry::New(0x0028, 0x0010, "US");
1139 DataEntry *newCol=DataEntry::New(0x0028, 0x0011, "US");
1141 newRow->Copy(oldCol);
1142 newCol->Copy(oldRow);
1144 newRow->SetString(oldCol->GetString());
1145 newCol->SetString(oldRow->GetString());
1147 Archive->Push(newRow);
1148 Archive->Push(newCol);
1154 DataEntry *libidoCode = CopyDataEntry(0x0008,0x0010,"LO");
1155 libidoCode->SetString("ACRNEMA_LIBIDO_1.1");
1156 Archive->Push(libidoCode);
1157 libidoCode->Delete();
1161 * \brief Set the Write not to No Libido format
1163 void FileHelper::SetWriteToNoLibido()
1165 DataEntry *recCode = FileInternal->GetDataEntry(0x0008,0x0010);
1168 if ( recCode->GetString() == "ACRNEMA_LIBIDO_1.1" )
1170 DataEntry *libidoCode = CopyDataEntry(0x0008,0x0010,"LO");
1171 libidoCode->SetString("");
1172 Archive->Push(libidoCode);
1173 libidoCode->Delete();
1179 * \brief Restore the Write format
1181 void FileHelper::RestoreWriteOfLibido()
1183 Archive->Restore(0x0028,0x0010);
1184 Archive->Restore(0x0028,0x0011);
1185 Archive->Restore(0x0008,0x0010);
1187 // Restore 'LibIDO-special' entries, if any
1188 Archive->Restore(0x0028,0x0015);
1189 Archive->Restore(0x0028,0x0016);
1190 Archive->Restore(0x0028,0x0017);
1191 Archive->Restore(0x0028,0x00199);
1195 * \brief Duplicates a DataEntry or creates it.
1196 * @param group Group number of the Entry
1197 * @param elem Element number of the Entry
1198 * @param vr Value Representation of the Entry
1199 * \return pointer to the new Bin Entry (NULL when creation failed).
1201 DataEntry *FileHelper::CopyDataEntry(uint16_t group, uint16_t elem,
1204 DocEntry *oldE = FileInternal->GetDocEntry(group, elem);
1207 if ( oldE && vr != GDCM_VRUNKNOWN )
1208 if ( oldE->GetVR() != vr )
1213 //newE = DataEntry::New(oldE->GetDictEntry());
1214 newE = DataEntry::New(group, elem, vr);
1219 newE = GetFile()->NewDataEntry(group, elem, vr);
1226 * \brief This method is called automatically, just before writting
1227 * in order to produce a 'True Dicom V3' image.
1229 * We cannot know *how* the user made the File :
1230 * (reading an old ACR-NEMA file or a not very clean DICOM file ...)
1231 * Just before writting :
1232 * - we check the Entries
1233 * - we create the mandatory entries if they are missing
1234 * - we modify the values if necessary
1235 * - we push the sensitive entries to the Archive
1236 * The writing process will restore the entries as they where before
1237 * entering FileHelper::CheckMandatoryElements, so the user will always
1238 * see the entries just as they were before he decided to write.
1241 * - Entries whose type is 1 are mandatory, with a mandatory value
1242 * - Entries whose type is 1c are mandatory-inside-a-Sequence,
1243 * with a mandatory value
1244 * - Entries whose type is 2 are mandatory, with an optional value
1245 * - Entries whose type is 2c are mandatory-inside-a-Sequence,
1246 * with an optional value
1247 * - Entries whose type is 3 are optional
1250 * - warn the user if we had to add some entries :
1251 * even if a mandatory entry is missing, we add it, with a default value
1252 * (we don't want to give up the writting process if user forgot to
1253 * specify Lena's Patient ID, for instance ...)
1254 * - read the whole PS 3.3 Part of DICOM (890 pages)
1255 * and write a *full* checker (probably one method per Modality ...)
1256 * Any contribution is welcome.
1257 * - write a user callable full checker, to allow post reading
1258 * and/or pre writting image consistency check.
1261 /* -------------------------------------------------------------------------------------
1262 To be moved to User's guide / WIKI ?
1264 We have to deal with 4 *very* different cases :
1265 -1) user created ex nihilo his own image and wants to write it as a Dicom image.
1267 -2) user modified the pixels of an existing image.
1269 -3) user created a new image, using a set of existing images (eg MIP, MPR, cartography image)
1271 -4) user modified/added some tags *without processing* the pixels (anonymization..
1272 UNMODIFIED_PIXELS_IMAGE
1273 -Probabely some more to be added
1275 gdcm::FileHelper::CheckMandatoryElements() deals automatically with these cases.
1278 0008 0012 Instance Creation Date
1279 0008 0013 Instance Creation Time
1280 0008 0018 SOP Instance UID
1281 are *always* created with the current values; user has *no* possible intervention on
1284 'Serie Instance UID'(0x0020,0x000e)
1285 'Study Instance UID'(0x0020,0x000d) are kept as is if already exist,
1286 created if it doesn't.
1287 The user is allowed to create his own Series/Studies,
1288 keeping the same 'Serie Instance UID' / 'Study Instance UID' for various images
1290 The user shouldn't add any image to a 'Manufacturer Serie'
1291 but there is no way no to allow him to do that
1293 None of the 'shadow elements' are droped out.
1297 'Conversion Type (0x0008,0x0064) is forced to 'SYN' (Synthetic Image).
1300 'Media Storage SOP Class UID' (0x0002,0x0002)
1301 'SOP Class UID' (0x0008,0x0016) are set to
1302 [Secondary Capture Image Storage]
1303 'Image Type' (0x0008,0x0008) is forced to "DERIVED\PRIMARY"
1304 Conversion Type (0x0008,0x0064) is forced to 'SYN' (Synthetic Image)
1307 If 'SOP Class UID' exists in the native image ('true DICOM' image)
1308 we create the 'Source Image Sequence' SeqEntry (0x0008, 0x2112)
1309 --> 'Referenced SOP Class UID' (0x0008, 0x1150)
1310 whose value is the original 'SOP Class UID'
1311 --> 'Referenced SOP Instance UID' (0x0008, 0x1155)
1312 whose value is the original 'SOP Class UID'
1314 3) TODO : find a trick to allow user to pass to the writter the list of the Dicom images
1315 or the Series, (or the Study ?) he used to created his image
1316 (MIP, MPR, cartography image, ...)
1317 These info should be stored (?)
1318 0008 1110 SQ 1 Referenced Study Sequence
1319 0008 1115 SQ 1 Referenced Series Sequence
1320 0008 1140 SQ 1 Referenced Image Sequence
1322 4) When user *knows* he didn't modified the pixels, we keep some informations unchanged :
1323 'Media Storage SOP Class UID' (0x0002,0x0002)
1324 'SOP Class UID' (0x0008,0x0016)
1325 'Image Type' (0x0008,0x0008)
1326 'Conversion Type' (0x0008,0x0064)
1329 Bellow follows the full description (hope so !) of the consistency checks performed
1330 by gdcm::FileHelper::CheckMandatoryElements()
1333 -->'Media Storage SOP Class UID' (0x0002,0x0002)
1334 -->'SOP Class UID' (0x0008,0x0016) are defaulted to
1335 [Secondary Capture Image Storage]
1336 --> 'Image Type' (0x0008,0x0008)
1337 is forced to "DERIVED\PRIMARY"
1338 (The written image is no longer an 'ORIGINAL' one)
1339 Except if user knows he didn't modify the image (e.g. : he just anonymized the file)
1341 --> Conversion Type (0x0008,0x0064)
1342 is defaulted to 'SYN' (Synthetic Image)
1343 when *he* knows he created his own image ex nihilo
1345 --> 'Modality' (0x0008,0x0060)
1346 is defaulted to "OT" (other) if missing.
1347 (a fully user created image belongs to *no* modality)
1349 --> 'Media Storage SOP Instance UID' (0x0002,0x0003)
1350 --> 'Implementation Class UID' (0x0002,0x0012)
1351 are automatically generated; no user intervention possible
1353 --> 'Serie Instance UID'(0x0020,0x000e)
1354 --> 'Study Instance UID'(0x0020,0x000d) are kept as is if already exist
1355 created if it doesn't.
1356 The user is allowed to create his own Series/Studies,
1357 keeping the same 'Serie Instance UID' / 'Study Instance UID'
1360 The user shouldn't add any image to a 'Manufacturer Serie'
1361 but there is no way no to allowed him to do that
1363 --> If 'SOP Class UID' exists in the native image ('true DICOM' image)
1364 we create the 'Source Image Sequence' SeqEntry (0x0008, 0x2112)
1366 --> 'Referenced SOP Class UID' (0x0008, 0x1150)
1367 whose value is the original 'SOP Class UID'
1368 --> 'Referenced SOP Instance UID' (0x0008, 0x1155)
1369 whose value is the original 'SOP Class UID'
1371 --> Bits Stored, Bits Allocated, Hight Bit Position are checked for consistency
1372 --> Pixel Spacing (0x0028,0x0030) is defaulted to "1.0\1.0"
1373 --> Samples Per Pixel (0x0028,0x0002) is defaulted to 1 (grayscale)
1375 --> Imager Pixel Spacing (0x0018,0x1164) : defaulted to Pixel Spacing value
1377 --> Instance Creation Date, Instance Creation Time are forced to current Date and Time
1379 --> Study Date, Study Time are defaulted to current Date and Time
1380 (they remain unchanged if they exist)
1382 --> Patient Orientation : (0x0020,0x0020), if not present, is deduced from
1383 Image Orientation (Patient) : (0020|0037) or from
1384 Image Orientation (RET) : (0020 0035)
1386 --> Study ID, Series Number, Instance Number, Patient Orientation (Type 2)
1387 are created, with empty value if there are missing.
1389 --> Manufacturer, Institution Name, Patient's Name, (Type 2)
1390 are defaulted with a 'gdcm' value.
1392 --> Patient ID, Patient's Birth Date, Patient's Sex, (Type 2)
1393 --> Referring Physician's Name (Type 2)
1394 are created, with empty value if there are missing.
1396 -------------------------------------------------------------------------------------*/
1398 void FileHelper::CheckMandatoryElements()
1400 std::string sop = Util::CreateUniqueUID();
1402 // --------------------- For Meta Elements ---------------------
1403 // just to remember : 'official' 0002 group
1404 if ( WriteType != ACR && WriteType != ACR_LIBIDO )
1406 // Group 000002 (Meta Elements) already pushed out
1408 //0002 0000 UL 1 Meta Group Length
1409 //0002 0001 OB 1 File Meta Information Version
1410 //0002 0002 UI 1 Media Stored SOP Class UID
1411 //0002 0003 UI 1 Media Stored SOP Instance UID
1412 //0002 0010 UI 1 Transfer Syntax UID
1413 //0002 0012 UI 1 Implementation Class UID
1414 //0002 0013 SH 1 Implementation Version Name
1415 //0002 0016 AE 1 Source Application Entity Title
1416 //0002 0100 UI 1 Private Information Creator
1417 //0002 0102 OB 1 Private Information
1419 // Push out 'ACR-NEMA-special' entries, if any
1420 Archive->Push(0x0008,0x0001); // Length to End
1421 Archive->Push(0x0008,0x0010); // Recognition Code
1422 Archive->Push(0x0028,0x0005); // Image Dimension
1424 // Create them if not found
1425 // Always modify the value
1426 // Push the entries to the archive.
1427 CopyMandatoryEntry(0x0002,0x0000,"0","UL");
1429 DataEntry *e_0002_0001 = CopyDataEntry(0x0002,0x0001, "OB");
1430 e_0002_0001->SetBinArea((uint8_t*)Util::GetFileMetaInformationVersion(),
1432 e_0002_0001->SetLength(2);
1433 Archive->Push(e_0002_0001);
1434 e_0002_0001->Delete();
1436 if ( ContentType == FILTERED_IMAGE || ContentType == UNMODIFIED_PIXELS_IMAGE)
1438 // we keep the original 'Media Storage SOP Class UID', we default it if missing
1439 CheckMandatoryEntry(0x0002,0x0002,"1.2.840.10008.5.1.4.1.1.7","UI");
1443 // It's *not* an image comming straight from a source. We force
1444 // 'Media Storage SOP Class UID' --> [Secondary Capture Image Storage]
1445 CopyMandatoryEntry(0x0002,0x0002,"1.2.840.10008.5.1.4.1.1.7","UI");
1448 // 'Media Storage SOP Instance UID'
1449 CopyMandatoryEntry(0x0002,0x0003,sop,"UI");
1451 // 'Implementation Class UID'
1452 // FIXME : in all examples we have, 0x0002,0x0012 is not so long :
1453 // seems to be Root UID + 4 digits (?)
1454 CopyMandatoryEntry(0x0002,0x0012,Util::CreateUniqueUID(),"UI");
1456 // 'Implementation Version Name'
1457 std::string version = "GDCM ";
1458 version += Util::GetVersion();
1459 CopyMandatoryEntry(0x0002,0x0013,version,"SH");
1462 // --------------------- For DataSet ---------------------
1464 if ( ContentType != USER_OWN_IMAGE) // when it's not a user made image
1467 gdcmDebugMacro( "USER_OWN_IMAGE (1)");
1468 // If 'SOP Class UID' exists ('true DICOM' image)
1469 // we create the 'Source Image Sequence' SeqEntry
1470 // to hold informations about the Source Image
1472 DataEntry *e_0008_0016 = FileInternal->GetDataEntry(0x0008, 0x0016);
1475 // Create 'Source Image Sequence' SeqEntry
1476 // SeqEntry *sis = SeqEntry::New (
1477 // Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x2112) );
1478 SeqEntry *sis = SeqEntry::New (0x0008, 0x2112);
1479 SQItem *sqi = SQItem::New(1);
1480 // (we assume 'SOP Instance UID' exists too)
1481 // create 'Referenced SOP Class UID'
1482 // DataEntry *e_0008_1150 = DataEntry::New(
1483 // Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x1150) );
1484 DataEntry *e_0008_1150 = DataEntry::New(0x0008, 0x1150, "UI");
1485 e_0008_1150->SetString( e_0008_0016->GetString());
1486 sqi->AddEntry(e_0008_1150);
1487 e_0008_1150->Delete();
1489 // create 'Referenced SOP Instance UID'
1490 DataEntry *e_0008_0018 = FileInternal->GetDataEntry(0x0008, 0x0018);
1491 // DataEntry *e_0008_1155 = DataEntry::New(
1492 // Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x1155) );
1493 DataEntry *e_0008_1155 = DataEntry::New(0x0008, 0x1155, "UI");
1494 e_0008_1155->SetString( e_0008_0018->GetString());
1495 sqi->AddEntry(e_0008_1155);
1496 e_0008_1155->Delete();
1498 sis->AddSQItem(sqi,1);
1501 // temporarily replaces any previous 'Source Image Sequence'
1505 // FIXME : is 'Image Type' *really* depending on the presence of 'SOP Class UID'?
1506 if ( ContentType == FILTERED_IMAGE)
1507 // the user *knows* he just modified the pixels
1508 // the image is no longer an 'Original' one
1509 CopyMandatoryEntry(0x0008,0x0008,"DERIVED\\PRIMARY","CS");
1513 if ( ContentType == FILTERED_IMAGE || ContentType == UNMODIFIED_PIXELS_IMAGE)
1515 // we keep the original 'Media Storage SOP Class UID', we default it if missing (it should be present !)
1516 CheckMandatoryEntry(0x0008,0x0016,"1.2.840.10008.5.1.4.1.1.7","UI");
1520 // It's *not* an image comming straight from a source. We force
1521 // 'Media Storage SOP Class UID' --> [Secondary Capture Image Storage]
1522 CopyMandatoryEntry(0x0008,0x0016,"1.2.840.10008.5.1.4.1.1.7", "UI");
1525 Archive->Push(0x0028,0x005); // [Image Dimensions (RET)
1526 // Push out 'LibIDO-special' entries, if any
1527 Archive->Push(0x0028,0x0015);
1528 Archive->Push(0x0028,0x0016);
1529 Archive->Push(0x0028,0x0017);
1530 Archive->Push(0x0028,0x0198); // very old versions
1531 Archive->Push(0x0028,0x0199);
1533 // Replace deprecated 0028 0012 US Planes
1534 // by new 0028 0008 IS Number of Frames
1536 ///\todo : find if there is a rule!
1537 DataEntry *e_0028_0012 = FileInternal->GetDataEntry(0x0028, 0x0012);
1540 CopyMandatoryEntry(0x0028, 0x0008,e_0028_0012->GetString(),"IS");
1541 Archive->Push(0x0028,0x0012);
1544 // Deal with the pb of (Bits Stored = 12)
1545 // - we're gonna write the image as Bits Stored = 16
1546 if ( FileInternal->GetEntryString(0x0028,0x0100) == "12")
1548 CopyMandatoryEntry(0x0028,0x0100,"16","US");
1551 // Check if user wasn't drunk ;-)
1553 std::ostringstream s;
1554 // check 'Bits Allocated' vs decent values
1555 int nbBitsAllocated = FileInternal->GetBitsAllocated();
1556 if ( nbBitsAllocated == 0 || nbBitsAllocated > 32)
1558 CopyMandatoryEntry(0x0028,0x0100,"16","US");
1559 gdcmWarningMacro("(0028,0100) changed from "
1560 << nbBitsAllocated << " to 16 for consistency purpose");
1561 nbBitsAllocated = 16;
1563 // check 'Bits Stored' vs 'Bits Allocated'
1564 int nbBitsStored = FileInternal->GetBitsStored();
1565 if ( nbBitsStored == 0 || nbBitsStored > nbBitsAllocated )
1568 s << nbBitsAllocated;
1569 CopyMandatoryEntry(0x0028,0x0101,s.str(),"US");
1570 gdcmWarningMacro("(0028,0101) changed from "
1571 << nbBitsStored << " to " << nbBitsAllocated
1572 << " for consistency purpose" );
1573 nbBitsStored = nbBitsAllocated;
1575 // check 'Hight Bit Position' vs 'Bits Allocated' and 'Bits Stored'
1576 int highBitPosition = FileInternal->GetHighBitPosition();
1577 if ( highBitPosition == 0 ||
1578 highBitPosition > nbBitsAllocated-1 ||
1579 highBitPosition < nbBitsStored-1 )
1582 s << nbBitsStored - 1;
1583 CopyMandatoryEntry(0x0028,0x0102,s.str(),"US");
1584 gdcmWarningMacro("(0028,0102) changed from "
1585 << highBitPosition << " to " << nbBitsAllocated-1
1586 << " for consistency purpose");
1589 std::string pixelSpacing = FileInternal->GetEntryString(0x0028,0x0030);
1590 if ( pixelSpacing == GDCM_UNFOUND )
1592 pixelSpacing = "1.0\\1.0";
1593 // if missing, Pixel Spacing forced to "1.0\1.0"
1594 CopyMandatoryEntry(0x0028,0x0030,pixelSpacing,"DS");
1597 // 'Imager Pixel Spacing' : defaulted to 'Pixel Spacing'
1598 // --> This one is the *legal* one !
1599 if ( ContentType != USER_OWN_IMAGE)
1600 // we write it only when we are *sure* the image comes from
1601 // an imager (see also 0008,0x0064)
1602 CheckMandatoryEntry(0x0018,0x1164,pixelSpacing,"DS");
1607 ///Exact meaning of RETired fields
1609 // See page 73 of ACR-NEMA_300-1988.pdf !
1611 // 0020,0020 : Patient Orientation :
1612 Patient direction of the first row and
1613 column of the images. The first entry id the direction of the raws, given by the
1614 direction of the last pixel in the first row from the first pixel in tha row.
1615 the second entry is the direction of the columns, given by the direction of the
1616 last pixel in the first column from the first pixel in that column.
1617 L : Left, F : Feet, A : Anterior, P : Posterior.
1618 Up to 3 letters can be used in combination to indicate oblique planes.
1620 //0020,0030 Image Position (RET)
1621 x,y,z coordinates im mm of the first pixel in the image
1623 // 0020,0035 Image Orientation (RET)
1624 Direction cosines of the R axis of the image system with respect to the
1625 equipment coordinate axes x,y,z, followed by direction cosines of the C axis of
1626 the image system with respect to the same axes
1628 //0020,0050 Location
1629 An image location reference, standard for the modality (such as CT bed
1630 position), used to indicate position. Calculation of position for other purposes
1631 is only from (0020,0030) and (0020,0035)
1635 // if imagePositionPatient not found, default it with imagePositionRet, if any
1636 // if imageOrientationPatient not found, default it with imageOrientationRet, if any
1638 std::string imagePositionRet = FileInternal->GetEntryString(0x0020,0x0030);
1639 std::string imageOrientationRet = FileInternal->GetEntryString(0x0020,0x0035);
1640 std::string imagePositionPatient = FileInternal->GetEntryString(0x0020,0x0032);
1641 std::string imageOrientationPatient = FileInternal->GetEntryString(0x0020,0x0037);
1643 if( imagePositionPatient == GDCM_UNFOUND && imageOrientationPatient == GDCM_UNFOUND
1644 && imagePositionRet != GDCM_UNFOUND && imageOrientationRet != GDCM_UNFOUND)
1646 CopyMandatoryEntry(0x0020, 0x0032,imagePositionRet,"DS");
1647 Archive->Push(0x0020,0x0030);
1648 CopyMandatoryEntry(0x0020, 0x0037,imageOrientationRet,"DS");
1649 Archive->Push(0x0020,0x0035);
1653 // Samples Per Pixel (type 1) : default to grayscale
1654 CheckMandatoryEntry(0x0028,0x0002,"1","US");
1656 // --- Check UID-related Entries ---
1658 // At the end, not to overwrite the original ones,
1659 // needed by 'Referenced SOP Instance UID', 'Referenced SOP Class UID'
1660 // 'SOP Instance UID'
1661 CopyMandatoryEntry(0x0008,0x0018,sop,"UI");
1663 if ( ContentType == USER_OWN_IMAGE)
1665 gdcmDebugMacro( "USER_OWN_IMAGE (2)");
1667 // Other possible values are :
1668 // See PS 3.3, Page 408
1670 // DV = Digitized Video
1671 // DI = Digital Interface
1672 // DF = Digitized Film
1673 // WSD = Workstation
1674 // SD = Scanned Document
1675 // SI = Scanned Image
1677 // SYN = Synthetic Image
1679 CheckMandatoryEntry(0x0008,0x0064,"SYN","CS"); // Why not?
1682 if ( ContentType == CREATED_IMAGE)
1684 /// \todo : find a trick to pass the Media Storage SOP Instance UID of the images used to create the current image
1689 // ---- The user will never have to take any action on the following ----
1691 // new value for 'SOP Instance UID'
1692 //SetMandatoryEntry(0x0008,0x0018,Util::CreateUniqueUID());
1694 // Instance Creation Date
1695 const std::string &date = Util::GetCurrentDate();
1696 CopyMandatoryEntry(0x0008,0x0012,date,"DA");
1698 // Instance Creation Time
1699 const std::string &time = Util::GetCurrentTime();
1700 CopyMandatoryEntry(0x0008,0x0013,time,"TM");
1703 CheckMandatoryEntry(0x0008,0x0020,date,"DA");
1705 CheckMandatoryEntry(0x0008,0x0030,time,"TM");
1708 //CopyMandatoryEntry(0x0008,0x0050,"");
1709 CheckMandatoryEntry(0x0008,0x0050,"","SH");
1712 // ----- Add Mandatory Entries if missing ---
1713 // Entries whose type is 1 are mandatory, with a mandatory value
1714 // Entries whose type is 1c are mandatory-inside-a-Sequence,
1715 // with a mandatory value
1716 // Entries whose type is 2 are mandatory, with an optional value
1717 // Entries whose type is 2c are mandatory-inside-a-Sequence,
1718 // with an optional value
1719 // Entries whose type is 3 are optional
1721 // 'Study Instance UID'
1722 // Keep the value if exists
1723 // The user is allowed to create his own Study,
1724 // keeping the same 'Study Instance UID' for various images
1725 // The user may add images to a 'Manufacturer Study',
1726 // adding new Series to an already existing Study
1727 CheckMandatoryEntry(0x0020,0x000d,Util::CreateUniqueUID(),"UI");
1729 // 'Serie Instance UID'
1730 // Keep the value if exists
1731 // The user is allowed to create his own Series,
1732 // keeping the same 'Serie Instance UID' for various images
1733 // The user shouldn't add any image to a 'Manufacturer Serie'
1734 // but there is no way no to prevent him for doing that
1735 CheckMandatoryEntry(0x0020,0x000e,Util::CreateUniqueUID(),"UI");
1738 CheckMandatoryEntry(0x0020,0x0010,"","SH");
1741 CheckMandatoryEntry(0x0020,0x0011,"","IS");
1744 CheckMandatoryEntry(0x0020,0x0013,"","IS");
1746 // Patient Orientation
1747 // Can be computed from (0020|0037) : Image Orientation (Patient)
1748 gdcm::Orientation *o = gdcm::Orientation::New();
1749 std::string ori = o->GetOrientation ( FileInternal );
1751 if (ori != "\\" && ori != GDCM_UNFOUND)
1752 CheckMandatoryEntry(0x0020,0x0020,ori,"CS");
1754 CheckMandatoryEntry(0x0020,0x0020,"","CS");
1756 // Default Patient Position to HFS
1757 CheckMandatoryEntry(0x0018,0x5100,"HFS","CS");
1759 // Modality : if missing we set it to 'OTher'
1760 CheckMandatoryEntry(0x0008,0x0060,"OT","CS");
1762 // Manufacturer : if missing we set it to 'GDCM Factory'
1763 CheckMandatoryEntry(0x0008,0x0070,"GDCM Factory","LO");
1765 // Institution Name : if missing we set it to 'GDCM Hospital'
1766 CheckMandatoryEntry(0x0008,0x0080,"GDCM Hospital","LO");
1768 // Patient's Name : if missing, we set it to 'GDCM^Patient'
1769 CheckMandatoryEntry(0x0010,0x0010,"GDCM^Patient","PN");
1771 // Patient ID : some clinical softwares *demand* it although it's a 'type 2' entry.
1772 CheckMandatoryEntry(0x0010,0x0020,"gdcm ID","LO");
1774 // Patient's Birth Date : 'type 2' entry -> must exist, value not mandatory
1775 CheckMandatoryEntry(0x0010,0x0030,"","DA");
1777 // Patient's Sex :'type 2' entry -> must exist, value not mandatory
1778 CheckMandatoryEntry(0x0010,0x0040,"","CS");
1780 // Referring Physician's Name :'type 2' entry -> must exist, value not mandatory
1781 CheckMandatoryEntry(0x0008,0x0090,"","PN");
1784 // Deal with element 0x0000 (group length) of each group.
1785 // First stage : get all the different Groups
1788 DocEntry *d = FileInternal->GetFirstEntry();
1791 grHT[d->GetGroup()] = 0;
1792 d=FileInternal->GetNextEntry();
1794 // Second stage : add the missing ones (if any)
1795 for (GroupHT::iterator it = grHT.begin(); it != grHT.end(); ++it)
1797 CheckMandatoryEntry(it->first, 0x0000, "0");
1799 // Third stage : update all 'zero level' groups length
1804 void FileHelper::CheckMandatoryEntry(uint16_t group,uint16_t elem,std::string value,const VRKey &vr )
1806 DataEntry *entry = FileInternal->GetDataEntry(group,elem);
1809 //entry = DataEntry::New(Global::GetDicts()->GetDefaultPubDict()->GetEntry(group,elem));
1810 entry = DataEntry::New(group,elem,vr);
1811 entry->SetString(value);
1812 Archive->Push(entry);
1817 /// \todo : what is it used for ? (FileHelper::SetMandatoryEntry)
1818 void FileHelper::SetMandatoryEntry(uint16_t group,uint16_t elem,std::string value,const VRKey &vr)
1820 //DataEntry *entry = DataEntry::New(Global::GetDicts()->GetDefaultPubDict()->GetEntry(group,elem));
1821 DataEntry *entry = DataEntry::New(group,elem,vr);
1822 entry->SetString(value);
1823 Archive->Push(entry);
1827 void FileHelper::CopyMandatoryEntry(uint16_t group,uint16_t elem,std::string value,const VRKey &vr)
1829 DataEntry *entry = CopyDataEntry(group,elem,vr);
1830 entry->SetString(value);
1831 Archive->Push(entry);
1836 * \brief Restore in the File the initial group 0002
1838 void FileHelper::RestoreWriteMandatory()
1840 // group 0002 may be pushed out for ACR-NEMA writting purposes
1841 Archive->Restore(0x0002,0x0000);
1842 Archive->Restore(0x0002,0x0001);
1843 Archive->Restore(0x0002,0x0002);
1844 Archive->Restore(0x0002,0x0003);
1845 Archive->Restore(0x0002,0x0010);
1846 Archive->Restore(0x0002,0x0012);
1847 Archive->Restore(0x0002,0x0013);
1848 Archive->Restore(0x0002,0x0016);
1849 Archive->Restore(0x0002,0x0100);
1850 Archive->Restore(0x0002,0x0102);
1852 // FIXME : Check if none is missing !
1854 Archive->Restore(0x0008,0x0012);
1855 Archive->Restore(0x0008,0x0013);
1856 Archive->Restore(0x0008,0x0016);
1857 Archive->Restore(0x0008,0x0018);
1858 Archive->Restore(0x0008,0x0060);
1859 Archive->Restore(0x0008,0x0070);
1860 Archive->Restore(0x0008,0x0080);
1861 Archive->Restore(0x0008,0x0090);
1862 Archive->Restore(0x0008,0x2112);
1864 Archive->Restore(0x0010,0x0010);
1865 Archive->Restore(0x0010,0x0030);
1866 Archive->Restore(0x0010,0x0040);
1868 Archive->Restore(0x0020,0x000d);
1869 Archive->Restore(0x0020,0x000e);
1874 * \brief CallStartMethod
1876 void FileHelper::CallStartMethod()
1880 CommandManager::ExecuteCommand(this,CMD_STARTPROGRESS);
1884 * \brief CallProgressMethod
1886 void FileHelper::CallProgressMethod()
1888 CommandManager::ExecuteCommand(this,CMD_PROGRESS);
1892 * \brief CallEndMethod
1894 void FileHelper::CallEndMethod()
1897 CommandManager::ExecuteCommand(this,CMD_ENDPROGRESS);
1900 //-----------------------------------------------------------------------------
1903 * \brief Factorization for various forms of constructors.
1905 void FileHelper::Initialize()
1908 ContentType = USER_OWN_IMAGE;
1910 WriteMode = WMODE_RAW;
1911 WriteType = ExplicitVR;
1913 PixelReadConverter = new PixelReadConvert;
1914 PixelWriteConverter = new PixelWriteConvert;
1915 Archive = new DocEntryArchive( FileInternal );
1919 * \brief Reads/[decompresses] the pixels,
1920 * *without* making RGB from Palette Colors
1921 * @return the pixels area, whatever its type
1922 * (uint8_t is just for prototyping : feel free to Cast it)
1924 uint8_t *FileHelper::GetRaw()
1926 PixelReadConverter->SetUserFunction( UserFunction );
1928 uint8_t *raw = PixelReadConverter->GetRaw();
1931 // The Raw image migth not be loaded yet:
1932 std::ifstream *fp = FileInternal->OpenFile();
1933 PixelReadConverter->ReadAndDecompressPixelData( fp );
1935 FileInternal->CloseFile();
1937 raw = PixelReadConverter->GetRaw();
1940 gdcmWarningMacro( "Read/decompress of pixel data apparently went wrong.");
1947 //-----------------------------------------------------------------------------
1949 * \brief Prints the common part of DataEntry, SeqEntry
1950 * @param os ostream we want to print in
1951 * @param indent (unused)
1953 void FileHelper::Print(std::ostream &os, std::string const &)
1955 FileInternal->SetPrintLevel(PrintLevel);
1956 FileInternal->Print(os);
1958 if ( FileInternal->IsReadable() )
1960 PixelReadConverter->SetPrintLevel(PrintLevel);
1961 PixelReadConverter->Print(os);
1965 //-----------------------------------------------------------------------------
1966 } // end namespace gdcm
1969 /* Probabely something to be added to use Rescale Slope/Intercept
1970 Have a look ,at ITK code !
1972 // Internal function to rescale pixel according to Rescale Slope/Intercept
1973 template<class TBuffer, class TSource>
1974 void RescaleFunction(TBuffer* buffer, TSource *source,
1975 double slope, double intercept, size_t size)
1977 size /= sizeof(TSource);
1979 if (slope != 1.0 && intercept != 0.0)
1981 // Duff's device. Instead of this code:
1983 // for(unsigned int i=0; i<size; i++)
1985 // buffer[i] = (TBuffer)(source[i]*slope + intercept);
1988 // use Duff's device which exploits "fall through"
1989 register size_t n = (size + 7) / 8;
1992 case 0: do { *buffer++ = (TBuffer)((*source++)*slope + intercept);
1993 case 7: *buffer++ = (TBuffer)((*source++)*slope + intercept);
1994 case 6: *buffer++ = (TBuffer)((*source++)*slope + intercept);
1995 case 5: *buffer++ = (TBuffer)((*source++)*slope + intercept);
1996 case 4: *buffer++ = (TBuffer)((*source++)*slope + intercept);
1997 case 3: *buffer++ = (TBuffer)((*source++)*slope + intercept);
1998 case 2: *buffer++ = (TBuffer)((*source++)*slope + intercept);
1999 case 1: *buffer++ = (TBuffer)((*source++)*slope + intercept);
2003 else if (slope == 1.0 && intercept != 0.0)
2005 // Duff's device. Instead of this code:
2007 // for(unsigned int i=0; i<size; i++)
2009 // buffer[i] = (TBuffer)(source[i] + intercept);
2012 // use Duff's device which exploits "fall through"
2013 register size_t n = (size + 7) / 8;
2016 case 0: do { *buffer++ = (TBuffer)(*source++ + intercept);
2017 case 7: *buffer++ = (TBuffer)(*source++ + intercept);
2018 case 6: *buffer++ = (TBuffer)(*source++ + intercept);
2019 case 5: *buffer++ = (TBuffer)(*source++ + intercept);
2020 case 4: *buffer++ = (TBuffer)(*source++ + intercept);
2021 case 3: *buffer++ = (TBuffer)(*source++ + intercept);
2022 case 2: *buffer++ = (TBuffer)(*source++ + intercept);
2023 case 1: *buffer++ = (TBuffer)(*source++ + intercept);
2027 else if (slope != 1.0 && intercept == 0.0)
2029 // Duff's device. Instead of this code:
2031 // for(unsigned int i=0; i<size; i++)
2033 // buffer[i] = (TBuffer)(source[i]*slope);
2036 // use Duff's device which exploits "fall through"
2037 register size_t n = (size + 7) / 8;
2040 case 0: do { *buffer++ = (TBuffer)((*source++)*slope);
2041 case 7: *buffer++ = (TBuffer)((*source++)*slope);
2042 case 6: *buffer++ = (TBuffer)((*source++)*slope);
2043 case 5: *buffer++ = (TBuffer)((*source++)*slope);
2044 case 4: *buffer++ = (TBuffer)((*source++)*slope);
2045 case 3: *buffer++ = (TBuffer)((*source++)*slope);
2046 case 2: *buffer++ = (TBuffer)((*source++)*slope);
2047 case 1: *buffer++ = (TBuffer)((*source++)*slope);
2053 // Duff's device. Instead of this code:
2055 // for(unsigned int i=0; i<size; i++)
2057 // buffer[i] = (TBuffer)(source[i]);
2060 // use Duff's device which exploits "fall through"
2061 register size_t n = (size + 7) / 8;
2064 case 0: do { *buffer++ = (TBuffer)(*source++);
2065 case 7: *buffer++ = (TBuffer)(*source++);
2066 case 6: *buffer++ = (TBuffer)(*source++);
2067 case 5: *buffer++ = (TBuffer)(*source++);
2068 case 4: *buffer++ = (TBuffer)(*source++);
2069 case 3: *buffer++ = (TBuffer)(*source++);
2070 case 2: *buffer++ = (TBuffer)(*source++);
2071 case 1: *buffer++ = (TBuffer)(*source++);
2080 template<class TSource>
2081 void RescaleFunction(ImageIOBase::IOComponentType bufferType,
2082 void* buffer, TSource *source,
2083 double slope, double intercept, size_t size)
2087 case ImageIOBase::UCHAR:
2088 RescaleFunction( (unsigned char *)buffer, source, slope, intercept, size);
2090 case ImageIOBase::CHAR:
2091 RescaleFunction( (char *)buffer, source, slope, intercept, size);
2093 case ImageIOBase::USHORT:
2094 RescaleFunction( (unsigned short *)buffer, source, slope, intercept,size);
2096 case ImageIOBase::SHORT:
2097 RescaleFunction( (short *)buffer, source, slope, intercept, size);
2099 case ImageIOBase::UINT:
2100 RescaleFunction( (unsigned int *)buffer, source, slope, intercept, size);
2102 case ImageIOBase::INT:
2103 RescaleFunction( (int *)buffer, source, slope, intercept, size);
2105 case ImageIOBase::FLOAT:
2106 RescaleFunction( (float *)buffer, source, slope, intercept, size);
2108 case ImageIOBase::DOUBLE:
2109 RescaleFunction( (double *)buffer, source, slope, intercept, size);
2112 ::itk::OStringStream message;
2113 message << "itk::ERROR: GDCMImageIO: Unknown component type : " << bufferType;
2114 ::itk::ExceptionObject e(__FILE__, __LINE__, message.str().c_str(),ITK_LOCATION);