]> Creatis software - gdcm.git/blob - src/gdcmFileHelper.cxx
Add comments on the way gdcm::FileHelper:CheckMandatoryElements() works
[gdcm.git] / src / gdcmFileHelper.cxx
1 /*=========================================================================
2                                                                                 
3   Program:   gdcm
4   Module:    $RCSfile: gdcmFileHelper.cxx,v $
5   Language:  C++
6
7   Date:      $Date: 2006/01/19 11:46:45 $
8   Version:   $Revision: 1.88 $
9                                                                                 
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.
13                                                                                 
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.
17                                                                                 
18 =========================================================================*/
19
20 #include "gdcmFileHelper.h"
21 #include "gdcmGlobal.h"
22 #include "gdcmTS.h"
23 #include "gdcmDocument.h"
24 #include "gdcmDebug.h"
25 #include "gdcmUtil.h"
26 #include "gdcmSeqEntry.h"
27 #include "gdcmSQItem.h"
28 #include "gdcmDataEntry.h"
29 #include "gdcmFile.h"
30 #include "gdcmPixelReadConvert.h"
31 #include "gdcmPixelWriteConvert.h"
32 #include "gdcmDocEntryArchive.h"
33 #include "gdcmDictSet.h"
34 #include "gdcmOrientation.h"
35
36 #include <fstream>
37
38 /*
39 // ----------------------------- WARNING -------------------------
40
41 These lines will be moved to the document-to-be 'User's Guide'
42
43 // To read an image, user needs a gdcm::File
44 gdcm::File *f = new gdcm::File(fileName);
45 // or (advanced) :
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);
53 f->Load();
54
55 // user can now check some values
56 std::string v = f->GetEntryValue(groupNb,ElementNb);
57
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 = ...
65
66 To re-write the image, user re-uses the gdcm::FileHelper
67
68 fh->SetImageData( userPixels, userPixelsLength);
69 fh->SetTypeToRaw(); // Even if it was possible to convert Palette to RGB
70                      // (WriteMode is set)
71  
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
78
79 // or :
80 fh->WriteDcmExplVR(newFileName);
81
82
83 // ----------------------------- WARNING -------------------------
84
85 These lines will be moved to the document-to-be 'Developer's Guide'
86
87 WriteMode : WMODE_RAW / WMODE_RGB
88 WriteType : ImplicitVR, ExplicitVR, ACR, ACR_LIBIDO
89
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( );
103    BitsAllocated 12->16
104       RemoveEntry(palettes, etc)
105       Document::WriteContent(fp, writetype);
106    RestoreWrite();
107       (moves back to the File all the archived elements)
108    RestoreWriteFileType();
109       (pushes back group 0002, with TransferSyntax)
110 */
111
112
113
114
115 namespace gdcm 
116 {
117 typedef std::map<uint16_t, int> GroupHT;    //  Hash Table
118 //-------------------------------------------------------------------------
119 // Constructor / Destructor
120 /**
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).   
132  */
133 FileHelper::FileHelper( )
134
135    FileInternal = File::New( );
136    Initialize();
137 }
138
139 /**
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
152  */
153 FileHelper::FileHelper(File *header)
154 {
155    gdcmAssertMacro(header);
156
157    FileInternal = header;
158    FileInternal->Register();
159    Initialize();
160    if ( FileInternal->IsReadable() )
161    {
162       PixelReadConverter->GrabInformationsFromFile( FileInternal, this );
163    }
164 }
165
166 /**
167  * \brief canonical destructor
168  * \note  If the header (gdcm::File) was created by the FileHelper constructor,
169  *        it is destroyed by the FileHelper
170  */
171 FileHelper::~FileHelper()
172
173    if ( PixelReadConverter )
174    {
175       delete PixelReadConverter;
176    }
177    if ( PixelWriteConverter )
178    {
179       delete PixelWriteConverter;
180    }
181    if ( Archive )
182    {
183       delete Archive;
184    }
185
186    FileInternal->Unregister();
187 }
188
189 //-----------------------------------------------------------------------------
190 // Public
191
192 /**
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    
199  */
200 void FileHelper::SetLoadMode(int loadMode) 
201
202    GetFile()->SetLoadMode( loadMode ); 
203 }
204 /**
205  * \brief Sets the LoadMode of the internal gdcm::File
206  * @param  fileName name of the file to be open  
207  */
208 void FileHelper::SetFileName(std::string const &fileName)
209 {
210    FileInternal->SetFileName( fileName );
211 }
212
213 /**
214  * \brief   Loader  
215  * @return false if file cannot be open or no swap info was found,
216  *         or no tag was found.
217  */
218 bool FileHelper::Load()
219
220    if ( !FileInternal->Load() )
221       return false;
222
223    PixelReadConverter->GrabInformationsFromFile( FileInternal, this );
224    return true;
225 }
226
227 /**
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
234  */
235 bool FileHelper::SetEntryString(std::string const &content,
236                                     uint16_t group, uint16_t elem)
237
238    return FileInternal->SetEntryString(content, group, elem);
239 }
240
241
242 /**
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
250  */
251 bool FileHelper::SetEntryBinArea(uint8_t *content, int lgth,
252                                      uint16_t group, uint16_t elem)
253 {
254    return FileInternal->SetEntryBinArea(content, lgth, group, elem);
255 }
256
257 /**
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
264  *          failed).
265  */ 
266 DataEntry *FileHelper::InsertEntryString(std::string const &content,
267                                                 uint16_t group, uint16_t elem)
268 {
269    return FileInternal->InsertEntryString(content, group, elem);
270 }
271
272 /**
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
281  *          failed).
282  */
283 DataEntry *FileHelper::InsertEntryBinArea(uint8_t *binArea, int lgth,
284                                                  uint16_t group, uint16_t elem)
285 {
286    return FileInternal->InsertEntryBinArea(binArea, lgth, group, elem);
287 }
288
289 /**
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
295  *          failed).
296  */
297 SeqEntry *FileHelper::InsertSeqEntry(uint16_t group, uint16_t elem)
298 {
299    return FileInternal->InsertSeqEntry(group, elem);
300 }
301
302 /**
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
309  */
310 size_t FileHelper::GetImageDataSize()
311 {
312    if ( PixelWriteConverter->GetUserData() )
313    {
314       return PixelWriteConverter->GetUserDataSize();
315    }
316    return PixelReadConverter->GetRGBSize();
317 }
318
319 /**
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
325  */
326 size_t FileHelper::GetImageDataRawSize()
327 {
328    if ( PixelWriteConverter->GetUserData() )
329    {
330       return PixelWriteConverter->GetUserDataSize();
331    }
332    return PixelReadConverter->GetRawSize();
333 }
334
335 /**
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 
346  */
347 uint8_t *FileHelper::GetImageData()
348 {
349    if ( PixelWriteConverter->GetUserData() )
350    {
351       return PixelWriteConverter->GetUserData();
352    }
353
354    if ( ! GetRaw() )
355    {
356       // If the decompression failed nothing can be done.
357       return 0;
358    }
359
360    if ( FileInternal->HasLUT() && PixelReadConverter->BuildRGBImage() )
361    {
362       return PixelReadConverter->GetRGB();
363    }
364    else
365    {
366       // When no LUT or LUT conversion fails, return the Raw
367       return PixelReadConverter->GetRaw();
368    }
369 }
370
371 /**
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 
381  */
382 uint8_t *FileHelper::GetImageDataRaw ()
383 {
384    return GetRaw();
385 }
386
387 #ifndef GDCM_LEGACY_REMOVE
388 /*
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
396  *          memory space.
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
405  *     
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.
413  */
414 size_t FileHelper::GetImageDataIntoVector (void *destination, size_t maxSize)
415 {
416    if ( ! GetRaw() )
417    {
418       // If the decompression failed nothing can be done.
419       return 0;
420    }
421
422    if ( FileInternal->HasLUT() && PixelReadConverter->BuildRGBImage() )
423    {
424       if ( PixelReadConverter->GetRGBSize() > maxSize )
425       {
426          gdcmWarningMacro( "Pixel data bigger than caller's expected MaxSize");
427          return 0;
428       }
429       memcpy( destination,
430               (void*)PixelReadConverter->GetRGB(),
431               PixelReadConverter->GetRGBSize() );
432       return PixelReadConverter->GetRGBSize();
433    }
434
435    // Either no LUT conversion necessary or LUT conversion failed
436    if ( PixelReadConverter->GetRawSize() > maxSize )
437    {
438       gdcmWarningMacro( "Pixel data bigger than caller's expected MaxSize");
439       return 0;
440    }
441    memcpy( destination,
442            (void *)PixelReadConverter->GetRaw(),
443            PixelReadConverter->GetRawSize() );
444    return PixelReadConverter->GetRawSize();
445 }
446 #endif
447
448 /**
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
458  *               given in bytes) 
459  * @param expectedSize total image size, *in Bytes*
460  */
461 void FileHelper::SetImageData(uint8_t *inData, size_t expectedSize)
462 {
463    SetUserData(inData, expectedSize);
464 }
465
466 /**
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
471  *               given in bytes) 
472  * @param expectedSize total image size, *in Bytes* 
473  */
474 void FileHelper::SetUserData(uint8_t *inData, size_t expectedSize)
475 {
476    PixelWriteConverter->SetUserData(inData, expectedSize);
477 }
478
479 /**
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
482  */
483 uint8_t *FileHelper::GetUserData()
484 {
485    return PixelWriteConverter->GetUserData();
486 }
487
488 /**
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
491  */
492 size_t FileHelper::GetUserDataSize()
493 {
494    return PixelWriteConverter->GetUserDataSize();
495 }
496
497 /**
498  * \brief   Get the image data from the file.
499  *          If a LUT is found, the data are expanded to be RGB
500  */
501 uint8_t *FileHelper::GetRGBData()
502 {
503    return PixelReadConverter->GetRGB();
504 }
505
506 /**
507  * \brief   Get the image data size from the file.
508  *          If a LUT is found, the data are expanded to be RGB
509  */
510 size_t FileHelper::GetRGBDataSize()
511 {
512    return PixelReadConverter->GetRGBSize();
513 }
514
515 /**
516  * \brief   Get the image data from the file.
517  *          Even when a LUT is found, the data are not expanded to RGB!
518  */
519 uint8_t *FileHelper::GetRawData()
520 {
521    return PixelReadConverter->GetRaw();
522 }
523
524 /**
525  * \brief   Get the image data size from the file.
526  *          Even when a LUT is found, the data are not expanded to RGB!
527  */
528 size_t FileHelper::GetRawDataSize()
529 {
530    return PixelReadConverter->GetRawSize();
531 }
532
533 /**
534  * \brief Access to the underlying \ref PixelReadConverter RGBA LUT
535  */
536 uint8_t* FileHelper::GetLutRGBA()
537 {
538    if ( PixelReadConverter->GetLutRGBA() ==0 )
539       PixelReadConverter->BuildLUTRGBA();
540    return PixelReadConverter->GetLutRGBA();
541 }
542
543 /**
544  * \brief Access to the underlying \ref PixelReadConverter RGBA LUT Item Number
545  */
546 int FileHelper::GetLutItemNumber()
547 {
548    return PixelReadConverter->GetLutItemNumber();
549 }
550
551 /**
552  * \brief Access to the underlying \ref PixelReadConverter RGBA LUT Item Size
553  */
554 int FileHelper::GetLutItemSize()
555 {
556    return PixelReadConverter->GetLutItemSize();
557 }
558
559 /**
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
566  */
567 bool FileHelper::WriteRawData(std::string const &fileName)
568 {
569    std::ofstream fp1(fileName.c_str(), std::ios::out | std::ios::binary );
570    if (!fp1)
571    {
572       gdcmWarningMacro( "Fail to open (write) file:" << fileName.c_str());
573       return false;
574    }
575
576    if ( PixelWriteConverter->GetUserData() )
577    {
578       fp1.write( (char *)PixelWriteConverter->GetUserData(), 
579                  PixelWriteConverter->GetUserDataSize() );
580    }
581    else if ( PixelReadConverter->GetRGB() )
582    {
583       fp1.write( (char *)PixelReadConverter->GetRGB(), 
584                  PixelReadConverter->GetRGBSize());
585    }
586    else if ( PixelReadConverter->GetRaw() )
587    {
588       fp1.write( (char *)PixelReadConverter->GetRaw(), 
589                  PixelReadConverter->GetRawSize());
590    }
591    else
592    {
593       gdcmErrorMacro( "Nothing written." );
594    }
595
596    fp1.close();
597
598    return true;
599 }
600
601 /**
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
608  */
609
610 bool FileHelper::WriteDcmImplVR (std::string const &fileName)
611 {
612    SetWriteTypeToDcmImplVR();
613    return Write(fileName);
614 }
615
616 /**
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
623  */
624
625 bool FileHelper::WriteDcmExplVR (std::string const &fileName)
626 {
627    SetWriteTypeToDcmExplVR();
628    return Write(fileName);
629 }
630
631 /**
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
643  */
644
645 bool FileHelper::WriteAcr (std::string const &fileName)
646 {
647    SetWriteTypeToAcr();
648    return Write(fileName);
649 }
650
651 /**
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
656  */
657 bool FileHelper::Write(std::string const &fileName)
658 {
659    switch(WriteType)
660    {
661       case ImplicitVR:
662          SetWriteFileTypeToImplicitVR();
663          break;
664       case Unknown:  // should never happen; ExplicitVR is the default value
665       case ExplicitVR:
666          SetWriteFileTypeToExplicitVR();
667          break;
668       case ACR:
669       case ACR_LIBIDO:
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
674       // (shame on him !)
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 !
680          break;
681       case JPEG:
682          SetWriteFileTypeToJPEG();
683          std::cerr << "Writting as JPEG" << std::endl;
684          break;
685    }
686    CheckMandatoryElements();
687
688    // --------------------------------------------------------------
689    // Special Patch to allow gdcm to re-write ACR-LibIDO formated images
690    //
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
696    
697    if ( WriteType == ACR_LIBIDO )
698    {
699       SetWriteToLibido();
700    }
701    else
702    {
703       SetWriteToNoLibido();
704    }
705    // ----------------- End of Special Patch ----------------
706   
707    switch(WriteMode)
708    {
709       case WMODE_RAW :
710          SetWriteToRaw(); // modifies and pushes to the archive, when necessary
711          break;
712       case WMODE_RGB :
713          SetWriteToRGB(); // modifies and pushes to the archive, when necessary
714          break;
715    }
716
717    bool check = CheckWriteIntegrity(); // verifies length
718    if (WriteType == JPEG ) check = true;
719    if (check)
720    {
721       check = FileInternal->Write(fileName,WriteType);
722    }
723
724    RestoreWrite();
725    RestoreWriteFileType();
726    RestoreWriteMandatory();
727
728    // --------------------------------------------------------------
729    // Special Patch to allow gdcm to re-write ACR-LibIDO formated images
730    // 
731    // ...and we restore the header to be Dicom Compliant again 
732    // just after writting
733    RestoreWriteOfLibido();
734    // ----------------- End of Special Patch ----------------
735
736    return check;
737 }
738
739 //-----------------------------------------------------------------------------
740 // Protected
741 /**
742  * \brief Checks the write integrity
743  *
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
748  */
749 bool FileHelper::CheckWriteIntegrity()
750 {
751    if ( PixelWriteConverter->GetUserData() )
752    {
753       int numberBitsAllocated = FileInternal->GetBitsAllocated();
754       if ( numberBitsAllocated == 0 || numberBitsAllocated == 12 )
755       {
756          gdcmWarningMacro( "numberBitsAllocated changed from " 
757                           << numberBitsAllocated << " to 16 " 
758                           << " for consistency purpose" );
759          numberBitsAllocated = 16;
760       }
761
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;
770
771       switch(WriteMode)
772       {
773          case WMODE_RAW :
774             if ( decSize!=PixelWriteConverter->GetUserDataSize() )
775             {
776                gdcmWarningMacro( "Data size (Raw) is incorrect. Should be " 
777                            << decSize << " / Found :" 
778                            << PixelWriteConverter->GetUserDataSize() );
779                return false;
780             }
781             break;
782          case WMODE_RGB :
783             if ( rgbSize!=PixelWriteConverter->GetUserDataSize() )
784             {
785                gdcmWarningMacro( "Data size (RGB) is incorrect. Should be " 
786                           << decSize << " / Found " 
787                           << PixelWriteConverter->GetUserDataSize() );
788                return false;
789             }
790             break;
791       }
792    }
793
794    return true;
795 }
796
797 /**
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)
801  */ 
802 void FileHelper::SetWriteToRaw()
803 {
804    if ( FileInternal->GetNumberOfScalarComponents() == 3 
805     && !FileInternal->HasLUT() )
806    {
807       SetWriteToRGB();
808    } 
809    else
810    {
811       DataEntry *photInt = CopyDataEntry(0x0028,0x0004);
812       if (FileInternal->HasLUT() )
813       {
814          photInt->SetString("PALETTE COLOR ");
815       }
816       else
817       {
818          photInt->SetString("MONOCHROME2 ");
819       }
820
821       PixelWriteConverter->SetReadData(PixelReadConverter->GetRaw(),
822                                        PixelReadConverter->GetRawSize());
823
824       std::string vr = "OB";
825       if ( FileInternal->GetBitsAllocated()>8 )
826          vr = "OW";
827       if ( FileInternal->GetBitsAllocated()==24 ) // For RGB ACR files 
828          vr = "OB";
829       DataEntry *pixel = 
830          CopyDataEntry(GetFile()->GetGrPixel(),GetFile()->GetNumPixel(),vr);
831       pixel->SetFlag(DataEntry::FLAG_PIXELDATA);
832       pixel->SetBinArea(PixelWriteConverter->GetData(),false);
833       pixel->SetLength(PixelWriteConverter->GetDataSize());
834
835       Archive->Push(photInt);
836       Archive->Push(pixel);
837
838       photInt->Delete();
839       pixel->Delete();
840    }
841 }
842
843 /**
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, )
849  */ 
850 void FileHelper::SetWriteToRGB()
851 {
852    if ( FileInternal->GetNumberOfScalarComponents()==3 )
853    {
854       PixelReadConverter->BuildRGBImage();
855       
856       DataEntry *spp = CopyDataEntry(0x0028,0x0002);
857       spp->SetString("3 ");
858
859       DataEntry *planConfig = CopyDataEntry(0x0028,0x0006);
860       planConfig->SetString("0 ");
861
862       DataEntry *photInt = CopyDataEntry(0x0028,0x0004);
863       photInt->SetString("RGB ");
864
865       if ( PixelReadConverter->GetRGB() )
866       {
867          PixelWriteConverter->SetReadData(PixelReadConverter->GetRGB(),
868                                           PixelReadConverter->GetRGBSize());
869       }
870       else // Raw data
871       {
872          PixelWriteConverter->SetReadData(PixelReadConverter->GetRaw(),
873                                           PixelReadConverter->GetRawSize());
874       }
875
876       std::string vr = "OB";
877       if ( FileInternal->GetBitsAllocated()>8 )
878          vr = "OW";
879       if ( FileInternal->GetBitsAllocated()==24 ) // For RGB ACR files 
880          vr = "OB";
881       DataEntry *pixel = 
882          CopyDataEntry(GetFile()->GetGrPixel(),GetFile()->GetNumPixel(),vr);
883       pixel->SetFlag(DataEntry::FLAG_PIXELDATA);
884       pixel->SetBinArea(PixelWriteConverter->GetData(),false);
885       pixel->SetLength(PixelWriteConverter->GetDataSize());
886
887       Archive->Push(spp);
888       Archive->Push(planConfig);
889       Archive->Push(photInt);
890       Archive->Push(pixel);
891
892       spp->Delete();
893       planConfig->Delete();
894       photInt->Delete();
895       pixel->Delete();
896
897       // Remove any LUT
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);
904
905       // push out Palette Color Lookup Table UID, if any
906       Archive->Push(0x0028,0x1199);
907
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 ) 
912       {
913          DataEntry *bitsAlloc = CopyDataEntry(0x0028,0x0100);
914          bitsAlloc->SetString("8 ");
915
916          DataEntry *bitsStored = CopyDataEntry(0x0028,0x0101);
917          bitsStored->SetString("8 ");
918
919          DataEntry *highBit = CopyDataEntry(0x0028,0x0102);
920          highBit->SetString("7 ");
921
922          Archive->Push(bitsAlloc);
923          Archive->Push(bitsStored);
924          Archive->Push(highBit);
925
926          bitsAlloc->Delete();
927          bitsStored->Delete();
928          highBit->Delete();
929       }
930    }
931    else
932    {
933       SetWriteToRaw();
934    }
935 }
936
937 /**
938  * \brief Restore the File write mode  
939  */ 
940 void FileHelper::RestoreWrite()
941 {
942    Archive->Restore(0x0028,0x0002);
943    Archive->Restore(0x0028,0x0004);
944    Archive->Restore(0x0028,0x0006);
945    Archive->Restore(GetFile()->GetGrPixel(),GetFile()->GetNumPixel());
946
947    // For old ACR-NEMA (24 bits problem)
948    Archive->Restore(0x0028,0x0100);
949    Archive->Restore(0x0028,0x0101);
950    Archive->Restore(0x0028,0x0102);
951
952    // For the LUT
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);
959
960    // For the Palette Color Lookup Table UID
961    Archive->Restore(0x0028,0x1203); 
962
963
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);
975 }
976
977 /**
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
983  */ 
984 void FileHelper::SetWriteFileTypeToACR()
985 {
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);
996 }
997
998 /**
999  * \brief Sets in the File the TransferSyntax to 'Explicit VR Little Endian"   
1000  */ 
1001 void FileHelper::SetWriteFileTypeToJPEG()
1002 {
1003    std::string ts = Util::DicomString( 
1004       Global::GetTS()->GetSpecialTransferSyntax(TS::JPEGBaselineProcess1) );
1005
1006    DataEntry *tss = CopyDataEntry(0x0002,0x0010);
1007    tss->SetString(ts);
1008
1009    Archive->Push(tss);
1010    tss->Delete();
1011 }
1012
1013 void FileHelper::SetWriteFileTypeToExplicitVR()
1014 {
1015    std::string ts = Util::DicomString( 
1016       Global::GetTS()->GetSpecialTransferSyntax(TS::ExplicitVRLittleEndian) );
1017
1018    DataEntry *tss = CopyDataEntry(0x0002,0x0010);
1019    tss->SetString(ts);
1020
1021    Archive->Push(tss);
1022    tss->Delete();
1023 }
1024
1025 /**
1026  * \brief Sets in the File the TransferSyntax to 'Implicit VR Little Endian"   
1027  */ 
1028 void FileHelper::SetWriteFileTypeToImplicitVR()
1029 {
1030    std::string ts = Util::DicomString(
1031       Global::GetTS()->GetSpecialTransferSyntax(TS::ImplicitVRLittleEndian) );
1032
1033    DataEntry *tss = CopyDataEntry(0x0002,0x0010);
1034    tss->SetString(ts);
1035
1036    Archive->Push(tss);
1037    tss->Delete();
1038 }
1039
1040
1041 /**
1042  * \brief Restore in the File the initial group 0002
1043  */ 
1044 void FileHelper::RestoreWriteFileType()
1045 {
1046 }
1047
1048 /**
1049  * \brief Set the Write not to Libido format
1050  */ 
1051 void FileHelper::SetWriteToLibido()
1052 {
1053    DataEntry *oldRow = FileInternal->GetDataEntry(0x0028, 0x0010);
1054    DataEntry *oldCol = FileInternal->GetDataEntry(0x0028, 0x0011);
1055    
1056    if ( oldRow && oldCol )
1057    {
1058       std::string rows, columns; 
1059
1060       DataEntry *newRow=DataEntry::New(oldRow->GetDictEntry());
1061       DataEntry *newCol=DataEntry::New(oldCol->GetDictEntry());
1062
1063       newRow->Copy(oldCol);
1064       newCol->Copy(oldRow);
1065
1066       newRow->SetString(oldCol->GetString());
1067       newCol->SetString(oldRow->GetString());
1068
1069       Archive->Push(newRow);
1070       Archive->Push(newCol);
1071
1072       newRow->Delete();
1073       newCol->Delete();
1074    }
1075
1076    DataEntry *libidoCode = CopyDataEntry(0x0008,0x0010);
1077    libidoCode->SetString("ACRNEMA_LIBIDO_1.1");
1078    Archive->Push(libidoCode);
1079    libidoCode->Delete();
1080 }
1081
1082 /**
1083  * \brief Set the Write not to No Libido format
1084  */ 
1085 void FileHelper::SetWriteToNoLibido()
1086 {
1087    DataEntry *recCode = FileInternal->GetDataEntry(0x0008,0x0010);
1088    if ( recCode )
1089    {
1090       if ( recCode->GetString() == "ACRNEMA_LIBIDO_1.1" )
1091       {
1092          DataEntry *libidoCode = CopyDataEntry(0x0008,0x0010);
1093          libidoCode->SetString("");
1094          Archive->Push(libidoCode);
1095          libidoCode->Delete();
1096       }
1097    }
1098 }
1099
1100 /**
1101  * \brief Restore the Write format
1102  */ 
1103 void FileHelper::RestoreWriteOfLibido()
1104 {
1105    Archive->Restore(0x0028,0x0010);
1106    Archive->Restore(0x0028,0x0011);
1107    Archive->Restore(0x0008,0x0010);
1108
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);
1114 }
1115
1116 /**
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).
1123  */ 
1124 DataEntry *FileHelper::CopyDataEntry(uint16_t group, uint16_t elem,
1125                                    const TagName &vr)
1126 {
1127    DocEntry *oldE = FileInternal->GetDocEntry(group, elem);
1128    DataEntry *newE;
1129
1130    if ( oldE && vr != GDCM_VRUNKNOWN ) 
1131       if ( oldE->GetVR() != vr )
1132          oldE = NULL;
1133
1134    if ( oldE )
1135    {
1136       newE = DataEntry::New(oldE->GetDictEntry());
1137       newE->Copy(oldE);
1138    }
1139    else
1140    {
1141       newE = GetFile()->NewDataEntry(group, elem, vr);
1142    }
1143
1144    return newE;
1145 }
1146
1147 /**
1148  * \brief   This method is called automatically, just before writting
1149  *         in order to produce a 'True Dicom V3' image.
1150  *
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.
1161  *
1162  * \note
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
1170  * 
1171  * \todo 
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.           
1181  */ 
1182
1183 /* -------------------------------------------------------------------------------------
1184 To be moved to User's guide / WIKI  ?
1185
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.
1191
1192 gdcm::FileHelper::CheckMandatoryElements() deals automatically with these cases.
1193
1194 1)2)3)4)
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
1199 them.
1200
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
1206  Warning :     
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
1209      
1210  None of the 'shadow elements' are droped out.
1211      
1212
1213 1)
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.
1217  
1218 1)2)3)
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)
1224
1225 2)4)
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'
1232
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
1240        
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)
1249
1250
1251 Bellow follows the full description (hope so !) of the consistency checks performed 
1252 by gdcm::FileHelper::CheckMandatoryElements()
1253
1254
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)
1262
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)
1268    
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)
1273             
1274 --> 'Modality' (0x0008,0x0060)   
1275     is defaulted to "OT" (other) if missing.   
1276     (a fully user created image belongs to *no* modality)
1277       
1278 --> 'Media Storage SOP Instance UID' (0x0002,0x0003)
1279 --> 'Implementation Class UID'       (0x0002,0x0012)
1280     are automatically generated; no user intervention possible
1281
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' 
1287      for various images
1288      Warning :     
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 
1291              
1292 --> If 'SOP Class UID' exists in the native image  ('true DICOM' image)
1293     we create the 'Source Image Sequence' SeqEntry (0x0008, 0x2112)
1294     
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'
1299     
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)
1303
1304 --> Imager Pixel Spacing (0x0018,0x1164) : defaulted to Pixel Spacing value
1305
1306 --> Instance Creation Date, Instance Creation Time are forced to current Date and Time
1307
1308 --> Study Date, Study Time are defaulted to current Date and Time
1309    (they remain unchanged if they exist)
1310
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)
1314    
1315 --> Study ID, Series Number, Instance Number, Patient Orientation (Type 2)
1316     are created, with empty value if there are missing.
1317
1318 --> Manufacturer, Institution Name, Patient's Name, (Type 2)
1319     are defaulted with a 'gdcm' value.
1320     
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.  
1324
1325  -------------------------------------------------------------------------------------*/
1326  
1327 void FileHelper::CheckMandatoryElements()
1328 {
1329    std::string sop =  Util::CreateUniqueUID();
1330    
1331    // just to remember : 'official' 0002 group
1332    if ( WriteType != ACR && WriteType != ACR_LIBIDO )
1333    {
1334      // Group 000002 (Meta Elements) already pushed out
1335   
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
1346   
1347    // Create them if not found
1348    // Always modify the value
1349    // Push the entries to the archive.
1350       CopyMandatoryEntry(0x0002,0x0000,"0");
1351  
1352       DataEntry *e_0002_0001 = CopyDataEntry(0x0002,0x0001, "OB");
1353       e_0002_0001->SetBinArea((uint8_t*)Util::GetFileMetaInformationVersion(),
1354                                false);
1355       e_0002_0001->SetLength(2);
1356       Archive->Push(e_0002_0001);
1357       e_0002_0001->Delete(); 
1358
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");    
1363       else
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");    
1367        
1368    // 'Media Storage SOP Instance UID'   
1369       CopyMandatoryEntry(0x0002,0x0003,sop);
1370       
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());
1375
1376    // 'Implementation Version Name'
1377       std::string version = "GDCM ";
1378       version += Util::GetVersion();
1379       CopyMandatoryEntry(0x0002,0x0013,version);
1380    }
1381
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);
1387
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")
1391    {
1392       CopyMandatoryEntry(0x0028,0x0100,"16");
1393    }
1394
1395    // Check if user wasn't drunk ;-)
1396
1397    std::ostringstream s;
1398    // check 'Bits Allocated' vs decent values
1399    int nbBitsAllocated = FileInternal->GetBitsAllocated();
1400    if ( nbBitsAllocated == 0 || nbBitsAllocated > 32)
1401    {
1402       CopyMandatoryEntry(0x0028,0x0100,"16");
1403       gdcmWarningMacro("(0028,0100) changed from "
1404          << nbBitsAllocated << " to 16 for consistency purpose");
1405       nbBitsAllocated = 16; 
1406    }
1407    // check 'Bits Stored' vs 'Bits Allocated'   
1408    int nbBitsStored = FileInternal->GetBitsStored();
1409    if ( nbBitsStored == 0 || nbBitsStored > nbBitsAllocated )
1410    {
1411       s.str("");
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; 
1418     }
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  )
1424    {
1425       s.str("");
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");
1431    }
1432
1433    std::string pixelSpacing = FileInternal->GetEntryString(0x0028,0x0030);
1434    if ( pixelSpacing == GDCM_UNFOUND )
1435    {
1436       pixelSpacing = "1.0\\1.0";
1437        // if missing, Pixel Spacing forced to "1.0\1.0"
1438       CopyMandatoryEntry(0x0028,0x0030,pixelSpacing);
1439    }
1440    
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);
1446    
1447    // Samples Per Pixel (type 1) : default to grayscale 
1448    CheckMandatoryEntry(0x0028,0x0002,"1");
1449    
1450    // --- Check UID-related Entries ---
1451
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
1455
1456    DataEntry *e_0008_0016 = FileInternal->GetDataEntry(0x0008, 0x0016);
1457    if ( e_0008_0016 )
1458    {
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();
1470       
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();
1478
1479       sis->AddSQItem(sqi,1);
1480       sqi->Delete();
1481
1482       // temporarily replaces any previous 'Source Image Sequence' 
1483       Archive->Push(sis);
1484       sis->Delete();
1485  
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");    
1492       else
1493    // Potentialy this is a post-processed image 
1494    // (The written image is no longer an 'ORIGINAL' one)
1495       CopyMandatoryEntry(0x0008,0x0008,"DERIVED\\PRIMARY");
1496
1497    }
1498
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);
1503    
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'
1507    
1508       // 'Media Storage SOP Class UID' : [Secondary Capture Image Storage]
1509    if ( KeepMediaStorageSOPClassUID)
1510    {      
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");    
1514    }
1515    else
1516    {
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");    
1520
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)
1526
1527        // Conversion Type.
1528        // Other possible values are :
1529        // See PS 3.3, Page 408
1530    
1531        // DV = Digitized Video
1532        // DI = Digital Interface   
1533        // DF = Digitized Film
1534        // WSD = Workstation
1535        // SD = Scanned Document
1536        // SI = Scanned Image
1537        // DRW = Drawing
1538        // SYN = Synthetic Image
1539      
1540       CheckMandatoryEntry(0x0008,0x0064,"SYN");
1541    }   
1542            
1543    // ---- The user will never have to take any action on the following ----
1544    
1545    // new value for 'SOP Instance UID'
1546    //SetMandatoryEntry(0x0008,0x0018,Util::CreateUniqueUID());
1547
1548    // Instance Creation Date
1549    const std::string &date = Util::GetCurrentDate();
1550    CopyMandatoryEntry(0x0008,0x0012,date);
1551  
1552    // Instance Creation Time
1553    const std::string &time = Util::GetCurrentTime();
1554    CopyMandatoryEntry(0x0008,0x0013,time);
1555
1556    // Study Date
1557    CheckMandatoryEntry(0x0008,0x0020,date);
1558    // Study Time
1559    CheckMandatoryEntry(0x0008,0x0030,time);
1560
1561    // Accession Number
1562    //CopyMandatoryEntry(0x0008,0x0050,"");
1563    CheckMandatoryEntry(0x0008,0x0050,"");
1564    
1565
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
1574
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());
1582
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());
1590
1591    // Study ID
1592    CheckMandatoryEntry(0x0020,0x0010,"");
1593
1594    // Series Number
1595    CheckMandatoryEntry(0x0020,0x0011,"");
1596
1597    // Instance Number
1598    CheckMandatoryEntry(0x0020,0x0013,"");
1599    
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 );
1604    o->Delete();
1605    if (ori != "\\" )
1606       CheckMandatoryEntry(0x0020,0x0020,ori);
1607    else   
1608       CheckMandatoryEntry(0x0020,0x0020,"");
1609    
1610    // Modality : if missing we set it to 'OTher'
1611    CheckMandatoryEntry(0x0008,0x0060,"OT");
1612
1613    // Manufacturer : if missing we set it to 'GDCM Factory'
1614    CheckMandatoryEntry(0x0008,0x0070,"GDCM Factory");
1615
1616    // Institution Name : if missing we set it to 'GDCM Hospital'
1617    CheckMandatoryEntry(0x0008,0x0080,"GDCM Hospital");
1618
1619    // Patient's Name : if missing, we set it to 'GDCM^Patient'
1620    CheckMandatoryEntry(0x0010,0x0010,"GDCM^Patient");
1621
1622    // Patient ID
1623    CheckMandatoryEntry(0x0010,0x0020,"");
1624
1625    // Patient's Birth Date : 'type 2' entry -> must exist, value not mandatory
1626    CheckMandatoryEntry(0x0010,0x0030,"");
1627
1628    // Patient's Sex :'type 2' entry -> must exist, value not mandatory
1629    CheckMandatoryEntry(0x0010,0x0040,"");
1630
1631    // Referring Physician's Name :'type 2' entry -> must exist, value not mandatory
1632    CheckMandatoryEntry(0x0008,0x0090,"");
1633    
1634    // Remove some inconstencies (probably some more will be added)
1635
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);
1640    if ( !e_0028_0008 )
1641    {
1642       Archive->Push(0x0020, 0x0052);
1643    }
1644  
1645    // Deal with element 0x0000 (group length) of each group.
1646    // First stage : get all the different Groups
1647  /*
1648   GroupHT grHT;
1649   DocEntry *d = FileInternal->GetFirstEntry();
1650   while(d)
1651   {
1652     grHT[d->GetGroup()] = 0;
1653     d=FileInternal->GetNextEntry();
1654   }
1655   // Second stage : add the missing ones (if any)
1656   for (GroupHT::iterator it = grHT.begin(); it != grHT.end(); ++it)  
1657   {
1658       CheckMandatoryEntry(it->first, 0x0000, "0"); 
1659   }    
1660   // Third stage : update all 'zero level' groups length
1661 */   
1662
1663
1664 void FileHelper::CheckMandatoryEntry(uint16_t group,uint16_t elem,std::string value)
1665 {
1666    DataEntry *entry = FileInternal->GetDataEntry(group,elem);
1667    if ( !entry )
1668    {
1669       entry = DataEntry::New(Global::GetDicts()->GetDefaultPubDict()->GetEntry(group,elem));
1670       entry->SetString(value);
1671       Archive->Push(entry);
1672       entry->Delete();
1673    }
1674 }
1675
1676 void FileHelper::SetMandatoryEntry(uint16_t group,uint16_t elem,std::string value)
1677 {
1678    DataEntry *entry = DataEntry::New(Global::GetDicts()->GetDefaultPubDict()->GetEntry(group,elem));
1679    entry->SetString(value);
1680    Archive->Push(entry);
1681    entry->Delete();
1682 }
1683
1684 void FileHelper::CopyMandatoryEntry(uint16_t group,uint16_t elem,std::string value)
1685 {
1686    DataEntry *entry = CopyDataEntry(group,elem);
1687    entry->SetString(value);
1688    Archive->Push(entry);
1689    entry->Delete();
1690 }
1691
1692 /**
1693  * \brief Restore in the File the initial group 0002
1694  */
1695 void FileHelper::RestoreWriteMandatory()
1696 {
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);
1708
1709    // FIXME : Check if none is missing !
1710    
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);
1720
1721    Archive->Restore(0x0010,0x0010);
1722    Archive->Restore(0x0010,0x0030);
1723    Archive->Restore(0x0010,0x0040);
1724
1725    Archive->Restore(0x0020,0x000d);
1726    Archive->Restore(0x0020,0x000e);
1727 }
1728
1729
1730 /**
1731  * \brief   CallStartMethod
1732  */
1733 void FileHelper::CallStartMethod()
1734 {
1735    Progress = 0.0f;
1736    Abort    = false;
1737    CommandManager::ExecuteCommand(this,CMD_STARTPROGRESS);
1738 }
1739
1740 /**
1741  * \brief   CallProgressMethod
1742  */
1743 void FileHelper::CallProgressMethod()
1744 {
1745    CommandManager::ExecuteCommand(this,CMD_PROGRESS);
1746 }
1747
1748 /**
1749  * \brief   CallEndMethod
1750  */
1751 void FileHelper::CallEndMethod()
1752 {
1753    Progress = 1.0f;
1754    CommandManager::ExecuteCommand(this,CMD_ENDPROGRESS);
1755 }
1756
1757 //-----------------------------------------------------------------------------
1758 // Private
1759 /**
1760  * \brief Factorization for various forms of constructors.
1761  */
1762 void FileHelper::Initialize()
1763 {
1764    UserFunction = 0;
1765    KeepMediaStorageSOPClassUID = false;
1766    
1767    WriteMode = WMODE_RAW;
1768    WriteType = ExplicitVR;
1769
1770    PixelReadConverter  = new PixelReadConvert;
1771    PixelWriteConverter = new PixelWriteConvert;
1772    Archive = new DocEntryArchive( FileInternal );
1773 }
1774
1775 /**
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) 
1780  */ 
1781 uint8_t *FileHelper::GetRaw()
1782 {
1783    PixelReadConverter->SetUserFunction( UserFunction );
1784
1785    uint8_t *raw = PixelReadConverter->GetRaw();
1786    if ( ! raw )
1787    {
1788       // The Raw image migth not be loaded yet:
1789       std::ifstream *fp = FileInternal->OpenFile();
1790       PixelReadConverter->ReadAndDecompressPixelData( fp );
1791       if ( fp ) 
1792          FileInternal->CloseFile();
1793
1794       raw = PixelReadConverter->GetRaw();
1795       if ( ! raw )
1796       {
1797          gdcmWarningMacro( "Read/decompress of pixel data apparently went wrong.");
1798          return 0;
1799       }
1800    }
1801    return raw;
1802 }
1803
1804 //-----------------------------------------------------------------------------
1805 /**
1806  * \brief   Prints the common part of DataEntry, SeqEntry
1807  * @param   os ostream we want to print in
1808  * @param indent (unused)
1809  */
1810 void FileHelper::Print(std::ostream &os, std::string const &)
1811 {
1812    FileInternal->SetPrintLevel(PrintLevel);
1813    FileInternal->Print(os);
1814
1815    if ( FileInternal->IsReadable() )
1816    {
1817       PixelReadConverter->SetPrintLevel(PrintLevel);
1818       PixelReadConverter->Print(os);
1819    }
1820 }
1821
1822 //-----------------------------------------------------------------------------
1823 } // end namespace gdcm