]> Creatis software - gdcm.git/blob - src/gdcmFileHelper.cxx
Unnormalized 4th dimension is now dealt with.
[gdcm.git] / src / gdcmFileHelper.cxx
1 /*=========================================================================
2                                                                                 
3   Program:   gdcm
4   Module:    $RCSfile: gdcmFileHelper.cxx,v $
5   Language:  C++
6
7   Date:      $Date: 2006/03/29 16:09:48 $
8   Version:   $Revision: 1.98 $
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 "gdcmDocEntry.h"
30 #include "gdcmFile.h"
31 #include "gdcmPixelReadConvert.h"
32 #include "gdcmPixelWriteConvert.h"
33 #include "gdcmDocEntryArchive.h"
34 #include "gdcmDictSet.h"
35 #include "gdcmOrientation.h"
36  
37 #if defined(__BORLANDC__)
38    #include <mem.h> // for memset
39 #endif 
40
41 #include <fstream>
42
43 /*
44 // ----------------------------- WARNING -------------------------
45
46 These lines will be moved to the document-to-be 'User's Guide'
47
48 // To read an image, user needs a gdcm::File
49 gdcm::File *f = new gdcm::File(fileName);
50 // or (advanced) :
51 // user may also decide he doesn't want to load some parts of the header
52 gdcm::File *f = new gdcm::File();
53 f->SetFileName(fileName);
54    f->SetLoadMode(LD_NOSEQ);             // or      
55    f->SetLoadMode(LD_NOSHADOW);          // or
56    f->SetLoadMode(LD_NOSEQ | LD_NOSHADOW); // or
57    f->SetLoadMode(LD_NOSHADOWSEQ);
58 f->Load();
59
60 // user can now check some values
61 std::string v = f->GetEntryValue(groupNb,ElementNb);
62
63 // to get the pixels, user needs a gdcm::FileHelper
64 gdcm::FileHelper *fh = new gdcm::FileHelper(f);
65 // user may ask not to convert Palette to RGB
66 uint8_t *pixels = fh->GetImageDataRaw();
67 int imageLength = fh->GetImageDataRawSize();
68 // He can now use the pixels, create a new image, ...
69 uint8_t *userPixels = ...
70
71 To re-write the image, user re-uses the gdcm::FileHelper
72
73 fh->SetImageData( userPixels, userPixelsLength);
74 fh->SetTypeToRaw(); // Even if it was possible to convert Palette to RGB
75                      // (WriteMode is set)
76  
77 fh->SetWriteTypeToDcmExpl();  // he wants Explicit Value Representation
78                               // Little Endian is the default
79                               // no other value is allowed
80                                 (-->SetWriteType(ExplicitVR);)
81                                    -->WriteType = ExplicitVR;
82 fh->Write(newFileName);      // overwrites the file, if any
83
84 // or :
85 fh->WriteDcmExplVR(newFileName);
86
87
88 // ----------------------------- WARNING -------------------------
89
90 These lines will be moved to the document-to-be 'Developer's Guide'
91
92 WriteMode : WMODE_RAW / WMODE_RGB
93 WriteType : ImplicitVR, ExplicitVR, ACR, ACR_LIBIDO
94
95 fh1->Write(newFileName);
96    SetWriteFileTypeToImplicitVR() / SetWriteFileTypeToExplicitVR();
97    (modifies TransferSyntax)
98    SetWriteToRaw(); / SetWriteToRGB();
99       (modifies, when necessary : photochromatic interpretation, 
100          samples per pixel, Planar configuration, 
101          bits allocated, bits stored, high bit -ACR 24 bits-
102          Pixels element VR, pushes out the LUT )
103    CheckWriteIntegrity();
104       (checks user given pixels length)
105    FileInternal->Write(fileName,WriteType)
106    fp = opens file(fileName);
107    ComputeGroup0002Length( );
108    BitsAllocated 12->16
109       RemoveEntry(palettes, etc)
110       Document::WriteContent(fp, writetype);
111    RestoreWrite();
112       (moves back to the File all the archived elements)
113    RestoreWriteFileType();
114       (pushes back group 0002, with TransferSyntax)
115 */
116
117
118
119
120 namespace gdcm 
121 {
122 typedef std::map<uint16_t, int> GroupHT;    //  Hash Table
123 //-------------------------------------------------------------------------
124 // Constructor / Destructor
125 /**
126  * \brief Constructor dedicated to deal with the *pixels* area of a ACR/DICOMV3
127  *        file (gdcm::File only deals with the ... header)
128  *        Opens (in read only and when possible) an existing file and checks
129  *        for DICOM compliance. Returns NULL on failure.
130  *        It will be up to the user to load the pixels into memory
131  *        ( GetImageDataSize() + GetImageData() methods)
132  * \note  the in-memory representation of all available tags found in
133  *        the DICOM header is post-poned to first header information access.
134  *        This avoid a double parsing of public part of the header when
135  *        one sets an a posteriori shadow dictionary (efficiency can be
136  *        seen as a side effect).   
137  */
138 FileHelper::FileHelper( )
139
140    FileInternal = File::New( );
141    Initialize();
142 }
143
144 /**
145  * \brief Constructor dedicated to deal with the *pixels* area of a ACR/DICOMV3
146  *        file (File only deals with the ... header)
147  *        Opens (in read only and when possible) an existing file and checks
148  *        for DICOM compliance. Returns NULL on failure.
149  *        It will be up to the user to load the pixels into memory
150  *        ( GetImageDataSize() + GetImageData() methods)
151  * \note  the in-memory representation of all available tags found in
152  *        the DICOM header is post-poned to first header information access.
153  *        This avoid a double parsing of public part of the header when
154  *        user sets an a posteriori shadow dictionary (efficiency can be
155  *        seen as a side effect).   
156  * @param header already built Header
157  */
158 FileHelper::FileHelper(File *header)
159 {
160    gdcmAssertMacro(header);
161
162    FileInternal = header;
163    FileInternal->Register();
164    Initialize();
165    if ( FileInternal->IsReadable() )
166    {
167       PixelReadConverter->GrabInformationsFromFile( FileInternal, this );
168    }
169 }
170
171 /**
172  * \brief canonical destructor
173  * \note  If the header (gdcm::File) was created by the FileHelper constructor,
174  *        it is destroyed by the FileHelper
175  */
176 FileHelper::~FileHelper()
177
178    if ( PixelReadConverter )
179    {
180       delete PixelReadConverter;
181    }
182    if ( PixelWriteConverter )
183    {
184       delete PixelWriteConverter;
185    }
186    if ( Archive )
187    {
188       delete Archive;
189    }
190
191    FileInternal->Unregister();
192 }
193
194 //-----------------------------------------------------------------------------
195 // Public
196
197 /**
198  * \brief Sets the LoadMode of the internal gdcm::File as a boolean string. 
199  *        NO_SEQ, NO_SHADOW, NO_SHADOWSEQ ... (nothing more, right now)
200  *        WARNING : before using NO_SHADOW, be sure *all* your files
201  *        contain accurate values in the 0x0000 element (if any) 
202  *        of *each* Shadow Group. The parser will fail if the size is wrong !
203  * @param   loadMode Load mode to be used    
204  */
205 void FileHelper::SetLoadMode(int loadMode) 
206
207    GetFile()->SetLoadMode( loadMode ); 
208 }
209 /**
210  * \brief Sets the LoadMode of the internal gdcm::File
211  * @param  fileName name of the file to be open  
212  */
213 void FileHelper::SetFileName(std::string const &fileName)
214 {
215    FileInternal->SetFileName( fileName );
216 }
217
218 /**
219  * \brief   Loader  
220  * @return false if file cannot be open or no swap info was found,
221  *         or no tag was found.
222  */
223 bool FileHelper::Load()
224
225    if ( !FileInternal->Load() )
226       return false;
227
228    PixelReadConverter->GrabInformationsFromFile( FileInternal, this );
229    return true;
230 }
231
232 /**
233  * \brief   Accesses an existing DataEntry through it's (group, element) 
234  *          and modifies it's content with the given value.
235  * @param   content new value (string) to substitute with
236  * @param   group  group number of the Dicom Element to modify
237  * @param   elem element number of the Dicom Element to modify
238  * \return  false if DataEntry not found
239  */
240 bool FileHelper::SetEntryString(std::string const &content,
241                                 uint16_t group, uint16_t elem)
242
243    return FileInternal->SetEntryString(content, group, elem);
244 }
245
246
247 /**
248  * \brief   Accesses an existing DataEntry through it's (group, element) 
249  *          and modifies it's content with the given value.
250  * @param   content new value (void*  -> uint8_t*) to substitute with
251  * @param   lgth new value length
252  * @param   group  group number of the Dicom Element to modify
253  * @param   elem element number of the Dicom Element to modify
254  * \return  false if DataEntry not found
255  */
256 bool FileHelper::SetEntryBinArea(uint8_t *content, int lgth,
257                                  uint16_t group, uint16_t elem)
258 {
259    return FileInternal->SetEntryBinArea(content, lgth, group, elem);
260 }
261
262 /**
263  * \brief   Modifies the value of a given DataEntry when it exists.
264  *          Creates it with the given value when unexistant.
265  * @param   content (string) value to be set
266  * @param   group   Group number of the Entry 
267  * @param   elem  Element number of the Entry
268  * \return  pointer to the modified/created DataEntry (NULL when creation
269  *          failed).
270  */ 
271 DataEntry *FileHelper::InsertEntryString(std::string const &content,
272                                                 uint16_t group, uint16_t elem)
273 {
274    return FileInternal->InsertEntryString(content, group, elem);
275 }
276
277 /**
278  * \brief   Modifies the value of a given DataEntry when it exists.
279  *          Creates it with the given value when unexistant.
280  *          A copy of the binArea is made to be kept in the Document.
281  * @param   binArea (binary)value to be set
282  * @param   lgth new value length
283  * @param   group   Group number of the Entry 
284  * @param   elem  Element number of the Entry
285  * \return  pointer to the modified/created DataEntry (NULL when creation
286  *          failed).
287  */
288 DataEntry *FileHelper::InsertEntryBinArea(uint8_t *binArea, int lgth,
289                                           uint16_t group, uint16_t elem)
290 {
291    return FileInternal->InsertEntryBinArea(binArea, lgth, group, elem);
292 }
293
294 /**
295  * \brief   Adds an empty SeqEntry 
296  *          (remove any existing entry with same group,elem)
297  * @param   group   Group number of the Entry 
298  * @param   elem  Element number of the Entry
299  * \return  pointer to the created SeqEntry (NULL when creation
300  *          failed).
301  */
302 SeqEntry *FileHelper::InsertSeqEntry(uint16_t group, uint16_t elem)
303 {
304    return FileInternal->InsertSeqEntry(group, elem);
305 }
306
307 /**
308  * \brief   Get the size of the image data
309  *          If the image can be RGB (with a lut or by default), the size 
310  *          corresponds to the RGB image
311  *         (use GetImageDataRawSize if you want to be sure to get *only*
312  *          the size of the pixels)
313  * @return  The image size
314  */
315 size_t FileHelper::GetImageDataSize()
316 {
317    if ( PixelWriteConverter->GetUserData() )
318    {
319       return PixelWriteConverter->GetUserDataSize();
320    }
321    return PixelReadConverter->GetRGBSize();
322 }
323
324 /**
325  * \brief   Get the size of the image data.
326  *          If the image could be converted to RGB using a LUT, 
327  *          this transformation is not taken into account by GetImageDataRawSize
328  *          (use GetImageDataSize if you wish)
329  * @return  The raw image size
330  */
331 size_t FileHelper::GetImageDataRawSize()
332 {
333    if ( PixelWriteConverter->GetUserData() )
334    {
335       return PixelWriteConverter->GetUserDataSize();
336    }
337    return PixelReadConverter->GetRawSize();
338 }
339
340 /**
341  * \brief brings pixels into memory :  
342  *          - Allocates necessary memory,
343  *          - Reads the pixels from disk (uncompress if necessary),
344  *          - Transforms YBR pixels, if any, into RGB pixels,
345  *          - Transforms 3 planes R, G, B, if any, into a single RGB Plane
346  *          - Transforms single Grey plane + 3 Palettes into a RGB Plane
347  *          - Copies the pixel data (image[s]/volume[s]) to newly allocated zone.
348  * @return  Pointer to newly allocated pixel data.
349  *          (uint8_t is just for prototyping. feel free to cast)
350  *          NULL if alloc fails 
351  */
352 uint8_t *FileHelper::GetImageData()
353 {
354    if ( PixelWriteConverter->GetUserData() )
355    {
356       return PixelWriteConverter->GetUserData();
357    }
358
359    if ( ! GetRaw() )
360    {
361       // If the decompression failed nothing can be done.
362       return 0;
363    }
364
365    if ( FileInternal->HasLUT() && PixelReadConverter->BuildRGBImage() )
366    {
367       return PixelReadConverter->GetRGB();
368    }
369    else
370    {
371       // When no LUT or LUT conversion fails, return the Raw
372       return PixelReadConverter->GetRaw();
373    }
374 }
375
376 /**
377  * \brief brings pixels into memory :  
378  *          - Allocates necessary memory, 
379  *          - Transforms YBR pixels (if any) into RGB pixels
380  *          - Transforms 3 planes R, G, B  (if any) into a single RGB Plane
381  *          - Copies the pixel data (image[s]/volume[s]) to newly allocated zone. 
382  *          - DOES NOT transform Grey plane + 3 Palettes into a RGB Plane
383  * @return  Pointer to newly allocated pixel data.
384  *          (uint8_t is just for prototyping. feel free to cast)
385  *          NULL if alloc fails
386  */
387 uint8_t *FileHelper::GetImageDataRaw ()
388 {
389    return GetRaw();
390 }
391
392 #ifndef GDCM_LEGACY_REMOVE
393 /*
394  * \brief   Useless function, since PixelReadConverter forces us 
395  *          copy the Pixels anyway.  
396  *          Reads the pixels from disk (uncompress if necessary),
397  *          Transforms YBR pixels, if any, into RGB pixels
398  *          Transforms 3 planes R, G, B, if any, into a single RGB Plane
399  *          Transforms single Grey plane + 3 Palettes into a RGB Plane   
400  *          Copies at most MaxSize bytes of pixel data to caller allocated
401  *          memory space.
402  * \warning This function allows people that want to build a volume
403  *          from an image stack *not to* have, first to get the image pixels, 
404  *          and then move them to the volume area.
405  *          It's absolutely useless for any VTK user since vtk chooses 
406  *          to invert the lines of an image, that is the last line comes first
407  *          (for some axis related reasons?). Hence he will have 
408  *          to load the image line by line, starting from the end.
409  *          VTK users have to call GetImageData
410  *     
411  * @param   destination Address (in caller's memory space) at which the
412  *          pixel data should be copied
413  * @param   maxSize Maximum number of bytes to be copied. When MaxSize
414  *          is not sufficient to hold the pixel data the copy is not
415  *          executed (i.e. no partial copy).
416  * @return  On success, the number of bytes actually copied. Zero on
417  *          failure e.g. MaxSize is lower than necessary.
418  */
419 size_t FileHelper::GetImageDataIntoVector (void *destination, size_t maxSize)
420 {
421    if ( ! GetRaw() )
422    {
423       // If the decompression failed nothing can be done.
424       return 0;
425    }
426
427    if ( FileInternal->HasLUT() && PixelReadConverter->BuildRGBImage() )
428    {
429       if ( PixelReadConverter->GetRGBSize() > maxSize )
430       {
431          gdcmWarningMacro( "Pixel data bigger than caller's expected MaxSize");
432          return 0;
433       }
434       memcpy( destination,
435               (void*)PixelReadConverter->GetRGB(),
436               PixelReadConverter->GetRGBSize() );
437       return PixelReadConverter->GetRGBSize();
438    }
439
440    // Either no LUT conversion necessary or LUT conversion failed
441    if ( PixelReadConverter->GetRawSize() > maxSize )
442    {
443       gdcmWarningMacro( "Pixel data bigger than caller's expected MaxSize");
444       return 0;
445    }
446    memcpy( destination,
447            (void *)PixelReadConverter->GetRaw(),
448            PixelReadConverter->GetRawSize() );
449    return PixelReadConverter->GetRawSize();
450 }
451 #endif
452
453 /**
454  * \brief   Points the internal pointer to the callers inData
455  *          image representation, BUT WITHOUT COPYING THE DATA.
456  *          'image' Pixels are presented as C-like 2D arrays : line per line.
457  *          'volume'Pixels are presented as C-like 3D arrays : plane per plane 
458  * \warning Since the pixels are not copied, it is the caller's responsability
459  *          not to deallocate its data before gdcm uses them (e.g. with
460  *          the Write() method )
461  * @param inData user supplied pixel area (uint8_t* is just for the compiler.
462  *               user is allowed to pass any kind of pixelsn since the size is
463  *               given in bytes) 
464  * @param expectedSize total image size, *in Bytes*
465  */
466 void FileHelper::SetImageData(uint8_t *inData, size_t expectedSize)
467 {
468    SetUserData(inData, expectedSize);
469 }
470
471 /**
472  * \brief   Set the image data defined by the user
473  * \warning When writting the file, this data are get as default data to write
474  * @param inData user supplied pixel area (uint8_t* is just for the compiler.
475  *               user is allowed to pass any kind of pixels since the size is
476  *               given in bytes) 
477  * @param expectedSize total image size, *in Bytes* 
478  */
479 void FileHelper::SetUserData(uint8_t *inData, size_t expectedSize)
480 {
481    PixelWriteConverter->SetUserData(inData, expectedSize);
482 }
483
484 /**
485  * \brief   Get the image data defined by the user
486  * \warning When writting the file, this data are get as default data to write
487  */
488 uint8_t *FileHelper::GetUserData()
489 {
490    return PixelWriteConverter->GetUserData();
491 }
492
493 /**
494  * \brief   Get the image data size defined by the user
495  * \warning When writting the file, this data are get as default data to write
496  */
497 size_t FileHelper::GetUserDataSize()
498 {
499    return PixelWriteConverter->GetUserDataSize();
500 }
501
502 /**
503  * \brief   Get the image data from the file.
504  *          If a LUT is found, the data are expanded to be RGB
505  */
506 uint8_t *FileHelper::GetRGBData()
507 {
508    return PixelReadConverter->GetRGB();
509 }
510
511 /**
512  * \brief   Get the image data size from the file.
513  *          If a LUT is found, the data are expanded to be RGB
514  */
515 size_t FileHelper::GetRGBDataSize()
516 {
517    return PixelReadConverter->GetRGBSize();
518 }
519
520 /**
521  * \brief   Get the image data from the file.
522  *          Even when a LUT is found, the data are not expanded to RGB!
523  */
524 uint8_t *FileHelper::GetRawData()
525 {
526    return PixelReadConverter->GetRaw();
527 }
528
529 /**
530  * \brief   Get the image data size from the file.
531  *          Even when a LUT is found, the data are not expanded to RGB!
532  */
533 size_t FileHelper::GetRawDataSize()
534 {
535    return PixelReadConverter->GetRawSize();
536 }
537
538 /**
539  * \brief Access to the underlying \ref PixelReadConverter RGBA LUT
540  */
541 uint8_t* FileHelper::GetLutRGBA()
542 {
543    if ( PixelReadConverter->GetLutRGBA() ==0 )
544       PixelReadConverter->BuildLUTRGBA();
545    return PixelReadConverter->GetLutRGBA();
546 }
547
548 /**
549  * \brief Access to the underlying \ref PixelReadConverter RGBA LUT Item Number
550  */
551 int FileHelper::GetLutItemNumber()
552 {
553    return PixelReadConverter->GetLutItemNumber();
554 }
555
556 /**
557  * \brief Access to the underlying \ref PixelReadConverter RGBA LUT Item Size
558  */
559 int FileHelper::GetLutItemSize()
560 {
561    return PixelReadConverter->GetLutItemSize();
562 }
563
564 /**
565  * \brief Writes on disk A SINGLE Dicom file
566  *        NO test is performed on  processor "Endiannity".
567  *        It's up to the user to call his Reader properly
568  * @param fileName name of the file to be created
569  *                 (any already existing file is over written)
570  * @return false if write fails
571  */
572 bool FileHelper::WriteRawData(std::string const &fileName)
573 {
574    std::ofstream fp1(fileName.c_str(), std::ios::out | std::ios::binary );
575    if (!fp1)
576    {
577       gdcmWarningMacro( "Fail to open (write) file:" << fileName.c_str());
578       return false;
579    }
580
581    if ( PixelWriteConverter->GetUserData() )
582    {
583       fp1.write( (char *)PixelWriteConverter->GetUserData(), 
584                  PixelWriteConverter->GetUserDataSize() );
585    }
586    else if ( PixelReadConverter->GetRGB() )
587    {
588       fp1.write( (char *)PixelReadConverter->GetRGB(), 
589                  PixelReadConverter->GetRGBSize());
590    }
591    else if ( PixelReadConverter->GetRaw() )
592    {
593       fp1.write( (char *)PixelReadConverter->GetRaw(), 
594                  PixelReadConverter->GetRawSize());
595    }
596    else
597    {
598       gdcmErrorMacro( "Nothing written." );
599    }
600
601    fp1.close();
602
603    return true;
604 }
605
606 /**
607  * \brief Writes on disk A SINGLE Dicom file, 
608  *        using the Implicit Value Representation convention
609  *        NO test is performed on  processor "Endianity".
610  * @param fileName name of the file to be created
611  *                 (any already existing file is overwritten)
612  * @return false if write fails
613  */
614
615 bool FileHelper::WriteDcmImplVR (std::string const &fileName)
616 {
617    SetWriteTypeToDcmImplVR();
618    return Write(fileName);
619 }
620
621 /**
622 * \brief Writes on disk A SINGLE Dicom file, 
623  *        using the Explicit Value Representation convention
624  *        NO test is performed on  processor "Endiannity". 
625  * @param fileName name of the file to be created
626  *                 (any already existing file is overwritten)
627  * @return false if write fails
628  */
629
630 bool FileHelper::WriteDcmExplVR (std::string const &fileName)
631 {
632    SetWriteTypeToDcmExplVR();
633    return Write(fileName);
634 }
635
636 /**
637  * \brief Writes on disk A SINGLE Dicom file, 
638  *        using the ACR-NEMA convention
639  *        NO test is performed on  processor "Endiannity".
640  *        (a l'attention des logiciels cliniques 
641  *        qui ne prennent en entrée QUE des images ACR ...
642  * \warning if a DICOM_V3 header is supplied,
643  *         groups < 0x0008 and shadow groups are ignored
644  * \warning NO TEST is performed on processor "Endiannity".
645  * @param fileName name of the file to be created
646  *                 (any already existing file is overwritten)
647  * @return false if write fails
648  */
649
650 bool FileHelper::WriteAcr (std::string const &fileName)
651 {
652    SetWriteTypeToAcr();
653    return Write(fileName);
654 }
655
656 /**
657  * \brief Writes on disk A SINGLE Dicom file, 
658  * @param fileName name of the file to be created
659  *                 (any already existing file is overwritten)
660  * @return false if write fails
661  */
662 bool FileHelper::Write(std::string const &fileName)
663 {
664    bool flag = false;
665    DocEntry *e;   
666    switch(WriteType)
667    {
668       case ImplicitVR:
669          SetWriteFileTypeToImplicitVR();
670          break;
671  
672       case Unknown:  // should never happen; ExplicitVR is the default value
673       case ExplicitVR:
674       
675    // User should ask gdcm to write an image in Explicit VR mode
676    // only when he is sure *all* the VR of *all* the DataElements is known.
677    // i.e : when there are *only* Public Groups
678    // or *all* the Shadow Groups are fully described in the relevant Shadow
679    // Dictionnary
680    // Let's just *dream* about it; *never* trust a user !
681    // We turn to Implicit VR if at least the VR of one element is unknown.
682    
683          e = FileInternal->GetFirstEntry();
684          while (e != 0)
685          {
686             if (e->GetVR() == "  ")
687             {
688                SetWriteTypeToDcmImplVR();
689                SetWriteFileTypeToImplicitVR();
690                flag = true;
691                break;         
692             } 
693             e = FileInternal->GetNextEntry();
694          }        
695
696          if (!flag)
697          {
698             SetWriteFileTypeToExplicitVR();
699          }
700          break;
701  
702       case ACR:
703       case ACR_LIBIDO:
704       // NOTHING is done here just for LibIDO.
705       // Just to avoid further trouble if user creates a file ex-nihilo,
706       // wants to write it as an ACR-NEMA file,
707       // and forgets to create any Entry belonging to group 0008
708       // (shame on him !)
709       // We add Recognition Code (RET)
710         if ( ! FileInternal->GetDataEntry(0x0008, 0x0010) )
711             FileInternal->InsertEntryString("ACR-NEMA V1.0 ", 0x0008, 0x0010);
712          SetWriteFileTypeToACR();
713         // SetWriteFileTypeToImplicitVR(); // ACR IS implicit VR !
714          break;
715  
716       /// \todo FIXME : JPEG may be either ExplicitVR or ImplicitVR
717       case JPEG:
718          SetWriteFileTypeToJPEG();
719          break;
720    }
721    
722    CheckMandatoryElements();
723
724    // --------------------------------------------------------------
725    // Special Patch to allow gdcm to re-write ACR-LibIDO formated images
726    //
727    // if recognition code tells us we dealt with a LibIDO image
728    // we reproduce on disk the switch between lineNumber and columnNumber
729    // just before writting ...
730    /// \todo the best trick would be *change* the recognition code
731    ///       but pb expected if user deals with, e.g. COMPLEX images
732    
733    if ( WriteType == ACR_LIBIDO )
734    {
735       SetWriteToLibido();
736    }
737    else
738    {
739       SetWriteToNoLibido();
740    }
741    // ----------------- End of Special Patch ----------------
742   
743    switch(WriteMode)
744    {
745       case WMODE_RAW :
746          SetWriteToRaw(); // modifies and pushes to the archive, when necessary
747          break;
748       case WMODE_RGB :
749          SetWriteToRGB(); // modifies and pushes to the archive, when necessary
750          break;
751    }
752
753    bool check = CheckWriteIntegrity(); // verifies length
754    if (WriteType == JPEG ) check = true;
755    if (check)
756    {
757       check = FileInternal->Write(fileName,WriteType);
758    }
759
760    RestoreWrite();
761    RestoreWriteFileType();
762    RestoreWriteMandatory();
763
764    // --------------------------------------------------------------
765    // Special Patch to allow gdcm to re-write ACR-LibIDO formated images
766    // 
767    // ...and we restore the header to be Dicom Compliant again 
768    // just after writting
769    RestoreWriteOfLibido();
770    // ----------------- End of Special Patch ----------------
771
772    return check;
773 }
774
775 //-----------------------------------------------------------------------------
776 // Protected
777 /**
778  * \brief Checks the write integrity
779  *
780  * The tests made are :
781  *  - verify the size of the image to write with the possible write
782  *    when the user set an image data
783  * @return true if check is successfull
784  */
785 bool FileHelper::CheckWriteIntegrity()
786 {
787    if ( PixelWriteConverter->GetUserData() )
788    {
789       int numberBitsAllocated = FileInternal->GetBitsAllocated();
790       if ( numberBitsAllocated == 0 || numberBitsAllocated == 12 )
791       {
792          gdcmWarningMacro( "numberBitsAllocated changed from " 
793                           << numberBitsAllocated << " to 16 " 
794                           << " for consistency purpose" );
795          numberBitsAllocated = 16;
796       }
797
798       size_t decSize = FileInternal->GetXSize()
799                      * FileInternal->GetYSize() 
800                      * FileInternal->GetZSize()
801                      * FileInternal->GetTSize()     
802                      * FileInternal->GetSamplesPerPixel()
803                      * ( numberBitsAllocated / 8 );
804       size_t rgbSize = decSize;
805       if ( FileInternal->HasLUT() )
806          rgbSize = decSize * 3;
807
808       switch(WriteMode)
809       {
810          case WMODE_RAW :
811             if ( decSize!=PixelWriteConverter->GetUserDataSize() )
812             {
813                gdcmWarningMacro( "Data size (Raw) is incorrect. Should be " 
814                            << decSize << " / Found :" 
815                            << PixelWriteConverter->GetUserDataSize() );
816                return false;
817             }
818             break;
819          case WMODE_RGB :
820             if ( rgbSize!=PixelWriteConverter->GetUserDataSize() )
821             {
822                gdcmWarningMacro( "Data size (RGB) is incorrect. Should be " 
823                           << decSize << " / Found " 
824                           << PixelWriteConverter->GetUserDataSize() );
825                return false;
826             }
827             break;
828       }
829    }
830
831    return true;
832 }
833
834 /**
835  * \brief Updates the File to write RAW data (as opposed to RGB data)
836  *       (modifies, when necessary, photochromatic interpretation, 
837  *       bits allocated, Pixels element VR)
838  */ 
839 void FileHelper::SetWriteToRaw()
840 {
841    if ( FileInternal->GetNumberOfScalarComponents() == 3 
842     && !FileInternal->HasLUT() )
843    {
844       SetWriteToRGB();
845    } 
846    else
847    {
848       DataEntry *photInt = CopyDataEntry(0x0028,0x0004);
849       if (FileInternal->HasLUT() )
850       {
851          photInt->SetString("PALETTE COLOR ");
852       }
853       else
854       {
855          photInt->SetString("MONOCHROME2 ");
856       }
857
858       PixelWriteConverter->SetReadData(PixelReadConverter->GetRaw(),
859                                        PixelReadConverter->GetRawSize());
860
861       std::string vr = "OB";
862       if ( FileInternal->GetBitsAllocated()>8 )
863          vr = "OW";
864       if ( FileInternal->GetBitsAllocated()==24 ) // For RGB ACR files 
865          vr = "OB";
866       DataEntry *pixel = 
867          CopyDataEntry(GetFile()->GetGrPixel(),GetFile()->GetNumPixel(),vr);
868       pixel->SetFlag(DataEntry::FLAG_PIXELDATA);
869       pixel->SetBinArea(PixelWriteConverter->GetData(),false);
870       pixel->SetLength(PixelWriteConverter->GetDataSize());
871
872       Archive->Push(photInt);
873       Archive->Push(pixel);
874
875       photInt->Delete();
876       pixel->Delete();
877    }
878 }
879
880 /**
881  * \brief Updates the File to write RGB data (as opposed to RAW data)
882  *       (modifies, when necessary, photochromatic interpretation, 
883  *       samples per pixel, Planar configuration, 
884  *       bits allocated, bits stored, high bit -ACR 24 bits-
885  *       Pixels element VR, pushes out the LUT, )
886  */ 
887 void FileHelper::SetWriteToRGB()
888 {
889    if ( FileInternal->GetNumberOfScalarComponents()==3 )
890    {
891       PixelReadConverter->BuildRGBImage();
892       
893       DataEntry *spp = CopyDataEntry(0x0028,0x0002);
894       spp->SetString("3 ");
895
896       DataEntry *planConfig = CopyDataEntry(0x0028,0x0006);
897       planConfig->SetString("0 ");
898
899       DataEntry *photInt = CopyDataEntry(0x0028,0x0004);
900       photInt->SetString("RGB ");
901
902       if ( PixelReadConverter->GetRGB() )
903       {
904          PixelWriteConverter->SetReadData(PixelReadConverter->GetRGB(),
905                                           PixelReadConverter->GetRGBSize());
906       }
907       else // Raw data
908       {
909          PixelWriteConverter->SetReadData(PixelReadConverter->GetRaw(),
910                                           PixelReadConverter->GetRawSize());
911       }
912
913       std::string vr = "OB";
914       if ( FileInternal->GetBitsAllocated()>8 )
915          vr = "OW";
916       if ( FileInternal->GetBitsAllocated()==24 ) // For RGB ACR files 
917          vr = "OB";
918       DataEntry *pixel = 
919          CopyDataEntry(GetFile()->GetGrPixel(),GetFile()->GetNumPixel(),vr);
920       pixel->SetFlag(DataEntry::FLAG_PIXELDATA);
921       pixel->SetBinArea(PixelWriteConverter->GetData(),false);
922       pixel->SetLength(PixelWriteConverter->GetDataSize());
923
924       Archive->Push(spp);
925       Archive->Push(planConfig);
926       Archive->Push(photInt);
927       Archive->Push(pixel);
928
929       spp->Delete();
930       planConfig->Delete();
931       photInt->Delete();
932       pixel->Delete();
933
934       // Remove any LUT
935       Archive->Push(0x0028,0x1101);
936       Archive->Push(0x0028,0x1102);
937       Archive->Push(0x0028,0x1103);
938       Archive->Push(0x0028,0x1201);
939       Archive->Push(0x0028,0x1202);
940       Archive->Push(0x0028,0x1203);
941
942       // push out Palette Color Lookup Table UID, if any
943       Archive->Push(0x0028,0x1199);
944
945       // For old '24 Bits' ACR-NEMA
946       // Thus, we have a RGB image and the bits allocated = 24 and 
947       // samples per pixels = 1 (in the read file)
948       if ( FileInternal->GetBitsAllocated()==24 ) 
949       {
950          DataEntry *bitsAlloc = CopyDataEntry(0x0028,0x0100);
951          bitsAlloc->SetString("8 ");
952
953          DataEntry *bitsStored = CopyDataEntry(0x0028,0x0101);
954          bitsStored->SetString("8 ");
955
956          DataEntry *highBit = CopyDataEntry(0x0028,0x0102);
957          highBit->SetString("7 ");
958
959          Archive->Push(bitsAlloc);
960          Archive->Push(bitsStored);
961          Archive->Push(highBit);
962
963          bitsAlloc->Delete();
964          bitsStored->Delete();
965          highBit->Delete();
966       }
967    }
968    else
969    {
970       SetWriteToRaw();
971    }
972 }
973
974 /**
975  * \brief Restore the File write mode  
976  */ 
977 void FileHelper::RestoreWrite()
978 {
979    Archive->Restore(0x0028,0x0002);
980    Archive->Restore(0x0028,0x0004);
981    Archive->Restore(0x0028,0x0006);
982    Archive->Restore(GetFile()->GetGrPixel(),GetFile()->GetNumPixel());
983
984    // For old ACR-NEMA (24 bits problem)
985    Archive->Restore(0x0028,0x0100);
986    Archive->Restore(0x0028,0x0101);
987    Archive->Restore(0x0028,0x0102);
988
989    // For the LUT
990    Archive->Restore(0x0028,0x1101);
991    Archive->Restore(0x0028,0x1102);
992    Archive->Restore(0x0028,0x1103);
993    Archive->Restore(0x0028,0x1201);
994    Archive->Restore(0x0028,0x1202);
995    Archive->Restore(0x0028,0x1203);
996
997    // For the Palette Color Lookup Table UID
998    Archive->Restore(0x0028,0x1203); 
999
1000
1001    // group 0002 may be pushed out for ACR-NEMA writting purposes 
1002    Archive->Restore(0x0002,0x0000);
1003    Archive->Restore(0x0002,0x0001);
1004    Archive->Restore(0x0002,0x0002);
1005    Archive->Restore(0x0002,0x0003);
1006    Archive->Restore(0x0002,0x0010);
1007    Archive->Restore(0x0002,0x0012);
1008    Archive->Restore(0x0002,0x0013);
1009    Archive->Restore(0x0002,0x0016);
1010    Archive->Restore(0x0002,0x0100);
1011    Archive->Restore(0x0002,0x0102);
1012 }
1013
1014 /**
1015  * \brief Pushes out the whole group 0002
1016  *        FIXME : better, set a flag to tell the writer not to write it ...
1017  *        FIXME : method should probably have an other name !
1018  *                SetWriteFileTypeToACR is NOT opposed to 
1019  *                SetWriteFileTypeToExplicitVR and SetWriteFileTypeToImplicitVR
1020  */ 
1021 void FileHelper::SetWriteFileTypeToACR()
1022 {
1023    Archive->Push(0x0002,0x0000);
1024    Archive->Push(0x0002,0x0001);
1025    Archive->Push(0x0002,0x0002);
1026    Archive->Push(0x0002,0x0003);
1027    Archive->Push(0x0002,0x0010);
1028    Archive->Push(0x0002,0x0012);
1029    Archive->Push(0x0002,0x0013);
1030    Archive->Push(0x0002,0x0016);
1031    Archive->Push(0x0002,0x0100);
1032    Archive->Push(0x0002,0x0102);
1033 }
1034
1035 /**
1036  * \brief Sets in the File the TransferSyntax to 'JPEG'
1037  */ 
1038 void FileHelper::SetWriteFileTypeToJPEG()
1039 {
1040    std::string ts = Util::DicomString( 
1041       Global::GetTS()->GetSpecialTransferSyntax(TS::JPEGBaselineProcess1) );
1042
1043    DataEntry *tss = CopyDataEntry(0x0002,0x0010);
1044    tss->SetString(ts);
1045
1046    Archive->Push(tss);
1047    tss->Delete();
1048 }
1049
1050 /**
1051  * \brief Sets in the File the TransferSyntax to 'Explicit VR Little Endian"   
1052  */ 
1053 void FileHelper::SetWriteFileTypeToExplicitVR()
1054 {
1055    std::string ts = Util::DicomString( 
1056       Global::GetTS()->GetSpecialTransferSyntax(TS::ExplicitVRLittleEndian) );
1057
1058    DataEntry *tss = CopyDataEntry(0x0002,0x0010);
1059    tss->SetString(ts);
1060    Archive->Push(tss);
1061    tss->Delete();
1062 }
1063
1064 /**
1065  * \brief Sets in the File the TransferSyntax to 'Implicit VR Little Endian"   
1066  */ 
1067 void FileHelper::SetWriteFileTypeToImplicitVR()
1068 {
1069    std::string ts = Util::DicomString(
1070       Global::GetTS()->GetSpecialTransferSyntax(TS::ImplicitVRLittleEndian) );
1071
1072    DataEntry *tss = CopyDataEntry(0x0002,0x0010);
1073    tss->SetString(ts);
1074    Archive->Push(tss);
1075    tss->Delete();
1076 }
1077
1078
1079 /**
1080  * \brief Restore in the File the initial group 0002
1081  */ 
1082 void FileHelper::RestoreWriteFileType()
1083 {
1084 }
1085
1086 /**
1087  * \brief Set the Write not to Libido format
1088  */ 
1089 void FileHelper::SetWriteToLibido()
1090 {
1091    DataEntry *oldRow = FileInternal->GetDataEntry(0x0028, 0x0010);
1092    DataEntry *oldCol = FileInternal->GetDataEntry(0x0028, 0x0011);
1093    
1094    if ( oldRow && oldCol )
1095    {
1096       std::string rows, columns; 
1097
1098       DataEntry *newRow=DataEntry::New(oldRow->GetDictEntry());
1099       DataEntry *newCol=DataEntry::New(oldCol->GetDictEntry());
1100
1101       newRow->Copy(oldCol);
1102       newCol->Copy(oldRow);
1103
1104       newRow->SetString(oldCol->GetString());
1105       newCol->SetString(oldRow->GetString());
1106
1107       Archive->Push(newRow);
1108       Archive->Push(newCol);
1109
1110       newRow->Delete();
1111       newCol->Delete();
1112    }
1113
1114    DataEntry *libidoCode = CopyDataEntry(0x0008,0x0010);
1115    libidoCode->SetString("ACRNEMA_LIBIDO_1.1");
1116    Archive->Push(libidoCode);
1117    libidoCode->Delete();
1118 }
1119
1120 /**
1121  * \brief Set the Write not to No Libido format
1122  */ 
1123 void FileHelper::SetWriteToNoLibido()
1124 {
1125    DataEntry *recCode = FileInternal->GetDataEntry(0x0008,0x0010);
1126    if ( recCode )
1127    {
1128       if ( recCode->GetString() == "ACRNEMA_LIBIDO_1.1" )
1129       {
1130          DataEntry *libidoCode = CopyDataEntry(0x0008,0x0010);
1131          libidoCode->SetString("");
1132          Archive->Push(libidoCode);
1133          libidoCode->Delete();
1134       }
1135    }
1136 }
1137
1138 /**
1139  * \brief Restore the Write format
1140  */ 
1141 void FileHelper::RestoreWriteOfLibido()
1142 {
1143    Archive->Restore(0x0028,0x0010);
1144    Archive->Restore(0x0028,0x0011);
1145    Archive->Restore(0x0008,0x0010);
1146
1147    // Restore 'LibIDO-special' entries, if any
1148    Archive->Restore(0x0028,0x0015);
1149    Archive->Restore(0x0028,0x0016);
1150    Archive->Restore(0x0028,0x0017);
1151    Archive->Restore(0x0028,0x00199);
1152 }
1153
1154 /**
1155  * \brief   Duplicates a DataEntry or creates it.
1156  * @param   group   Group number of the Entry 
1157  * @param   elem  Element number of the Entry
1158  * @param   vr  Value Representation of the Entry
1159  *          FIXME : what is it used for?
1160  * \return  pointer to the new Bin Entry (NULL when creation failed).
1161  */ 
1162 DataEntry *FileHelper::CopyDataEntry(uint16_t group, uint16_t elem,
1163                                    const TagName &vr)
1164 {
1165    DocEntry *oldE = FileInternal->GetDocEntry(group, elem);
1166    DataEntry *newE;
1167
1168    if ( oldE && vr != GDCM_VRUNKNOWN ) 
1169       if ( oldE->GetVR() != vr )
1170          oldE = NULL;
1171
1172    if ( oldE )
1173    {
1174       newE = DataEntry::New(oldE->GetDictEntry());
1175       newE->Copy(oldE);
1176    }
1177    else
1178    {
1179       newE = GetFile()->NewDataEntry(group, elem, vr);
1180    }
1181
1182    return newE;
1183 }
1184
1185 /**
1186  * \brief   This method is called automatically, just before writting
1187  *         in order to produce a 'True Dicom V3' image.
1188  *
1189  *         We cannot know *how* the user made the File :
1190  *         (reading an old ACR-NEMA file or a not very clean DICOM file ...) 
1191  *          Just before writting :
1192  *             - we check the Entries
1193  *             - we create the mandatory entries if they are missing
1194  *             - we modify the values if necessary
1195  *             - we push the sensitive entries to the Archive
1196  *          The writing process will restore the entries as they where before 
1197  *          entering FileHelper::CheckMandatoryElements, so the user will always
1198  *          see the entries just as they were before he decided to write.
1199  *
1200  * \note
1201  *       -  Entries whose type is 1 are mandatory, with a mandatory value
1202  *       -  Entries whose type is 1c are mandatory-inside-a-Sequence,
1203  *                             with a mandatory value
1204  *       -  Entries whose type is 2 are mandatory, with an optional value
1205  *       -  Entries whose type is 2c are mandatory-inside-a-Sequence,
1206  *                             with an optional value
1207  *       -  Entries whose type is 3 are optional
1208  * 
1209  * \todo 
1210  *         - warn the user if we had to add some entries :
1211  *         even if a mandatory entry is missing, we add it, with a default value
1212  *         (we don't want to give up the writting process if user forgot to
1213  *         specify Lena's Patient ID, for instance ...)
1214  *         - read the whole PS 3.3 Part of DICOM  (890 pages)
1215  *         and write a *full* checker (probably one method per Modality ...)
1216  *         Any contribution is welcome. 
1217  *         - write a user callable full checker, to allow post reading
1218  *         and/or pre writting image consistency check.           
1219  */ 
1220
1221 /* -------------------------------------------------------------------------------------
1222 To be moved to User's guide / WIKI  ?
1223
1224 We have to deal with 4 *very* different cases :
1225 -1) user created ex nihilo his own image and wants to write it as a Dicom image.
1226     USER_OWN_IMAGE
1227 -2) user modified the pixels of an existing image.
1228    FILTERED_IMAGE
1229 -3) user created a new image, using existing images (eg MIP, MPR, cartography image)
1230    CREATED_IMAGE
1231 -4) user modified/added some tags *without processing* the pixels (anonymization..
1232    UNMODIFIED_PIXELS_IMAGE
1233    
1234 gdcm::FileHelper::CheckMandatoryElements() deals automatically with these cases.
1235
1236 1)2)3)4)
1237 0008 0012 Instance Creation Date
1238 0008 0013 Instance Creation Time
1239 0008 0018 SOP Instance UID
1240 are *always* created with the current values; user has *no* possible intervention on
1241 them.
1242
1243 'Serie Instance UID'(0x0020,0x000e)
1244 'Study Instance UID'(0x0020,0x000d) are kept as is if already exist,
1245                                     created  if it doesn't.
1246  The user is allowed to create his own Series/Studies, 
1247      keeping the same 'Serie Instance UID' / 'Study Instance UID' for various images
1248  Warning :     
1249  The user shouldn't add any image to a 'Manufacturer Serie'
1250      but there is no way no to allow him to do that
1251      
1252  None of the 'shadow elements' are droped out.
1253      
1254
1255 1)
1256 'Conversion Type (0x0008,0x0064) is forced to 'SYN' (Synthetic Image).
1257  
1258 1)3)
1259 'Media Storage SOP Class UID' (0x0002,0x0002)
1260 'SOP Class UID'               (0x0008,0x0016) are set to 
1261                                                [Secondary Capture Image Storage]
1262 'Image Type'                  (0x0008,0x0008) is forced to  "DERIVED\PRIMARY"
1263 Conversion Type               (0x0008,0x0064) is forced to 'SYN' (Synthetic Image)
1264
1265 2)4)
1266 If 'SOP Class UID' exists in the native image  ('true DICOM' image)
1267     we create the 'Source Image Sequence' SeqEntry (0x0008, 0x2112)    
1268     --> 'Referenced SOP Class UID' (0x0008, 0x1150)
1269          whose value is the original 'SOP Class UID'
1270     --> 'Referenced SOP Instance UID' (0x0008, 0x1155)
1271          whose value is the original 'SOP Class UID'
1272
1273 3) TODO : find a trick to allow user to pass to the writter the list of the Dicom images 
1274           or the Series, (or the Study ?) he used to created his image 
1275           (MIP, MPR, cartography image, ...)
1276            These info should be stored (?)
1277           0008 1110 SQ 1 Referenced Study Sequence
1278           0008 1115 SQ 1 Referenced Series Sequence
1279           0008 1140 SQ 1 Referenced Image Sequence
1280        
1281 4) When user *knows* he didn't modified the pixels, we keep some informations unchanged :
1282 'Media Storage SOP Class UID' (0x0002,0x0002)
1283 'SOP Class UID'               (0x0008,0x0016)
1284 'Image Type'                  (0x0008,0x0008)
1285 'Conversion Type'             (0x0008,0x0064)
1286
1287
1288 Bellow follows the full description (hope so !) of the consistency checks performed 
1289 by gdcm::FileHelper::CheckMandatoryElements()
1290
1291
1292 -->'Media Storage SOP Class UID' (0x0002,0x0002)
1293 -->'SOP Class UID'               (0x0008,0x0016) are defaulted to 
1294                                                [Secondary Capture Image Storage]
1295 --> 'Image Type'  (0x0008,0x0008)
1296      is forced to  "DERIVED\PRIMARY"
1297      (The written image is no longer an 'ORIGINAL' one)
1298   Except if user knows he didn't modify the image (e.g. : he just anonymized the file)
1299    
1300  -->  Conversion Type (0x0008,0x0064)
1301      is defaulted to 'SYN' (Synthetic Image)
1302   when *he* knows he created his own image ex nihilo
1303             
1304 --> 'Modality' (0x0008,0x0060)   
1305     is defaulted to "OT" (other) if missing.   
1306     (a fully user created image belongs to *no* modality)
1307       
1308 --> 'Media Storage SOP Instance UID' (0x0002,0x0003)
1309 --> 'Implementation Class UID'       (0x0002,0x0012)
1310     are automatically generated; no user intervention possible
1311
1312 --> 'Serie Instance UID'(0x0020,0x000e)
1313 --> 'Study Instance UID'(0x0020,0x000d) are kept as is if already exist
1314                                              created  if it doesn't.
1315      The user is allowed to create his own Series/Studies, 
1316      keeping the same 'Serie Instance UID' / 'Study Instance UID' 
1317      for various images
1318      Warning :     
1319      The user shouldn't add any image to a 'Manufacturer Serie'
1320      but there is no way no to allowed him to do that 
1321              
1322 --> If 'SOP Class UID' exists in the native image  ('true DICOM' image)
1323     we create the 'Source Image Sequence' SeqEntry (0x0008, 0x2112)
1324     
1325     --> 'Referenced SOP Class UID' (0x0008, 0x1150)
1326          whose value is the original 'SOP Class UID'
1327     --> 'Referenced SOP Instance UID' (0x0008, 0x1155)
1328          whose value is the original 'SOP Class UID'
1329     
1330 --> Bits Stored, Bits Allocated, Hight Bit Position are checked for consistency
1331 --> Pixel Spacing     (0x0028,0x0030) is defaulted to "1.0\1.0"
1332 --> Samples Per Pixel (0x0028,0x0002) is defaulted to 1 (grayscale)
1333
1334 --> Imager Pixel Spacing (0x0018,0x1164) : defaulted to Pixel Spacing value
1335
1336 --> Instance Creation Date, Instance Creation Time are forced to current Date and Time
1337
1338 --> Study Date, Study Time are defaulted to current Date and Time
1339    (they remain unchanged if they exist)
1340
1341 --> Patient Orientation : (0x0020,0x0020), if not present, is deduced from 
1342     Image Orientation (Patient) : (0020|0037) or from
1343     Image Orientation (RET)     : (0020 0035)
1344    
1345 --> Study ID, Series Number, Instance Number, Patient Orientation (Type 2)
1346     are created, with empty value if there are missing.
1347
1348 --> Manufacturer, Institution Name, Patient's Name, (Type 2)
1349     are defaulted with a 'gdcm' value.
1350     
1351 --> Patient ID, Patient's Birth Date, Patient's Sex, (Type 2)
1352 --> Referring Physician's Name  (Type 2)
1353     are created, with empty value if there are missing.
1354
1355  -------------------------------------------------------------------------------------*/
1356
1357 void FileHelper::CheckMandatoryElements()
1358 {
1359    std::string sop =  Util::CreateUniqueUID();
1360
1361    // --------------------- For Meta Elements ---------------------
1362    // just to remember : 'official' 0002 group
1363    if ( WriteType != ACR && WriteType != ACR_LIBIDO )
1364    {
1365      // Group 000002 (Meta Elements) already pushed out
1366   
1367    //0002 0000 UL 1 Meta Group Length
1368    //0002 0001 OB 1 File Meta Information Version
1369    //0002 0002 UI 1 Media Stored SOP Class UID
1370    //0002 0003 UI 1 Media Stored SOP Instance UID
1371    //0002 0010 UI 1 Transfer Syntax UID
1372    //0002 0012 UI 1 Implementation Class UID
1373    //0002 0013 SH 1 Implementation Version Name
1374    //0002 0016 AE 1 Source Application Entity Title
1375    //0002 0100 UI 1 Private Information Creator
1376    //0002 0102 OB 1 Private Information
1377   
1378    // Create them if not found
1379    // Always modify the value
1380    // Push the entries to the archive.
1381       CopyMandatoryEntry(0x0002,0x0000,"0");
1382
1383       DataEntry *e_0002_0001 = CopyDataEntry(0x0002,0x0001, "OB");
1384       e_0002_0001->SetBinArea((uint8_t*)Util::GetFileMetaInformationVersion(),
1385                                false);
1386       e_0002_0001->SetLength(2);
1387       Archive->Push(e_0002_0001);
1388       e_0002_0001->Delete(); 
1389
1390       if ( ContentType == FILTERED_IMAGE || ContentType == UNMODIFIED_PIXELS_IMAGE)
1391       {      
1392    // we keep the original 'Media Storage SOP Class UID', we default it if missing
1393          CheckMandatoryEntry(0x0002,0x0002,"1.2.840.10008.5.1.4.1.1.7"); 
1394       }
1395       else
1396       {
1397    // It's *not* an image comming straight from a source. We force
1398    // 'Media Storage SOP Class UID'  --> [Secondary Capture Image Storage]
1399          CopyMandatoryEntry(0x0002,0x0002,"1.2.840.10008.5.1.4.1.1.7");
1400       }
1401       
1402    // 'Media Storage SOP Instance UID'   
1403       CopyMandatoryEntry(0x0002,0x0003,sop);
1404       
1405    // 'Implementation Class UID'
1406    // FIXME : in all examples we have, 0x0002,0x0012 is not so long :
1407    //         seems to be Root UID + 4 digits (?)
1408       CopyMandatoryEntry(0x0002,0x0012,Util::CreateUniqueUID());
1409
1410    // 'Implementation Version Name'
1411       std::string version = "GDCM ";
1412       version += Util::GetVersion();
1413       CopyMandatoryEntry(0x0002,0x0013,version);
1414    }
1415
1416    // --------------------- For DataSet ---------------------
1417       
1418    if ( ContentType == FILTERED_IMAGE || ContentType == UNMODIFIED_PIXELS_IMAGE)
1419    {      
1420    // we keep the original 'Media Storage SOP Class UID', we default it if missing (it should be present !)
1421          CheckMandatoryEntry(0x0008,0x0016,"1.2.840.10008.5.1.4.1.1.7");      
1422    }
1423    else
1424    {
1425    // It's *not* an image comming straight from a source. We force
1426    // 'Media Storage SOP Class UID'  --> [Secondary Capture Image Storage]
1427          CopyMandatoryEntry(0x0008,0x0016,"1.2.840.10008.5.1.4.1.1.7");      
1428    }   
1429
1430    // Push out 'LibIDO-special' entries, if any
1431    Archive->Push(0x0028,0x0015);
1432    Archive->Push(0x0028,0x0016);
1433    Archive->Push(0x0028,0x0017);
1434    Archive->Push(0x0028,0x00199);
1435
1436    // Deal with the pb of (Bits Stored = 12)
1437    // - we're gonna write the image as Bits Stored = 16
1438    if ( FileInternal->GetEntryString(0x0028,0x0100) ==  "12")
1439    {
1440       CopyMandatoryEntry(0x0028,0x0100,"16");
1441    }
1442
1443    // Check if user wasn't drunk ;-)
1444
1445    std::ostringstream s;
1446    // check 'Bits Allocated' vs decent values
1447    int nbBitsAllocated = FileInternal->GetBitsAllocated();
1448    if ( nbBitsAllocated == 0 || nbBitsAllocated > 32)
1449    {
1450       CopyMandatoryEntry(0x0028,0x0100,"16");
1451       gdcmWarningMacro("(0028,0100) changed from "
1452          << nbBitsAllocated << " to 16 for consistency purpose");
1453       nbBitsAllocated = 16; 
1454    }
1455    // check 'Bits Stored' vs 'Bits Allocated'   
1456    int nbBitsStored = FileInternal->GetBitsStored();
1457    if ( nbBitsStored == 0 || nbBitsStored > nbBitsAllocated )
1458    {
1459       s.str("");
1460       s << nbBitsAllocated;
1461       CopyMandatoryEntry(0x0028,0x0101,s.str());
1462       gdcmWarningMacro("(0028,0101) changed from "
1463                        << nbBitsStored << " to " << nbBitsAllocated
1464                        << " for consistency purpose" );
1465       nbBitsStored = nbBitsAllocated; 
1466     }
1467    // check 'Hight Bit Position' vs 'Bits Allocated' and 'Bits Stored'
1468    int highBitPosition = FileInternal->GetHighBitPosition();
1469    if ( highBitPosition == 0 || 
1470         highBitPosition > nbBitsAllocated-1 ||
1471         highBitPosition < nbBitsStored-1  )
1472    {
1473       s.str("");
1474       s << nbBitsStored - 1; 
1475       CopyMandatoryEntry(0x0028,0x0102,s.str());
1476       gdcmWarningMacro("(0028,0102) changed from "
1477                        << highBitPosition << " to " << nbBitsAllocated-1
1478                        << " for consistency purpose");
1479    }
1480
1481    std::string pixelSpacing = FileInternal->GetEntryString(0x0028,0x0030);
1482    if ( pixelSpacing == GDCM_UNFOUND )
1483    {
1484       pixelSpacing = "1.0\\1.0";
1485        // if missing, Pixel Spacing forced to "1.0\1.0"
1486       CopyMandatoryEntry(0x0028,0x0030,pixelSpacing);
1487    }
1488    
1489    // 'Imager Pixel Spacing' : defaulted to 'Pixel Spacing'
1490    // --> This one is the *legal* one !
1491    if ( ContentType != USER_OWN_IMAGE)
1492    //  we write it only when we are *sure* the image comes from
1493    //         an imager (see also 0008,0x0064)          
1494       CheckMandatoryEntry(0x0018,0x1164,pixelSpacing);
1495    
1496    // Samples Per Pixel (type 1) : default to grayscale 
1497    CheckMandatoryEntry(0x0028,0x0002,"1");
1498
1499    // --- Check UID-related Entries ---
1500
1501
1502    if ( ContentType != USER_OWN_IMAGE) // when it's not a user made image
1503    { 
1504     // If 'SOP Class UID' exists ('true DICOM' image)
1505    // we create the 'Source Image Sequence' SeqEntry
1506    // to hold informations about the Source Image
1507   
1508       DataEntry *e_0008_0016 = FileInternal->GetDataEntry(0x0008, 0x0016);
1509       if ( e_0008_0016 )
1510       {
1511       // Create 'Source Image Sequence' SeqEntry
1512       SeqEntry *sis = SeqEntry::New (
1513             Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x2112) );
1514       SQItem *sqi = SQItem::New(1);
1515       // (we assume 'SOP Instance UID' exists too) 
1516       // create 'Referenced SOP Class UID'
1517       DataEntry *e_0008_1150 = DataEntry::New(
1518             Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x1150) );
1519       e_0008_1150->SetString( e_0008_0016->GetString());
1520       sqi->AddEntry(e_0008_1150);
1521       e_0008_1150->Delete();
1522       
1523       // create 'Referenced SOP Instance UID'
1524       DataEntry *e_0008_0018 = FileInternal->GetDataEntry(0x0008, 0x0018);
1525       DataEntry *e_0008_1155 = DataEntry::New(
1526             Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x1155) );
1527       e_0008_1155->SetString( e_0008_0018->GetString());
1528       sqi->AddEntry(e_0008_1155);
1529       e_0008_1155->Delete();
1530
1531       sis->AddSQItem(sqi,1);
1532       sqi->Delete();
1533
1534       // temporarily replaces any previous 'Source Image Sequence' 
1535       Archive->Push(sis);
1536       sis->Delete();
1537  
1538       // FIXME : is 'Image Type' *really* depending on the presence of 'SOP Class UID'?
1539        if ( ContentType == FILTERED_IMAGE)      
1540       // the user *knows* he just modified the pixels
1541       // the image is no longer an 'Original' one
1542          CopyMandatoryEntry(0x0008,0x0008,"DERIVED\\PRIMARY");    
1543       }
1544    }    
1545
1546    // At the end, not to overwrite the original ones,
1547    // needed by 'Referenced SOP Instance UID', 'Referenced SOP Class UID'   
1548    // 'SOP Instance UID'  
1549    CopyMandatoryEntry(0x0008,0x0018,sop);
1550
1551    if ( ContentType == USER_OWN_IMAGE)
1552    {
1553        // Conversion Type.
1554        // Other possible values are :
1555        // See PS 3.3, Page 408
1556    
1557        // DV = Digitized Video
1558        // DI = Digital Interface   
1559        // DF = Digitized Film
1560        // WSD = Workstation
1561        // SD = Scanned Document
1562        // SI = Scanned Image
1563        // DRW = Drawing
1564        // SYN = Synthetic Image
1565            
1566       CheckMandatoryEntry(0x0008,0x0064,"SYN"); // Why not?
1567    } 
1568 /*
1569    if ( ContentType == CREATED_IMAGE)
1570    {
1571    /// \todo : find a trick to pass the Media Storage SOP Instance UID of the images used to create the current image
1572    
1573    }
1574 */
1575            
1576    // ---- The user will never have to take any action on the following ----
1577
1578    // new value for 'SOP Instance UID'
1579    //SetMandatoryEntry(0x0008,0x0018,Util::CreateUniqueUID());
1580
1581    // Instance Creation Date
1582    const std::string &date = Util::GetCurrentDate();
1583    CopyMandatoryEntry(0x0008,0x0012,date);
1584  
1585    // Instance Creation Time
1586    const std::string &time = Util::GetCurrentTime();
1587    CopyMandatoryEntry(0x0008,0x0013,time);
1588
1589    // Study Date
1590    CheckMandatoryEntry(0x0008,0x0020,date);
1591    // Study Time
1592    CheckMandatoryEntry(0x0008,0x0030,time);
1593
1594    // Accession Number
1595    //CopyMandatoryEntry(0x0008,0x0050,"");
1596    CheckMandatoryEntry(0x0008,0x0050,"");
1597    
1598
1599    // ----- Add Mandatory Entries if missing ---
1600    // Entries whose type is 1 are mandatory, with a mandatory value
1601    // Entries whose type is 1c are mandatory-inside-a-Sequence,
1602    //                          with a mandatory value
1603    // Entries whose type is 2 are mandatory, with an optional value
1604    // Entries whose type is 2c are mandatory-inside-a-Sequence,
1605    //                          with an optional value
1606    // Entries whose type is 3 are optional
1607
1608    // 'Study Instance UID'
1609    // Keep the value if exists
1610    // The user is allowed to create his own Study, 
1611    //          keeping the same 'Study Instance UID' for various images
1612    // The user may add images to a 'Manufacturer Study',
1613    //          adding new Series to an already existing Study 
1614    CheckMandatoryEntry(0x0020,0x000d,Util::CreateUniqueUID());
1615
1616    // 'Serie Instance UID'
1617    // Keep the value if exists
1618    // The user is allowed to create his own Series, 
1619    // keeping the same 'Serie Instance UID' for various images
1620    // The user shouldn't add any image to a 'Manufacturer Serie'
1621    // but there is no way no to prevent him for doing that 
1622    CheckMandatoryEntry(0x0020,0x000e,Util::CreateUniqueUID());
1623
1624    // Study ID
1625    CheckMandatoryEntry(0x0020,0x0010,"");
1626
1627    // Series Number
1628    CheckMandatoryEntry(0x0020,0x0011,"");
1629
1630    // Instance Number
1631    CheckMandatoryEntry(0x0020,0x0013,"");
1632    
1633    // Patient Orientation
1634    // Can be computed from (0020|0037) :  Image Orientation (Patient)
1635    gdcm::Orientation *o = gdcm::Orientation::New();
1636    std::string ori = o->GetOrientation ( FileInternal );
1637    o->Delete();
1638    if (ori != "\\" && ori != GDCM_UNFOUND)
1639       CheckMandatoryEntry(0x0020,0x0020,ori);
1640    else   
1641       CheckMandatoryEntry(0x0020,0x0020,"");
1642
1643    // Default Patient Position to HFS
1644    CheckMandatoryEntry(0x0018,0x5100,"HFS");
1645
1646    // Modality : if missing we set it to 'OTher'
1647    CheckMandatoryEntry(0x0008,0x0060,"OT");
1648
1649    // Manufacturer : if missing we set it to 'GDCM Factory'
1650    CheckMandatoryEntry(0x0008,0x0070,"GDCM Factory");
1651
1652    // Institution Name : if missing we set it to 'GDCM Hospital'
1653    CheckMandatoryEntry(0x0008,0x0080,"GDCM Hospital");
1654
1655    // Patient's Name : if missing, we set it to 'GDCM^Patient'
1656    CheckMandatoryEntry(0x0010,0x0010,"GDCM^Patient");
1657
1658    // Patient ID
1659    CheckMandatoryEntry(0x0010,0x0020,"");
1660
1661    // Patient's Birth Date : 'type 2' entry -> must exist, value not mandatory
1662    CheckMandatoryEntry(0x0010,0x0030,"");
1663
1664    // Patient's Sex :'type 2' entry -> must exist, value not mandatory
1665    CheckMandatoryEntry(0x0010,0x0040,"");
1666
1667    // Referring Physician's Name :'type 2' entry -> must exist, value not mandatory
1668    CheckMandatoryEntry(0x0008,0x0090,"");
1669
1670  /*
1671    // Deal with element 0x0000 (group length) of each group.
1672    // First stage : get all the different Groups
1673    
1674   GroupHT grHT;
1675   DocEntry *d = FileInternal->GetFirstEntry();
1676   while(d)
1677   {
1678     grHT[d->GetGroup()] = 0;
1679     d=FileInternal->GetNextEntry();
1680   }
1681   // Second stage : add the missing ones (if any)
1682   for (GroupHT::iterator it = grHT.begin(); it != grHT.end(); ++it)  
1683   {
1684       CheckMandatoryEntry(it->first, 0x0000, "0"); 
1685   }    
1686   // Third stage : update all 'zero level' groups length
1687 */ 
1688
1689
1690
1691 void FileHelper::CheckMandatoryEntry(uint16_t group,uint16_t elem,std::string value)
1692 {
1693    DataEntry *entry = FileInternal->GetDataEntry(group,elem);
1694    if ( !entry )
1695    {
1696       entry = DataEntry::New(Global::GetDicts()->GetDefaultPubDict()->GetEntry(group,elem));
1697       entry->SetString(value);
1698       Archive->Push(entry);
1699       entry->Delete();
1700    }
1701 }
1702
1703 void FileHelper::SetMandatoryEntry(uint16_t group,uint16_t elem,std::string value)
1704 {
1705    DataEntry *entry = DataEntry::New(Global::GetDicts()->GetDefaultPubDict()->GetEntry(group,elem));
1706    entry->SetString(value);
1707    Archive->Push(entry);
1708    entry->Delete();
1709 }
1710
1711 void FileHelper::CopyMandatoryEntry(uint16_t group,uint16_t elem,std::string value)
1712 {
1713    DataEntry *entry = CopyDataEntry(group,elem);
1714    entry->SetString(value);
1715    Archive->Push(entry);
1716    entry->Delete();
1717 }
1718
1719 /**
1720  * \brief Restore in the File the initial group 0002
1721  */
1722 void FileHelper::RestoreWriteMandatory()
1723 {
1724    // group 0002 may be pushed out for ACR-NEMA writting purposes 
1725    Archive->Restore(0x0002,0x0000);
1726    Archive->Restore(0x0002,0x0001);
1727    Archive->Restore(0x0002,0x0002);
1728    Archive->Restore(0x0002,0x0003);
1729    Archive->Restore(0x0002,0x0010);
1730    Archive->Restore(0x0002,0x0012);
1731    Archive->Restore(0x0002,0x0013);
1732    Archive->Restore(0x0002,0x0016);
1733    Archive->Restore(0x0002,0x0100);
1734    Archive->Restore(0x0002,0x0102);
1735
1736    // FIXME : Check if none is missing !
1737    
1738    Archive->Restore(0x0008,0x0012);
1739    Archive->Restore(0x0008,0x0013);
1740    Archive->Restore(0x0008,0x0016);
1741    Archive->Restore(0x0008,0x0018);
1742    Archive->Restore(0x0008,0x0060);
1743    Archive->Restore(0x0008,0x0070);
1744    Archive->Restore(0x0008,0x0080);
1745    Archive->Restore(0x0008,0x0090);
1746    Archive->Restore(0x0008,0x2112);
1747
1748    Archive->Restore(0x0010,0x0010);
1749    Archive->Restore(0x0010,0x0030);
1750    Archive->Restore(0x0010,0x0040);
1751
1752    Archive->Restore(0x0020,0x000d);
1753    Archive->Restore(0x0020,0x000e);
1754 }
1755
1756
1757 /**
1758  * \brief   CallStartMethod
1759  */
1760 void FileHelper::CallStartMethod()
1761 {
1762    Progress = 0.0f;
1763    Abort    = false;
1764    CommandManager::ExecuteCommand(this,CMD_STARTPROGRESS);
1765 }
1766
1767 /**
1768  * \brief   CallProgressMethod
1769  */
1770 void FileHelper::CallProgressMethod()
1771 {
1772    CommandManager::ExecuteCommand(this,CMD_PROGRESS);
1773 }
1774
1775 /**
1776  * \brief   CallEndMethod
1777  */
1778 void FileHelper::CallEndMethod()
1779 {
1780    Progress = 1.0f;
1781    CommandManager::ExecuteCommand(this,CMD_ENDPROGRESS);
1782 }
1783
1784 //-----------------------------------------------------------------------------
1785 // Private
1786 /**
1787  * \brief Factorization for various forms of constructors.
1788  */
1789 void FileHelper::Initialize()
1790 {
1791    UserFunction = 0;
1792    ContentType = USER_OWN_IMAGE;
1793    
1794    WriteMode = WMODE_RAW;
1795    WriteType = ExplicitVR;
1796
1797    PixelReadConverter  = new PixelReadConvert;
1798    PixelWriteConverter = new PixelWriteConvert;
1799    Archive = new DocEntryArchive( FileInternal );
1800 }
1801
1802 /**
1803  * \brief Reads/[decompresses] the pixels, 
1804  *        *without* making RGB from Palette Colors 
1805  * @return the pixels area, whatever its type 
1806  *         (uint8_t is just for prototyping : feel free to Cast it) 
1807  */ 
1808 uint8_t *FileHelper::GetRaw()
1809 {
1810    PixelReadConverter->SetUserFunction( UserFunction );
1811
1812    uint8_t *raw = PixelReadConverter->GetRaw();
1813    if ( ! raw )
1814    {
1815       // The Raw image migth not be loaded yet:
1816       std::ifstream *fp = FileInternal->OpenFile();
1817       PixelReadConverter->ReadAndDecompressPixelData( fp );
1818       if ( fp ) 
1819          FileInternal->CloseFile();
1820
1821       raw = PixelReadConverter->GetRaw();
1822       if ( ! raw )
1823       {
1824          gdcmWarningMacro( "Read/decompress of pixel data apparently went wrong.");
1825          return 0;
1826       }
1827    }
1828    return raw;
1829 }
1830
1831 //-----------------------------------------------------------------------------
1832 /**
1833  * \brief   Prints the common part of DataEntry, SeqEntry
1834  * @param   os ostream we want to print in
1835  * @param indent (unused)
1836  */
1837 void FileHelper::Print(std::ostream &os, std::string const &)
1838 {
1839    FileInternal->SetPrintLevel(PrintLevel);
1840    FileInternal->Print(os);
1841
1842    if ( FileInternal->IsReadable() )
1843    {
1844       PixelReadConverter->SetPrintLevel(PrintLevel);
1845       PixelReadConverter->Print(os);
1846    }
1847 }
1848
1849 //-----------------------------------------------------------------------------
1850 } // end namespace gdcm