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