]> Creatis software - gdcm.git/blob - src/gdcmDicomDir.cxx
9d44e72ffd059bf0158c9edb68b418f52b86796d
[gdcm.git] / src / gdcmDicomDir.cxx
1 /*=========================================================================
2   
3   Program:   gdcm
4   Module:    $RCSfile: gdcmDicomDir.cxx,v $
5   Language:  C++
6   Date:      $Date: 2008/02/13 18:53:33 $
7   Version:   $Revision: 1.197 $
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       patCurID           = (*it)->GetEntryString(0x0010,0x0011);
948       studCurInstanceUID = (*it)->GetEntryString(0x0020,0x000d);
949       studCurID          = (*it)->GetEntryString(0x0020,0x0010);
950       serCurInstanceUID  = (*it)->GetEntryString(0x0020,0x000e);
951       serCurID           = (*it)->GetEntryString(0x0020,0x0011);
952
953       if ( patCurName != patPrevName || patCurID != patPrevID || first )
954       {
955          SetElement(path, GDCM_DICOMDIR_PATIENT, *it);
956          first = true;
957       }
958
959       // if new Study, deal with 'STUDY' Elements   
960       if ( studCurInstanceUID != studPrevInstanceUID || studCurID != studPrevID 
961          || first )
962       {
963          SetElement(path, GDCM_DICOMDIR_STUDY, *it);
964          first = true;
965       }
966
967       // if new Serie, deal with 'SERIE' Elements   
968       if ( serCurInstanceUID != serPrevInstanceUID || serCurID != serPrevID
969          || first )
970       {
971          SetElement(path, GDCM_DICOMDIR_SERIE, *it);
972       }
973       
974       // Always Deal with 'IMAGE' Elements  
975       SetElement(path, GDCM_DICOMDIR_IMAGE, *it);
976
977       patPrevName         = patCurName;
978       patPrevID           = patCurID;
979       studPrevInstanceUID = studCurInstanceUID;
980       studPrevID          = studCurID;
981       serPrevInstanceUID  = serCurInstanceUID;
982       serPrevID           = serCurID;
983       first = false;
984    }
985 }
986
987 /**
988  * \brief   adds to the HTable 
989  *          the Entries (Dicom Elements) corresponding to the given type
990  * @param   path full path file name (only used when type = GDCM_DICOMDIR_IMAGE
991  * @param   type DicomDirObject type to create (GDCM_DICOMDIR_PATIENT,
992  *          GDCM_DICOMDIR_STUDY, GDCM_DICOMDIR_SERIE ...)
993  * @param   header Header of the current file
994  */
995 void DicomDir::SetElement(std::string const &path, DicomDirType type,
996                           Document *header)
997 {
998    ListDicomDirElem elemList;
999    ListDicomDirElem::const_iterator it;
1000    uint16_t tmpGr, tmpEl;
1001    //DictEntry *dictEntry;
1002    DataEntry *entry;
1003    std::string val;
1004    SQItem *si;
1005    switch( type )
1006    {
1007       case GDCM_DICOMDIR_IMAGE:
1008          elemList = Global::GetDicomDirElements()->GetDicomDirImageElements();
1009          si = DicomDirImage::New(true);
1010          if ( !AddImageToEnd(static_cast<DicomDirImage *>(si)) )
1011          {
1012             si->Delete();
1013             gdcmErrorMacro( "Add ImageToEnd failed");
1014          }
1015          break;
1016       case GDCM_DICOMDIR_SERIE:
1017          elemList = Global::GetDicomDirElements()->GetDicomDirSerieElements();
1018          si = DicomDirSerie::New(true);
1019          if ( !AddSerieToEnd(static_cast<DicomDirSerie *>(si)) )
1020          {
1021             si->Delete();
1022             gdcmErrorMacro( "Add SerieToEnd failed");
1023          }
1024          break;
1025       case GDCM_DICOMDIR_STUDY:
1026          elemList = Global::GetDicomDirElements()->GetDicomDirStudyElements();
1027          si = DicomDirStudy::New(true);
1028          if ( !AddStudyToEnd(static_cast<DicomDirStudy *>(si)) )
1029          {
1030             si->Delete();
1031             gdcmErrorMacro( "Add StudyToEnd failed");
1032          }
1033          break;
1034       case GDCM_DICOMDIR_PATIENT:
1035          elemList = Global::GetDicomDirElements()->GetDicomDirPatientElements();
1036          si = DicomDirPatient::New(true);
1037          if ( !AddPatientToEnd(static_cast<DicomDirPatient *>(si)) )
1038          {
1039             si->Delete();
1040             gdcmErrorMacro( "Add PatientToEnd failed");
1041          }
1042          break;
1043       case GDCM_DICOMDIR_META:  // never used ?!? --> Done within DoTheLoadingJob
1044          if ( MetaElems )
1045          {
1046             MetaElems->Delete();
1047             gdcmErrorMacro( "MetaElements already exist, they will be destroyed");
1048          }
1049          elemList = Global::GetDicomDirElements()->GetDicomDirMetaElements();
1050          MetaElems = DicomDirMeta::New(true);
1051          si = MetaElems;
1052          break;
1053       default:
1054          return;
1055    }
1056
1057    // FIXME : troubles found when it's a SeqEntry
1058
1059    // removed all the seems-to-be-useless stuff about Referenced Image Sequence
1060    // to avoid further troubles
1061    // imageElem 0008 1140 "" // Referenced Image Sequence
1062    // imageElem fffe e000 "" // 'no length' item : length to be set to 0xffffffff later
1063    // imageElem 0008 1150 "" // Referenced SOP Class UID    : to be set/forged later
1064    // imageElem 0008 1155 "" // Referenced SOP Instance UID : to be set/forged later
1065    // imageElem fffe e00d "" // Item delimitation : length to be set to ZERO later
1066  
1067    std::string referencedVal;
1068    // for all the relevant elements found in their own spot of the DicomDir.dic
1069    for( it = elemList.begin(); it != elemList.end(); ++it)
1070    {
1071       tmpGr     = it->Group;
1072       tmpEl     = it->Elem;
1073        
1074       entry     = DataEntry::New(tmpGr, tmpEl, it->VR); // dicomelements file was modified, to store VR
1075       entry->SetOffset(0); // just to avoid further missprinting
1076
1077       if ( header )
1078       {
1079          // NULL when we Build Up (ex nihilo) a DICOMDIR
1080          //   or when we add the META elems
1081          val = header->GetEntryString(tmpGr, tmpEl); 
1082       }
1083       else
1084       {
1085          val = GDCM_UNFOUND;
1086       }
1087
1088       if ( val == GDCM_UNFOUND) 
1089       {
1090          if ( tmpGr == 0x0004 ) // never present in File !     
1091          {
1092             switch (tmpEl)
1093             {
1094             case 0x1130: // File-set ID
1095                // force to the *end* File Name
1096                val = Util::GetName( path );
1097                break;
1098       
1099             case 0x1500: // Only used for image    
1100                if ( header->GetFileName().substr(0, path.length()) != path )
1101                { 
1102                  gdcmWarningMacro( "The base path of file name is incorrect");
1103                  val = header->GetFileName();
1104                }
1105                else
1106                { 
1107                  // avoid the first '/' in File name !
1108                  if ( header->GetFileName().c_str()[path.length()] 
1109                                                       == GDCM_FILESEPARATOR )
1110                     val = &(header->GetFileName().c_str()[path.length()+1]);
1111                  else  
1112                     val = &(header->GetFileName().c_str()[path.length()]);   
1113                }
1114                break;
1115     
1116              case 0x1510:  // Referenced SOP Class UID in File
1117                referencedVal = header->GetEntryString(0x0008, 0x0016);
1118                // FIXME : probabely something to check
1119                val = referencedVal;
1120                break;
1121        
1122              case 0x1511: // Referenced SOP Instance UID in File
1123                referencedVal = header->GetEntryString(0x0008, 0x0018);
1124                // FIXME : probabely something to check
1125                val = referencedVal;
1126                break;
1127     
1128             case 0x1512: // Referenced Transfer Syntax UID in File
1129                referencedVal = header->GetEntryString(0x0002, 0x0010);
1130                // FIXME : probabely something to check
1131                val = referencedVal;
1132                break;
1133     
1134             default :
1135                val = it->Value;   
1136             } 
1137          }
1138          else
1139          {
1140             // If the entry is not found in the Header, don't write its 'value' in the DICOMDIR !
1141             entry->Delete();
1142             continue;
1143           }
1144       }
1145       else
1146       {
1147          if ( header->GetEntryLength(tmpGr,tmpEl) == 0 )
1148          {
1149             val = it->Value;
1150             // Don't polute the DICOMDIR with empty fields
1151             if (val == "")
1152             {
1153                entry->Delete();
1154                continue;
1155             }  
1156          }    
1157       }
1158
1159 /* FIX later the pb of creating the 'Implementation Version Name'!
1160
1161       if (val == GDCM_UNFOUND)
1162          val = "";
1163
1164       if ( tmpGr == 0x0002 && tmpEl == 0x0013)
1165       { 
1166          // 'Implementation Version Name'
1167          std::string val = "GDCM ";
1168          val += Util::GetVersion();
1169       }
1170 */ 
1171
1172       entry->SetString( val ); // troubles expected when vr=SQ ...
1173
1174       if ( type == GDCM_DICOMDIR_META ) // fusible : should never print !
1175       {
1176          gdcmDebugMacro("GDCM_DICOMDIR_META ?!? should never print that");
1177       }
1178       
1179       si->AddEntry(entry);
1180       entry->Delete();
1181    }
1182 }
1183
1184 /**
1185  * \brief   Move the content of the source SQItem to the destination SQItem
1186  *          Only DocEntry's are moved
1187  * @param dst destination SQItem
1188  * @param src source SQItem
1189  */
1190 void DicomDir::MoveSQItem(DocEntrySet *dst, DocEntrySet *src)
1191
1192    DocEntry *entry;
1193 // todo : rewrite the whole stuff, without using RemoveEntry an AddEntry,
1194 //        to save time
1195    entry = src->GetFirstEntry();
1196    while(entry)
1197    {
1198       dst->AddEntry(entry);  // use it, *before* removing it!
1199       src->RemoveEntry(entry);
1200       // we destroyed -> the current iterator is not longer valid
1201       entry = src->GetFirstEntry();
1202    }
1203 }
1204
1205 /**
1206  * \brief   compares two files
1207  */
1208 bool DicomDir::HeaderLessThan(Document *header1, Document *header2)
1209 {
1210    return *header1 < *header2;
1211 }
1212
1213 //-----------------------------------------------------------------------------
1214 // Print
1215 /**
1216  * \brief  Canonical Printer 
1217  * @param   os ostream we want to print in
1218  * @param indent Indentation string to be prepended during printing
1219  */
1220 void DicomDir::Print(std::ostream &os, std::string const & )
1221 {
1222    if ( MetaElems )
1223    {
1224       MetaElems->SetPrintLevel(PrintLevel);
1225       MetaElems->Print(os);   
1226    }   
1227    for(ListDicomDirPatient::iterator cc  = Patients.begin();
1228                                      cc != Patients.end();
1229                                    ++cc)
1230    {
1231      (*cc)->SetPrintLevel(PrintLevel);
1232      (*cc)->Print(os);
1233    }
1234 }
1235
1236 //-----------------------------------------------------------------------------
1237 } // end namespace gdcm