1 /*=========================================================================
4 Module: $RCSfile: gdcmFileHelper.cxx,v $
7 Date: $Date: 2006/01/19 11:46:45 $
8 Version: $Revision: 1.88 $
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"
39 // ----------------------------- WARNING -------------------------
41 These lines will be moved to the document-to-be 'User's Guide'
43 // To read an image, user needs a gdcm::File
44 gdcm::File *f = new gdcm::File(fileName);
46 // user may also decide he doesn't want to load some parts of the header
47 gdcm::File *f = new gdcm::File();
48 f->SetFileName(fileName);
49 f->SetLoadMode(LD_NOSEQ); // or
50 f->SetLoadMode(LD_NOSHADOW); // or
51 f->SetLoadMode(LD_NOSEQ | LD_NOSHADOW); // or
52 f->SetLoadMode(LD_NOSHADOWSEQ);
55 // user can now check some values
56 std::string v = f->GetEntryValue(groupNb,ElementNb);
58 // to get the pixels, user needs a gdcm::FileHelper
59 gdcm::FileHelper *fh = new gdcm::FileHelper(f);
60 // user may ask not to convert Palette to RGB
61 uint8_t *pixels = fh->GetImageDataRaw();
62 int imageLength = fh->GetImageDataRawSize();
63 // He can now use the pixels, create a new image, ...
64 uint8_t *userPixels = ...
66 To re-write the image, user re-uses the gdcm::FileHelper
68 fh->SetImageData( userPixels, userPixelsLength);
69 fh->SetTypeToRaw(); // Even if it was possible to convert Palette to RGB
72 fh->SetWriteTypeToDcmExpl(); // he wants Explicit Value Representation
73 // Little Endian is the default
74 // no other value is allowed
75 (-->SetWriteType(ExplicitVR);)
76 -->WriteType = ExplicitVR;
77 fh->Write(newFileName); // overwrites the file, if any
80 fh->WriteDcmExplVR(newFileName);
83 // ----------------------------- WARNING -------------------------
85 These lines will be moved to the document-to-be 'Developer's Guide'
87 WriteMode : WMODE_RAW / WMODE_RGB
88 WriteType : ImplicitVR, ExplicitVR, ACR, ACR_LIBIDO
90 fh1->Write(newFileName);
91 SetWriteFileTypeToImplicitVR() / SetWriteFileTypeToExplicitVR();
92 (modifies TransferSyntax)
93 SetWriteToRaw(); / SetWriteToRGB();
94 (modifies, when necessary : photochromatic interpretation,
95 samples per pixel, Planar configuration,
96 bits allocated, bits stored, high bit -ACR 24 bits-
97 Pixels element VR, pushes out the LUT )
98 CheckWriteIntegrity();
99 (checks user given pixels length)
100 FileInternal->Write(fileName,WriteType)
101 fp = opens file(fileName);
102 ComputeGroup0002Length( );
104 RemoveEntry(palettes, etc)
105 Document::WriteContent(fp, writetype);
107 (moves back to the File all the archived elements)
108 RestoreWriteFileType();
109 (pushes back group 0002, with TransferSyntax)
117 typedef std::map<uint16_t, int> GroupHT; // Hash Table
118 //-------------------------------------------------------------------------
119 // Constructor / Destructor
121 * \brief Constructor dedicated to deal with the *pixels* area of a ACR/DICOMV3
122 * file (gdcm::File only deals with the ... header)
123 * Opens (in read only and when possible) an existing file and checks
124 * for DICOM compliance. Returns NULL on failure.
125 * It will be up to the user to load the pixels into memory
126 * ( GetImageDataSize() + GetImageData() methods)
127 * \note the in-memory representation of all available tags found in
128 * the DICOM header is post-poned to first header information access.
129 * This avoid a double parsing of public part of the header when
130 * one sets an a posteriori shadow dictionary (efficiency can be
131 * seen as a side effect).
133 FileHelper::FileHelper( )
135 FileInternal = File::New( );
140 * \brief Constructor dedicated to deal with the *pixels* area of a ACR/DICOMV3
141 * file (File only deals with the ... header)
142 * Opens (in read only and when possible) an existing file and checks
143 * for DICOM compliance. Returns NULL on failure.
144 * It will be up to the user to load the pixels into memory
145 * ( GetImageDataSize() + GetImageData() methods)
146 * \note the in-memory representation of all available tags found in
147 * the DICOM header is post-poned to first header information access.
148 * This avoid a double parsing of public part of the header when
149 * user sets an a posteriori shadow dictionary (efficiency can be
150 * seen as a side effect).
151 * @param header already built Header
153 FileHelper::FileHelper(File *header)
155 gdcmAssertMacro(header);
157 FileInternal = header;
158 FileInternal->Register();
160 if ( FileInternal->IsReadable() )
162 PixelReadConverter->GrabInformationsFromFile( FileInternal, this );
167 * \brief canonical destructor
168 * \note If the header (gdcm::File) was created by the FileHelper constructor,
169 * it is destroyed by the FileHelper
171 FileHelper::~FileHelper()
173 if ( PixelReadConverter )
175 delete PixelReadConverter;
177 if ( PixelWriteConverter )
179 delete PixelWriteConverter;
186 FileInternal->Unregister();
189 //-----------------------------------------------------------------------------
193 * \brief Sets the LoadMode of the internal gdcm::File as a boolean string.
194 * NO_SEQ, NO_SHADOW, NO_SHADOWSEQ ... (nothing more, right now)
195 * WARNING : before using NO_SHADOW, be sure *all* your files
196 * contain accurate values in the 0x0000 element (if any)
197 * of *each* Shadow Group. The parser will fail if the size is wrong !
198 * @param loadMode Load mode to be used
200 void FileHelper::SetLoadMode(int loadMode)
202 GetFile()->SetLoadMode( loadMode );
205 * \brief Sets the LoadMode of the internal gdcm::File
206 * @param fileName name of the file to be open
208 void FileHelper::SetFileName(std::string const &fileName)
210 FileInternal->SetFileName( fileName );
215 * @return false if file cannot be open or no swap info was found,
216 * or no tag was found.
218 bool FileHelper::Load()
220 if ( !FileInternal->Load() )
223 PixelReadConverter->GrabInformationsFromFile( FileInternal, this );
228 * \brief Accesses an existing DataEntry through it's (group, element)
229 * and modifies it's content with the given value.
230 * @param content new value (string) to substitute with
231 * @param group group number of the Dicom Element to modify
232 * @param elem element number of the Dicom Element to modify
233 * \return false if DataEntry not found
235 bool FileHelper::SetEntryString(std::string const &content,
236 uint16_t group, uint16_t elem)
238 return FileInternal->SetEntryString(content, group, elem);
243 * \brief Accesses an existing DataEntry through it's (group, element)
244 * and modifies it's content with the given value.
245 * @param content new value (void* -> uint8_t*) to substitute with
246 * @param lgth new value length
247 * @param group group number of the Dicom Element to modify
248 * @param elem element number of the Dicom Element to modify
249 * \return false if DataEntry not found
251 bool FileHelper::SetEntryBinArea(uint8_t *content, int lgth,
252 uint16_t group, uint16_t elem)
254 return FileInternal->SetEntryBinArea(content, lgth, group, elem);
258 * \brief Modifies the value of a given DataEntry when it exists.
259 * Creates it with the given value when unexistant.
260 * @param content (string) value to be set
261 * @param group Group number of the Entry
262 * @param elem Element number of the Entry
263 * \return pointer to the modified/created DataEntry (NULL when creation
266 DataEntry *FileHelper::InsertEntryString(std::string const &content,
267 uint16_t group, uint16_t elem)
269 return FileInternal->InsertEntryString(content, group, elem);
273 * \brief Modifies the value of a given DataEntry when it exists.
274 * Creates it with the given value when unexistant.
275 * A copy of the binArea is made to be kept in the Document.
276 * @param binArea (binary)value to be set
277 * @param lgth new value length
278 * @param group Group number of the Entry
279 * @param elem Element number of the Entry
280 * \return pointer to the modified/created DataEntry (NULL when creation
283 DataEntry *FileHelper::InsertEntryBinArea(uint8_t *binArea, int lgth,
284 uint16_t group, uint16_t elem)
286 return FileInternal->InsertEntryBinArea(binArea, lgth, group, elem);
290 * \brief Adds an empty SeqEntry
291 * (remove any existing entry with same group,elem)
292 * @param group Group number of the Entry
293 * @param elem Element number of the Entry
294 * \return pointer to the created SeqEntry (NULL when creation
297 SeqEntry *FileHelper::InsertSeqEntry(uint16_t group, uint16_t elem)
299 return FileInternal->InsertSeqEntry(group, elem);
303 * \brief Get the size of the image data
304 * If the image can be RGB (with a lut or by default), the size
305 * corresponds to the RGB image
306 * (use GetImageDataRawSize if you want to be sure to get *only*
307 * the size of the pixels)
308 * @return The image size
310 size_t FileHelper::GetImageDataSize()
312 if ( PixelWriteConverter->GetUserData() )
314 return PixelWriteConverter->GetUserDataSize();
316 return PixelReadConverter->GetRGBSize();
320 * \brief Get the size of the image data.
321 * If the image could be converted to RGB using a LUT,
322 * this transformation is not taken into account by GetImageDataRawSize
323 * (use GetImageDataSize if you wish)
324 * @return The raw image size
326 size_t FileHelper::GetImageDataRawSize()
328 if ( PixelWriteConverter->GetUserData() )
330 return PixelWriteConverter->GetUserDataSize();
332 return PixelReadConverter->GetRawSize();
336 * \brief brings pixels into memory :
337 * - Allocates necessary memory,
338 * - Reads the pixels from disk (uncompress if necessary),
339 * - Transforms YBR pixels, if any, into RGB pixels,
340 * - Transforms 3 planes R, G, B, if any, into a single RGB Plane
341 * - Transforms single Grey plane + 3 Palettes into a RGB Plane
342 * - Copies the pixel data (image[s]/volume[s]) to newly allocated zone.
343 * @return Pointer to newly allocated pixel data.
344 * (uint8_t is just for prototyping. feel free to cast)
345 * NULL if alloc fails
347 uint8_t *FileHelper::GetImageData()
349 if ( PixelWriteConverter->GetUserData() )
351 return PixelWriteConverter->GetUserData();
356 // If the decompression failed nothing can be done.
360 if ( FileInternal->HasLUT() && PixelReadConverter->BuildRGBImage() )
362 return PixelReadConverter->GetRGB();
366 // When no LUT or LUT conversion fails, return the Raw
367 return PixelReadConverter->GetRaw();
372 * \brief brings pixels into memory :
373 * - Allocates necessary memory,
374 * - Transforms YBR pixels (if any) into RGB pixels
375 * - Transforms 3 planes R, G, B (if any) into a single RGB Plane
376 * - Copies the pixel data (image[s]/volume[s]) to newly allocated zone.
377 * - DOES NOT transform Grey plane + 3 Palettes into a RGB Plane
378 * @return Pointer to newly allocated pixel data.
379 * (uint8_t is just for prototyping. feel free to cast)
380 * NULL if alloc fails
382 uint8_t *FileHelper::GetImageDataRaw ()
387 #ifndef GDCM_LEGACY_REMOVE
389 * \ brief Useless function, since PixelReadConverter forces us
390 * copy the Pixels anyway.
391 * Reads the pixels from disk (uncompress if necessary),
392 * Transforms YBR pixels, if any, into RGB pixels
393 * Transforms 3 planes R, G, B, if any, into a single RGB Plane
394 * Transforms single Grey plane + 3 Palettes into a RGB Plane
395 * Copies at most MaxSize bytes of pixel data to caller allocated
397 * \ warning This function allows people that want to build a volume
398 * from an image stack *not to* have, first to get the image pixels,
399 * and then move them to the volume area.
400 * It's absolutely useless for any VTK user since vtk chooses
401 * to invert the lines of an image, that is the last line comes first
402 * (for some axis related reasons?). Hence he will have
403 * to load the image line by line, starting from the end.
404 * VTK users have to call GetImageData
406 * @ param destination Address (in caller's memory space) at which the
407 * pixel data should be copied
408 * @ param maxSize Maximum number of bytes to be copied. When MaxSize
409 * is not sufficient to hold the pixel data the copy is not
410 * executed (i.e. no partial copy).
411 * @ return On success, the number of bytes actually copied. Zero on
412 * failure e.g. MaxSize is lower than necessary.
414 size_t FileHelper::GetImageDataIntoVector (void *destination, size_t maxSize)
418 // If the decompression failed nothing can be done.
422 if ( FileInternal->HasLUT() && PixelReadConverter->BuildRGBImage() )
424 if ( PixelReadConverter->GetRGBSize() > maxSize )
426 gdcmWarningMacro( "Pixel data bigger than caller's expected MaxSize");
430 (void*)PixelReadConverter->GetRGB(),
431 PixelReadConverter->GetRGBSize() );
432 return PixelReadConverter->GetRGBSize();
435 // Either no LUT conversion necessary or LUT conversion failed
436 if ( PixelReadConverter->GetRawSize() > maxSize )
438 gdcmWarningMacro( "Pixel data bigger than caller's expected MaxSize");
442 (void *)PixelReadConverter->GetRaw(),
443 PixelReadConverter->GetRawSize() );
444 return PixelReadConverter->GetRawSize();
449 * \brief Points the internal pointer to the callers inData
450 * image representation, BUT WITHOUT COPYING THE DATA.
451 * 'image' Pixels are presented as C-like 2D arrays : line per line.
452 * 'volume'Pixels are presented as C-like 3D arrays : plane per plane
453 * \warning Since the pixels are not copied, it is the caller's responsability
454 * not to deallocate its data before gdcm uses them (e.g. with
455 * the Write() method )
456 * @param inData user supplied pixel area (uint8_t* is just for the compiler.
457 * user is allowed to pass any kind of pixelsn since the size is
459 * @param expectedSize total image size, *in Bytes*
461 void FileHelper::SetImageData(uint8_t *inData, size_t expectedSize)
463 SetUserData(inData, expectedSize);
467 * \brief Set the image data defined by the user
468 * \warning When writting the file, this data are get as default data to write
469 * @param inData user supplied pixel area (uint8_t* is just for the compiler.
470 * user is allowed to pass any kind of pixels since the size is
472 * @param expectedSize total image size, *in Bytes*
474 void FileHelper::SetUserData(uint8_t *inData, size_t expectedSize)
476 PixelWriteConverter->SetUserData(inData, expectedSize);
480 * \brief Get the image data defined by the user
481 * \warning When writting the file, this data are get as default data to write
483 uint8_t *FileHelper::GetUserData()
485 return PixelWriteConverter->GetUserData();
489 * \brief Get the image data size defined by the user
490 * \warning When writting the file, this data are get as default data to write
492 size_t FileHelper::GetUserDataSize()
494 return PixelWriteConverter->GetUserDataSize();
498 * \brief Get the image data from the file.
499 * If a LUT is found, the data are expanded to be RGB
501 uint8_t *FileHelper::GetRGBData()
503 return PixelReadConverter->GetRGB();
507 * \brief Get the image data size from the file.
508 * If a LUT is found, the data are expanded to be RGB
510 size_t FileHelper::GetRGBDataSize()
512 return PixelReadConverter->GetRGBSize();
516 * \brief Get the image data from the file.
517 * Even when a LUT is found, the data are not expanded to RGB!
519 uint8_t *FileHelper::GetRawData()
521 return PixelReadConverter->GetRaw();
525 * \brief Get the image data size from the file.
526 * Even when a LUT is found, the data are not expanded to RGB!
528 size_t FileHelper::GetRawDataSize()
530 return PixelReadConverter->GetRawSize();
534 * \brief Access to the underlying \ref PixelReadConverter RGBA LUT
536 uint8_t* FileHelper::GetLutRGBA()
538 if ( PixelReadConverter->GetLutRGBA() ==0 )
539 PixelReadConverter->BuildLUTRGBA();
540 return PixelReadConverter->GetLutRGBA();
544 * \brief Access to the underlying \ref PixelReadConverter RGBA LUT Item Number
546 int FileHelper::GetLutItemNumber()
548 return PixelReadConverter->GetLutItemNumber();
552 * \brief Access to the underlying \ref PixelReadConverter RGBA LUT Item Size
554 int FileHelper::GetLutItemSize()
556 return PixelReadConverter->GetLutItemSize();
560 * \brief Writes on disk A SINGLE Dicom file
561 * NO test is performed on processor "Endiannity".
562 * It's up to the user to call his Reader properly
563 * @param fileName name of the file to be created
564 * (any already existing file is over written)
565 * @return false if write fails
567 bool FileHelper::WriteRawData(std::string const &fileName)
569 std::ofstream fp1(fileName.c_str(), std::ios::out | std::ios::binary );
572 gdcmWarningMacro( "Fail to open (write) file:" << fileName.c_str());
576 if ( PixelWriteConverter->GetUserData() )
578 fp1.write( (char *)PixelWriteConverter->GetUserData(),
579 PixelWriteConverter->GetUserDataSize() );
581 else if ( PixelReadConverter->GetRGB() )
583 fp1.write( (char *)PixelReadConverter->GetRGB(),
584 PixelReadConverter->GetRGBSize());
586 else if ( PixelReadConverter->GetRaw() )
588 fp1.write( (char *)PixelReadConverter->GetRaw(),
589 PixelReadConverter->GetRawSize());
593 gdcmErrorMacro( "Nothing written." );
602 * \brief Writes on disk A SINGLE Dicom file,
603 * using the Implicit Value Representation convention
604 * NO test is performed on processor "Endianity".
605 * @param fileName name of the file to be created
606 * (any already existing file is overwritten)
607 * @return false if write fails
610 bool FileHelper::WriteDcmImplVR (std::string const &fileName)
612 SetWriteTypeToDcmImplVR();
613 return Write(fileName);
617 * \brief Writes on disk A SINGLE Dicom file,
618 * using the Explicit Value Representation convention
619 * NO test is performed on processor "Endiannity".
620 * @param fileName name of the file to be created
621 * (any already existing file is overwritten)
622 * @return false if write fails
625 bool FileHelper::WriteDcmExplVR (std::string const &fileName)
627 SetWriteTypeToDcmExplVR();
628 return Write(fileName);
632 * \brief Writes on disk A SINGLE Dicom file,
633 * using the ACR-NEMA convention
634 * NO test is performed on processor "Endiannity".
635 * (a l'attention des logiciels cliniques
636 * qui ne prennent en entrée QUE des images ACR ...
637 * \warning if a DICOM_V3 header is supplied,
638 * groups < 0x0008 and shadow groups are ignored
639 * \warning NO TEST is performed on processor "Endiannity".
640 * @param fileName name of the file to be created
641 * (any already existing file is overwritten)
642 * @return false if write fails
645 bool FileHelper::WriteAcr (std::string const &fileName)
648 return Write(fileName);
652 * \brief Writes on disk A SINGLE Dicom file,
653 * @param fileName name of the file to be created
654 * (any already existing file is overwritten)
655 * @return false if write fails
657 bool FileHelper::Write(std::string const &fileName)
662 SetWriteFileTypeToImplicitVR();
664 case Unknown: // should never happen; ExplicitVR is the default value
666 SetWriteFileTypeToExplicitVR();
670 // NOTHING is done here just for LibIDO.
671 // Just to avoid further trouble if user creates a file ex-nihilo,
672 // wants to write it as an ACR-NEMA file,
673 // and forgets to create any Entry belonging to group 0008
675 // We add Recognition Code (RET)
676 if ( ! FileInternal->GetDataEntry(0x0008, 0x0010) )
677 FileInternal->InsertEntryString("ACR-NEMA V1.0 ", 0x0008, 0x0010);
678 SetWriteFileTypeToACR();
679 // SetWriteFileTypeToImplicitVR(); // ACR IS implicit VR !
682 SetWriteFileTypeToJPEG();
683 std::cerr << "Writting as JPEG" << std::endl;
686 CheckMandatoryElements();
688 // --------------------------------------------------------------
689 // Special Patch to allow gdcm to re-write ACR-LibIDO formated images
691 // if recognition code tells us we dealt with a LibIDO image
692 // we reproduce on disk the switch between lineNumber and columnNumber
693 // just before writting ...
694 /// \todo the best trick would be *change* the recognition code
695 /// but pb expected if user deals with, e.g. COMPLEX images
697 if ( WriteType == ACR_LIBIDO )
703 SetWriteToNoLibido();
705 // ----------------- End of Special Patch ----------------
710 SetWriteToRaw(); // modifies and pushes to the archive, when necessary
713 SetWriteToRGB(); // modifies and pushes to the archive, when necessary
717 bool check = CheckWriteIntegrity(); // verifies length
718 if (WriteType == JPEG ) check = true;
721 check = FileInternal->Write(fileName,WriteType);
725 RestoreWriteFileType();
726 RestoreWriteMandatory();
728 // --------------------------------------------------------------
729 // Special Patch to allow gdcm to re-write ACR-LibIDO formated images
731 // ...and we restore the header to be Dicom Compliant again
732 // just after writting
733 RestoreWriteOfLibido();
734 // ----------------- End of Special Patch ----------------
739 //-----------------------------------------------------------------------------
742 * \brief Checks the write integrity
744 * The tests made are :
745 * - verify the size of the image to write with the possible write
746 * when the user set an image data
747 * @return true if check is successfull
749 bool FileHelper::CheckWriteIntegrity()
751 if ( PixelWriteConverter->GetUserData() )
753 int numberBitsAllocated = FileInternal->GetBitsAllocated();
754 if ( numberBitsAllocated == 0 || numberBitsAllocated == 12 )
756 gdcmWarningMacro( "numberBitsAllocated changed from "
757 << numberBitsAllocated << " to 16 "
758 << " for consistency purpose" );
759 numberBitsAllocated = 16;
762 size_t decSize = FileInternal->GetXSize()
763 * FileInternal->GetYSize()
764 * FileInternal->GetZSize()
765 * FileInternal->GetSamplesPerPixel()
766 * ( numberBitsAllocated / 8 );
767 size_t rgbSize = decSize;
768 if ( FileInternal->HasLUT() )
769 rgbSize = decSize * 3;
774 if ( decSize!=PixelWriteConverter->GetUserDataSize() )
776 gdcmWarningMacro( "Data size (Raw) is incorrect. Should be "
777 << decSize << " / Found :"
778 << PixelWriteConverter->GetUserDataSize() );
783 if ( rgbSize!=PixelWriteConverter->GetUserDataSize() )
785 gdcmWarningMacro( "Data size (RGB) is incorrect. Should be "
786 << decSize << " / Found "
787 << PixelWriteConverter->GetUserDataSize() );
798 * \brief Updates the File to write RAW data (as opposed to RGB data)
799 * (modifies, when necessary, photochromatic interpretation,
800 * bits allocated, Pixels element VR)
802 void FileHelper::SetWriteToRaw()
804 if ( FileInternal->GetNumberOfScalarComponents() == 3
805 && !FileInternal->HasLUT() )
811 DataEntry *photInt = CopyDataEntry(0x0028,0x0004);
812 if (FileInternal->HasLUT() )
814 photInt->SetString("PALETTE COLOR ");
818 photInt->SetString("MONOCHROME2 ");
821 PixelWriteConverter->SetReadData(PixelReadConverter->GetRaw(),
822 PixelReadConverter->GetRawSize());
824 std::string vr = "OB";
825 if ( FileInternal->GetBitsAllocated()>8 )
827 if ( FileInternal->GetBitsAllocated()==24 ) // For RGB ACR files
830 CopyDataEntry(GetFile()->GetGrPixel(),GetFile()->GetNumPixel(),vr);
831 pixel->SetFlag(DataEntry::FLAG_PIXELDATA);
832 pixel->SetBinArea(PixelWriteConverter->GetData(),false);
833 pixel->SetLength(PixelWriteConverter->GetDataSize());
835 Archive->Push(photInt);
836 Archive->Push(pixel);
844 * \brief Updates the File to write RGB data (as opposed to RAW data)
845 * (modifies, when necessary, photochromatic interpretation,
846 * samples per pixel, Planar configuration,
847 * bits allocated, bits stored, high bit -ACR 24 bits-
848 * Pixels element VR, pushes out the LUT, )
850 void FileHelper::SetWriteToRGB()
852 if ( FileInternal->GetNumberOfScalarComponents()==3 )
854 PixelReadConverter->BuildRGBImage();
856 DataEntry *spp = CopyDataEntry(0x0028,0x0002);
857 spp->SetString("3 ");
859 DataEntry *planConfig = CopyDataEntry(0x0028,0x0006);
860 planConfig->SetString("0 ");
862 DataEntry *photInt = CopyDataEntry(0x0028,0x0004);
863 photInt->SetString("RGB ");
865 if ( PixelReadConverter->GetRGB() )
867 PixelWriteConverter->SetReadData(PixelReadConverter->GetRGB(),
868 PixelReadConverter->GetRGBSize());
872 PixelWriteConverter->SetReadData(PixelReadConverter->GetRaw(),
873 PixelReadConverter->GetRawSize());
876 std::string vr = "OB";
877 if ( FileInternal->GetBitsAllocated()>8 )
879 if ( FileInternal->GetBitsAllocated()==24 ) // For RGB ACR files
882 CopyDataEntry(GetFile()->GetGrPixel(),GetFile()->GetNumPixel(),vr);
883 pixel->SetFlag(DataEntry::FLAG_PIXELDATA);
884 pixel->SetBinArea(PixelWriteConverter->GetData(),false);
885 pixel->SetLength(PixelWriteConverter->GetDataSize());
888 Archive->Push(planConfig);
889 Archive->Push(photInt);
890 Archive->Push(pixel);
893 planConfig->Delete();
898 Archive->Push(0x0028,0x1101);
899 Archive->Push(0x0028,0x1102);
900 Archive->Push(0x0028,0x1103);
901 Archive->Push(0x0028,0x1201);
902 Archive->Push(0x0028,0x1202);
903 Archive->Push(0x0028,0x1203);
905 // push out Palette Color Lookup Table UID, if any
906 Archive->Push(0x0028,0x1199);
908 // For old '24 Bits' ACR-NEMA
909 // Thus, we have a RGB image and the bits allocated = 24 and
910 // samples per pixels = 1 (in the read file)
911 if ( FileInternal->GetBitsAllocated()==24 )
913 DataEntry *bitsAlloc = CopyDataEntry(0x0028,0x0100);
914 bitsAlloc->SetString("8 ");
916 DataEntry *bitsStored = CopyDataEntry(0x0028,0x0101);
917 bitsStored->SetString("8 ");
919 DataEntry *highBit = CopyDataEntry(0x0028,0x0102);
920 highBit->SetString("7 ");
922 Archive->Push(bitsAlloc);
923 Archive->Push(bitsStored);
924 Archive->Push(highBit);
927 bitsStored->Delete();
938 * \brief Restore the File write mode
940 void FileHelper::RestoreWrite()
942 Archive->Restore(0x0028,0x0002);
943 Archive->Restore(0x0028,0x0004);
944 Archive->Restore(0x0028,0x0006);
945 Archive->Restore(GetFile()->GetGrPixel(),GetFile()->GetNumPixel());
947 // For old ACR-NEMA (24 bits problem)
948 Archive->Restore(0x0028,0x0100);
949 Archive->Restore(0x0028,0x0101);
950 Archive->Restore(0x0028,0x0102);
953 Archive->Restore(0x0028,0x1101);
954 Archive->Restore(0x0028,0x1102);
955 Archive->Restore(0x0028,0x1103);
956 Archive->Restore(0x0028,0x1201);
957 Archive->Restore(0x0028,0x1202);
958 Archive->Restore(0x0028,0x1203);
960 // For the Palette Color Lookup Table UID
961 Archive->Restore(0x0028,0x1203);
964 // group 0002 may be pushed out for ACR-NEMA writting purposes
965 Archive->Restore(0x0002,0x0000);
966 Archive->Restore(0x0002,0x0001);
967 Archive->Restore(0x0002,0x0002);
968 Archive->Restore(0x0002,0x0003);
969 Archive->Restore(0x0002,0x0010);
970 Archive->Restore(0x0002,0x0012);
971 Archive->Restore(0x0002,0x0013);
972 Archive->Restore(0x0002,0x0016);
973 Archive->Restore(0x0002,0x0100);
974 Archive->Restore(0x0002,0x0102);
978 * \brief Pushes out the whole group 0002
979 * FIXME : better, set a flag to tell the writer not to write it ...
980 * FIXME : method should probably have an other name !
981 * SetWriteFileTypeToACR is NOT opposed to
982 * SetWriteFileTypeToExplicitVR and SetWriteFileTypeToImplicitVR
984 void FileHelper::SetWriteFileTypeToACR()
986 Archive->Push(0x0002,0x0000);
987 Archive->Push(0x0002,0x0001);
988 Archive->Push(0x0002,0x0002);
989 Archive->Push(0x0002,0x0003);
990 Archive->Push(0x0002,0x0010);
991 Archive->Push(0x0002,0x0012);
992 Archive->Push(0x0002,0x0013);
993 Archive->Push(0x0002,0x0016);
994 Archive->Push(0x0002,0x0100);
995 Archive->Push(0x0002,0x0102);
999 * \brief Sets in the File the TransferSyntax to 'Explicit VR Little Endian"
1001 void FileHelper::SetWriteFileTypeToJPEG()
1003 std::string ts = Util::DicomString(
1004 Global::GetTS()->GetSpecialTransferSyntax(TS::JPEGBaselineProcess1) );
1006 DataEntry *tss = CopyDataEntry(0x0002,0x0010);
1013 void FileHelper::SetWriteFileTypeToExplicitVR()
1015 std::string ts = Util::DicomString(
1016 Global::GetTS()->GetSpecialTransferSyntax(TS::ExplicitVRLittleEndian) );
1018 DataEntry *tss = CopyDataEntry(0x0002,0x0010);
1026 * \brief Sets in the File the TransferSyntax to 'Implicit VR Little Endian"
1028 void FileHelper::SetWriteFileTypeToImplicitVR()
1030 std::string ts = Util::DicomString(
1031 Global::GetTS()->GetSpecialTransferSyntax(TS::ImplicitVRLittleEndian) );
1033 DataEntry *tss = CopyDataEntry(0x0002,0x0010);
1042 * \brief Restore in the File the initial group 0002
1044 void FileHelper::RestoreWriteFileType()
1049 * \brief Set the Write not to Libido format
1051 void FileHelper::SetWriteToLibido()
1053 DataEntry *oldRow = FileInternal->GetDataEntry(0x0028, 0x0010);
1054 DataEntry *oldCol = FileInternal->GetDataEntry(0x0028, 0x0011);
1056 if ( oldRow && oldCol )
1058 std::string rows, columns;
1060 DataEntry *newRow=DataEntry::New(oldRow->GetDictEntry());
1061 DataEntry *newCol=DataEntry::New(oldCol->GetDictEntry());
1063 newRow->Copy(oldCol);
1064 newCol->Copy(oldRow);
1066 newRow->SetString(oldCol->GetString());
1067 newCol->SetString(oldRow->GetString());
1069 Archive->Push(newRow);
1070 Archive->Push(newCol);
1076 DataEntry *libidoCode = CopyDataEntry(0x0008,0x0010);
1077 libidoCode->SetString("ACRNEMA_LIBIDO_1.1");
1078 Archive->Push(libidoCode);
1079 libidoCode->Delete();
1083 * \brief Set the Write not to No Libido format
1085 void FileHelper::SetWriteToNoLibido()
1087 DataEntry *recCode = FileInternal->GetDataEntry(0x0008,0x0010);
1090 if ( recCode->GetString() == "ACRNEMA_LIBIDO_1.1" )
1092 DataEntry *libidoCode = CopyDataEntry(0x0008,0x0010);
1093 libidoCode->SetString("");
1094 Archive->Push(libidoCode);
1095 libidoCode->Delete();
1101 * \brief Restore the Write format
1103 void FileHelper::RestoreWriteOfLibido()
1105 Archive->Restore(0x0028,0x0010);
1106 Archive->Restore(0x0028,0x0011);
1107 Archive->Restore(0x0008,0x0010);
1109 // Restore 'LibIDO-special' entries, if any
1110 Archive->Restore(0x0028,0x0015);
1111 Archive->Restore(0x0028,0x0016);
1112 Archive->Restore(0x0028,0x0017);
1113 Archive->Restore(0x0028,0x00199);
1117 * \brief Duplicates a DataEntry or creates it.
1118 * @param group Group number of the Entry
1119 * @param elem Element number of the Entry
1120 * @param vr Value Representation of the Entry
1121 * FIXME : what is it used for?
1122 * \return pointer to the new Bin Entry (NULL when creation failed).
1124 DataEntry *FileHelper::CopyDataEntry(uint16_t group, uint16_t elem,
1127 DocEntry *oldE = FileInternal->GetDocEntry(group, elem);
1130 if ( oldE && vr != GDCM_VRUNKNOWN )
1131 if ( oldE->GetVR() != vr )
1136 newE = DataEntry::New(oldE->GetDictEntry());
1141 newE = GetFile()->NewDataEntry(group, elem, vr);
1148 * \brief This method is called automatically, just before writting
1149 * in order to produce a 'True Dicom V3' image.
1151 * We cannot know *how* the user made the File :
1152 * (reading an old ACR-NEMA file or a not very clean DICOM file ...)
1153 * Just before writting :
1154 * - we check the Entries
1155 * - we create the mandatory entries if they are missing
1156 * - we modify the values if necessary
1157 * - we push the sensitive entries to the Archive
1158 * The writing process will restore the entries as they where before
1159 * entering FileHelper::CheckMandatoryElements, so the user will always
1160 * see the entries just as they were before he decided to write.
1163 * - Entries whose type is 1 are mandatory, with a mandatory value
1164 * - Entries whose type is 1c are mandatory-inside-a-Sequence,
1165 * with a mandatory value
1166 * - Entries whose type is 2 are mandatory, with an optional value
1167 * - Entries whose type is 2c are mandatory-inside-a-Sequence,
1168 * with an optional value
1169 * - Entries whose type is 3 are optional
1172 * - warn the user if we had to add some entries :
1173 * even if a mandatory entry is missing, we add it, with a default value
1174 * (we don't want to give up the writting process if user forgot to
1175 * specify Lena's Patient ID, for instance ...)
1176 * - read the whole PS 3.3 Part of DICOM (890 pages)
1177 * and write a *full* checker (probably one method per Modality ...)
1178 * Any contribution is welcome.
1179 * - write a user callable full checker, to allow post reading
1180 * and/or pre writting image consistency check.
1183 /* -------------------------------------------------------------------------------------
1184 To be moved to User's guide / WIKI ?
1186 We have to deal with 4 *very* different cases :
1187 -1) user created ex nihilo his own image and wants to write it as a Dicom image.
1188 -2) user modified the pixels of an existing image.
1189 -3) user created a new image, using existing images (eg MIP, MPR, cartography image)
1190 -4) user anonymized an image without processing the pixels.
1192 gdcm::FileHelper::CheckMandatoryElements() deals automatically with these cases.
1195 0008 0012 Instance Creation Date
1196 0008 0013 Instance Creation Time
1197 0008 0018 SOP Instance UID
1198 are *always* created with the current values; user has *no* possible intervention on
1201 'Serie Instance UID'(0x0020,0x000e)
1202 'Study Instance UID'(0x0020,0x000d) are kept as is if already exist,
1203 created if it doesn't.
1204 The user is allowed to create his own Series/Studies,
1205 keeping the same 'Serie Instance UID' / 'Study Instance UID' for various images
1207 The user shouldn't add any image to a 'Manufacturer Serie'
1208 but there is no way no to allowed him to do that
1210 None of the 'shadow elements' are droped out.
1214 'Modality' (0x0008,0x0060) is defaulted to "OT" (other) if missing.
1215 'Conversion Type (0x0008,0x0064) is forced to 'SYN' (Synthetic Image).
1216 'Study Date', 'Study Time' are defaulted to current Date and Time.
1219 'Media Storage SOP Class UID' (0x0002,0x0002)
1220 'SOP Class UID' (0x0008,0x0016) are set to
1221 [Secondary Capture Image Storage]
1222 'Image Type' (0x0008,0x0008) is forced to "DERIVED\PRIMARY"
1223 Conversion Type (0x0008,0x0064) is forced to 'SYN' (Synthetic Image)
1226 If 'SOP Class UID' exists in the native image ('true DICOM' image)
1227 we create the 'Source Image Sequence' SeqEntry (0x0008, 0x2112)
1228 --> 'Referenced SOP Class UID' (0x0008, 0x1150)
1229 whose value is the original 'SOP Class UID'
1230 --> 'Referenced SOP Instance UID' (0x0008, 0x1155)
1231 whose value is the original 'SOP Class UID'
1233 3) TODO : find a trick to allow user to pass to the writter the list of the Dicom images
1234 or the Series, (or the Study ?) he used to created his image
1235 (MIP, MPR, cartography image, ...)
1236 These info should be stored (?)
1237 0008 1110 SQ 1 Referenced Study Sequence
1238 0008 1115 SQ 1 Referenced Series Sequence
1239 0008 1140 SQ 1 Referenced Image Sequence
1241 4) When user *knows* he didn't modified the pixels, he may ask the writer to keep some
1242 informations unchanged :
1243 'Media Storage SOP Class UID' (0x0002,0x0002)
1244 'SOP Class UID' (0x0008,0x0016)
1245 'Image Type' (0x0008,0x0008)
1246 'Conversion Type' (0x0008,0x0064)
1247 He has to use gdcm::FileHelper::SetKeepMediaStorageSOPClassUID(true)
1248 (probabely name has to be changed)
1251 Bellow follows the full description (hope so !) of the consistency checks performed
1252 by gdcm::FileHelper::CheckMandatoryElements()
1255 -->'Media Storage SOP Class UID' (0x0002,0x0002)
1256 -->'SOP Class UID' (0x0008,0x0016) are set to
1257 [Secondary Capture Image Storage]
1258 (Potentialy, the image was modified by user, and post-processed;
1259 it's no longer a 'native' image)
1260 Except if user told he wants to keep MediaStorageSOPClassUID,
1261 when *he* knows he didn't modify the image (e.g. : he just anonymized the file)
1263 --> 'Image Type' (0x0008,0x0008)
1264 is forced to "DERIVED\PRIMARY"
1265 (The written image is no longer an 'ORIGINAL' one)
1266 Except if user told he wants to keep MediaStorageSOPClassUID,
1267 when *he* knows he didn't modify the image (e.g. : he just anonymized the file)
1269 --> Conversion Type (0x0008,0x0064)
1270 is forced to 'SYN' (Synthetic Image)
1271 Except if user told he wants to keep MediaStorageSOPClassUID,
1272 when *he* knows he didn't modify the image (e.g. : he just anonymized the file)
1274 --> 'Modality' (0x0008,0x0060)
1275 is defaulted to "OT" (other) if missing.
1276 (a fully user created image belongs to *no* modality)
1278 --> 'Media Storage SOP Instance UID' (0x0002,0x0003)
1279 --> 'Implementation Class UID' (0x0002,0x0012)
1280 are automatically generated; no user intervention possible
1282 --> 'Serie Instance UID'(0x0020,0x000e)
1283 --> 'Study Instance UID'(0x0020,0x000d) are kept as is if already exist
1284 created if it doesn't.
1285 The user is allowed to create his own Series/Studies,
1286 keeping the same 'Serie Instance UID' / 'Study Instance UID'
1289 The user shouldn't add any image to a 'Manufacturer Serie'
1290 but there is no way no to allowed him to do that
1292 --> If 'SOP Class UID' exists in the native image ('true DICOM' image)
1293 we create the 'Source Image Sequence' SeqEntry (0x0008, 0x2112)
1295 --> 'Referenced SOP Class UID' (0x0008, 0x1150)
1296 whose value is the original 'SOP Class UID'
1297 --> 'Referenced SOP Instance UID' (0x0008, 0x1155)
1298 whose value is the original 'SOP Class UID'
1300 --> Bits Stored, Bits Allocated, Hight Bit Position are checked for consistency
1301 --> Pixel Spacing (0x0028,0x0030) is defaulted to "1.0\1.0"
1302 --> Samples Per Pixel (0x0028,0x0002) is defaulted to 1 (grayscale)
1304 --> Imager Pixel Spacing (0x0018,0x1164) : defaulted to Pixel Spacing value
1306 --> Instance Creation Date, Instance Creation Time are forced to current Date and Time
1308 --> Study Date, Study Time are defaulted to current Date and Time
1309 (they remain unchanged if they exist)
1311 --> Patient Orientation : (0x0020,0x0020), if not present, is deduced from
1312 Image Orientation (Patient) : (0020|0037) or from
1313 Image Orientation (RET) : (0020 0035)
1315 --> Study ID, Series Number, Instance Number, Patient Orientation (Type 2)
1316 are created, with empty value if there are missing.
1318 --> Manufacturer, Institution Name, Patient's Name, (Type 2)
1319 are defaulted with a 'gdcm' value.
1321 --> Patient ID, Patient's Birth Date, Patient's Sex, (Type 2)
1322 --> Referring Physician's Name (Type 2)
1323 are created, with empty value if there are missing.
1325 -------------------------------------------------------------------------------------*/
1327 void FileHelper::CheckMandatoryElements()
1329 std::string sop = Util::CreateUniqueUID();
1331 // just to remember : 'official' 0002 group
1332 if ( WriteType != ACR && WriteType != ACR_LIBIDO )
1334 // Group 000002 (Meta Elements) already pushed out
1336 //0002 0000 UL 1 Meta Group Length
1337 //0002 0001 OB 1 File Meta Information Version
1338 //0002 0002 UI 1 Media Stored SOP Class UID
1339 //0002 0003 UI 1 Media Stored SOP Instance UID
1340 //0002 0010 UI 1 Transfer Syntax UID
1341 //0002 0012 UI 1 Implementation Class UID
1342 //0002 0013 SH 1 Implementation Version Name
1343 //0002 0016 AE 1 Source Application Entity Title
1344 //0002 0100 UI 1 Private Information Creator
1345 //0002 0102 OB 1 Private Information
1347 // Create them if not found
1348 // Always modify the value
1349 // Push the entries to the archive.
1350 CopyMandatoryEntry(0x0002,0x0000,"0");
1352 DataEntry *e_0002_0001 = CopyDataEntry(0x0002,0x0001, "OB");
1353 e_0002_0001->SetBinArea((uint8_t*)Util::GetFileMetaInformationVersion(),
1355 e_0002_0001->SetLength(2);
1356 Archive->Push(e_0002_0001);
1357 e_0002_0001->Delete();
1359 if ( KeepMediaStorageSOPClassUID)
1360 // It up to the use to *know* whether he modified the pixels or not.
1361 // he is allowed to keep the original 'Media Storage SOP Class UID'
1362 CheckMandatoryEntry(0x0002,0x0002,"1.2.840.10008.5.1.4.1.1.7");
1364 // Potentialy this is a post-processed image
1365 // 'Media Storage SOP Class UID' --> [Secondary Capture Image Storage]
1366 CopyMandatoryEntry(0x0002,0x0002,"1.2.840.10008.5.1.4.1.1.7");
1368 // 'Media Storage SOP Instance UID'
1369 CopyMandatoryEntry(0x0002,0x0003,sop);
1371 // 'Implementation Class UID'
1372 // FIXME : in all examples we have, 0x0002,0x0012 is not so long :
1373 // semms to be Root UID + 4 digits (?)
1374 CopyMandatoryEntry(0x0002,0x0012,Util::CreateUniqueUID());
1376 // 'Implementation Version Name'
1377 std::string version = "GDCM ";
1378 version += Util::GetVersion();
1379 CopyMandatoryEntry(0x0002,0x0013,version);
1382 // Push out 'LibIDO-special' entries, if any
1383 Archive->Push(0x0028,0x0015);
1384 Archive->Push(0x0028,0x0016);
1385 Archive->Push(0x0028,0x0017);
1386 Archive->Push(0x0028,0x00199);
1388 // Deal with the pb of (Bits Stored = 12)
1389 // - we're gonna write the image as Bits Stored = 16
1390 if ( FileInternal->GetEntryString(0x0028,0x0100) == "12")
1392 CopyMandatoryEntry(0x0028,0x0100,"16");
1395 // Check if user wasn't drunk ;-)
1397 std::ostringstream s;
1398 // check 'Bits Allocated' vs decent values
1399 int nbBitsAllocated = FileInternal->GetBitsAllocated();
1400 if ( nbBitsAllocated == 0 || nbBitsAllocated > 32)
1402 CopyMandatoryEntry(0x0028,0x0100,"16");
1403 gdcmWarningMacro("(0028,0100) changed from "
1404 << nbBitsAllocated << " to 16 for consistency purpose");
1405 nbBitsAllocated = 16;
1407 // check 'Bits Stored' vs 'Bits Allocated'
1408 int nbBitsStored = FileInternal->GetBitsStored();
1409 if ( nbBitsStored == 0 || nbBitsStored > nbBitsAllocated )
1412 s << nbBitsAllocated;
1413 CopyMandatoryEntry(0x0028,0x0101,s.str());
1414 gdcmWarningMacro("(0028,0101) changed from "
1415 << nbBitsStored << " to " << nbBitsAllocated
1416 << " for consistency purpose" );
1417 nbBitsStored = nbBitsAllocated;
1419 // check 'Hight Bit Position' vs 'Bits Allocated' and 'Bits Stored'
1420 int highBitPosition = FileInternal->GetHighBitPosition();
1421 if ( highBitPosition == 0 ||
1422 highBitPosition > nbBitsAllocated-1 ||
1423 highBitPosition < nbBitsStored-1 )
1426 s << nbBitsStored - 1;
1427 CopyMandatoryEntry(0x0028,0x0102,s.str());
1428 gdcmWarningMacro("(0028,0102) changed from "
1429 << highBitPosition << " to " << nbBitsAllocated-1
1430 << " for consistency purpose");
1433 std::string pixelSpacing = FileInternal->GetEntryString(0x0028,0x0030);
1434 if ( pixelSpacing == GDCM_UNFOUND )
1436 pixelSpacing = "1.0\\1.0";
1437 // if missing, Pixel Spacing forced to "1.0\1.0"
1438 CopyMandatoryEntry(0x0028,0x0030,pixelSpacing);
1441 // 'Imager Pixel Spacing' : defaulted to 'Pixel Spacing'
1442 // --> This one is the *legal* one !
1443 // FIXME : we should write it only when we are *sure* the image comes from
1444 // an imager (see also 0008,0x0064)
1445 CheckMandatoryEntry(0x0018,0x1164,pixelSpacing);
1447 // Samples Per Pixel (type 1) : default to grayscale
1448 CheckMandatoryEntry(0x0028,0x0002,"1");
1450 // --- Check UID-related Entries ---
1452 // If 'SOP Class UID' exists ('true DICOM' image)
1453 // we create the 'Source Image Sequence' SeqEntry
1454 // to hold informations about the Source Image
1456 DataEntry *e_0008_0016 = FileInternal->GetDataEntry(0x0008, 0x0016);
1459 // Create 'Source Image Sequence' SeqEntry
1460 SeqEntry *sis = SeqEntry::New (
1461 Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x2112) );
1462 SQItem *sqi = SQItem::New(1);
1463 // (we assume 'SOP Instance UID' exists too)
1464 // create 'Referenced SOP Class UID'
1465 DataEntry *e_0008_1150 = DataEntry::New(
1466 Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x1150) );
1467 e_0008_1150->SetString( e_0008_0016->GetString());
1468 sqi->AddEntry(e_0008_1150);
1469 e_0008_1150->Delete();
1471 // create 'Referenced SOP Instance UID'
1472 DataEntry *e_0008_0018 = FileInternal->GetDataEntry(0x0008, 0x0018);
1473 DataEntry *e_0008_1155 = DataEntry::New(
1474 Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x1155) );
1475 e_0008_1155->SetString( e_0008_0018->GetString());
1476 sqi->AddEntry(e_0008_1155);
1477 e_0008_1155->Delete();
1479 sis->AddSQItem(sqi,1);
1482 // temporarily replaces any previous 'Source Image Sequence'
1486 // FIXME : is 'Image Type' *really* depending on the presence of'SOP Class UID'?
1487 if ( KeepMediaStorageSOPClassUID)
1488 // It up to the use to *know* whether he modified the pixels or not.
1489 // he is allowed to keep the original 'Media Storage SOP Class UID'
1490 // and 'Image Type' as well
1491 CheckMandatoryEntry(0x0008,0x0008,"DERIVED\\PRIMARY");
1493 // Potentialy this is a post-processed image
1494 // (The written image is no longer an 'ORIGINAL' one)
1495 CopyMandatoryEntry(0x0008,0x0008,"DERIVED\\PRIMARY");
1499 // At the end, not to overwrite the original ones,
1500 // needed by 'Referenced SOP Instance UID', 'Referenced SOP Class UID'
1501 // 'SOP Instance UID'
1502 CopyMandatoryEntry(0x0008,0x0018,sop);
1504 // the gdcm written image is a [Secondary Capture Image Storage]
1505 // except if user told us he dind't modify the pixels, and, therefore
1506 // he want to keep the 'Media Storage SOP Class UID'
1508 // 'Media Storage SOP Class UID' : [Secondary Capture Image Storage]
1509 if ( KeepMediaStorageSOPClassUID)
1511 // It up to the use to *know* whether he modified the pixels or not.
1512 // he is allowed to keep the original 'Media Storage SOP Class UID'
1513 CheckMandatoryEntry(0x0008,0x0016,"1.2.840.10008.5.1.4.1.1.7");
1517 // Potentialy this is a post-processed image
1518 // 'Media Storage SOP Class UID' --> [Secondary Capture Image Storage]
1519 CopyMandatoryEntry(0x0008,0x0016,"1.2.840.10008.5.1.4.1.1.7");
1521 // FIXME : Must we Force Value, or Default value ?
1522 // Is it Type 1 for any Modality ?
1523 // --> Answer seems to be NO :-(
1524 // FIXME : we should write it only when we are *sure* the image
1525 // *does not* come from an imager (see also 0018,0x1164)
1528 // Other possible values are :
1529 // See PS 3.3, Page 408
1531 // DV = Digitized Video
1532 // DI = Digital Interface
1533 // DF = Digitized Film
1534 // WSD = Workstation
1535 // SD = Scanned Document
1536 // SI = Scanned Image
1538 // SYN = Synthetic Image
1540 CheckMandatoryEntry(0x0008,0x0064,"SYN");
1543 // ---- The user will never have to take any action on the following ----
1545 // new value for 'SOP Instance UID'
1546 //SetMandatoryEntry(0x0008,0x0018,Util::CreateUniqueUID());
1548 // Instance Creation Date
1549 const std::string &date = Util::GetCurrentDate();
1550 CopyMandatoryEntry(0x0008,0x0012,date);
1552 // Instance Creation Time
1553 const std::string &time = Util::GetCurrentTime();
1554 CopyMandatoryEntry(0x0008,0x0013,time);
1557 CheckMandatoryEntry(0x0008,0x0020,date);
1559 CheckMandatoryEntry(0x0008,0x0030,time);
1562 //CopyMandatoryEntry(0x0008,0x0050,"");
1563 CheckMandatoryEntry(0x0008,0x0050,"");
1566 // ----- Add Mandatory Entries if missing ---
1567 // Entries whose type is 1 are mandatory, with a mandatory value
1568 // Entries whose type is 1c are mandatory-inside-a-Sequence,
1569 // with a mandatory value
1570 // Entries whose type is 2 are mandatory, with an optional value
1571 // Entries whose type is 2c are mandatory-inside-a-Sequence,
1572 // with an optional value
1573 // Entries whose type is 3 are optional
1575 // 'Study Instance UID'
1576 // Keep the value if exists
1577 // The user is allowed to create his own Study,
1578 // keeping the same 'Study Instance UID' for various images
1579 // The user may add images to a 'Manufacturer Study',
1580 // adding new Series to an already existing Study
1581 CheckMandatoryEntry(0x0020,0x000d,Util::CreateUniqueUID());
1583 // 'Serie Instance UID'
1584 // Keep the value if exists
1585 // The user is allowed to create his own Series,
1586 // keeping the same 'Serie Instance UID' for various images
1587 // The user shouldn't add any image to a 'Manufacturer Serie'
1588 // but there is no way no to prevent him for doing that
1589 CheckMandatoryEntry(0x0020,0x000e,Util::CreateUniqueUID());
1592 CheckMandatoryEntry(0x0020,0x0010,"");
1595 CheckMandatoryEntry(0x0020,0x0011,"");
1598 CheckMandatoryEntry(0x0020,0x0013,"");
1600 // Patient Orientation
1601 // Can be computed from (0020|0037) : Image Orientation (Patient)
1602 gdcm::Orientation *o = gdcm::Orientation::New();
1603 std::string ori = o->GetOrientation ( FileInternal );
1606 CheckMandatoryEntry(0x0020,0x0020,ori);
1608 CheckMandatoryEntry(0x0020,0x0020,"");
1610 // Modality : if missing we set it to 'OTher'
1611 CheckMandatoryEntry(0x0008,0x0060,"OT");
1613 // Manufacturer : if missing we set it to 'GDCM Factory'
1614 CheckMandatoryEntry(0x0008,0x0070,"GDCM Factory");
1616 // Institution Name : if missing we set it to 'GDCM Hospital'
1617 CheckMandatoryEntry(0x0008,0x0080,"GDCM Hospital");
1619 // Patient's Name : if missing, we set it to 'GDCM^Patient'
1620 CheckMandatoryEntry(0x0010,0x0010,"GDCM^Patient");
1623 CheckMandatoryEntry(0x0010,0x0020,"");
1625 // Patient's Birth Date : 'type 2' entry -> must exist, value not mandatory
1626 CheckMandatoryEntry(0x0010,0x0030,"");
1628 // Patient's Sex :'type 2' entry -> must exist, value not mandatory
1629 CheckMandatoryEntry(0x0010,0x0040,"");
1631 // Referring Physician's Name :'type 2' entry -> must exist, value not mandatory
1632 CheckMandatoryEntry(0x0008,0x0090,"");
1634 // Remove some inconstencies (probably some more will be added)
1636 // if (0028 0008)Number of Frames exists
1637 // Push out (0020 0052),Frame of Reference UID
1638 // (only meaningfull within a Serie)
1639 DataEntry *e_0028_0008 = FileInternal->GetDataEntry(0x0028, 0x0008);
1642 Archive->Push(0x0020, 0x0052);
1645 // Deal with element 0x0000 (group length) of each group.
1646 // First stage : get all the different Groups
1649 DocEntry *d = FileInternal->GetFirstEntry();
1652 grHT[d->GetGroup()] = 0;
1653 d=FileInternal->GetNextEntry();
1655 // Second stage : add the missing ones (if any)
1656 for (GroupHT::iterator it = grHT.begin(); it != grHT.end(); ++it)
1658 CheckMandatoryEntry(it->first, 0x0000, "0");
1660 // Third stage : update all 'zero level' groups length
1664 void FileHelper::CheckMandatoryEntry(uint16_t group,uint16_t elem,std::string value)
1666 DataEntry *entry = FileInternal->GetDataEntry(group,elem);
1669 entry = DataEntry::New(Global::GetDicts()->GetDefaultPubDict()->GetEntry(group,elem));
1670 entry->SetString(value);
1671 Archive->Push(entry);
1676 void FileHelper::SetMandatoryEntry(uint16_t group,uint16_t elem,std::string value)
1678 DataEntry *entry = DataEntry::New(Global::GetDicts()->GetDefaultPubDict()->GetEntry(group,elem));
1679 entry->SetString(value);
1680 Archive->Push(entry);
1684 void FileHelper::CopyMandatoryEntry(uint16_t group,uint16_t elem,std::string value)
1686 DataEntry *entry = CopyDataEntry(group,elem);
1687 entry->SetString(value);
1688 Archive->Push(entry);
1693 * \brief Restore in the File the initial group 0002
1695 void FileHelper::RestoreWriteMandatory()
1697 // group 0002 may be pushed out for ACR-NEMA writting purposes
1698 Archive->Restore(0x0002,0x0000);
1699 Archive->Restore(0x0002,0x0001);
1700 Archive->Restore(0x0002,0x0002);
1701 Archive->Restore(0x0002,0x0003);
1702 Archive->Restore(0x0002,0x0010);
1703 Archive->Restore(0x0002,0x0012);
1704 Archive->Restore(0x0002,0x0013);
1705 Archive->Restore(0x0002,0x0016);
1706 Archive->Restore(0x0002,0x0100);
1707 Archive->Restore(0x0002,0x0102);
1709 // FIXME : Check if none is missing !
1711 Archive->Restore(0x0008,0x0012);
1712 Archive->Restore(0x0008,0x0013);
1713 Archive->Restore(0x0008,0x0016);
1714 Archive->Restore(0x0008,0x0018);
1715 Archive->Restore(0x0008,0x0060);
1716 Archive->Restore(0x0008,0x0070);
1717 Archive->Restore(0x0008,0x0080);
1718 Archive->Restore(0x0008,0x0090);
1719 Archive->Restore(0x0008,0x2112);
1721 Archive->Restore(0x0010,0x0010);
1722 Archive->Restore(0x0010,0x0030);
1723 Archive->Restore(0x0010,0x0040);
1725 Archive->Restore(0x0020,0x000d);
1726 Archive->Restore(0x0020,0x000e);
1731 * \brief CallStartMethod
1733 void FileHelper::CallStartMethod()
1737 CommandManager::ExecuteCommand(this,CMD_STARTPROGRESS);
1741 * \brief CallProgressMethod
1743 void FileHelper::CallProgressMethod()
1745 CommandManager::ExecuteCommand(this,CMD_PROGRESS);
1749 * \brief CallEndMethod
1751 void FileHelper::CallEndMethod()
1754 CommandManager::ExecuteCommand(this,CMD_ENDPROGRESS);
1757 //-----------------------------------------------------------------------------
1760 * \brief Factorization for various forms of constructors.
1762 void FileHelper::Initialize()
1765 KeepMediaStorageSOPClassUID = false;
1767 WriteMode = WMODE_RAW;
1768 WriteType = ExplicitVR;
1770 PixelReadConverter = new PixelReadConvert;
1771 PixelWriteConverter = new PixelWriteConvert;
1772 Archive = new DocEntryArchive( FileInternal );
1776 * \brief Reads/[decompresses] the pixels,
1777 * *without* making RGB from Palette Colors
1778 * @return the pixels area, whatever its type
1779 * (uint8_t is just for prototyping : feel free to Cast it)
1781 uint8_t *FileHelper::GetRaw()
1783 PixelReadConverter->SetUserFunction( UserFunction );
1785 uint8_t *raw = PixelReadConverter->GetRaw();
1788 // The Raw image migth not be loaded yet:
1789 std::ifstream *fp = FileInternal->OpenFile();
1790 PixelReadConverter->ReadAndDecompressPixelData( fp );
1792 FileInternal->CloseFile();
1794 raw = PixelReadConverter->GetRaw();
1797 gdcmWarningMacro( "Read/decompress of pixel data apparently went wrong.");
1804 //-----------------------------------------------------------------------------
1806 * \brief Prints the common part of DataEntry, SeqEntry
1807 * @param os ostream we want to print in
1808 * @param indent (unused)
1810 void FileHelper::Print(std::ostream &os, std::string const &)
1812 FileInternal->SetPrintLevel(PrintLevel);
1813 FileInternal->Print(os);
1815 if ( FileInternal->IsReadable() )
1817 PixelReadConverter->SetPrintLevel(PrintLevel);
1818 PixelReadConverter->Print(os);
1822 //-----------------------------------------------------------------------------
1823 } // end namespace gdcm