1 /*=========================================================================
4 Module: $RCSfile: gdcmFileHelper.cxx,v $
7 Date: $Date: 2006/02/08 10:13:55 $
8 Version: $Revision: 1.91 $
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"
30 #include "gdcmPixelReadConvert.h"
31 #include "gdcmPixelWriteConvert.h"
32 #include "gdcmDocEntryArchive.h"
33 #include "gdcmDictSet.h"
34 #include "gdcmOrientation.h"
36 #if defined(__BORLANDC__)
37 #include <mem.h> // for memset
43 // ----------------------------- WARNING -------------------------
45 These lines will be moved to the document-to-be 'User's Guide'
47 // To read an image, user needs a gdcm::File
48 gdcm::File *f = new gdcm::File(fileName);
50 // user may also decide he doesn't want to load some parts of the header
51 gdcm::File *f = new gdcm::File();
52 f->SetFileName(fileName);
53 f->SetLoadMode(LD_NOSEQ); // or
54 f->SetLoadMode(LD_NOSHADOW); // or
55 f->SetLoadMode(LD_NOSEQ | LD_NOSHADOW); // or
56 f->SetLoadMode(LD_NOSHADOWSEQ);
59 // user can now check some values
60 std::string v = f->GetEntryValue(groupNb,ElementNb);
62 // to get the pixels, user needs a gdcm::FileHelper
63 gdcm::FileHelper *fh = new gdcm::FileHelper(f);
64 // user may ask not to convert Palette to RGB
65 uint8_t *pixels = fh->GetImageDataRaw();
66 int imageLength = fh->GetImageDataRawSize();
67 // He can now use the pixels, create a new image, ...
68 uint8_t *userPixels = ...
70 To re-write the image, user re-uses the gdcm::FileHelper
72 fh->SetImageData( userPixels, userPixelsLength);
73 fh->SetTypeToRaw(); // Even if it was possible to convert Palette to RGB
76 fh->SetWriteTypeToDcmExpl(); // he wants Explicit Value Representation
77 // Little Endian is the default
78 // no other value is allowed
79 (-->SetWriteType(ExplicitVR);)
80 -->WriteType = ExplicitVR;
81 fh->Write(newFileName); // overwrites the file, if any
84 fh->WriteDcmExplVR(newFileName);
87 // ----------------------------- WARNING -------------------------
89 These lines will be moved to the document-to-be 'Developer's Guide'
91 WriteMode : WMODE_RAW / WMODE_RGB
92 WriteType : ImplicitVR, ExplicitVR, ACR, ACR_LIBIDO
94 fh1->Write(newFileName);
95 SetWriteFileTypeToImplicitVR() / SetWriteFileTypeToExplicitVR();
96 (modifies TransferSyntax)
97 SetWriteToRaw(); / SetWriteToRGB();
98 (modifies, when necessary : photochromatic interpretation,
99 samples per pixel, Planar configuration,
100 bits allocated, bits stored, high bit -ACR 24 bits-
101 Pixels element VR, pushes out the LUT )
102 CheckWriteIntegrity();
103 (checks user given pixels length)
104 FileInternal->Write(fileName,WriteType)
105 fp = opens file(fileName);
106 ComputeGroup0002Length( );
108 RemoveEntry(palettes, etc)
109 Document::WriteContent(fp, writetype);
111 (moves back to the File all the archived elements)
112 RestoreWriteFileType();
113 (pushes back group 0002, with TransferSyntax)
121 typedef std::map<uint16_t, int> GroupHT; // Hash Table
122 //-------------------------------------------------------------------------
123 // Constructor / Destructor
125 * \brief Constructor dedicated to deal with the *pixels* area of a ACR/DICOMV3
126 * file (gdcm::File only deals with the ... header)
127 * Opens (in read only and when possible) an existing file and checks
128 * for DICOM compliance. Returns NULL on failure.
129 * It will be up to the user to load the pixels into memory
130 * ( GetImageDataSize() + GetImageData() methods)
131 * \note the in-memory representation of all available tags found in
132 * the DICOM header is post-poned to first header information access.
133 * This avoid a double parsing of public part of the header when
134 * one sets an a posteriori shadow dictionary (efficiency can be
135 * seen as a side effect).
137 FileHelper::FileHelper( )
139 FileInternal = File::New( );
144 * \brief Constructor dedicated to deal with the *pixels* area of a ACR/DICOMV3
145 * file (File only deals with the ... header)
146 * Opens (in read only and when possible) an existing file and checks
147 * for DICOM compliance. Returns NULL on failure.
148 * It will be up to the user to load the pixels into memory
149 * ( GetImageDataSize() + GetImageData() methods)
150 * \note the in-memory representation of all available tags found in
151 * the DICOM header is post-poned to first header information access.
152 * This avoid a double parsing of public part of the header when
153 * user sets an a posteriori shadow dictionary (efficiency can be
154 * seen as a side effect).
155 * @param header already built Header
157 FileHelper::FileHelper(File *header)
159 gdcmAssertMacro(header);
161 FileInternal = header;
162 FileInternal->Register();
164 if ( FileInternal->IsReadable() )
166 PixelReadConverter->GrabInformationsFromFile( FileInternal, this );
171 * \brief canonical destructor
172 * \note If the header (gdcm::File) was created by the FileHelper constructor,
173 * it is destroyed by the FileHelper
175 FileHelper::~FileHelper()
177 if ( PixelReadConverter )
179 delete PixelReadConverter;
181 if ( PixelWriteConverter )
183 delete PixelWriteConverter;
190 FileInternal->Unregister();
193 //-----------------------------------------------------------------------------
197 * \brief Sets the LoadMode of the internal gdcm::File as a boolean string.
198 * NO_SEQ, NO_SHADOW, NO_SHADOWSEQ ... (nothing more, right now)
199 * WARNING : before using NO_SHADOW, be sure *all* your files
200 * contain accurate values in the 0x0000 element (if any)
201 * of *each* Shadow Group. The parser will fail if the size is wrong !
202 * @param loadMode Load mode to be used
204 void FileHelper::SetLoadMode(int loadMode)
206 GetFile()->SetLoadMode( loadMode );
209 * \brief Sets the LoadMode of the internal gdcm::File
210 * @param fileName name of the file to be open
212 void FileHelper::SetFileName(std::string const &fileName)
214 FileInternal->SetFileName( fileName );
219 * @return false if file cannot be open or no swap info was found,
220 * or no tag was found.
222 bool FileHelper::Load()
224 if ( !FileInternal->Load() )
227 PixelReadConverter->GrabInformationsFromFile( FileInternal, this );
232 * \brief Accesses an existing DataEntry through it's (group, element)
233 * and modifies it's content with the given value.
234 * @param content new value (string) to substitute with
235 * @param group group number of the Dicom Element to modify
236 * @param elem element number of the Dicom Element to modify
237 * \return false if DataEntry not found
239 bool FileHelper::SetEntryString(std::string const &content,
240 uint16_t group, uint16_t elem)
242 return FileInternal->SetEntryString(content, group, elem);
247 * \brief Accesses an existing DataEntry through it's (group, element)
248 * and modifies it's content with the given value.
249 * @param content new value (void* -> uint8_t*) to substitute with
250 * @param lgth new value length
251 * @param group group number of the Dicom Element to modify
252 * @param elem element number of the Dicom Element to modify
253 * \return false if DataEntry not found
255 bool FileHelper::SetEntryBinArea(uint8_t *content, int lgth,
256 uint16_t group, uint16_t elem)
258 return FileInternal->SetEntryBinArea(content, lgth, group, elem);
262 * \brief Modifies the value of a given DataEntry when it exists.
263 * Creates it with the given value when unexistant.
264 * @param content (string) value to be set
265 * @param group Group number of the Entry
266 * @param elem Element number of the Entry
267 * \return pointer to the modified/created DataEntry (NULL when creation
270 DataEntry *FileHelper::InsertEntryString(std::string const &content,
271 uint16_t group, uint16_t elem)
273 return FileInternal->InsertEntryString(content, group, elem);
277 * \brief Modifies the value of a given DataEntry when it exists.
278 * Creates it with the given value when unexistant.
279 * A copy of the binArea is made to be kept in the Document.
280 * @param binArea (binary)value to be set
281 * @param lgth new value length
282 * @param group Group number of the Entry
283 * @param elem Element number of the Entry
284 * \return pointer to the modified/created DataEntry (NULL when creation
287 DataEntry *FileHelper::InsertEntryBinArea(uint8_t *binArea, int lgth,
288 uint16_t group, uint16_t elem)
290 return FileInternal->InsertEntryBinArea(binArea, lgth, group, elem);
294 * \brief Adds an empty SeqEntry
295 * (remove any existing entry with same group,elem)
296 * @param group Group number of the Entry
297 * @param elem Element number of the Entry
298 * \return pointer to the created SeqEntry (NULL when creation
301 SeqEntry *FileHelper::InsertSeqEntry(uint16_t group, uint16_t elem)
303 return FileInternal->InsertSeqEntry(group, elem);
307 * \brief Get the size of the image data
308 * If the image can be RGB (with a lut or by default), the size
309 * corresponds to the RGB image
310 * (use GetImageDataRawSize if you want to be sure to get *only*
311 * the size of the pixels)
312 * @return The image size
314 size_t FileHelper::GetImageDataSize()
316 if ( PixelWriteConverter->GetUserData() )
318 return PixelWriteConverter->GetUserDataSize();
320 return PixelReadConverter->GetRGBSize();
324 * \brief Get the size of the image data.
325 * If the image could be converted to RGB using a LUT,
326 * this transformation is not taken into account by GetImageDataRawSize
327 * (use GetImageDataSize if you wish)
328 * @return The raw image size
330 size_t FileHelper::GetImageDataRawSize()
332 if ( PixelWriteConverter->GetUserData() )
334 return PixelWriteConverter->GetUserDataSize();
336 return PixelReadConverter->GetRawSize();
340 * \brief brings pixels into memory :
341 * - Allocates necessary memory,
342 * - Reads the pixels from disk (uncompress if necessary),
343 * - Transforms YBR pixels, if any, into RGB pixels,
344 * - Transforms 3 planes R, G, B, if any, into a single RGB Plane
345 * - Transforms single Grey plane + 3 Palettes into a RGB Plane
346 * - Copies the pixel data (image[s]/volume[s]) to newly allocated zone.
347 * @return Pointer to newly allocated pixel data.
348 * (uint8_t is just for prototyping. feel free to cast)
349 * NULL if alloc fails
351 uint8_t *FileHelper::GetImageData()
353 if ( PixelWriteConverter->GetUserData() )
355 return PixelWriteConverter->GetUserData();
360 // If the decompression failed nothing can be done.
364 if ( FileInternal->HasLUT() && PixelReadConverter->BuildRGBImage() )
366 return PixelReadConverter->GetRGB();
370 // When no LUT or LUT conversion fails, return the Raw
371 return PixelReadConverter->GetRaw();
376 * \brief brings pixels into memory :
377 * - Allocates necessary memory,
378 * - Transforms YBR pixels (if any) into RGB pixels
379 * - Transforms 3 planes R, G, B (if any) into a single RGB Plane
380 * - Copies the pixel data (image[s]/volume[s]) to newly allocated zone.
381 * - DOES NOT transform Grey plane + 3 Palettes into a RGB Plane
382 * @return Pointer to newly allocated pixel data.
383 * (uint8_t is just for prototyping. feel free to cast)
384 * NULL if alloc fails
386 uint8_t *FileHelper::GetImageDataRaw ()
391 #ifndef GDCM_LEGACY_REMOVE
393 * \ brief Useless function, since PixelReadConverter forces us
394 * copy the Pixels anyway.
395 * Reads the pixels from disk (uncompress if necessary),
396 * Transforms YBR pixels, if any, into RGB pixels
397 * Transforms 3 planes R, G, B, if any, into a single RGB Plane
398 * Transforms single Grey plane + 3 Palettes into a RGB Plane
399 * Copies at most MaxSize bytes of pixel data to caller allocated
401 * \ warning This function allows people that want to build a volume
402 * from an image stack *not to* have, first to get the image pixels,
403 * and then move them to the volume area.
404 * It's absolutely useless for any VTK user since vtk chooses
405 * to invert the lines of an image, that is the last line comes first
406 * (for some axis related reasons?). Hence he will have
407 * to load the image line by line, starting from the end.
408 * VTK users have to call GetImageData
410 * @ param destination Address (in caller's memory space) at which the
411 * pixel data should be copied
412 * @ param maxSize Maximum number of bytes to be copied. When MaxSize
413 * is not sufficient to hold the pixel data the copy is not
414 * executed (i.e. no partial copy).
415 * @ return On success, the number of bytes actually copied. Zero on
416 * failure e.g. MaxSize is lower than necessary.
418 size_t FileHelper::GetImageDataIntoVector (void *destination, size_t maxSize)
422 // If the decompression failed nothing can be done.
426 if ( FileInternal->HasLUT() && PixelReadConverter->BuildRGBImage() )
428 if ( PixelReadConverter->GetRGBSize() > maxSize )
430 gdcmWarningMacro( "Pixel data bigger than caller's expected MaxSize");
434 (void*)PixelReadConverter->GetRGB(),
435 PixelReadConverter->GetRGBSize() );
436 return PixelReadConverter->GetRGBSize();
439 // Either no LUT conversion necessary or LUT conversion failed
440 if ( PixelReadConverter->GetRawSize() > maxSize )
442 gdcmWarningMacro( "Pixel data bigger than caller's expected MaxSize");
446 (void *)PixelReadConverter->GetRaw(),
447 PixelReadConverter->GetRawSize() );
448 return PixelReadConverter->GetRawSize();
453 * \brief Points the internal pointer to the callers inData
454 * image representation, BUT WITHOUT COPYING THE DATA.
455 * 'image' Pixels are presented as C-like 2D arrays : line per line.
456 * 'volume'Pixels are presented as C-like 3D arrays : plane per plane
457 * \warning Since the pixels are not copied, it is the caller's responsability
458 * not to deallocate its data before gdcm uses them (e.g. with
459 * the Write() method )
460 * @param inData user supplied pixel area (uint8_t* is just for the compiler.
461 * user is allowed to pass any kind of pixelsn since the size is
463 * @param expectedSize total image size, *in Bytes*
465 void FileHelper::SetImageData(uint8_t *inData, size_t expectedSize)
467 SetUserData(inData, expectedSize);
471 * \brief Set the image data defined by the user
472 * \warning When writting the file, this data are get as default data to write
473 * @param inData user supplied pixel area (uint8_t* is just for the compiler.
474 * user is allowed to pass any kind of pixels since the size is
476 * @param expectedSize total image size, *in Bytes*
478 void FileHelper::SetUserData(uint8_t *inData, size_t expectedSize)
480 PixelWriteConverter->SetUserData(inData, expectedSize);
484 * \brief Get the image data defined by the user
485 * \warning When writting the file, this data are get as default data to write
487 uint8_t *FileHelper::GetUserData()
489 return PixelWriteConverter->GetUserData();
493 * \brief Get the image data size defined by the user
494 * \warning When writting the file, this data are get as default data to write
496 size_t FileHelper::GetUserDataSize()
498 return PixelWriteConverter->GetUserDataSize();
502 * \brief Get the image data from the file.
503 * If a LUT is found, the data are expanded to be RGB
505 uint8_t *FileHelper::GetRGBData()
507 return PixelReadConverter->GetRGB();
511 * \brief Get the image data size from the file.
512 * If a LUT is found, the data are expanded to be RGB
514 size_t FileHelper::GetRGBDataSize()
516 return PixelReadConverter->GetRGBSize();
520 * \brief Get the image data from the file.
521 * Even when a LUT is found, the data are not expanded to RGB!
523 uint8_t *FileHelper::GetRawData()
525 return PixelReadConverter->GetRaw();
529 * \brief Get the image data size from the file.
530 * Even when a LUT is found, the data are not expanded to RGB!
532 size_t FileHelper::GetRawDataSize()
534 return PixelReadConverter->GetRawSize();
538 * \brief Access to the underlying \ref PixelReadConverter RGBA LUT
540 uint8_t* FileHelper::GetLutRGBA()
542 if ( PixelReadConverter->GetLutRGBA() ==0 )
543 PixelReadConverter->BuildLUTRGBA();
544 return PixelReadConverter->GetLutRGBA();
548 * \brief Access to the underlying \ref PixelReadConverter RGBA LUT Item Number
550 int FileHelper::GetLutItemNumber()
552 return PixelReadConverter->GetLutItemNumber();
556 * \brief Access to the underlying \ref PixelReadConverter RGBA LUT Item Size
558 int FileHelper::GetLutItemSize()
560 return PixelReadConverter->GetLutItemSize();
564 * \brief Writes on disk A SINGLE Dicom file
565 * NO test is performed on processor "Endiannity".
566 * It's up to the user to call his Reader properly
567 * @param fileName name of the file to be created
568 * (any already existing file is over written)
569 * @return false if write fails
571 bool FileHelper::WriteRawData(std::string const &fileName)
573 std::ofstream fp1(fileName.c_str(), std::ios::out | std::ios::binary );
576 gdcmWarningMacro( "Fail to open (write) file:" << fileName.c_str());
580 if ( PixelWriteConverter->GetUserData() )
582 fp1.write( (char *)PixelWriteConverter->GetUserData(),
583 PixelWriteConverter->GetUserDataSize() );
585 else if ( PixelReadConverter->GetRGB() )
587 fp1.write( (char *)PixelReadConverter->GetRGB(),
588 PixelReadConverter->GetRGBSize());
590 else if ( PixelReadConverter->GetRaw() )
592 fp1.write( (char *)PixelReadConverter->GetRaw(),
593 PixelReadConverter->GetRawSize());
597 gdcmErrorMacro( "Nothing written." );
606 * \brief Writes on disk A SINGLE Dicom file,
607 * using the Implicit Value Representation convention
608 * NO test is performed on processor "Endianity".
609 * @param fileName name of the file to be created
610 * (any already existing file is overwritten)
611 * @return false if write fails
614 bool FileHelper::WriteDcmImplVR (std::string const &fileName)
616 SetWriteTypeToDcmImplVR();
617 return Write(fileName);
621 * \brief Writes on disk A SINGLE Dicom file,
622 * using the Explicit Value Representation convention
623 * NO test is performed on processor "Endiannity".
624 * @param fileName name of the file to be created
625 * (any already existing file is overwritten)
626 * @return false if write fails
629 bool FileHelper::WriteDcmExplVR (std::string const &fileName)
631 SetWriteTypeToDcmExplVR();
632 return Write(fileName);
636 * \brief Writes on disk A SINGLE Dicom file,
637 * using the ACR-NEMA convention
638 * NO test is performed on processor "Endiannity".
639 * (a l'attention des logiciels cliniques
640 * qui ne prennent en entrée QUE des images ACR ...
641 * \warning if a DICOM_V3 header is supplied,
642 * groups < 0x0008 and shadow groups are ignored
643 * \warning NO TEST is performed on processor "Endiannity".
644 * @param fileName name of the file to be created
645 * (any already existing file is overwritten)
646 * @return false if write fails
649 bool FileHelper::WriteAcr (std::string const &fileName)
652 return Write(fileName);
656 * \brief Writes on disk A SINGLE Dicom file,
657 * @param fileName name of the file to be created
658 * (any already existing file is overwritten)
659 * @return false if write fails
661 bool FileHelper::Write(std::string const &fileName)
666 SetWriteFileTypeToImplicitVR();
668 case Unknown: // should never happen; ExplicitVR is the default value
670 SetWriteFileTypeToExplicitVR();
674 // NOTHING is done here just for LibIDO.
675 // Just to avoid further trouble if user creates a file ex-nihilo,
676 // wants to write it as an ACR-NEMA file,
677 // and forgets to create any Entry belonging to group 0008
679 // We add Recognition Code (RET)
680 if ( ! FileInternal->GetDataEntry(0x0008, 0x0010) )
681 FileInternal->InsertEntryString("ACR-NEMA V1.0 ", 0x0008, 0x0010);
682 SetWriteFileTypeToACR();
683 // SetWriteFileTypeToImplicitVR(); // ACR IS implicit VR !
686 SetWriteFileTypeToJPEG();
687 std::cerr << "Writting as JPEG" << std::endl;
690 CheckMandatoryElements();
692 // --------------------------------------------------------------
693 // Special Patch to allow gdcm to re-write ACR-LibIDO formated images
695 // if recognition code tells us we dealt with a LibIDO image
696 // we reproduce on disk the switch between lineNumber and columnNumber
697 // just before writting ...
698 /// \todo the best trick would be *change* the recognition code
699 /// but pb expected if user deals with, e.g. COMPLEX images
701 if ( WriteType == ACR_LIBIDO )
707 SetWriteToNoLibido();
709 // ----------------- End of Special Patch ----------------
714 SetWriteToRaw(); // modifies and pushes to the archive, when necessary
717 SetWriteToRGB(); // modifies and pushes to the archive, when necessary
721 bool check = CheckWriteIntegrity(); // verifies length
722 if (WriteType == JPEG ) check = true;
725 check = FileInternal->Write(fileName,WriteType);
729 RestoreWriteFileType();
730 RestoreWriteMandatory();
732 // --------------------------------------------------------------
733 // Special Patch to allow gdcm to re-write ACR-LibIDO formated images
735 // ...and we restore the header to be Dicom Compliant again
736 // just after writting
737 RestoreWriteOfLibido();
738 // ----------------- End of Special Patch ----------------
743 //-----------------------------------------------------------------------------
746 * \brief Checks the write integrity
748 * The tests made are :
749 * - verify the size of the image to write with the possible write
750 * when the user set an image data
751 * @return true if check is successfull
753 bool FileHelper::CheckWriteIntegrity()
755 if ( PixelWriteConverter->GetUserData() )
757 int numberBitsAllocated = FileInternal->GetBitsAllocated();
758 if ( numberBitsAllocated == 0 || numberBitsAllocated == 12 )
760 gdcmWarningMacro( "numberBitsAllocated changed from "
761 << numberBitsAllocated << " to 16 "
762 << " for consistency purpose" );
763 numberBitsAllocated = 16;
766 size_t decSize = FileInternal->GetXSize()
767 * FileInternal->GetYSize()
768 * FileInternal->GetZSize()
769 * FileInternal->GetSamplesPerPixel()
770 * ( numberBitsAllocated / 8 );
771 size_t rgbSize = decSize;
772 if ( FileInternal->HasLUT() )
773 rgbSize = decSize * 3;
778 if ( decSize!=PixelWriteConverter->GetUserDataSize() )
780 gdcmWarningMacro( "Data size (Raw) is incorrect. Should be "
781 << decSize << " / Found :"
782 << PixelWriteConverter->GetUserDataSize() );
787 if ( rgbSize!=PixelWriteConverter->GetUserDataSize() )
789 gdcmWarningMacro( "Data size (RGB) is incorrect. Should be "
790 << decSize << " / Found "
791 << PixelWriteConverter->GetUserDataSize() );
802 * \brief Updates the File to write RAW data (as opposed to RGB data)
803 * (modifies, when necessary, photochromatic interpretation,
804 * bits allocated, Pixels element VR)
806 void FileHelper::SetWriteToRaw()
808 if ( FileInternal->GetNumberOfScalarComponents() == 3
809 && !FileInternal->HasLUT() )
815 DataEntry *photInt = CopyDataEntry(0x0028,0x0004);
816 if (FileInternal->HasLUT() )
818 photInt->SetString("PALETTE COLOR ");
822 photInt->SetString("MONOCHROME2 ");
825 PixelWriteConverter->SetReadData(PixelReadConverter->GetRaw(),
826 PixelReadConverter->GetRawSize());
828 std::string vr = "OB";
829 if ( FileInternal->GetBitsAllocated()>8 )
831 if ( FileInternal->GetBitsAllocated()==24 ) // For RGB ACR files
834 CopyDataEntry(GetFile()->GetGrPixel(),GetFile()->GetNumPixel(),vr);
835 pixel->SetFlag(DataEntry::FLAG_PIXELDATA);
836 pixel->SetBinArea(PixelWriteConverter->GetData(),false);
837 pixel->SetLength(PixelWriteConverter->GetDataSize());
839 Archive->Push(photInt);
840 Archive->Push(pixel);
848 * \brief Updates the File to write RGB data (as opposed to RAW data)
849 * (modifies, when necessary, photochromatic interpretation,
850 * samples per pixel, Planar configuration,
851 * bits allocated, bits stored, high bit -ACR 24 bits-
852 * Pixels element VR, pushes out the LUT, )
854 void FileHelper::SetWriteToRGB()
856 if ( FileInternal->GetNumberOfScalarComponents()==3 )
858 PixelReadConverter->BuildRGBImage();
860 DataEntry *spp = CopyDataEntry(0x0028,0x0002);
861 spp->SetString("3 ");
863 DataEntry *planConfig = CopyDataEntry(0x0028,0x0006);
864 planConfig->SetString("0 ");
866 DataEntry *photInt = CopyDataEntry(0x0028,0x0004);
867 photInt->SetString("RGB ");
869 if ( PixelReadConverter->GetRGB() )
871 PixelWriteConverter->SetReadData(PixelReadConverter->GetRGB(),
872 PixelReadConverter->GetRGBSize());
876 PixelWriteConverter->SetReadData(PixelReadConverter->GetRaw(),
877 PixelReadConverter->GetRawSize());
880 std::string vr = "OB";
881 if ( FileInternal->GetBitsAllocated()>8 )
883 if ( FileInternal->GetBitsAllocated()==24 ) // For RGB ACR files
886 CopyDataEntry(GetFile()->GetGrPixel(),GetFile()->GetNumPixel(),vr);
887 pixel->SetFlag(DataEntry::FLAG_PIXELDATA);
888 pixel->SetBinArea(PixelWriteConverter->GetData(),false);
889 pixel->SetLength(PixelWriteConverter->GetDataSize());
892 Archive->Push(planConfig);
893 Archive->Push(photInt);
894 Archive->Push(pixel);
897 planConfig->Delete();
902 Archive->Push(0x0028,0x1101);
903 Archive->Push(0x0028,0x1102);
904 Archive->Push(0x0028,0x1103);
905 Archive->Push(0x0028,0x1201);
906 Archive->Push(0x0028,0x1202);
907 Archive->Push(0x0028,0x1203);
909 // push out Palette Color Lookup Table UID, if any
910 Archive->Push(0x0028,0x1199);
912 // For old '24 Bits' ACR-NEMA
913 // Thus, we have a RGB image and the bits allocated = 24 and
914 // samples per pixels = 1 (in the read file)
915 if ( FileInternal->GetBitsAllocated()==24 )
917 DataEntry *bitsAlloc = CopyDataEntry(0x0028,0x0100);
918 bitsAlloc->SetString("8 ");
920 DataEntry *bitsStored = CopyDataEntry(0x0028,0x0101);
921 bitsStored->SetString("8 ");
923 DataEntry *highBit = CopyDataEntry(0x0028,0x0102);
924 highBit->SetString("7 ");
926 Archive->Push(bitsAlloc);
927 Archive->Push(bitsStored);
928 Archive->Push(highBit);
931 bitsStored->Delete();
942 * \brief Restore the File write mode
944 void FileHelper::RestoreWrite()
946 Archive->Restore(0x0028,0x0002);
947 Archive->Restore(0x0028,0x0004);
948 Archive->Restore(0x0028,0x0006);
949 Archive->Restore(GetFile()->GetGrPixel(),GetFile()->GetNumPixel());
951 // For old ACR-NEMA (24 bits problem)
952 Archive->Restore(0x0028,0x0100);
953 Archive->Restore(0x0028,0x0101);
954 Archive->Restore(0x0028,0x0102);
957 Archive->Restore(0x0028,0x1101);
958 Archive->Restore(0x0028,0x1102);
959 Archive->Restore(0x0028,0x1103);
960 Archive->Restore(0x0028,0x1201);
961 Archive->Restore(0x0028,0x1202);
962 Archive->Restore(0x0028,0x1203);
964 // For the Palette Color Lookup Table UID
965 Archive->Restore(0x0028,0x1203);
968 // group 0002 may be pushed out for ACR-NEMA writting purposes
969 Archive->Restore(0x0002,0x0000);
970 Archive->Restore(0x0002,0x0001);
971 Archive->Restore(0x0002,0x0002);
972 Archive->Restore(0x0002,0x0003);
973 Archive->Restore(0x0002,0x0010);
974 Archive->Restore(0x0002,0x0012);
975 Archive->Restore(0x0002,0x0013);
976 Archive->Restore(0x0002,0x0016);
977 Archive->Restore(0x0002,0x0100);
978 Archive->Restore(0x0002,0x0102);
982 * \brief Pushes out the whole group 0002
983 * FIXME : better, set a flag to tell the writer not to write it ...
984 * FIXME : method should probably have an other name !
985 * SetWriteFileTypeToACR is NOT opposed to
986 * SetWriteFileTypeToExplicitVR and SetWriteFileTypeToImplicitVR
988 void FileHelper::SetWriteFileTypeToACR()
990 Archive->Push(0x0002,0x0000);
991 Archive->Push(0x0002,0x0001);
992 Archive->Push(0x0002,0x0002);
993 Archive->Push(0x0002,0x0003);
994 Archive->Push(0x0002,0x0010);
995 Archive->Push(0x0002,0x0012);
996 Archive->Push(0x0002,0x0013);
997 Archive->Push(0x0002,0x0016);
998 Archive->Push(0x0002,0x0100);
999 Archive->Push(0x0002,0x0102);
1003 * \brief Sets in the File the TransferSyntax to 'Explicit VR Little Endian"
1005 void FileHelper::SetWriteFileTypeToJPEG()
1007 std::string ts = Util::DicomString(
1008 Global::GetTS()->GetSpecialTransferSyntax(TS::JPEGBaselineProcess1) );
1010 DataEntry *tss = CopyDataEntry(0x0002,0x0010);
1017 void FileHelper::SetWriteFileTypeToExplicitVR()
1019 std::string ts = Util::DicomString(
1020 Global::GetTS()->GetSpecialTransferSyntax(TS::ExplicitVRLittleEndian) );
1022 DataEntry *tss = CopyDataEntry(0x0002,0x0010);
1030 * \brief Sets in the File the TransferSyntax to 'Implicit VR Little Endian"
1032 void FileHelper::SetWriteFileTypeToImplicitVR()
1034 std::string ts = Util::DicomString(
1035 Global::GetTS()->GetSpecialTransferSyntax(TS::ImplicitVRLittleEndian) );
1037 DataEntry *tss = CopyDataEntry(0x0002,0x0010);
1046 * \brief Restore in the File the initial group 0002
1048 void FileHelper::RestoreWriteFileType()
1053 * \brief Set the Write not to Libido format
1055 void FileHelper::SetWriteToLibido()
1057 DataEntry *oldRow = FileInternal->GetDataEntry(0x0028, 0x0010);
1058 DataEntry *oldCol = FileInternal->GetDataEntry(0x0028, 0x0011);
1060 if ( oldRow && oldCol )
1062 std::string rows, columns;
1064 DataEntry *newRow=DataEntry::New(oldRow->GetDictEntry());
1065 DataEntry *newCol=DataEntry::New(oldCol->GetDictEntry());
1067 newRow->Copy(oldCol);
1068 newCol->Copy(oldRow);
1070 newRow->SetString(oldCol->GetString());
1071 newCol->SetString(oldRow->GetString());
1073 Archive->Push(newRow);
1074 Archive->Push(newCol);
1080 DataEntry *libidoCode = CopyDataEntry(0x0008,0x0010);
1081 libidoCode->SetString("ACRNEMA_LIBIDO_1.1");
1082 Archive->Push(libidoCode);
1083 libidoCode->Delete();
1087 * \brief Set the Write not to No Libido format
1089 void FileHelper::SetWriteToNoLibido()
1091 DataEntry *recCode = FileInternal->GetDataEntry(0x0008,0x0010);
1094 if ( recCode->GetString() == "ACRNEMA_LIBIDO_1.1" )
1096 DataEntry *libidoCode = CopyDataEntry(0x0008,0x0010);
1097 libidoCode->SetString("");
1098 Archive->Push(libidoCode);
1099 libidoCode->Delete();
1105 * \brief Restore the Write format
1107 void FileHelper::RestoreWriteOfLibido()
1109 Archive->Restore(0x0028,0x0010);
1110 Archive->Restore(0x0028,0x0011);
1111 Archive->Restore(0x0008,0x0010);
1113 // Restore 'LibIDO-special' entries, if any
1114 Archive->Restore(0x0028,0x0015);
1115 Archive->Restore(0x0028,0x0016);
1116 Archive->Restore(0x0028,0x0017);
1117 Archive->Restore(0x0028,0x00199);
1121 * \brief Duplicates a DataEntry or creates it.
1122 * @param group Group number of the Entry
1123 * @param elem Element number of the Entry
1124 * @param vr Value Representation of the Entry
1125 * FIXME : what is it used for?
1126 * \return pointer to the new Bin Entry (NULL when creation failed).
1128 DataEntry *FileHelper::CopyDataEntry(uint16_t group, uint16_t elem,
1131 DocEntry *oldE = FileInternal->GetDocEntry(group, elem);
1134 if ( oldE && vr != GDCM_VRUNKNOWN )
1135 if ( oldE->GetVR() != vr )
1140 newE = DataEntry::New(oldE->GetDictEntry());
1145 newE = GetFile()->NewDataEntry(group, elem, vr);
1152 * \brief This method is called automatically, just before writting
1153 * in order to produce a 'True Dicom V3' image.
1155 * We cannot know *how* the user made the File :
1156 * (reading an old ACR-NEMA file or a not very clean DICOM file ...)
1157 * Just before writting :
1158 * - we check the Entries
1159 * - we create the mandatory entries if they are missing
1160 * - we modify the values if necessary
1161 * - we push the sensitive entries to the Archive
1162 * The writing process will restore the entries as they where before
1163 * entering FileHelper::CheckMandatoryElements, so the user will always
1164 * see the entries just as they were before he decided to write.
1167 * - Entries whose type is 1 are mandatory, with a mandatory value
1168 * - Entries whose type is 1c are mandatory-inside-a-Sequence,
1169 * with a mandatory value
1170 * - Entries whose type is 2 are mandatory, with an optional value
1171 * - Entries whose type is 2c are mandatory-inside-a-Sequence,
1172 * with an optional value
1173 * - Entries whose type is 3 are optional
1176 * - warn the user if we had to add some entries :
1177 * even if a mandatory entry is missing, we add it, with a default value
1178 * (we don't want to give up the writting process if user forgot to
1179 * specify Lena's Patient ID, for instance ...)
1180 * - read the whole PS 3.3 Part of DICOM (890 pages)
1181 * and write a *full* checker (probably one method per Modality ...)
1182 * Any contribution is welcome.
1183 * - write a user callable full checker, to allow post reading
1184 * and/or pre writting image consistency check.
1187 /* -------------------------------------------------------------------------------------
1188 To be moved to User's guide / WIKI ?
1190 We have to deal with 4 *very* different cases :
1191 -1) user created ex nihilo his own image and wants to write it as a Dicom image.
1192 -2) user modified the pixels of an existing image.
1193 -3) user created a new image, using existing images (eg MIP, MPR, cartography image)
1194 -4) user anonymized an image without processing the pixels.
1196 gdcm::FileHelper::CheckMandatoryElements() deals automatically with these cases.
1199 0008 0012 Instance Creation Date
1200 0008 0013 Instance Creation Time
1201 0008 0018 SOP Instance UID
1202 are *always* created with the current values; user has *no* possible intervention on
1205 'Serie Instance UID'(0x0020,0x000e)
1206 'Study Instance UID'(0x0020,0x000d) are kept as is if already exist,
1207 created if it doesn't.
1208 The user is allowed to create his own Series/Studies,
1209 keeping the same 'Serie Instance UID' / 'Study Instance UID' for various images
1211 The user shouldn't add any image to a 'Manufacturer Serie'
1212 but there is no way no to allowed him to do that
1214 None of the 'shadow elements' are droped out.
1218 'Modality' (0x0008,0x0060) is defaulted to "OT" (other) if missing.
1219 'Conversion Type (0x0008,0x0064) is forced to 'SYN' (Synthetic Image).
1220 'Study Date', 'Study Time' are defaulted to current Date and Time.
1223 'Media Storage SOP Class UID' (0x0002,0x0002)
1224 'SOP Class UID' (0x0008,0x0016) are set to
1225 [Secondary Capture Image Storage]
1226 'Image Type' (0x0008,0x0008) is forced to "DERIVED\PRIMARY"
1227 Conversion Type (0x0008,0x0064) is forced to 'SYN' (Synthetic Image)
1230 If 'SOP Class UID' exists in the native image ('true DICOM' image)
1231 we create the 'Source Image Sequence' SeqEntry (0x0008, 0x2112)
1232 --> 'Referenced SOP Class UID' (0x0008, 0x1150)
1233 whose value is the original 'SOP Class UID'
1234 --> 'Referenced SOP Instance UID' (0x0008, 0x1155)
1235 whose value is the original 'SOP Class UID'
1237 3) TODO : find a trick to allow user to pass to the writter the list of the Dicom images
1238 or the Series, (or the Study ?) he used to created his image
1239 (MIP, MPR, cartography image, ...)
1240 These info should be stored (?)
1241 0008 1110 SQ 1 Referenced Study Sequence
1242 0008 1115 SQ 1 Referenced Series Sequence
1243 0008 1140 SQ 1 Referenced Image Sequence
1245 4) When user *knows* he didn't modified the pixels, he may ask the writer to keep some
1246 informations unchanged :
1247 'Media Storage SOP Class UID' (0x0002,0x0002)
1248 'SOP Class UID' (0x0008,0x0016)
1249 'Image Type' (0x0008,0x0008)
1250 'Conversion Type' (0x0008,0x0064)
1251 He has to use gdcm::FileHelper::SetKeepMediaStorageSOPClassUID(true)
1252 (probabely name has to be changed)
1255 Bellow follows the full description (hope so !) of the consistency checks performed
1256 by gdcm::FileHelper::CheckMandatoryElements()
1259 -->'Media Storage SOP Class UID' (0x0002,0x0002)
1260 -->'SOP Class UID' (0x0008,0x0016) are set to
1261 [Secondary Capture Image Storage]
1262 (Potentialy, the image was modified by user, and post-processed;
1263 it's no longer a 'native' image)
1264 Except if user told he wants to keep MediaStorageSOPClassUID,
1265 when *he* knows he didn't modify the image (e.g. : he just anonymized the file)
1267 --> 'Image Type' (0x0008,0x0008)
1268 is forced to "DERIVED\PRIMARY"
1269 (The written image is no longer an 'ORIGINAL' one)
1270 Except if user told he wants to keep MediaStorageSOPClassUID,
1271 when *he* knows he didn't modify the image (e.g. : he just anonymized the file)
1273 --> Conversion Type (0x0008,0x0064)
1274 is forced to 'SYN' (Synthetic Image)
1275 Except if user told he wants to keep MediaStorageSOPClassUID,
1276 when *he* knows he didn't modify the image (e.g. : he just anonymized the file)
1278 --> 'Modality' (0x0008,0x0060)
1279 is defaulted to "OT" (other) if missing.
1280 (a fully user created image belongs to *no* modality)
1282 --> 'Media Storage SOP Instance UID' (0x0002,0x0003)
1283 --> 'Implementation Class UID' (0x0002,0x0012)
1284 are automatically generated; no user intervention possible
1286 --> 'Serie Instance UID'(0x0020,0x000e)
1287 --> 'Study Instance UID'(0x0020,0x000d) are kept as is if already exist
1288 created if it doesn't.
1289 The user is allowed to create his own Series/Studies,
1290 keeping the same 'Serie Instance UID' / 'Study Instance UID'
1293 The user shouldn't add any image to a 'Manufacturer Serie'
1294 but there is no way no to allowed him to do that
1296 --> If 'SOP Class UID' exists in the native image ('true DICOM' image)
1297 we create the 'Source Image Sequence' SeqEntry (0x0008, 0x2112)
1299 --> 'Referenced SOP Class UID' (0x0008, 0x1150)
1300 whose value is the original 'SOP Class UID'
1301 --> 'Referenced SOP Instance UID' (0x0008, 0x1155)
1302 whose value is the original 'SOP Class UID'
1304 --> Bits Stored, Bits Allocated, Hight Bit Position are checked for consistency
1305 --> Pixel Spacing (0x0028,0x0030) is defaulted to "1.0\1.0"
1306 --> Samples Per Pixel (0x0028,0x0002) is defaulted to 1 (grayscale)
1308 --> Imager Pixel Spacing (0x0018,0x1164) : defaulted to Pixel Spacing value
1310 --> Instance Creation Date, Instance Creation Time are forced to current Date and Time
1312 --> Study Date, Study Time are defaulted to current Date and Time
1313 (they remain unchanged if they exist)
1315 --> Patient Orientation : (0x0020,0x0020), if not present, is deduced from
1316 Image Orientation (Patient) : (0020|0037) or from
1317 Image Orientation (RET) : (0020 0035)
1319 --> Study ID, Series Number, Instance Number, Patient Orientation (Type 2)
1320 are created, with empty value if there are missing.
1322 --> Manufacturer, Institution Name, Patient's Name, (Type 2)
1323 are defaulted with a 'gdcm' value.
1325 --> Patient ID, Patient's Birth Date, Patient's Sex, (Type 2)
1326 --> Referring Physician's Name (Type 2)
1327 are created, with empty value if there are missing.
1329 -------------------------------------------------------------------------------------*/
1331 void FileHelper::CheckMandatoryElements()
1333 std::string sop = Util::CreateUniqueUID();
1335 // just to remember : 'official' 0002 group
1336 if ( WriteType != ACR && WriteType != ACR_LIBIDO )
1338 // Group 000002 (Meta Elements) already pushed out
1340 //0002 0000 UL 1 Meta Group Length
1341 //0002 0001 OB 1 File Meta Information Version
1342 //0002 0002 UI 1 Media Stored SOP Class UID
1343 //0002 0003 UI 1 Media Stored SOP Instance UID
1344 //0002 0010 UI 1 Transfer Syntax UID
1345 //0002 0012 UI 1 Implementation Class UID
1346 //0002 0013 SH 1 Implementation Version Name
1347 //0002 0016 AE 1 Source Application Entity Title
1348 //0002 0100 UI 1 Private Information Creator
1349 //0002 0102 OB 1 Private Information
1351 // Create them if not found
1352 // Always modify the value
1353 // Push the entries to the archive.
1354 CopyMandatoryEntry(0x0002,0x0000,"0");
1356 DataEntry *e_0002_0001 = CopyDataEntry(0x0002,0x0001, "OB");
1357 e_0002_0001->SetBinArea((uint8_t*)Util::GetFileMetaInformationVersion(),
1359 e_0002_0001->SetLength(2);
1360 Archive->Push(e_0002_0001);
1361 e_0002_0001->Delete();
1363 if ( KeepMediaStorageSOPClassUID)
1364 // It up to the use to *know* whether he modified the pixels or not.
1365 // he is allowed to keep the original 'Media Storage SOP Class UID'
1366 CheckMandatoryEntry(0x0002,0x0002,"1.2.840.10008.5.1.4.1.1.7");
1368 // Potentialy this is a post-processed image
1369 // 'Media Storage SOP Class UID' --> [Secondary Capture Image Storage]
1370 CopyMandatoryEntry(0x0002,0x0002,"1.2.840.10008.5.1.4.1.1.7");
1372 // 'Media Storage SOP Instance UID'
1373 CopyMandatoryEntry(0x0002,0x0003,sop);
1375 // 'Implementation Class UID'
1376 // FIXME : in all examples we have, 0x0002,0x0012 is not so long :
1377 // semms to be Root UID + 4 digits (?)
1378 CopyMandatoryEntry(0x0002,0x0012,Util::CreateUniqueUID());
1380 // 'Implementation Version Name'
1381 std::string version = "GDCM ";
1382 version += Util::GetVersion();
1383 CopyMandatoryEntry(0x0002,0x0013,version);
1386 // Push out 'LibIDO-special' entries, if any
1387 Archive->Push(0x0028,0x0015);
1388 Archive->Push(0x0028,0x0016);
1389 Archive->Push(0x0028,0x0017);
1390 Archive->Push(0x0028,0x00199);
1392 // Deal with the pb of (Bits Stored = 12)
1393 // - we're gonna write the image as Bits Stored = 16
1394 if ( FileInternal->GetEntryString(0x0028,0x0100) == "12")
1396 CopyMandatoryEntry(0x0028,0x0100,"16");
1399 // Check if user wasn't drunk ;-)
1401 std::ostringstream s;
1402 // check 'Bits Allocated' vs decent values
1403 int nbBitsAllocated = FileInternal->GetBitsAllocated();
1404 if ( nbBitsAllocated == 0 || nbBitsAllocated > 32)
1406 CopyMandatoryEntry(0x0028,0x0100,"16");
1407 gdcmWarningMacro("(0028,0100) changed from "
1408 << nbBitsAllocated << " to 16 for consistency purpose");
1409 nbBitsAllocated = 16;
1411 // check 'Bits Stored' vs 'Bits Allocated'
1412 int nbBitsStored = FileInternal->GetBitsStored();
1413 if ( nbBitsStored == 0 || nbBitsStored > nbBitsAllocated )
1416 s << nbBitsAllocated;
1417 CopyMandatoryEntry(0x0028,0x0101,s.str());
1418 gdcmWarningMacro("(0028,0101) changed from "
1419 << nbBitsStored << " to " << nbBitsAllocated
1420 << " for consistency purpose" );
1421 nbBitsStored = nbBitsAllocated;
1423 // check 'Hight Bit Position' vs 'Bits Allocated' and 'Bits Stored'
1424 int highBitPosition = FileInternal->GetHighBitPosition();
1425 if ( highBitPosition == 0 ||
1426 highBitPosition > nbBitsAllocated-1 ||
1427 highBitPosition < nbBitsStored-1 )
1430 s << nbBitsStored - 1;
1431 CopyMandatoryEntry(0x0028,0x0102,s.str());
1432 gdcmWarningMacro("(0028,0102) changed from "
1433 << highBitPosition << " to " << nbBitsAllocated-1
1434 << " for consistency purpose");
1437 std::string pixelSpacing = FileInternal->GetEntryString(0x0028,0x0030);
1438 if ( pixelSpacing == GDCM_UNFOUND )
1440 pixelSpacing = "1.0\\1.0";
1441 // if missing, Pixel Spacing forced to "1.0\1.0"
1442 CopyMandatoryEntry(0x0028,0x0030,pixelSpacing);
1445 // 'Imager Pixel Spacing' : defaulted to 'Pixel Spacing'
1446 // --> This one is the *legal* one !
1447 // FIXME : we should write it only when we are *sure* the image comes from
1448 // an imager (see also 0008,0x0064)
1449 CheckMandatoryEntry(0x0018,0x1164,pixelSpacing);
1451 // Samples Per Pixel (type 1) : default to grayscale
1452 CheckMandatoryEntry(0x0028,0x0002,"1");
1454 // --- Check UID-related Entries ---
1456 // If 'SOP Class UID' exists ('true DICOM' image)
1457 // we create the 'Source Image Sequence' SeqEntry
1458 // to hold informations about the Source Image
1460 DataEntry *e_0008_0016 = FileInternal->GetDataEntry(0x0008, 0x0016);
1463 // Create 'Source Image Sequence' SeqEntry
1464 SeqEntry *sis = SeqEntry::New (
1465 Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x2112) );
1466 SQItem *sqi = SQItem::New(1);
1467 // (we assume 'SOP Instance UID' exists too)
1468 // create 'Referenced SOP Class UID'
1469 DataEntry *e_0008_1150 = DataEntry::New(
1470 Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x1150) );
1471 e_0008_1150->SetString( e_0008_0016->GetString());
1472 sqi->AddEntry(e_0008_1150);
1473 e_0008_1150->Delete();
1475 // create 'Referenced SOP Instance UID'
1476 DataEntry *e_0008_0018 = FileInternal->GetDataEntry(0x0008, 0x0018);
1477 DataEntry *e_0008_1155 = DataEntry::New(
1478 Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x1155) );
1479 e_0008_1155->SetString( e_0008_0018->GetString());
1480 sqi->AddEntry(e_0008_1155);
1481 e_0008_1155->Delete();
1483 sis->AddSQItem(sqi,1);
1486 // temporarily replaces any previous 'Source Image Sequence'
1490 // FIXME : is 'Image Type' *really* depending on the presence of'SOP Class UID'?
1491 if ( KeepMediaStorageSOPClassUID)
1492 // It up to the use to *know* whether he modified the pixels or not.
1493 // he is allowed to keep the original 'Media Storage SOP Class UID'
1494 // and 'Image Type' as well
1495 CheckMandatoryEntry(0x0008,0x0008,"DERIVED\\PRIMARY");
1497 // Potentialy this is a post-processed image
1498 // (The written image is no longer an 'ORIGINAL' one)
1499 CopyMandatoryEntry(0x0008,0x0008,"DERIVED\\PRIMARY");
1503 // At the end, not to overwrite the original ones,
1504 // needed by 'Referenced SOP Instance UID', 'Referenced SOP Class UID'
1505 // 'SOP Instance UID'
1506 CopyMandatoryEntry(0x0008,0x0018,sop);
1508 // the gdcm written image is a [Secondary Capture Image Storage]
1509 // except if user told us he dind't modify the pixels, and, therefore
1510 // he want to keep the 'Media Storage SOP Class UID'
1512 // 'Media Storage SOP Class UID' : [Secondary Capture Image Storage]
1513 if ( KeepMediaStorageSOPClassUID)
1515 // It up to the use to *know* whether he modified the pixels or not.
1516 // he is allowed to keep the original 'Media Storage SOP Class UID'
1517 CheckMandatoryEntry(0x0008,0x0016,"1.2.840.10008.5.1.4.1.1.7");
1521 // Potentialy this is a post-processed image
1522 // 'Media Storage SOP Class UID' --> [Secondary Capture Image Storage]
1523 CopyMandatoryEntry(0x0008,0x0016,"1.2.840.10008.5.1.4.1.1.7");
1525 // FIXME : Must we Force Value, or Default value ?
1526 // Is it Type 1 for any Modality ?
1527 // --> Answer seems to be NO :-(
1528 // FIXME : we should write it only when we are *sure* the image
1529 // *does not* come from an imager (see also 0018,0x1164)
1532 // Other possible values are :
1533 // See PS 3.3, Page 408
1535 // DV = Digitized Video
1536 // DI = Digital Interface
1537 // DF = Digitized Film
1538 // WSD = Workstation
1539 // SD = Scanned Document
1540 // SI = Scanned Image
1542 // SYN = Synthetic Image
1544 CheckMandatoryEntry(0x0008,0x0064,"SYN");
1547 // ---- The user will never have to take any action on the following ----
1549 // new value for 'SOP Instance UID'
1550 //SetMandatoryEntry(0x0008,0x0018,Util::CreateUniqueUID());
1552 // Instance Creation Date
1553 const std::string &date = Util::GetCurrentDate();
1554 CopyMandatoryEntry(0x0008,0x0012,date);
1556 // Instance Creation Time
1557 const std::string &time = Util::GetCurrentTime();
1558 CopyMandatoryEntry(0x0008,0x0013,time);
1561 CheckMandatoryEntry(0x0008,0x0020,date);
1563 CheckMandatoryEntry(0x0008,0x0030,time);
1566 //CopyMandatoryEntry(0x0008,0x0050,"");
1567 CheckMandatoryEntry(0x0008,0x0050,"");
1570 // ----- Add Mandatory Entries if missing ---
1571 // Entries whose type is 1 are mandatory, with a mandatory value
1572 // Entries whose type is 1c are mandatory-inside-a-Sequence,
1573 // with a mandatory value
1574 // Entries whose type is 2 are mandatory, with an optional value
1575 // Entries whose type is 2c are mandatory-inside-a-Sequence,
1576 // with an optional value
1577 // Entries whose type is 3 are optional
1579 // 'Study Instance UID'
1580 // Keep the value if exists
1581 // The user is allowed to create his own Study,
1582 // keeping the same 'Study Instance UID' for various images
1583 // The user may add images to a 'Manufacturer Study',
1584 // adding new Series to an already existing Study
1585 CheckMandatoryEntry(0x0020,0x000d,Util::CreateUniqueUID());
1587 // 'Serie Instance UID'
1588 // Keep the value if exists
1589 // The user is allowed to create his own Series,
1590 // keeping the same 'Serie Instance UID' for various images
1591 // The user shouldn't add any image to a 'Manufacturer Serie'
1592 // but there is no way no to prevent him for doing that
1593 CheckMandatoryEntry(0x0020,0x000e,Util::CreateUniqueUID());
1596 CheckMandatoryEntry(0x0020,0x0010,"");
1599 CheckMandatoryEntry(0x0020,0x0011,"");
1602 CheckMandatoryEntry(0x0020,0x0013,"");
1604 // Patient Orientation
1605 // Can be computed from (0020|0037) : Image Orientation (Patient)
1606 gdcm::Orientation *o = gdcm::Orientation::New();
1607 std::string ori = o->GetOrientation ( FileInternal );
1609 if (ori != "\\" && ori != GDCM_UNFOUND)
1610 CheckMandatoryEntry(0x0020,0x0020,ori);
1612 CheckMandatoryEntry(0x0020,0x0020,"");
1614 // Modality : if missing we set it to 'OTher'
1615 CheckMandatoryEntry(0x0008,0x0060,"OT");
1617 // Manufacturer : if missing we set it to 'GDCM Factory'
1618 CheckMandatoryEntry(0x0008,0x0070,"GDCM Factory");
1620 // Institution Name : if missing we set it to 'GDCM Hospital'
1621 CheckMandatoryEntry(0x0008,0x0080,"GDCM Hospital");
1623 // Patient's Name : if missing, we set it to 'GDCM^Patient'
1624 CheckMandatoryEntry(0x0010,0x0010,"GDCM^Patient");
1627 CheckMandatoryEntry(0x0010,0x0020,"");
1629 // Patient's Birth Date : 'type 2' entry -> must exist, value not mandatory
1630 CheckMandatoryEntry(0x0010,0x0030,"");
1632 // Patient's Sex :'type 2' entry -> must exist, value not mandatory
1633 CheckMandatoryEntry(0x0010,0x0040,"");
1635 // Referring Physician's Name :'type 2' entry -> must exist, value not mandatory
1636 CheckMandatoryEntry(0x0008,0x0090,"");
1638 // Remove some inconstencies (probably some more will be added)
1640 // if (0028 0008)Number of Frames exists
1641 // Push out (0020 0052),Frame of Reference UID
1642 // (only meaningfull within a Serie)
1646 // dciodvfy complains when missing (Type 1 element)
1647 /// todo add it when missing (?)
1649 DataEntry *e_0028_0008 = FileInternal->GetDataEntry(0x0028, 0x0008);
1652 Archive->Push(0x0020, 0x0052);
1656 // Deal with element 0x0000 (group length) of each group.
1657 // First stage : get all the different Groups
1660 DocEntry *d = FileInternal->GetFirstEntry();
1663 grHT[d->GetGroup()] = 0;
1664 d=FileInternal->GetNextEntry();
1666 // Second stage : add the missing ones (if any)
1667 for (GroupHT::iterator it = grHT.begin(); it != grHT.end(); ++it)
1669 CheckMandatoryEntry(it->first, 0x0000, "0");
1671 // Third stage : update all 'zero level' groups length
1675 void FileHelper::CheckMandatoryEntry(uint16_t group,uint16_t elem,std::string value)
1677 DataEntry *entry = FileInternal->GetDataEntry(group,elem);
1680 entry = DataEntry::New(Global::GetDicts()->GetDefaultPubDict()->GetEntry(group,elem));
1681 entry->SetString(value);
1682 Archive->Push(entry);
1687 void FileHelper::SetMandatoryEntry(uint16_t group,uint16_t elem,std::string value)
1689 DataEntry *entry = DataEntry::New(Global::GetDicts()->GetDefaultPubDict()->GetEntry(group,elem));
1690 entry->SetString(value);
1691 Archive->Push(entry);
1695 void FileHelper::CopyMandatoryEntry(uint16_t group,uint16_t elem,std::string value)
1697 DataEntry *entry = CopyDataEntry(group,elem);
1698 entry->SetString(value);
1699 Archive->Push(entry);
1704 * \brief Restore in the File the initial group 0002
1706 void FileHelper::RestoreWriteMandatory()
1708 // group 0002 may be pushed out for ACR-NEMA writting purposes
1709 Archive->Restore(0x0002,0x0000);
1710 Archive->Restore(0x0002,0x0001);
1711 Archive->Restore(0x0002,0x0002);
1712 Archive->Restore(0x0002,0x0003);
1713 Archive->Restore(0x0002,0x0010);
1714 Archive->Restore(0x0002,0x0012);
1715 Archive->Restore(0x0002,0x0013);
1716 Archive->Restore(0x0002,0x0016);
1717 Archive->Restore(0x0002,0x0100);
1718 Archive->Restore(0x0002,0x0102);
1720 // FIXME : Check if none is missing !
1722 Archive->Restore(0x0008,0x0012);
1723 Archive->Restore(0x0008,0x0013);
1724 Archive->Restore(0x0008,0x0016);
1725 Archive->Restore(0x0008,0x0018);
1726 Archive->Restore(0x0008,0x0060);
1727 Archive->Restore(0x0008,0x0070);
1728 Archive->Restore(0x0008,0x0080);
1729 Archive->Restore(0x0008,0x0090);
1730 Archive->Restore(0x0008,0x2112);
1732 Archive->Restore(0x0010,0x0010);
1733 Archive->Restore(0x0010,0x0030);
1734 Archive->Restore(0x0010,0x0040);
1736 Archive->Restore(0x0020,0x000d);
1737 Archive->Restore(0x0020,0x000e);
1742 * \brief CallStartMethod
1744 void FileHelper::CallStartMethod()
1748 CommandManager::ExecuteCommand(this,CMD_STARTPROGRESS);
1752 * \brief CallProgressMethod
1754 void FileHelper::CallProgressMethod()
1756 CommandManager::ExecuteCommand(this,CMD_PROGRESS);
1760 * \brief CallEndMethod
1762 void FileHelper::CallEndMethod()
1765 CommandManager::ExecuteCommand(this,CMD_ENDPROGRESS);
1768 //-----------------------------------------------------------------------------
1771 * \brief Factorization for various forms of constructors.
1773 void FileHelper::Initialize()
1776 KeepMediaStorageSOPClassUID = false;
1778 WriteMode = WMODE_RAW;
1779 WriteType = ExplicitVR;
1781 PixelReadConverter = new PixelReadConvert;
1782 PixelWriteConverter = new PixelWriteConvert;
1783 Archive = new DocEntryArchive( FileInternal );
1787 * \brief Reads/[decompresses] the pixels,
1788 * *without* making RGB from Palette Colors
1789 * @return the pixels area, whatever its type
1790 * (uint8_t is just for prototyping : feel free to Cast it)
1792 uint8_t *FileHelper::GetRaw()
1794 PixelReadConverter->SetUserFunction( UserFunction );
1796 uint8_t *raw = PixelReadConverter->GetRaw();
1799 // The Raw image migth not be loaded yet:
1800 std::ifstream *fp = FileInternal->OpenFile();
1801 PixelReadConverter->ReadAndDecompressPixelData( fp );
1803 FileInternal->CloseFile();
1805 raw = PixelReadConverter->GetRaw();
1808 gdcmWarningMacro( "Read/decompress of pixel data apparently went wrong.");
1815 //-----------------------------------------------------------------------------
1817 * \brief Prints the common part of DataEntry, SeqEntry
1818 * @param os ostream we want to print in
1819 * @param indent (unused)
1821 void FileHelper::Print(std::ostream &os, std::string const &)
1823 FileInternal->SetPrintLevel(PrintLevel);
1824 FileInternal->Print(os);
1826 if ( FileInternal->IsReadable() )
1828 PixelReadConverter->SetPrintLevel(PrintLevel);
1829 PixelReadConverter->Print(os);
1833 //-----------------------------------------------------------------------------
1834 } // end namespace gdcm