]> Creatis software - gdcm.git/blob - src/gdcmFileHelper.cxx
BUG: Sync with ITK/gdcm CVS HEAD
[gdcm.git] / src / gdcmFileHelper.cxx
1 /*=========================================================================
2                                                                                 
3   Program:   gdcm
4   Module:    $RCSfile: gdcmFileHelper.cxx,v $
5   Language:  C++
6
7   Date:      $Date: 2006/02/15 21:58:54 $
8   Version:   $Revision: 1.92 $
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          std::cerr << "Writting as JPEG" << std::endl;
688          break;
689    }
690    CheckMandatoryElements();
691
692    // --------------------------------------------------------------
693    // Special Patch to allow gdcm to re-write ACR-LibIDO formated images
694    //
695    // if recognition code tells us we dealt with a LibIDO image
696    // we reproduce on disk the switch between lineNumber and columnNumber
697    // just before writting ...
698    /// \todo the best trick would be *change* the recognition code
699    ///       but pb expected if user deals with, e.g. COMPLEX images
700    
701    if ( WriteType == ACR_LIBIDO )
702    {
703       SetWriteToLibido();
704    }
705    else
706    {
707       SetWriteToNoLibido();
708    }
709    // ----------------- End of Special Patch ----------------
710   
711    switch(WriteMode)
712    {
713       case WMODE_RAW :
714          SetWriteToRaw(); // modifies and pushes to the archive, when necessary
715          break;
716       case WMODE_RGB :
717          SetWriteToRGB(); // modifies and pushes to the archive, when necessary
718          break;
719    }
720
721    bool check = CheckWriteIntegrity(); // verifies length
722    if (WriteType == JPEG ) check = true;
723    if (check)
724    {
725       check = FileInternal->Write(fileName,WriteType);
726    }
727
728    RestoreWrite();
729    RestoreWriteFileType();
730    RestoreWriteMandatory();
731
732    // --------------------------------------------------------------
733    // Special Patch to allow gdcm to re-write ACR-LibIDO formated images
734    // 
735    // ...and we restore the header to be Dicom Compliant again 
736    // just after writting
737    RestoreWriteOfLibido();
738    // ----------------- End of Special Patch ----------------
739
740    return check;
741 }
742
743 //-----------------------------------------------------------------------------
744 // Protected
745 /**
746  * \brief Checks the write integrity
747  *
748  * The tests made are :
749  *  - verify the size of the image to write with the possible write
750  *    when the user set an image data
751  * @return true if check is successfull
752  */
753 bool FileHelper::CheckWriteIntegrity()
754 {
755    if ( PixelWriteConverter->GetUserData() )
756    {
757       int numberBitsAllocated = FileInternal->GetBitsAllocated();
758       if ( numberBitsAllocated == 0 || numberBitsAllocated == 12 )
759       {
760          gdcmWarningMacro( "numberBitsAllocated changed from " 
761                           << numberBitsAllocated << " to 16 " 
762                           << " for consistency purpose" );
763          numberBitsAllocated = 16;
764       }
765
766       size_t decSize = FileInternal->GetXSize()
767                      * FileInternal->GetYSize() 
768                      * FileInternal->GetZSize()
769                      * FileInternal->GetSamplesPerPixel()
770                      * ( numberBitsAllocated / 8 );
771       size_t rgbSize = decSize;
772       if ( FileInternal->HasLUT() )
773          rgbSize = decSize * 3;
774
775       switch(WriteMode)
776       {
777          case WMODE_RAW :
778             if ( decSize!=PixelWriteConverter->GetUserDataSize() )
779             {
780                gdcmWarningMacro( "Data size (Raw) is incorrect. Should be " 
781                            << decSize << " / Found :" 
782                            << PixelWriteConverter->GetUserDataSize() );
783                return false;
784             }
785             break;
786          case WMODE_RGB :
787             if ( rgbSize!=PixelWriteConverter->GetUserDataSize() )
788             {
789                gdcmWarningMacro( "Data size (RGB) is incorrect. Should be " 
790                           << decSize << " / Found " 
791                           << PixelWriteConverter->GetUserDataSize() );
792                return false;
793             }
794             break;
795       }
796    }
797
798    return true;
799 }
800
801 /**
802  * \brief Updates the File to write RAW data (as opposed to RGB data)
803  *       (modifies, when necessary, photochromatic interpretation, 
804  *       bits allocated, Pixels element VR)
805  */ 
806 void FileHelper::SetWriteToRaw()
807 {
808    if ( FileInternal->GetNumberOfScalarComponents() == 3 
809     && !FileInternal->HasLUT() )
810    {
811       SetWriteToRGB();
812    } 
813    else
814    {
815       DataEntry *photInt = CopyDataEntry(0x0028,0x0004);
816       if (FileInternal->HasLUT() )
817       {
818          photInt->SetString("PALETTE COLOR ");
819       }
820       else
821       {
822          photInt->SetString("MONOCHROME2 ");
823       }
824
825       PixelWriteConverter->SetReadData(PixelReadConverter->GetRaw(),
826                                        PixelReadConverter->GetRawSize());
827
828       std::string vr = "OB";
829       if ( FileInternal->GetBitsAllocated()>8 )
830          vr = "OW";
831       if ( FileInternal->GetBitsAllocated()==24 ) // For RGB ACR files 
832          vr = "OB";
833       DataEntry *pixel = 
834          CopyDataEntry(GetFile()->GetGrPixel(),GetFile()->GetNumPixel(),vr);
835       pixel->SetFlag(DataEntry::FLAG_PIXELDATA);
836       pixel->SetBinArea(PixelWriteConverter->GetData(),false);
837       pixel->SetLength(PixelWriteConverter->GetDataSize());
838
839       Archive->Push(photInt);
840       Archive->Push(pixel);
841
842       photInt->Delete();
843       pixel->Delete();
844    }
845 }
846
847 /**
848  * \brief Updates the File to write RGB data (as opposed to RAW data)
849  *       (modifies, when necessary, photochromatic interpretation, 
850  *       samples per pixel, Planar configuration, 
851  *       bits allocated, bits stored, high bit -ACR 24 bits-
852  *       Pixels element VR, pushes out the LUT, )
853  */ 
854 void FileHelper::SetWriteToRGB()
855 {
856    if ( FileInternal->GetNumberOfScalarComponents()==3 )
857    {
858       PixelReadConverter->BuildRGBImage();
859       
860       DataEntry *spp = CopyDataEntry(0x0028,0x0002);
861       spp->SetString("3 ");
862
863       DataEntry *planConfig = CopyDataEntry(0x0028,0x0006);
864       planConfig->SetString("0 ");
865
866       DataEntry *photInt = CopyDataEntry(0x0028,0x0004);
867       photInt->SetString("RGB ");
868
869       if ( PixelReadConverter->GetRGB() )
870       {
871          PixelWriteConverter->SetReadData(PixelReadConverter->GetRGB(),
872                                           PixelReadConverter->GetRGBSize());
873       }
874       else // Raw data
875       {
876          PixelWriteConverter->SetReadData(PixelReadConverter->GetRaw(),
877                                           PixelReadConverter->GetRawSize());
878       }
879
880       std::string vr = "OB";
881       if ( FileInternal->GetBitsAllocated()>8 )
882          vr = "OW";
883       if ( FileInternal->GetBitsAllocated()==24 ) // For RGB ACR files 
884          vr = "OB";
885       DataEntry *pixel = 
886          CopyDataEntry(GetFile()->GetGrPixel(),GetFile()->GetNumPixel(),vr);
887       pixel->SetFlag(DataEntry::FLAG_PIXELDATA);
888       pixel->SetBinArea(PixelWriteConverter->GetData(),false);
889       pixel->SetLength(PixelWriteConverter->GetDataSize());
890
891       Archive->Push(spp);
892       Archive->Push(planConfig);
893       Archive->Push(photInt);
894       Archive->Push(pixel);
895
896       spp->Delete();
897       planConfig->Delete();
898       photInt->Delete();
899       pixel->Delete();
900
901       // Remove any LUT
902       Archive->Push(0x0028,0x1101);
903       Archive->Push(0x0028,0x1102);
904       Archive->Push(0x0028,0x1103);
905       Archive->Push(0x0028,0x1201);
906       Archive->Push(0x0028,0x1202);
907       Archive->Push(0x0028,0x1203);
908
909       // push out Palette Color Lookup Table UID, if any
910       Archive->Push(0x0028,0x1199);
911
912       // For old '24 Bits' ACR-NEMA
913       // Thus, we have a RGB image and the bits allocated = 24 and 
914       // samples per pixels = 1 (in the read file)
915       if ( FileInternal->GetBitsAllocated()==24 ) 
916       {
917          DataEntry *bitsAlloc = CopyDataEntry(0x0028,0x0100);
918          bitsAlloc->SetString("8 ");
919
920          DataEntry *bitsStored = CopyDataEntry(0x0028,0x0101);
921          bitsStored->SetString("8 ");
922
923          DataEntry *highBit = CopyDataEntry(0x0028,0x0102);
924          highBit->SetString("7 ");
925
926          Archive->Push(bitsAlloc);
927          Archive->Push(bitsStored);
928          Archive->Push(highBit);
929
930          bitsAlloc->Delete();
931          bitsStored->Delete();
932          highBit->Delete();
933       }
934    }
935    else
936    {
937       SetWriteToRaw();
938    }
939 }
940
941 /**
942  * \brief Restore the File write mode  
943  */ 
944 void FileHelper::RestoreWrite()
945 {
946    Archive->Restore(0x0028,0x0002);
947    Archive->Restore(0x0028,0x0004);
948    Archive->Restore(0x0028,0x0006);
949    Archive->Restore(GetFile()->GetGrPixel(),GetFile()->GetNumPixel());
950
951    // For old ACR-NEMA (24 bits problem)
952    Archive->Restore(0x0028,0x0100);
953    Archive->Restore(0x0028,0x0101);
954    Archive->Restore(0x0028,0x0102);
955
956    // For the LUT
957    Archive->Restore(0x0028,0x1101);
958    Archive->Restore(0x0028,0x1102);
959    Archive->Restore(0x0028,0x1103);
960    Archive->Restore(0x0028,0x1201);
961    Archive->Restore(0x0028,0x1202);
962    Archive->Restore(0x0028,0x1203);
963
964    // For the Palette Color Lookup Table UID
965    Archive->Restore(0x0028,0x1203); 
966
967
968    // group 0002 may be pushed out for ACR-NEMA writting purposes 
969    Archive->Restore(0x0002,0x0000);
970    Archive->Restore(0x0002,0x0001);
971    Archive->Restore(0x0002,0x0002);
972    Archive->Restore(0x0002,0x0003);
973    Archive->Restore(0x0002,0x0010);
974    Archive->Restore(0x0002,0x0012);
975    Archive->Restore(0x0002,0x0013);
976    Archive->Restore(0x0002,0x0016);
977    Archive->Restore(0x0002,0x0100);
978    Archive->Restore(0x0002,0x0102);
979 }
980
981 /**
982  * \brief Pushes out the whole group 0002
983  *        FIXME : better, set a flag to tell the writer not to write it ...
984  *        FIXME : method should probably have an other name !
985  *                SetWriteFileTypeToACR is NOT opposed to 
986  *                SetWriteFileTypeToExplicitVR and SetWriteFileTypeToImplicitVR
987  */ 
988 void FileHelper::SetWriteFileTypeToACR()
989 {
990    Archive->Push(0x0002,0x0000);
991    Archive->Push(0x0002,0x0001);
992    Archive->Push(0x0002,0x0002);
993    Archive->Push(0x0002,0x0003);
994    Archive->Push(0x0002,0x0010);
995    Archive->Push(0x0002,0x0012);
996    Archive->Push(0x0002,0x0013);
997    Archive->Push(0x0002,0x0016);
998    Archive->Push(0x0002,0x0100);
999    Archive->Push(0x0002,0x0102);
1000 }
1001
1002 /**
1003  * \brief Sets in the File the TransferSyntax to 'Explicit VR Little Endian"   
1004  */ 
1005 void FileHelper::SetWriteFileTypeToJPEG()
1006 {
1007    std::string ts = Util::DicomString( 
1008       Global::GetTS()->GetSpecialTransferSyntax(TS::JPEGBaselineProcess1) );
1009
1010    DataEntry *tss = CopyDataEntry(0x0002,0x0010);
1011    tss->SetString(ts);
1012
1013    Archive->Push(tss);
1014    tss->Delete();
1015 }
1016
1017 void FileHelper::SetWriteFileTypeToExplicitVR()
1018 {
1019    std::string ts = Util::DicomString( 
1020       Global::GetTS()->GetSpecialTransferSyntax(TS::ExplicitVRLittleEndian) );
1021
1022    DataEntry *tss = CopyDataEntry(0x0002,0x0010);
1023    tss->SetString(ts);
1024
1025    Archive->Push(tss);
1026    tss->Delete();
1027 }
1028
1029 /**
1030  * \brief Sets in the File the TransferSyntax to 'Implicit VR Little Endian"   
1031  */ 
1032 void FileHelper::SetWriteFileTypeToImplicitVR()
1033 {
1034    std::string ts = Util::DicomString(
1035       Global::GetTS()->GetSpecialTransferSyntax(TS::ImplicitVRLittleEndian) );
1036
1037    DataEntry *tss = CopyDataEntry(0x0002,0x0010);
1038    tss->SetString(ts);
1039
1040    Archive->Push(tss);
1041    tss->Delete();
1042 }
1043
1044
1045 /**
1046  * \brief Restore in the File the initial group 0002
1047  */ 
1048 void FileHelper::RestoreWriteFileType()
1049 {
1050 }
1051
1052 /**
1053  * \brief Set the Write not to Libido format
1054  */ 
1055 void FileHelper::SetWriteToLibido()
1056 {
1057    DataEntry *oldRow = FileInternal->GetDataEntry(0x0028, 0x0010);
1058    DataEntry *oldCol = FileInternal->GetDataEntry(0x0028, 0x0011);
1059    
1060    if ( oldRow && oldCol )
1061    {
1062       std::string rows, columns; 
1063
1064       DataEntry *newRow=DataEntry::New(oldRow->GetDictEntry());
1065       DataEntry *newCol=DataEntry::New(oldCol->GetDictEntry());
1066
1067       newRow->Copy(oldCol);
1068       newCol->Copy(oldRow);
1069
1070       newRow->SetString(oldCol->GetString());
1071       newCol->SetString(oldRow->GetString());
1072
1073       Archive->Push(newRow);
1074       Archive->Push(newCol);
1075
1076       newRow->Delete();
1077       newCol->Delete();
1078    }
1079
1080    DataEntry *libidoCode = CopyDataEntry(0x0008,0x0010);
1081    libidoCode->SetString("ACRNEMA_LIBIDO_1.1");
1082    Archive->Push(libidoCode);
1083    libidoCode->Delete();
1084 }
1085
1086 /**
1087  * \brief Set the Write not to No Libido format
1088  */ 
1089 void FileHelper::SetWriteToNoLibido()
1090 {
1091    DataEntry *recCode = FileInternal->GetDataEntry(0x0008,0x0010);
1092    if ( recCode )
1093    {
1094       if ( recCode->GetString() == "ACRNEMA_LIBIDO_1.1" )
1095       {
1096          DataEntry *libidoCode = CopyDataEntry(0x0008,0x0010);
1097          libidoCode->SetString("");
1098          Archive->Push(libidoCode);
1099          libidoCode->Delete();
1100       }
1101    }
1102 }
1103
1104 /**
1105  * \brief Restore the Write format
1106  */ 
1107 void FileHelper::RestoreWriteOfLibido()
1108 {
1109    Archive->Restore(0x0028,0x0010);
1110    Archive->Restore(0x0028,0x0011);
1111    Archive->Restore(0x0008,0x0010);
1112
1113    // Restore 'LibIDO-special' entries, if any
1114    Archive->Restore(0x0028,0x0015);
1115    Archive->Restore(0x0028,0x0016);
1116    Archive->Restore(0x0028,0x0017);
1117    Archive->Restore(0x0028,0x00199);
1118 }
1119
1120 /**
1121  * \brief   Duplicates a DataEntry or creates it.
1122  * @param   group   Group number of the Entry 
1123  * @param   elem  Element number of the Entry
1124  * @param   vr  Value Representation of the Entry
1125  *          FIXME : what is it used for?
1126  * \return  pointer to the new Bin Entry (NULL when creation failed).
1127  */ 
1128 DataEntry *FileHelper::CopyDataEntry(uint16_t group, uint16_t elem,
1129                                    const TagName &vr)
1130 {
1131    DocEntry *oldE = FileInternal->GetDocEntry(group, elem);
1132    DataEntry *newE;
1133
1134    if ( oldE && vr != GDCM_VRUNKNOWN ) 
1135       if ( oldE->GetVR() != vr )
1136          oldE = NULL;
1137
1138    if ( oldE )
1139    {
1140       newE = DataEntry::New(oldE->GetDictEntry());
1141       newE->Copy(oldE);
1142    }
1143    else
1144    {
1145       newE = GetFile()->NewDataEntry(group, elem, vr);
1146    }
1147
1148    return newE;
1149 }
1150
1151 /**
1152  * \brief   This method is called automatically, just before writting
1153  *         in order to produce a 'True Dicom V3' image.
1154  *
1155  *         We cannot know *how* the user made the File :
1156  *         (reading an old ACR-NEMA file or a not very clean DICOM file ...) 
1157  *          Just before writting :
1158  *             - we check the Entries
1159  *             - we create the mandatory entries if they are missing
1160  *             - we modify the values if necessary
1161  *             - we push the sensitive entries to the Archive
1162  *          The writing process will restore the entries as they where before 
1163  *          entering FileHelper::CheckMandatoryElements, so the user will always
1164  *          see the entries just as they were before he decided to write.
1165  *
1166  * \note
1167  *       -  Entries whose type is 1 are mandatory, with a mandatory value
1168  *       -  Entries whose type is 1c are mandatory-inside-a-Sequence,
1169  *                             with a mandatory value
1170  *       -  Entries whose type is 2 are mandatory, with an optional value
1171  *       -  Entries whose type is 2c are mandatory-inside-a-Sequence,
1172  *                             with an optional value
1173  *       -  Entries whose type is 3 are optional
1174  * 
1175  * \todo 
1176  *         - warn the user if we had to add some entries :
1177  *         even if a mandatory entry is missing, we add it, with a default value
1178  *         (we don't want to give up the writting process if user forgot to
1179  *         specify Lena's Patient ID, for instance ...)
1180  *         - read the whole PS 3.3 Part of DICOM  (890 pages)
1181  *         and write a *full* checker (probably one method per Modality ...)
1182  *         Any contribution is welcome. 
1183  *         - write a user callable full checker, to allow post reading
1184  *         and/or pre writting image consistency check.           
1185  */ 
1186
1187 /* -------------------------------------------------------------------------------------
1188 To be moved to User's guide / WIKI  ?
1189
1190 We have to deal with 4 *very* different cases :
1191 -1) user created ex nihilo his own image  and wants to write it as a Dicom image.
1192 -2) user modified the pixels of an existing image.
1193 -3) user created a new image, using existing images (eg MIP, MPR, cartography image)
1194 -4) user anonymized an image without processing the pixels.
1195
1196 gdcm::FileHelper::CheckMandatoryElements() deals automatically with these cases.
1197
1198 1)2)3)4)
1199 0008 0012 Instance Creation Date
1200 0008 0013 Instance Creation Time
1201 0008 0018 SOP Instance UID
1202 are *always* created with the current values; user has *no* possible intervention on
1203 them.
1204
1205 'Serie Instance UID'(0x0020,0x000e)
1206 'Study Instance UID'(0x0020,0x000d) are kept as is if already exist,
1207                                     created  if it doesn't.
1208  The user is allowed to create his own Series/Studies, 
1209      keeping the same 'Serie Instance UID' / 'Study Instance UID' for various images
1210  Warning :     
1211  The user shouldn't add any image to a 'Manufacturer Serie'
1212      but there is no way no to allowed him to do that
1213      
1214  None of the 'shadow elements' are droped out.
1215      
1216
1217 1)
1218 'Modality' (0x0008,0x0060)       is defaulted to "OT" (other) if missing.
1219 'Conversion Type (0x0008,0x0064) is forced to 'SYN' (Synthetic Image).
1220 'Study Date', 'Study Time' are defaulted to current Date and Time.
1221  
1222 1)2)3)
1223 'Media Storage SOP Class UID' (0x0002,0x0002)
1224 'SOP Class UID'               (0x0008,0x0016) are set to 
1225                                                [Secondary Capture Image Storage]
1226 'Image Type'                  (0x0008,0x0008) is forced to  "DERIVED\PRIMARY"
1227 Conversion Type               (0x0008,0x0064) is forced to 'SYN' (Synthetic Image)
1228
1229 2)4)
1230 If 'SOP Class UID' exists in the native image  ('true DICOM' image)
1231     we create the 'Source Image Sequence' SeqEntry (0x0008, 0x2112)    
1232     --> 'Referenced SOP Class UID' (0x0008, 0x1150)
1233          whose value is the original 'SOP Class UID'
1234     --> 'Referenced SOP Instance UID' (0x0008, 0x1155)
1235          whose value is the original 'SOP Class UID'
1236
1237 3) TODO : find a trick to allow user to pass to the writter the list of the Dicom images 
1238           or the Series, (or the Study ?) he used to created his image 
1239           (MIP, MPR, cartography image, ...)
1240            These info should be stored (?)
1241           0008 1110 SQ 1 Referenced Study Sequence
1242           0008 1115 SQ 1 Referenced Series Sequence
1243           0008 1140 SQ 1 Referenced Image Sequence
1244        
1245 4) When user *knows* he didn't modified the pixels, he may ask the writer to keep some
1246 informations unchanged :
1247 'Media Storage SOP Class UID' (0x0002,0x0002)
1248 'SOP Class UID'               (0x0008,0x0016)
1249 'Image Type'                  (0x0008,0x0008)
1250 'Conversion Type'             (0x0008,0x0064)
1251 He has to use gdcm::FileHelper::SetKeepMediaStorageSOPClassUID(true)
1252 (probabely name has to be changed)
1253
1254
1255 Bellow follows the full description (hope so !) of the consistency checks performed 
1256 by gdcm::FileHelper::CheckMandatoryElements()
1257
1258
1259 -->'Media Storage SOP Class UID' (0x0002,0x0002)
1260 -->'SOP Class UID'               (0x0008,0x0016) are set to 
1261                                                [Secondary Capture Image Storage]
1262    (Potentialy, the image was modified by user, and post-processed; 
1263     it's no longer a 'native' image)
1264   Except if user told he wants to keep MediaStorageSOPClassUID,
1265   when *he* knows he didn't modify the image (e.g. : he just anonymized the file)
1266
1267 --> 'Image Type'  (0x0008,0x0008)
1268      is forced to  "DERIVED\PRIMARY"
1269      (The written image is no longer an 'ORIGINAL' one)
1270   Except if user told he wants to keep MediaStorageSOPClassUID,
1271   when *he* knows he didn't modify the image (e.g. : he just anonymized the file)
1272    
1273  -->  Conversion Type (0x0008,0x0064)
1274      is forced to 'SYN' (Synthetic Image)
1275   Except if user told he wants to keep MediaStorageSOPClassUID,
1276   when *he* knows he didn't modify the image (e.g. : he just anonymized the file)
1277             
1278 --> 'Modality' (0x0008,0x0060)   
1279     is defaulted to "OT" (other) if missing.   
1280     (a fully user created image belongs to *no* modality)
1281       
1282 --> 'Media Storage SOP Instance UID' (0x0002,0x0003)
1283 --> 'Implementation Class UID'       (0x0002,0x0012)
1284     are automatically generated; no user intervention possible
1285
1286 --> 'Serie Instance UID'(0x0020,0x000e)
1287 --> 'Study Instance UID'(0x0020,0x000d) are kept as is if already exist
1288                                              created  if it doesn't.
1289      The user is allowed to create his own Series/Studies, 
1290      keeping the same 'Serie Instance UID' / 'Study Instance UID' 
1291      for various images
1292      Warning :     
1293      The user shouldn't add any image to a 'Manufacturer Serie'
1294      but there is no way no to allowed him to do that 
1295              
1296 --> If 'SOP Class UID' exists in the native image  ('true DICOM' image)
1297     we create the 'Source Image Sequence' SeqEntry (0x0008, 0x2112)
1298     
1299     --> 'Referenced SOP Class UID' (0x0008, 0x1150)
1300          whose value is the original 'SOP Class UID'
1301     --> 'Referenced SOP Instance UID' (0x0008, 0x1155)
1302          whose value is the original 'SOP Class UID'
1303     
1304 --> Bits Stored, Bits Allocated, Hight Bit Position are checked for consistency
1305 --> Pixel Spacing     (0x0028,0x0030) is defaulted to "1.0\1.0"
1306 --> Samples Per Pixel (0x0028,0x0002) is defaulted to 1 (grayscale)
1307
1308 --> Imager Pixel Spacing (0x0018,0x1164) : defaulted to Pixel Spacing value
1309
1310 --> Instance Creation Date, Instance Creation Time are forced to current Date and Time
1311
1312 --> Study Date, Study Time are defaulted to current Date and Time
1313    (they remain unchanged if they exist)
1314
1315 --> Patient Orientation : (0x0020,0x0020), if not present, is deduced from 
1316     Image Orientation (Patient) : (0020|0037) or from
1317     Image Orientation (RET)     : (0020 0035)
1318    
1319 --> Study ID, Series Number, Instance Number, Patient Orientation (Type 2)
1320     are created, with empty value if there are missing.
1321
1322 --> Manufacturer, Institution Name, Patient's Name, (Type 2)
1323     are defaulted with a 'gdcm' value.
1324     
1325 --> Patient ID, Patient's Birth Date, Patient's Sex, (Type 2)
1326 --> Referring Physician's Name  (Type 2)
1327     are created, with empty value if there are missing.  
1328
1329  -------------------------------------------------------------------------------------*/
1330  
1331 void FileHelper::CheckMandatoryElements()
1332 {
1333    std::string sop =  Util::CreateUniqueUID();
1334    
1335    // just to remember : 'official' 0002 group
1336    if ( WriteType != ACR && WriteType != ACR_LIBIDO )
1337    {
1338      // Group 000002 (Meta Elements) already pushed out
1339   
1340    //0002 0000 UL 1 Meta Group Length
1341    //0002 0001 OB 1 File Meta Information Version
1342    //0002 0002 UI 1 Media Stored SOP Class UID
1343    //0002 0003 UI 1 Media Stored SOP Instance UID
1344    //0002 0010 UI 1 Transfer Syntax UID
1345    //0002 0012 UI 1 Implementation Class UID
1346    //0002 0013 SH 1 Implementation Version Name
1347    //0002 0016 AE 1 Source Application Entity Title
1348    //0002 0100 UI 1 Private Information Creator
1349    //0002 0102 OB 1 Private Information
1350   
1351    // Create them if not found
1352    // Always modify the value
1353    // Push the entries to the archive.
1354       CopyMandatoryEntry(0x0002,0x0000,"0");
1355  
1356       DataEntry *e_0002_0001 = CopyDataEntry(0x0002,0x0001, "OB");
1357       e_0002_0001->SetBinArea((uint8_t*)Util::GetFileMetaInformationVersion(),
1358                                false);
1359       e_0002_0001->SetLength(2);
1360       Archive->Push(e_0002_0001);
1361       e_0002_0001->Delete(); 
1362
1363       if ( KeepMediaStorageSOPClassUID)      
1364    // It up to the use to *know* whether he modified the pixels or not.
1365    // he is allowed to keep the original 'Media Storage SOP Class UID'
1366          CheckMandatoryEntry(0x0002,0x0002,"1.2.840.10008.5.1.4.1.1.7");    
1367       else
1368    // Potentialy this is a post-processed image 
1369    // 'Media Storage SOP Class UID'  --> [Secondary Capture Image Storage]
1370          CopyMandatoryEntry(0x0002,0x0002,"1.2.840.10008.5.1.4.1.1.7");    
1371        
1372    // 'Media Storage SOP Instance UID'   
1373       CopyMandatoryEntry(0x0002,0x0003,sop);
1374       
1375    // 'Implementation Class UID'
1376    // FIXME : in all examples we have, 0x0002,0x0012 is not so long :
1377    //         semms to be Root UID + 4 digits (?)
1378       CopyMandatoryEntry(0x0002,0x0012,Util::CreateUniqueUID());
1379
1380    // 'Implementation Version Name'
1381       std::string version = "GDCM ";
1382       version += Util::GetVersion();
1383       CopyMandatoryEntry(0x0002,0x0013,version);
1384    }
1385
1386    // Push out 'LibIDO-special' entries, if any
1387    Archive->Push(0x0028,0x0015);
1388    Archive->Push(0x0028,0x0016);
1389    Archive->Push(0x0028,0x0017);
1390    Archive->Push(0x0028,0x00199);
1391
1392    // Deal with the pb of (Bits Stored = 12)
1393    // - we're gonna write the image as Bits Stored = 16
1394    if ( FileInternal->GetEntryString(0x0028,0x0100) ==  "12")
1395    {
1396       CopyMandatoryEntry(0x0028,0x0100,"16");
1397    }
1398
1399    // Check if user wasn't drunk ;-)
1400
1401    std::ostringstream s;
1402    // check 'Bits Allocated' vs decent values
1403    int nbBitsAllocated = FileInternal->GetBitsAllocated();
1404    if ( nbBitsAllocated == 0 || nbBitsAllocated > 32)
1405    {
1406       CopyMandatoryEntry(0x0028,0x0100,"16");
1407       gdcmWarningMacro("(0028,0100) changed from "
1408          << nbBitsAllocated << " to 16 for consistency purpose");
1409       nbBitsAllocated = 16; 
1410    }
1411    // check 'Bits Stored' vs 'Bits Allocated'   
1412    int nbBitsStored = FileInternal->GetBitsStored();
1413    if ( nbBitsStored == 0 || nbBitsStored > nbBitsAllocated )
1414    {
1415       s.str("");
1416       s << nbBitsAllocated;
1417       CopyMandatoryEntry(0x0028,0x0101,s.str());
1418       gdcmWarningMacro("(0028,0101) changed from "
1419                        << nbBitsStored << " to " << nbBitsAllocated
1420                        << " for consistency purpose" );
1421       nbBitsStored = nbBitsAllocated; 
1422     }
1423    // check 'Hight Bit Position' vs 'Bits Allocated' and 'Bits Stored'
1424    int highBitPosition = FileInternal->GetHighBitPosition();
1425    if ( highBitPosition == 0 || 
1426         highBitPosition > nbBitsAllocated-1 ||
1427         highBitPosition < nbBitsStored-1  )
1428    {
1429       s.str("");
1430       s << nbBitsStored - 1; 
1431       CopyMandatoryEntry(0x0028,0x0102,s.str());
1432       gdcmWarningMacro("(0028,0102) changed from "
1433                        << highBitPosition << " to " << nbBitsAllocated-1
1434                        << " for consistency purpose");
1435    }
1436
1437    std::string pixelSpacing = FileInternal->GetEntryString(0x0028,0x0030);
1438    if ( pixelSpacing == GDCM_UNFOUND )
1439    {
1440       pixelSpacing = "1.0\\1.0";
1441        // if missing, Pixel Spacing forced to "1.0\1.0"
1442       CopyMandatoryEntry(0x0028,0x0030,pixelSpacing);
1443    }
1444    
1445    // 'Imager Pixel Spacing' : defaulted to 'Pixel Spacing'
1446    // --> This one is the *legal* one !
1447    // FIXME : we should write it only when we are *sure* the image comes from
1448    //         an imager (see also 0008,0x0064)          
1449    CheckMandatoryEntry(0x0018,0x1164,pixelSpacing);
1450    
1451    // Samples Per Pixel (type 1) : default to grayscale 
1452    CheckMandatoryEntry(0x0028,0x0002,"1");
1453    
1454    // --- Check UID-related Entries ---
1455
1456    // If 'SOP Class UID' exists ('true DICOM' image)
1457    // we create the 'Source Image Sequence' SeqEntry
1458    // to hold informations about the Source Image
1459
1460    DataEntry *e_0008_0016 = FileInternal->GetDataEntry(0x0008, 0x0016);
1461    if ( e_0008_0016 )
1462    {
1463       // Create 'Source Image Sequence' SeqEntry
1464       SeqEntry *sis = SeqEntry::New (
1465             Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x2112) );
1466       SQItem *sqi = SQItem::New(1);
1467       // (we assume 'SOP Instance UID' exists too) 
1468       // create 'Referenced SOP Class UID'
1469       DataEntry *e_0008_1150 = DataEntry::New(
1470             Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x1150) );
1471       e_0008_1150->SetString( e_0008_0016->GetString());
1472       sqi->AddEntry(e_0008_1150);
1473       e_0008_1150->Delete();
1474       
1475       // create 'Referenced SOP Instance UID'
1476       DataEntry *e_0008_0018 = FileInternal->GetDataEntry(0x0008, 0x0018);
1477       DataEntry *e_0008_1155 = DataEntry::New(
1478             Global::GetDicts()->GetDefaultPubDict()->GetEntry(0x0008, 0x1155) );
1479       e_0008_1155->SetString( e_0008_0018->GetString());
1480       sqi->AddEntry(e_0008_1155);
1481       e_0008_1155->Delete();
1482
1483       sis->AddSQItem(sqi,1);
1484       sqi->Delete();
1485
1486       // temporarily replaces any previous 'Source Image Sequence' 
1487       Archive->Push(sis);
1488       sis->Delete();
1489  
1490       // FIXME : is 'Image Type' *really* depending on the presence of'SOP Class UID'?
1491        if ( KeepMediaStorageSOPClassUID)      
1492    // It up to the use to *know* whether he modified the pixels or not.
1493    // he is allowed to keep the original 'Media Storage SOP Class UID'
1494    // and 'Image Type' as well
1495          CheckMandatoryEntry(0x0008,0x0008,"DERIVED\\PRIMARY");    
1496       else
1497    // Potentialy this is a post-processed image 
1498    // (The written image is no longer an 'ORIGINAL' one)
1499       CopyMandatoryEntry(0x0008,0x0008,"DERIVED\\PRIMARY");
1500
1501    }
1502
1503    // At the end, not to overwrite the original ones,
1504    // needed by 'Referenced SOP Instance UID', 'Referenced SOP Class UID'   
1505    // 'SOP Instance UID'  
1506    CopyMandatoryEntry(0x0008,0x0018,sop);
1507    
1508    // the gdcm written image is a [Secondary Capture Image Storage]
1509    // except if user told us he dind't modify the pixels, and, therefore
1510    // he want to keep the 'Media Storage SOP Class UID'
1511    
1512       // 'Media Storage SOP Class UID' : [Secondary Capture Image Storage]
1513    if ( KeepMediaStorageSOPClassUID)
1514    {      
1515       // It up to the use to *know* whether he modified the pixels or not.
1516       // he is allowed to keep the original 'Media Storage SOP Class UID'
1517       CheckMandatoryEntry(0x0008,0x0016,"1.2.840.10008.5.1.4.1.1.7");    
1518    }
1519    else
1520    {
1521        // Potentialy this is a post-processed image 
1522        // 'Media Storage SOP Class UID'  --> [Secondary Capture Image Storage]
1523       CopyMandatoryEntry(0x0008,0x0016,"1.2.840.10008.5.1.4.1.1.7");    
1524
1525        // FIXME : Must we Force Value, or Default value ?
1526        // Is it Type 1 for any Modality ?
1527        //    --> Answer seems to be NO :-(
1528        // FIXME : we should write it only when we are *sure* the image 
1529        //         *does not* come from an imager (see also 0018,0x1164)
1530
1531        // Conversion Type.
1532        // Other possible values are :
1533        // See PS 3.3, Page 408
1534    
1535        // DV = Digitized Video
1536        // DI = Digital Interface   
1537        // DF = Digitized Film
1538        // WSD = Workstation
1539        // SD = Scanned Document
1540        // SI = Scanned Image
1541        // DRW = Drawing
1542        // SYN = Synthetic Image
1543      
1544       CheckMandatoryEntry(0x0008,0x0064,"SYN");
1545    }   
1546            
1547    // ---- The user will never have to take any action on the following ----
1548    
1549    // new value for 'SOP Instance UID'
1550    //SetMandatoryEntry(0x0008,0x0018,Util::CreateUniqueUID());
1551
1552    // Instance Creation Date
1553    const std::string &date = Util::GetCurrentDate();
1554    CopyMandatoryEntry(0x0008,0x0012,date);
1555  
1556    // Instance Creation Time
1557    const std::string &time = Util::GetCurrentTime();
1558    CopyMandatoryEntry(0x0008,0x0013,time);
1559
1560    // Study Date
1561    CheckMandatoryEntry(0x0008,0x0020,date);
1562    // Study Time
1563    CheckMandatoryEntry(0x0008,0x0030,time);
1564
1565    // Accession Number
1566    //CopyMandatoryEntry(0x0008,0x0050,"");
1567    CheckMandatoryEntry(0x0008,0x0050,"");
1568    
1569
1570    // ----- Add Mandatory Entries if missing ---
1571    // Entries whose type is 1 are mandatory, with a mandatory value
1572    // Entries whose type is 1c are mandatory-inside-a-Sequence,
1573    //                          with a mandatory value
1574    // Entries whose type is 2 are mandatory, with an optional value
1575    // Entries whose type is 2c are mandatory-inside-a-Sequence,
1576    //                          with an optional value
1577    // Entries whose type is 3 are optional
1578
1579    // 'Study Instance UID'
1580    // Keep the value if exists
1581    // The user is allowed to create his own Study, 
1582    //          keeping the same 'Study Instance UID' for various images
1583    // The user may add images to a 'Manufacturer Study',
1584    //          adding new Series to an already existing Study 
1585    CheckMandatoryEntry(0x0020,0x000d,Util::CreateUniqueUID());
1586
1587    // 'Serie Instance UID'
1588    // Keep the value if exists
1589    // The user is allowed to create his own Series, 
1590    // keeping the same 'Serie Instance UID' for various images
1591    // The user shouldn't add any image to a 'Manufacturer Serie'
1592    // but there is no way no to prevent him for doing that 
1593    CheckMandatoryEntry(0x0020,0x000e,Util::CreateUniqueUID());
1594
1595    // Study ID
1596    CheckMandatoryEntry(0x0020,0x0010,"");
1597
1598    // Series Number
1599    CheckMandatoryEntry(0x0020,0x0011,"");
1600
1601    // Instance Number
1602    CheckMandatoryEntry(0x0020,0x0013,"");
1603    
1604    // Patient Orientation
1605    // Can be computed from (0020|0037) :  Image Orientation (Patient)
1606    gdcm::Orientation *o = gdcm::Orientation::New();
1607    std::string ori = o->GetOrientation ( FileInternal );
1608    o->Delete();
1609    if (ori != "\\" && ori != GDCM_UNFOUND)
1610       CheckMandatoryEntry(0x0020,0x0020,ori);
1611    else   
1612       CheckMandatoryEntry(0x0020,0x0020,"");
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   GroupHT grHT;
1642   DocEntry *d = FileInternal->GetFirstEntry();
1643   while(d)
1644   {
1645     grHT[d->GetGroup()] = 0;
1646     d=FileInternal->GetNextEntry();
1647   }
1648   // Second stage : add the missing ones (if any)
1649   for (GroupHT::iterator it = grHT.begin(); it != grHT.end(); ++it)  
1650   {
1651       CheckMandatoryEntry(it->first, 0x0000, "0"); 
1652   }    
1653   // Third stage : update all 'zero level' groups length
1654 */   
1655
1656
1657 void FileHelper::CheckMandatoryEntry(uint16_t group,uint16_t elem,std::string value)
1658 {
1659    DataEntry *entry = FileInternal->GetDataEntry(group,elem);
1660    if ( !entry )
1661    {
1662       entry = DataEntry::New(Global::GetDicts()->GetDefaultPubDict()->GetEntry(group,elem));
1663       entry->SetString(value);
1664       Archive->Push(entry);
1665       entry->Delete();
1666    }
1667 }
1668
1669 void FileHelper::SetMandatoryEntry(uint16_t group,uint16_t elem,std::string value)
1670 {
1671    DataEntry *entry = DataEntry::New(Global::GetDicts()->GetDefaultPubDict()->GetEntry(group,elem));
1672    entry->SetString(value);
1673    Archive->Push(entry);
1674    entry->Delete();
1675 }
1676
1677 void FileHelper::CopyMandatoryEntry(uint16_t group,uint16_t elem,std::string value)
1678 {
1679    DataEntry *entry = CopyDataEntry(group,elem);
1680    entry->SetString(value);
1681    Archive->Push(entry);
1682    entry->Delete();
1683 }
1684
1685 /**
1686  * \brief Restore in the File the initial group 0002
1687  */
1688 void FileHelper::RestoreWriteMandatory()
1689 {
1690    // group 0002 may be pushed out for ACR-NEMA writting purposes 
1691    Archive->Restore(0x0002,0x0000);
1692    Archive->Restore(0x0002,0x0001);
1693    Archive->Restore(0x0002,0x0002);
1694    Archive->Restore(0x0002,0x0003);
1695    Archive->Restore(0x0002,0x0010);
1696    Archive->Restore(0x0002,0x0012);
1697    Archive->Restore(0x0002,0x0013);
1698    Archive->Restore(0x0002,0x0016);
1699    Archive->Restore(0x0002,0x0100);
1700    Archive->Restore(0x0002,0x0102);
1701
1702    // FIXME : Check if none is missing !
1703    
1704    Archive->Restore(0x0008,0x0012);
1705    Archive->Restore(0x0008,0x0013);
1706    Archive->Restore(0x0008,0x0016);
1707    Archive->Restore(0x0008,0x0018);
1708    Archive->Restore(0x0008,0x0060);
1709    Archive->Restore(0x0008,0x0070);
1710    Archive->Restore(0x0008,0x0080);
1711    Archive->Restore(0x0008,0x0090);
1712    Archive->Restore(0x0008,0x2112);
1713
1714    Archive->Restore(0x0010,0x0010);
1715    Archive->Restore(0x0010,0x0030);
1716    Archive->Restore(0x0010,0x0040);
1717
1718    Archive->Restore(0x0020,0x000d);
1719    Archive->Restore(0x0020,0x000e);
1720 }
1721
1722
1723 /**
1724  * \brief   CallStartMethod
1725  */
1726 void FileHelper::CallStartMethod()
1727 {
1728    Progress = 0.0f;
1729    Abort    = false;
1730    CommandManager::ExecuteCommand(this,CMD_STARTPROGRESS);
1731 }
1732
1733 /**
1734  * \brief   CallProgressMethod
1735  */
1736 void FileHelper::CallProgressMethod()
1737 {
1738    CommandManager::ExecuteCommand(this,CMD_PROGRESS);
1739 }
1740
1741 /**
1742  * \brief   CallEndMethod
1743  */
1744 void FileHelper::CallEndMethod()
1745 {
1746    Progress = 1.0f;
1747    CommandManager::ExecuteCommand(this,CMD_ENDPROGRESS);
1748 }
1749
1750 //-----------------------------------------------------------------------------
1751 // Private
1752 /**
1753  * \brief Factorization for various forms of constructors.
1754  */
1755 void FileHelper::Initialize()
1756 {
1757    UserFunction = 0;
1758    KeepMediaStorageSOPClassUID = false;
1759    
1760    WriteMode = WMODE_RAW;
1761    WriteType = ExplicitVR;
1762
1763    PixelReadConverter  = new PixelReadConvert;
1764    PixelWriteConverter = new PixelWriteConvert;
1765    Archive = new DocEntryArchive( FileInternal );
1766 }
1767
1768 /**
1769  * \brief Reads/[decompresses] the pixels, 
1770  *        *without* making RGB from Palette Colors 
1771  * @return the pixels area, whatever its type 
1772  *         (uint8_t is just for prototyping : feel free to Cast it) 
1773  */ 
1774 uint8_t *FileHelper::GetRaw()
1775 {
1776    PixelReadConverter->SetUserFunction( UserFunction );
1777
1778    uint8_t *raw = PixelReadConverter->GetRaw();
1779    if ( ! raw )
1780    {
1781       // The Raw image migth not be loaded yet:
1782       std::ifstream *fp = FileInternal->OpenFile();
1783       PixelReadConverter->ReadAndDecompressPixelData( fp );
1784       if ( fp ) 
1785          FileInternal->CloseFile();
1786
1787       raw = PixelReadConverter->GetRaw();
1788       if ( ! raw )
1789       {
1790          gdcmWarningMacro( "Read/decompress of pixel data apparently went wrong.");
1791          return 0;
1792       }
1793    }
1794    return raw;
1795 }
1796
1797 //-----------------------------------------------------------------------------
1798 /**
1799  * \brief   Prints the common part of DataEntry, SeqEntry
1800  * @param   os ostream we want to print in
1801  * @param indent (unused)
1802  */
1803 void FileHelper::Print(std::ostream &os, std::string const &)
1804 {
1805    FileInternal->SetPrintLevel(PrintLevel);
1806    FileInternal->Print(os);
1807
1808    if ( FileInternal->IsReadable() )
1809    {
1810       PixelReadConverter->SetPrintLevel(PrintLevel);
1811       PixelReadConverter->Print(os);
1812    }
1813 }
1814
1815 //-----------------------------------------------------------------------------
1816 } // end namespace gdcm