]> Creatis software - gdcm.git/blob - src/gdcmDicomDir.cxx
Thx to Jean-Michel Rouet for reporting compile error.
[gdcm.git] / src / gdcmDicomDir.cxx
1 /*=========================================================================
2   
3   Program:   gdcm
4   Module:    $RCSfile: gdcmDicomDir.cxx,v $
5   Language:  C++
6   Date:      $Date: 2010/04/09 15:23:40 $
7   Version:   $Revision: 1.198 $
8   
9   Copyright (c) CREATIS (Centre de Recherche et d'Applications en Traitement de
10   l'Image). All rights reserved. See Doc/License.txt or
11   http://www.creatis.insa-lyon.fr/Public/Gdcm/License.html for details.
12   
13      This software is distributed WITHOUT ANY WARRANTY; without even
14      the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15      PURPOSE.  See the above copyright notices for more information.
16   
17 =========================================================================*/
18
19 //-----------------------------------------------------------------------------
20 //  For full DICOMDIR description, see:
21 //  PS 3.3-2003, pages 731-750
22 //-----------------------------------------------------------------------------
23 #include "gdcmDicomDir.h"
24 #include "gdcmDicomDirObject.h"
25 #include "gdcmDicomDirStudy.h"
26 #include "gdcmDicomDirSerie.h"
27 #include "gdcmDicomDirVisit.h"
28 #include "gdcmDicomDirImage.h"
29 #include "gdcmDicomDirPrivate.h"
30 #include "gdcmDicomDirPatient.h"
31 #include "gdcmDicomDirMeta.h"
32 #include "gdcmDicomDirElement.h"
33 #include "gdcmDirList.h"
34 #include "gdcmUtil.h"
35 #include "gdcmDebug.h"
36 #include "gdcmGlobal.h"
37 #include "gdcmFile.h"
38 #include "gdcmSeqEntry.h"
39 #include "gdcmSQItem.h"
40 #include "gdcmDataEntry.h"
41 #include "gdcmCommandManager.h"
42
43 #include <fstream>
44 #include <string>
45 #include <algorithm>
46 #include <sys/types.h>
47
48 #ifdef _MSC_VER
49 #   define getcwd _getcwd
50 #endif
51
52 #if defined(_MSC_VER) || defined(__BORLANDC__)
53 #   include <direct.h>
54 #else
55 #   include <unistd.h>
56 #endif
57
58 #if defined(__BORLANDC__)
59    #include <mem.h> // for memset
60 #endif
61
62 // ----------------------------------------------------------------------------
63 //         Note for future developpers
64 // ----------------------------------------------------------------------------
65 //
66 //  Dicom PS 3.3 describes the relationship between Directory Records, as follow
67 //    (see also PS 4.3, 2004, page 50 for Entity-Relationship model)
68 //
69 //  Directory Record Type      Directory Record Types which may be included
70 //                                in the next lower-level directory Entity
71 //
72 // (Root directory Entity)     PATIENT, TOPIC, PRIVATE
73 //
74 // PATIENT                     STUDY, PRIVATE
75 //
76 // STUDY                       SERIES, VISIT, RESULTS, STUDY COMPONENT, PRIVATE
77 //
78 // SERIES                      IMAGE, OVERLAYS, MODALITY LUT, VOI LUT,
79 //                             CURVE, STORED PRINT, RT DOSE, RT STRUCTURE SET
80 //                             RT PLAN, RT TREAT RECORD, PRESENTATION, WAVEFORM,
81 //                             SR DOCUMENT, KEY OBJECT DOC, SPECTROSCOPY,
82 //                             RAW DATA, REGISTRATION, FIDUCIAL, PRIVATE,
83 //                             ENCAP DOC
84 // IMAGE
85 // OVERLAY
86 // MODALITY LUT
87 // VOI LUT
88 // CURVE
89 // STORED PRINT
90 // RT DOSE
91 // RT STRUCTURE SET
92 // RT PLAN
93 // RT TREAT RECORD
94 // PRESENTATION
95 // WAVEFORM
96 // SR DOCUMENT
97 // KEY OBJECT DOC
98 // SPECTROSCOPY
99 // RAW DATA
100 // REGISTRATION
101 // FIDUCIAL
102 // PRIVATE
103 // ENCAP DOC
104 // 
105
106 /*
107
108 // see also : ftp://medical.nema.org/medical/dicom/final/cp343_ft.doc
109
110 RELATIONSHIP BETWEEN DIRECTORY RECORDS
111
112 Directory Record Type      Directory Record Types which may be included 
113                            in the next lower-level directory Entity
114
115 (Root Directory Entity)    PATIENT, TOPIC, PRIVATE
116
117 PATIENT                    STUDY, PRIVATE
118
119 STUDY                      SERIES, VISIT, RESULTS, STUDY COMPONENT, PRIVATE
120
121 SERIES                     IMAGE, OVERLAY, MODALITY LUT, VOI LUT, CURVE, 
122                            STORED PRINT, RT DOSE, RT STRUCTURE SET, RT PLAN, 
123                            RT TREAT RECORD, PRESENTATION, WAVEFORM, SR DOCUMENT,
124                             KEY OBJECT DOC, SPECTROSCOPY, RAW DATA, PRIVATE
125
126 IMAGE                      PRIVATE
127 OVERLAY                    PRIVATE
128 MODALITY LUT               PRIVATE
129 VOI LUT                    PRIVATE
130 CURVE                      PRIVATE
131 STORED PRINT               PRIVATE
132 RT DOSE                    PRIVATE
133 RT STRUCTURE SET           PRIVATE
134 RT PLAN                    PRIVATE
135 RT TREAT RECORD            PRIVATE
136 PRESENTATION               PRIVATE
137 WAVEFORM                   PRIVATE
138 SR DOCUMENT                PRIVATE
139 KEY OBJECT DOC             PRIVATE
140 SPECTROSCOPY               PRIVATE
141 RAW DATA                   PRIVATE
142
143 TOPIC                      STUDY, SERIES, IMAGE, OVERLAY, MODALITY LUT, VOI LUT,
144                            CURVE, STORED PRINT, RT DOSE, RT STRUCTURE SET, 
145                            RT PLAN, RT TREAT RECORD, PRESENTATION, WAVEFORM, 
146                            SR DOCUMENT, KEY OBJECT DOC, SPECTROSCOPY, RAW DATA, 
147                            PRIVATE
148
149 VISIT                      PRIVATE
150
151 RESULTS                    INTERPRETATION, PRIVATE
152
153 INTERPRETATION             PRIVATE
154 STUDY COMPONENT            PRIVATE
155 PRIVATE                    PRIVATE, (any of the above as privately defined)
156 MRDR                      (Not applicable)
157
158 Note :   Directory Record Types PRINT QUEUE, FILM SESSION, FILM BOX, and 
159          IMAGE BOX  were previously defined in DICOM.  They have been retired.  
160          See PS 3.3-1998.
161 */
162
163 // ----------------------
164 // The current gdcm version only deals with :
165 //
166 // (Root directory Entity)     PATIENT
167 // PATIENT                     STUDY
168 // STUDY                       SERIES
169 // STUDY                       VISIT 
170 // SERIES                      IMAGE
171 // IMAGE                       /
172 //
173 // DicomDir::CreateDicomDir will have to be completed
174 // Treelike structure management will have to be upgraded
175 // ----------------------------------------------------------------------------
176     
177 namespace GDCM_NAME_SPACE 
178 {
179 //-----------------------------------------------------------------------------
180 // Constructor / Destructor
181 /**
182  * \brief   Constructor : creates an empty DicomDir
183  */
184 DicomDir::DicomDir()
185 {
186    Initialize();  // sets all private fields to NULL
187    ParseDir = false;
188    NewMeta();
189 }
190
191 //#ifndef GDCM_LEGACY_REMOVE
192 /**
193  * \brief Constructor Parses recursively the directory and creates the DicomDir
194  *        or uses an already built DICOMDIR, depending on 'parseDir' value.
195  * @param fileName  name 
196  *                      - of the root directory (parseDir = true)
197  *                      - of the DICOMDIR       (parseDir = false)
198  * @param parseDir boolean
199  *                      - true if user passed an entry point 
200  *                        and wants to explore recursively the directories
201  *                      - false if user passed an already built DICOMDIR file
202  *                        and wants to use it 
203  * @deprecated use : new DicomDir() + [ SetLoadMode(lm) + ] SetDirectoryName(name)
204  *              or : new DicomDir() + SetFileName(name)
205  */
206  /*
207 DicomDir::DicomDir(std::string const &fileName, bool parseDir ):
208    Document( )
209 {
210    // At this step, Document constructor is already executed,
211    // whatever user passed (either a root directory or a DICOMDIR)
212    // and whatever the value of parseDir was.
213    // (nothing is checked in Document constructor, to avoid overhead)
214
215    ParseDir = parseDir;
216    SetLoadMode (LD_ALL); // concerns only dicom files
217    SetFileName( fileName );
218    Load( );
219 }
220 */
221 //#endif
222
223 /**
224  * \brief  Canonical destructor 
225  */
226 DicomDir::~DicomDir() 
227 {
228    ClearPatient();
229    if ( MetaElems )
230    {
231       MetaElems->Delete();
232    }
233 }
234
235 //-----------------------------------------------------------------------------
236 // Public
237
238 /**
239  * \brief   Loader. use SetFileName(fn) 
240  *                  or SetLoadMode(lm) + SetDirectoryName(dn)  before !  
241  * @return false if file cannot be open or no swap info was found,
242  *         or no tag was found.
243  */
244 bool DicomDir::Load( ) 
245 {
246    if (!ParseDir)
247    {
248       if ( ! this->Document::Load( ) )
249          return false;
250    }
251    return DoTheLoadingJob( );   
252 }
253 //#ifndef GDCM_LEGACY_REMOVE
254 /**
255  * \brief   Loader. (DEPRECATED : kept not to break the API)
256  * @param   fileName file to be open for parsing
257  * @return false if file cannot be open or no swap info was found,
258  *         or no tag was found.
259  * @deprecated use SetFileName(n) + Load() instead
260  */
261  /*
262 bool DicomDir::Load(std::string const &fileName ) 
263 {
264    // We should clean out anything that already exists.
265    Initialize();  // sets all private fields to NULL
266
267    SetFileName( fileName );
268    if (!ParseDir)
269    {
270       if ( ! this->Document::Load( ) )
271          return false;
272    }
273    return DoTheLoadingJob( );
274 }
275 */
276 //#endif
277
278 /**
279  * \brief   Does the Loading Job (internal use only)
280  * @return false if file cannot be open or no swap info was found,
281  *         or no tag was found.
282  */
283 bool DicomDir::DoTheLoadingJob( ) 
284 {
285    Progress = 0.0f;
286    Abort = false;
287
288    if (!ParseDir)
289    {
290    // Only if user passed a DICOMDIR
291    // ------------------------------
292       Fp = 0;
293       if (!Document::Load() )
294       {
295          return false;
296       }
297
298       if ( GetFirstEntry() == 0 ) // when user passed a Directory to parse
299       {
300          gdcmWarningMacro( "Entry HT empty for file: "<< GetFileName());
301          return false;
302       }
303       // Directory record sequence
304       DocEntry *e = GetDocEntry(0x0004, 0x1220);
305       if ( !e )
306       {
307          gdcmWarningMacro( "NO 'Directory record sequence' (0x0004,0x1220)"
308                           << " in file " << GetFileName());
309          return false;
310       }
311       else
312       {
313          CreateDicomDir();
314       }
315    }
316    else
317    {
318    // Only if user passed a root directory
319    // ------------------------------------
320       if ( GetFileName() == "." )
321       {
322          // user passed '.' as Name
323          // we get current directory name
324          char buf[2048];
325          const char *cwd = getcwd(buf, 2048);
326          if( cwd )
327          {
328             SetFileName( buf ); // will be converted into a string
329          }
330          else
331          {
332             gdcmErrorMacro( "Path was too long to fit on 2048 bytes" );
333          }
334       }
335       NewMeta();
336       gdcmDebugMacro( "Parse directory and create the DicomDir : " 
337                          << GetFileName() );
338       ParseDirectory();
339    }
340    return true;
341 }
342
343 /**
344  * \brief  This predicate, based on hopefully reasonable heuristics,
345  *         decides whether or not the current document was properly parsed
346  *         and contains the mandatory information for being considered as
347  *         a well formed and usable DicomDir.
348  * @return true when Document is the one of a reasonable DicomDir,
349  *         false otherwise. 
350  */
351 bool DicomDir::IsReadable()
352 {
353    if ( Filetype == Unknown )
354    {
355       gdcmErrorMacro( "Wrong filetype for " << GetFileName());
356       return false;
357    }
358    if ( !MetaElems )
359    {
360       gdcmWarningMacro( "Meta Elements missing in DicomDir");
361       return false;
362    }
363    if ( Patients.size() <= 0 )
364    {
365       gdcmWarningMacro( "NO Patient in DicomDir");
366       return false;
367    }
368
369    return true;
370 }
371
372 /**
373  * \brief   adds *the* Meta to a partially created DICOMDIR
374  */  
375 DicomDirMeta *DicomDir::NewMeta()
376 {
377    if ( MetaElems )
378    {
379       MetaElems->Delete();
380    }
381    DocEntry *entry = GetFirstEntry();
382    if ( entry )
383    { 
384       MetaElems = DicomDirMeta::New(true); // true = empty
385
386       entry = GetFirstEntry();
387       while( entry )
388       {
389          if ( dynamic_cast<SeqEntry *>(entry) )
390             break;
391
392          MetaElems->AddEntry(entry);
393          RemoveEntry(entry);
394
395          entry = GetFirstEntry();
396       }
397    }
398    else  // after root directory parsing
399    {
400       MetaElems = DicomDirMeta::New(false); // false = not empty
401    }
402    MetaElems->SetSQItemNumber(0); // To avoid further missprinting
403    return MetaElems;  
404 }
405
406 /**
407  * \brief   adds a new Patient (with the basic elements) to a partially created
408  *          DICOMDIR
409  */
410 DicomDirPatient *DicomDir::NewPatient()
411 {
412    DicomDirPatient *dd = DicomDirPatient::New();
413    AddPatientToEnd( dd );
414    return dd;
415 }
416
417 /**
418  * \brief   Remove all Patients
419  */
420 void DicomDir::ClearPatient()
421 {
422    for(ListDicomDirPatient::iterator cc = Patients.begin();
423                                      cc!= Patients.end();
424                                    ++cc)
425    {
426       (*cc)->Unregister();
427    }
428    Patients.clear();
429 }
430
431 /**
432  * \brief   Get the first entry while visiting the DicomDirPatients
433  * \return  The first DicomDirPatient if found, otherwhise NULL
434  */ 
435 DicomDirPatient *DicomDir::GetFirstPatient()
436 {
437    ItPatient = Patients.begin();
438    if ( ItPatient != Patients.end() )
439       return *ItPatient;
440    return NULL;
441 }
442
443 /**
444  * \brief   Get the next entry while visiting the DicomDirPatients
445  * \note : meaningfull only if GetFirstEntry already called
446  * \return  The next DicomDirPatient if found, otherwhise NULL
447  */
448 DicomDirPatient *DicomDir::GetNextPatient()
449 {
450    gdcmAssertMacro (ItPatient != Patients.end());
451
452    ++ItPatient;
453    if ( ItPatient != Patients.end() )
454       return *ItPatient;
455    return NULL;
456 }
457
458 /**
459  * \brief  fills the whole structure, starting from a root Directory
460  */
461 void DicomDir::ParseDirectory()
462 {
463    CreateDicomDirChainedList( GetFileName() );
464    CreateDicomDir();
465 }
466
467 /**
468  * \brief    writes on disc a DICOMDIR
469  * \ warning does NOT add the missing elements in the header :
470  *           it's up to the user doing it !
471  * @param  fileName file to be written to 
472  * @return false only when fail to open
473  */
474  
475 bool DicomDir::Write(std::string const &fileName) 
476 {  
477    int i;
478    uint16_t sq[6] = { 0x0004, 0x1220, 0x5153, 0x0000, 0xffff, 0xffff };
479    uint16_t sqt[4]= { 0xfffe, 0xe0dd, 0x0000, 0x0000 };
480
481    std::ofstream *fp = new std::ofstream(fileName.c_str(),  
482                                          std::ios::out | std::ios::binary);
483    if ( !fp )
484    {
485       gdcmWarningMacro("Failed to open(write) File: " << fileName.c_str());
486       return false;
487    }
488
489    char filePreamble[128];
490    memset(filePreamble, 0, 128);
491    fp->write(filePreamble, 128);
492    binary_write( *fp, "DICM");
493  
494    DicomDirMeta *ptrMeta = GetMeta();
495    ptrMeta->WriteContent(fp, ExplicitVR, true, false);
496    
497    // force writing 0004|1220 [SQ ], that CANNOT exist within DicomDirMeta
498    for(i=0;i<6;++i)
499    {
500       binary_write(*fp, sq[i]);
501    }
502         
503    for(ListDicomDirPatient::iterator cc  = Patients.begin();
504                                      cc != Patients.end();
505                                    ++cc )
506    {
507       (*cc)->WriteContent( fp, ExplicitVR, false, true );
508    }
509    
510    // force writing Sequence Delimitation Item
511    for(i=0;i<4;++i)
512    {
513       binary_write(*fp, sqt[i]);  // fffe e0dd 0000 0000 
514    }
515
516    fp->close();
517    delete fp;
518
519    return true;
520 }
521
522 /**
523  * \brief    Anonymize a DICOMDIR
524  * @return true 
525  */
526  
527 bool DicomDir::Anonymize() 
528 {
529    DataEntry *v;
530    // Something clever to be found to forge the Patient names
531    std::ostringstream s;
532    int i = 1;
533    for(ListDicomDirPatient::iterator cc = Patients.begin();
534                                      cc!= Patients.end();
535                                    ++cc)
536    {
537       s << i;
538       v = (*cc)->GetDataEntry(0x0010, 0x0010) ; // Patient's Name
539       if (v)
540       {
541          v->SetString(s.str());
542       }
543
544       v = (*cc)->GetDataEntry(0x0010, 0x0020) ; // Patient ID
545       if (v)
546       {
547          v->SetString(" ");
548       }
549
550       v = (*cc)->GetDataEntry(0x0010, 0x0030) ; // Patient's BirthDate
551       if (v)
552       {
553          v->SetString(" ");
554       }
555       s << "";
556       i++;
557    }
558    return true;
559 }
560
561 /**
562  * \brief Copies all the attributes from an other DocEntrySet 
563  * @param set entry to copy from
564  * @remarks The contained DocEntries are not copied, only referenced
565  */
566 void DicomDir::Copy(DocEntrySet *set)
567 {
568    // Remove all previous childs
569    ClearPatient();
570
571    Document::Copy(set);
572
573    DicomDir *dd = dynamic_cast<DicomDir *>(set);
574    if( dd )
575    {
576       if(MetaElems)
577          MetaElems->Unregister();
578       MetaElems = dd->MetaElems;
579       if(MetaElems)
580          MetaElems->Register();
581
582       Patients = dd->Patients;
583       for(ItPatient = Patients.begin();ItPatient != Patients.end();++ItPatient)
584          (*ItPatient)->Register();
585    }
586 }
587
588 //-----------------------------------------------------------------------------
589 // Protected
590 /**
591  * \brief create a Document-like chained list from a root Directory 
592  * @param path entry point of the tree-like structure
593  */
594 void DicomDir::CreateDicomDirChainedList(std::string const &path)
595 {
596    CallStartMethod();
597    DirList dirList(path,1); // gets recursively the file list
598    unsigned int count = 0;
599    VectDocument list;
600    File *f;
601
602    DirListType fileList = dirList.GetFilenames();
603    unsigned int nbFile = fileList.size();
604    for( DirListType::iterator it  = fileList.begin();
605                               it != fileList.end();
606                               ++it )
607    {
608       Progress = (float)(count+1)/(float)nbFile;
609       CallProgressMethod();
610       if ( Abort )
611       {
612          break;
613       }
614
615       f = File::New( );
616       f->SetLoadMode(LoadMode); // we allow user not to load Sequences, 
617                                 //        or Shadow groups, or ......
618       f->SetFileName( it->c_str() );
619       f->Load( );
620
621       if ( f->IsReadable() )
622       {
623          // Add the file to the chained list:
624          list.push_back(f);
625          gdcmDebugMacro( "Readable " << it->c_str() );
626        }
627        else
628        {
629           f->Delete();
630        }
631        count++;
632    }
633    // sorts Patient/Study/Serie/
634    std::sort(list.begin(), list.end(), DicomDir::HeaderLessThan );
635    
636    std::string tmp = dirList.GetDirName();      
637    //for each File of the chained list, add/update the Patient/Study/Serie/Image info
638    SetElements(tmp, list);
639    CallEndMethod();
640
641    for(VectDocument::iterator itDoc=list.begin();
642        itDoc!=list.end();
643        ++itDoc)
644    {
645       dynamic_cast<File *>(*itDoc)->Delete();
646    }
647 }
648
649
650 //-----------------------------------------------------------------------------
651 // Private
652 /**
653  * \brief Sets all fields to NULL
654  */
655 void DicomDir::Initialize()
656 {
657    Progress = 0.0;
658    Abort = false;
659
660    MetaElems = NULL;   
661 }
662
663 /**
664  * \brief create a 'gdcm::DicomDir' from a DICOMDIR Header 
665  */
666 void DicomDir::CreateDicomDir()
667 {
668    // The SeqEntries of "Directory Record Sequence" are parsed. 
669    //  When a DicomDir tag ("PATIENT", "STUDY", "SERIE", "IMAGE") is found :
670    //                    N.B. :  VISIT, PRIVATE not fully dealt with
671    //  1 - we save the beginning iterator
672    //  2 - we continue to parse
673    //  3 - we find an other tag
674    //       + we create the object for the precedent tag
675    //       + loop to 1 -
676    gdcmDebugMacro("Create DicomDir");
677
678    // Directory record sequence
679    DocEntry *e = GetDocEntry(0x0004, 0x1220);
680    if ( !e )
681    {
682       gdcmWarningMacro( "No Directory Record Sequence (0004,1220) found");
683       return;         
684    }
685    
686    SeqEntry *s = dynamic_cast<SeqEntry *>(e);
687    if ( !s )
688    {
689       gdcmWarningMacro( "Element (0004,1220) is not a Sequence ?!?");
690       return;
691    }
692
693    NewMeta();
694    
695    DocEntry *d;
696    std::string v;
697    SQItem *si;
698
699    SQItem *tmpSI=s->GetFirstSQItem();
700    while(tmpSI)
701    {
702       d = tmpSI->GetDocEntry(0x0004, 0x1430); // Directory Record Type
703       if ( DataEntry *dataEntry = dynamic_cast<DataEntry *>(d) )
704       {
705          v = dataEntry->GetString();
706       }
707       else
708       {
709          gdcmWarningMacro( "(0004,1430) not a DataEntry ?!?");
710          continue;
711       }
712
713       // A decent DICOMDIR has much more images than series,
714       // more series than studies, and so on.
715       // This is the right order to perform the tests
716
717       if ( v == "IMAGE " ) 
718       {
719          si = DicomDirImage::New(true); // true = empty
720          if ( !AddImageToEnd( static_cast<DicomDirImage *>(si)) )
721          {
722             si->Delete();
723             si = NULL;
724             gdcmErrorMacro( "Add AddImageToEnd failed");
725          }
726       }
727       else if ( v == "SERIES" )
728       {
729          si = DicomDirSerie::New(true);  // true = empty
730          if ( !AddSerieToEnd( static_cast<DicomDirSerie *>(si)) )
731          {
732             si->Delete();
733             si = NULL;
734             gdcmErrorMacro( "Add AddSerieToEnd failed");
735          }
736       }
737       else if ( v == "VISIT " )
738       {
739          si = DicomDirVisit::New(true);  // true = empty
740          if ( !AddVisitToEnd( static_cast<DicomDirVisit *>(si)) )
741          {
742             si->Delete();
743             si = NULL;
744             gdcmErrorMacro( "Add AddVisitToEnd failed");
745          }
746       }
747       else if ( v == "STUDY " )
748       {
749          si = DicomDirStudy::New(true);  // true = empty
750          if ( !AddStudyToEnd( static_cast<DicomDirStudy *>(si)) )
751          {
752             si->Delete();
753             si = NULL;
754             gdcmErrorMacro( "Add AddStudyToEnd failed");
755          }
756       }
757       else if ( v == "PATIENT " )
758       {
759          si = DicomDirPatient::New(true);  // true = empty
760          if ( !AddPatientToEnd( static_cast<DicomDirPatient *>(si)) )
761          {
762             si->Delete();
763             si = NULL;
764             gdcmErrorMacro( "Add PatientToEnd failed");
765          }
766       }
767       /// \todo : deal with PRIVATE (not so easy, since PRIVATE appears 
768       ///                           at different levels ?!? )
769       
770       else if ( v == "PRIVATE " ) // for SIEMENS 'CSA Non Image'      
771       {
772       
773          gdcmWarningMacro( " -------------------------------------------"
774               << "a PRIVATE SQItem was found : " << v);
775          si = DicomDirPrivate::New(true);  // true = empty
776          if ( !AddPrivateToEnd( static_cast<DicomDirPrivate *>(si)) )
777          {
778             si->Delete();
779             si = NULL;
780             gdcmErrorMacro( "Add PrivateToEnd failed");
781          }
782       }      
783       else
784       {
785          // It was neither a 'PATIENT', nor a 'STUDY', nor a 'SERIE',
786          // nor an 'IMAGE' SQItem. Skip to next item.
787          gdcmWarningMacro( " -------------------------------------------"
788          << "a non PATIENT/STUDY/SERIE/IMAGE /VISIT/PRIVATE SQItem was found : "
789          << v);
790
791         // FIXME : deal with other item types !
792         tmpSI=s->GetNextSQItem(); // To avoid infinite loop
793         continue;
794       }
795       if ( si )
796          si->Copy(tmpSI);
797
798       tmpSI=s->GetNextSQItem();
799    }
800    ClearEntry();
801 }
802
803 /**
804  * \brief  AddPatientToEnd 
805  * @param   dd SQ Item to enqueue to the DicomPatient chained List
806  */
807 bool DicomDir::AddPatientToEnd(DicomDirPatient *dd)
808 {
809    Patients.push_back(dd);
810    return true;
811 }
812
813 /**
814  * \brief  AddStudyToEnd 
815  * @param   dd SQ Item to enqueue to the DicomDirStudy chained List
816  */
817 bool DicomDir::AddStudyToEnd(DicomDirStudy *dd)
818 {
819    if ( Patients.size() > 0 )
820    {
821       ListDicomDirPatient::iterator itp = Patients.end();
822       itp--;
823       (*itp)->AddStudy(dd);
824       return true;
825    }
826    return false;
827 }
828
829 /**
830  * \brief  AddSerieToEnd 
831  * @param   dd SQ Item to enqueue to the DicomDirSerie chained List
832  */
833 bool DicomDir::AddSerieToEnd(DicomDirSerie *dd)
834 {
835    if ( Patients.size() > 0 )
836    {
837       ListDicomDirPatient::iterator itp = Patients.end();
838       itp--;
839
840       DicomDirStudy *study = (*itp)->GetLastStudy();
841       if ( study )
842       {
843          study->AddSerie(dd);
844          return true;
845       }
846    }
847    return false;
848 }
849
850 /**
851  * \brief  AddVisitToEnd 
852  * @param   dd SQ Item to enqueue to the DicomDirVisit chained List
853  */
854 bool DicomDir::AddVisitToEnd(DicomDirVisit *dd)
855 {
856    if ( Patients.size() > 0 )
857    {
858       ListDicomDirPatient::iterator itp = Patients.end();
859       itp--;
860
861       DicomDirStudy *study = (*itp)->GetLastStudy();
862       if ( study )
863       {
864          study->AddVisit(dd);
865          return true;
866       }
867    }
868    return false;
869 }
870 /**
871  * \brief   AddImageToEnd
872  * @param   dd SQ Item to enqueue to the DicomDirImage chained List
873  */
874 bool DicomDir::AddImageToEnd(DicomDirImage *dd)
875 {
876    if ( Patients.size() > 0 )
877    {
878       ListDicomDirPatient::iterator itp = Patients.end();
879       itp--;
880
881       DicomDirStudy *study = (*itp)->GetLastStudy();
882       if ( study )
883       {
884          DicomDirSerie *serie = study->GetLastSerie();
885          if ( serie )
886          {
887             serie->AddImage(dd);
888             return true;
889          }
890       }
891    }
892    return false;
893 }
894
895 /**
896  * \brief   AddPrivateToEnd
897  * @param   dd SQ Item to enqueue to the DicomDirPrivate chained List
898  *          (checked for SIEMENS 'CSA non image')
899  */
900 bool DicomDir::AddPrivateToEnd(DicomDirPrivate *dd)
901 {
902    if ( Patients.size() > 0 )
903    {
904       ListDicomDirPatient::iterator itp = Patients.end();
905       itp--;
906
907       DicomDirStudy *study = (*itp)->GetLastStudy();
908       if ( study )
909       {
910          DicomDirSerie *serie = study->GetLastSerie();
911          if ( serie )
912          {
913             serie->AddPrivate(dd);
914             return true;
915          }
916       }
917    }
918    return false;
919 }
920
921 /**
922  * \brief  for each Header of the chained list, 
923  *         add/update the Patient/Study/Serie/Image info 
924  * @param   path path of the root directory
925  * @param   list chained list of Headers
926  */
927 void DicomDir::SetElements(std::string const &path, VectDocument const &list)
928 {
929    ClearEntry();
930    ClearPatient();
931
932    std::string patPrevName         = "", patPrevID  = "";
933    std::string studPrevInstanceUID = "", studPrevID = "";
934    std::string serPrevInstanceUID  = "", serPrevID  = "";
935
936    std::string patCurName,         patCurID;
937    std::string studCurInstanceUID, studCurID;
938    std::string serCurInstanceUID,  serCurID;
939
940    bool first = true;
941    for( VectDocument::const_iterator it = list.begin();
942                                      it != list.end(); 
943                                    ++it )
944    {
945       // get the current file characteristics
946       patCurName         = (*it)->GetEntryString(0x0010,0x0010);
947       if (patCurName == "") // to prevent further troubles when wild anonymization was performed
948          patCurName = "gdcm^Patient";
949       patCurID           = (*it)->GetEntryString(0x0010,0x0011);
950       studCurInstanceUID = (*it)->GetEntryString(0x0020,0x000d);
951       studCurID          = (*it)->GetEntryString(0x0020,0x0010);
952       serCurInstanceUID  = (*it)->GetEntryString(0x0020,0x000e);
953       serCurID           = (*it)->GetEntryString(0x0020,0x0011);
954
955       if ( patCurName != patPrevName || patCurID != patPrevID || first )
956       {
957          SetElement(path, GDCM_DICOMDIR_PATIENT, *it);
958          first = true;
959       }
960
961       // if new Study, deal with 'STUDY' Elements   
962       if ( studCurInstanceUID != studPrevInstanceUID || studCurID != studPrevID 
963          || first )
964       {
965          SetElement(path, GDCM_DICOMDIR_STUDY, *it);
966          first = true;
967       }
968
969       // if new Serie, deal with 'SERIE' Elements   
970       if ( serCurInstanceUID != serPrevInstanceUID || serCurID != serPrevID
971          || first )
972       {
973          SetElement(path, GDCM_DICOMDIR_SERIE, *it);
974       }
975       
976       // Always Deal with 'IMAGE' Elements  
977       SetElement(path, GDCM_DICOMDIR_IMAGE, *it);
978
979       patPrevName         = patCurName;
980       patPrevID           = patCurID;
981       studPrevInstanceUID = studCurInstanceUID;
982       studPrevID          = studCurID;
983       serPrevInstanceUID  = serCurInstanceUID;
984       serPrevID           = serCurID;
985       first = false;
986    }
987 }
988
989 /**
990  * \brief   adds to the HTable 
991  *          the Entries (Dicom Elements) corresponding to the given type
992  * @param   path full path file name (only used when type = GDCM_DICOMDIR_IMAGE
993  * @param   type DicomDirObject type to create (GDCM_DICOMDIR_PATIENT,
994  *          GDCM_DICOMDIR_STUDY, GDCM_DICOMDIR_SERIE ...)
995  * @param   header Header of the current file
996  */
997 void DicomDir::SetElement(std::string const &path, DicomDirType type,
998                           Document *header)
999 {
1000    ListDicomDirElem elemList;
1001    ListDicomDirElem::const_iterator it;
1002    uint16_t tmpGr, tmpEl;
1003    //DictEntry *dictEntry;
1004    DataEntry *entry;
1005    std::string val;
1006    SQItem *si;
1007    switch( type )
1008    {
1009       case GDCM_DICOMDIR_IMAGE:
1010          elemList = Global::GetDicomDirElements()->GetDicomDirImageElements();
1011          si = DicomDirImage::New(true);
1012          if ( !AddImageToEnd(static_cast<DicomDirImage *>(si)) )
1013          {
1014             si->Delete();
1015             gdcmErrorMacro( "Add ImageToEnd failed");
1016          }
1017          break;
1018       case GDCM_DICOMDIR_SERIE:
1019          elemList = Global::GetDicomDirElements()->GetDicomDirSerieElements();
1020          si = DicomDirSerie::New(true);
1021          if ( !AddSerieToEnd(static_cast<DicomDirSerie *>(si)) )
1022          {
1023             si->Delete();
1024             gdcmErrorMacro( "Add SerieToEnd failed");
1025          }
1026          break;
1027       case GDCM_DICOMDIR_STUDY:
1028          elemList = Global::GetDicomDirElements()->GetDicomDirStudyElements();
1029          si = DicomDirStudy::New(true);
1030          if ( !AddStudyToEnd(static_cast<DicomDirStudy *>(si)) )
1031          {
1032             si->Delete();
1033             gdcmErrorMacro( "Add StudyToEnd failed");
1034          }
1035          break;
1036       case GDCM_DICOMDIR_PATIENT:
1037          elemList = Global::GetDicomDirElements()->GetDicomDirPatientElements();
1038          si = DicomDirPatient::New(true);
1039          if ( !AddPatientToEnd(static_cast<DicomDirPatient *>(si)) )
1040          {
1041             si->Delete();
1042             gdcmErrorMacro( "Add PatientToEnd failed");
1043          }
1044          break;
1045       case GDCM_DICOMDIR_META:  // never used ?!? --> Done within DoTheLoadingJob
1046          if ( MetaElems )
1047          {
1048             MetaElems->Delete();
1049             gdcmErrorMacro( "MetaElements already exist, they will be destroyed");
1050          }
1051          elemList = Global::GetDicomDirElements()->GetDicomDirMetaElements();
1052          MetaElems = DicomDirMeta::New(true);
1053          si = MetaElems;
1054          break;
1055       default:
1056          return;
1057    }
1058
1059    // FIXME : troubles found when it's a SeqEntry
1060
1061    // removed all the seems-to-be-useless stuff about Referenced Image Sequence
1062    // to avoid further troubles
1063    // imageElem 0008 1140 "" // Referenced Image Sequence
1064    // imageElem fffe e000 "" // 'no length' item : length to be set to 0xffffffff later
1065    // imageElem 0008 1150 "" // Referenced SOP Class UID    : to be set/forged later
1066    // imageElem 0008 1155 "" // Referenced SOP Instance UID : to be set/forged later
1067    // imageElem fffe e00d "" // Item delimitation : length to be set to ZERO later
1068  
1069    std::string referencedVal;
1070    // for all the relevant elements found in their own spot of the DicomDir.dic
1071    for( it = elemList.begin(); it != elemList.end(); ++it)
1072    {
1073       tmpGr     = it->Group;
1074       tmpEl     = it->Elem;
1075        
1076       entry     = DataEntry::New(tmpGr, tmpEl, it->VR); // dicomelements file was modified, to store VR
1077       entry->SetOffset(0); // just to avoid further missprinting
1078
1079       if ( header )
1080       {
1081          // NULL when we Build Up (ex nihilo) a DICOMDIR
1082          //   or when we add the META elems
1083          val = header->GetEntryString(tmpGr, tmpEl); 
1084       }
1085       else
1086       {
1087          val = GDCM_UNFOUND;
1088       }
1089
1090       if ( val == GDCM_UNFOUND) 
1091       {
1092          if ( tmpGr == 0x0004 ) // never present in File !     
1093          {
1094             switch (tmpEl)
1095             {
1096             case 0x1130: // File-set ID
1097                // force to the *end* File Name
1098                val = Util::GetName( path );
1099                break;
1100       
1101             case 0x1500: // Only used for image    
1102                if ( header->GetFileName().substr(0, path.length()) != path )
1103                { 
1104                  gdcmWarningMacro( "The base path of file name is incorrect");
1105                  val = header->GetFileName();
1106                }
1107                else
1108                { 
1109                  // avoid the first '/' in File name !
1110                  if ( header->GetFileName().c_str()[path.length()] 
1111                                                       == GDCM_FILESEPARATOR )
1112                     val = &(header->GetFileName().c_str()[path.length()+1]);
1113                  else  
1114                     val = &(header->GetFileName().c_str()[path.length()]);   
1115                }
1116                break;
1117     
1118              case 0x1510:  // Referenced SOP Class UID in File
1119                referencedVal = header->GetEntryString(0x0008, 0x0016);
1120                // FIXME : probabely something to check
1121                val = referencedVal;
1122                break;
1123        
1124              case 0x1511: // Referenced SOP Instance UID in File
1125                referencedVal = header->GetEntryString(0x0008, 0x0018);
1126                // FIXME : probabely something to check
1127                val = referencedVal;
1128                break;
1129     
1130             case 0x1512: // Referenced Transfer Syntax UID in File
1131                referencedVal = header->GetEntryString(0x0002, 0x0010);
1132                // FIXME : probabely something to check
1133                val = referencedVal;
1134                break;
1135     
1136             default :
1137                val = it->Value;   
1138             } 
1139          }
1140          else
1141          {
1142             // If the entry is not found in the Header, don't write its 'value' in the DICOMDIR !
1143             entry->Delete();
1144             continue;
1145           }
1146       }
1147       else
1148       {
1149          if ( header->GetEntryLength(tmpGr,tmpEl) == 0 )
1150          {
1151             val = it->Value;
1152             // Don't polute the DICOMDIR with empty fields
1153             if (val == "")
1154             {
1155                entry->Delete();
1156                continue;
1157             }  
1158          }    
1159       }
1160
1161 /* FIX later the pb of creating the 'Implementation Version Name'!
1162
1163       if (val == GDCM_UNFOUND)
1164          val = "";
1165
1166       if ( tmpGr == 0x0002 && tmpEl == 0x0013)
1167       { 
1168          // 'Implementation Version Name'
1169          std::string val = "GDCM ";
1170          val += Util::GetVersion();
1171       }
1172 */ 
1173
1174       entry->SetString( val ); // troubles expected when vr=SQ ...
1175
1176       if ( type == GDCM_DICOMDIR_META ) // fusible : should never print !
1177       {
1178          gdcmDebugMacro("GDCM_DICOMDIR_META ?!? should never print that");
1179       }
1180       
1181       si->AddEntry(entry);
1182       entry->Delete();
1183    }
1184 }
1185
1186 /**
1187  * \brief   Move the content of the source SQItem to the destination SQItem
1188  *          Only DocEntry's are moved
1189  * @param dst destination SQItem
1190  * @param src source SQItem
1191  */
1192 void DicomDir::MoveSQItem(DocEntrySet *dst, DocEntrySet *src)
1193
1194    DocEntry *entry;
1195 // todo : rewrite the whole stuff, without using RemoveEntry an AddEntry,
1196 //        to save time
1197    entry = src->GetFirstEntry();
1198    while(entry)
1199    {
1200       dst->AddEntry(entry);  // use it, *before* removing it!
1201       src->RemoveEntry(entry);
1202       // we destroyed -> the current iterator is not longer valid
1203       entry = src->GetFirstEntry();
1204    }
1205 }
1206
1207 /**
1208  * \brief   compares two files
1209  */
1210 bool DicomDir::HeaderLessThan(Document *header1, Document *header2)
1211 {
1212    return *header1 < *header2;
1213 }
1214
1215 //-----------------------------------------------------------------------------
1216 // Print
1217 /**
1218  * \brief  Canonical Printer 
1219  * @param   os ostream we want to print in
1220  * @param indent Indentation string to be prepended during printing
1221  */
1222 void DicomDir::Print(std::ostream &os, std::string const & )
1223 {
1224    if ( MetaElems )
1225    {
1226       MetaElems->SetPrintLevel(PrintLevel);
1227       MetaElems->Print(os);   
1228    }   
1229    for(ListDicomDirPatient::iterator cc  = Patients.begin();
1230                                      cc != Patients.end();
1231                                    ++cc)
1232    {
1233      (*cc)->SetPrintLevel(PrintLevel);
1234      (*cc)->Print(os);
1235    }
1236 }
1237
1238 //-----------------------------------------------------------------------------
1239 } // end namespace gdcm