]> Creatis software - gdcm.git/blob - src/gdcmDicomDir.cxx
Typo
[gdcm.git] / src / gdcmDicomDir.cxx
1 /*=========================================================================
2   
3   Program:   gdcm
4   Module:    $RCSfile: gdcmDicomDir.cxx,v $
5   Language:  C++
6   Date:      $Date: 2005/01/26 09:49:53 $
7   Version:   $Revision: 1.119 $
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 #include "gdcmDicomDir.h"
20 #include "gdcmDicomDirStudy.h"
21 #include "gdcmDicomDirSerie.h"
22 #include "gdcmDicomDirImage.h"
23 #include "gdcmDicomDirPatient.h"
24 #include "gdcmDicomDirMeta.h"
25 #include "gdcmDicomDirElement.h"
26 #include "gdcmDirList.h"
27 #include "gdcmUtil.h"
28 #include "gdcmDebug.h"
29 #include "gdcmGlobal.h"
30 #include "gdcmFile.h"
31 #include "gdcmSeqEntry.h"
32 #include "gdcmSQItem.h"
33 #include "gdcmValEntry.h"
34
35 #include <fstream>
36 #include <string>
37 #include <algorithm>
38 #include <sys/types.h>
39
40 #ifdef _MSC_VER
41 #   define getcwd _getcwd
42 #endif
43
44 #if defined(_MSC_VER) || defined(__BORLANDC__)
45 #   include <direct.h>
46 #else
47 #   include <unistd.h>
48 #endif
49
50 namespace gdcm 
51 {
52
53 //-----------------------------------------------------------------------------
54 //  For full DICOMDIR description, see:
55 //  PS 3.3-2003, pages 731-750
56 //-----------------------------------------------------------------------------
57 // Constructor / Destructor
58
59 /**
60  * \brief   Constructor : creates an empty DicomDir
61  */
62 DicomDir::DicomDir()
63    :Document( )
64 {
65    Initialize();  // sets all private fields to NULL
66    NewMeta();
67 }
68
69 /**
70  * \brief Constructor Parses recursively the directory and creates the DicomDir
71  *        or uses an already built DICOMDIR, depending on 'parseDir' value.
72  * @param fileName        name 
73  *                      - of the root directory (parseDir = true)
74  *                      - of the DICOMDIR       (parseDir = false)
75  * @param parseDir boolean
76  *                      - true if user passed an entry point 
77  *                        and wants to explore recursively the directories
78  *                      - false if user passed an already built DICOMDIR file
79  *                        and wants to use it 
80  */
81 DicomDir::DicomDir(std::string const &fileName, bool parseDir ):
82    Document( fileName )
83 {
84    // At this step, Document constructor is already executed,
85    // whatever user passed (a root directory or a DICOMDIR)
86    // and whatever the value of parseDir was.
87    // (nothing is cheked in Document constructor, to avoid overhead)
88
89    Initialize();  // sets all private fields to NULL
90
91    // if user passed a root directory, sure we didn't get anything
92
93    if ( GetFirstEntry() == 0 ) // when user passed a Directory to parse
94    {
95       if (!parseDir)
96          gdcmVerboseMacro( "Entry HT empty for file: "<<fileName);
97
98    // Only if user passed a root directory
99    // ------------------------------------
100       if ( fileName == "." )
101       {
102          // user passed '.' as Name
103          // we get current directory name
104          char dummy[1000];
105          getcwd(dummy, (size_t)1000);
106          SetFileName( dummy ); // will be converted into a string
107       }
108
109       if ( parseDir ) // user asked for a recursive parsing of a root directory
110       {
111          NewMeta();
112
113          gdcmVerboseMacro( "Parse directory and create the DicomDir");
114          ParseDirectory();
115       }
116       else
117       {
118          /// \todo if parseDir == false, it should be tagged as an error
119          // NO ! user may just call ParseDirectory() *after* constructor
120       }
121    }
122    // Only if user passed a DICOMDIR
123    // ------------------------------
124    else 
125    {
126       // Directory record sequence
127       DocEntry *e = GetDocEntry(0x0004, 0x1220);
128       if ( !e )
129       {
130          gdcmVerboseMacro( "NO 'Directory record sequence' (0x0004,0x1220)"
131                           << " in file " << fileName);
132          /// \todo FIXME : what do we do when the parsed file IS NOT a
133          ///       DICOMDIR file ?         
134       }
135       else
136          CreateDicomDir();
137    }
138 }
139
140 /**
141  * \brief  Canonical destructor 
142  */
143 DicomDir::~DicomDir() 
144 {
145    SetStartMethod(NULL);
146    SetProgressMethod(NULL);
147    SetEndMethod(NULL);
148
149    ClearPatient();
150    if ( MetaElems )
151    {
152       delete MetaElems;
153    }
154 }
155
156 //-----------------------------------------------------------------------------
157 // Print
158 /**
159  * \brief  Canonical Printer 
160  * @param   os ostream we want to print in
161  * @param indent Indentation string to be prepended during printing
162  */
163 void DicomDir::Print(std::ostream &os, std::string const & )
164 {
165    if( MetaElems )
166    {
167       MetaElems->SetPrintLevel(PrintLevel);
168       MetaElems->Print(os);   
169    }   
170    for(ListDicomDirPatient::iterator cc  = Patients.begin();
171                                      cc != Patients.end();
172                                    ++cc)
173    {
174      (*cc)->SetPrintLevel(PrintLevel);
175      (*cc)->Print(os);
176    }
177 }
178
179 //-----------------------------------------------------------------------------
180 // Public
181 /**
182  * \brief  This predicate, based on hopefully reasonable heuristics,
183  *         decides whether or not the current document was properly parsed
184  *         and contains the mandatory information for being considered as
185  *         a well formed and usable DicomDir.
186  * @return true when Document is the one of a reasonable DicomDir,
187  *         false otherwise. 
188  */
189 bool DicomDir::IsReadable()
190 {
191    if( Filetype == Unknown)
192    {
193       gdcmVerboseMacro( "Wrong filetype");
194       return false;
195    }
196    if( !MetaElems )
197    {
198       gdcmVerboseMacro( "Meta Elements missing in DicomDir");
199       return false;
200    }
201    if( Patients.size() <= 0 )
202    {
203       gdcmVerboseMacro( "NO Patient in DicomDir");
204       return false;
205    }
206
207    return true;
208 }
209
210 /**
211  * \brief  fills the whole structure, starting from a root Directory
212  */
213 void DicomDir::ParseDirectory()
214 {
215    CreateDicomDirChainedList( GetFileName() );
216    CreateDicomDir();
217 }
218
219 /**
220  * \brief   Set the start method to call when the parsing of the
221  *          directory starts.
222  * @param   method Method to call
223  * @param   arg    Argument to pass to the method
224  * @param   argDelete    Argument 
225  * \warning In python : the arg parameter isn't considered
226  */
227 void DicomDir::SetStartMethod( DicomDir::Method *method, void *arg, 
228                                DicomDir::Method *argDelete )
229 {
230    if( StartArg && StartMethodArgDelete )
231    {
232       StartMethodArgDelete( StartArg );
233    }
234
235    StartMethod          = method;
236    StartArg             = arg;
237    StartMethodArgDelete = argDelete;
238 }
239
240 /**
241  * \brief   Set the method to delete the argument
242  *          The argument is destroyed when the method is changed or when the
243  *          class is destroyed
244  * @param   method Method to call to delete the argument
245  */
246 void DicomDir::SetStartMethodArgDelete( DicomDir::Method *method ) 
247 {
248    StartMethodArgDelete = method;
249 }
250
251 /**
252  * \brief   Set the progress method to call when the parsing of the
253  *          directory progress
254  * @param   method Method to call
255  * @param   arg    Argument to pass to the method
256  * @param   argDelete    Argument  
257  * \warning In python : the arg parameter isn't considered
258  */
259 void DicomDir::SetProgressMethod( DicomDir::Method *method, void *arg, 
260                                   DicomDir::Method *argDelete )
261 {
262    if( ProgressArg && ProgressMethodArgDelete )
263    {
264       ProgressMethodArgDelete( ProgressArg );
265    }
266
267    ProgressMethod          = method;
268    ProgressArg             = arg;
269    ProgressMethodArgDelete = argDelete;
270 }
271
272 /**
273  * \brief   Set the method to delete the argument
274  *          The argument is destroyed when the method is changed or when the 
275  *          class is destroyed          
276  * @param   method Method to call to delete the argument
277  */
278 void DicomDir::SetProgressMethodArgDelete( DicomDir::Method *method )
279 {
280    ProgressMethodArgDelete = method;
281 }
282
283 /**
284  * \brief   Set the end method to call when the parsing of the directory ends
285  * @param   method Method to call
286  * @param   arg    Argument to pass to the method
287  * @param   argDelete    Argument 
288  * \warning In python : the arg parameter isn't considered
289  */
290 void DicomDir::SetEndMethod( DicomDir::Method *method, void *arg, 
291                              DicomDir::Method *argDelete )
292 {
293    if( EndArg && EndMethodArgDelete )
294    {
295       EndMethodArgDelete( EndArg );
296    }
297
298    EndMethod          = method;
299    EndArg             = arg;
300    EndMethodArgDelete = argDelete;
301 }
302
303 /**
304  * \brief   Set the method to delete the argument
305  *          The argument is destroyed when the method is changed or when
306  *          the class is destroyed
307  * @param   method Method to call to delete the argument
308  */
309 void DicomDir::SetEndMethodArgDelete( DicomDir::Method *method )
310 {
311    EndMethodArgDelete = method;
312 }
313
314 /**
315  * \brief   Get the first entry while visiting the DicomDirPatients
316  * \return  The first DicomDirPatient if found, otherwhise NULL
317  */ 
318 DicomDirPatient *DicomDir::GetFirstPatient()
319 {
320    ItPatient = Patients.begin();
321    if ( ItPatient != Patients.end() )
322       return *ItPatient;
323    return NULL;
324 }
325
326 /**
327  * \brief   Get the next entry while visiting the DicomDirPatients
328  * \note : meaningfull only if GetFirstEntry already called
329  * \return  The next DicomDirPatient if found, otherwhise NULL
330  */
331 DicomDirPatient *DicomDir::GetNextPatient()
332 {
333    gdcmAssertMacro (ItPatient != Patients.end());
334
335    ++ItPatient;
336    if ( ItPatient != Patients.end() )
337       return *ItPatient;
338    return NULL;
339 }
340
341 /**
342  * \brief    writes on disc a DICOMDIR
343  * \ warning does NOT add the missing elements in the header :
344  *           it's up to the user doing it !
345  * \todo : to be re-written using the DICOMDIR tree-like structure
346  *         *not* the chained list
347  *         (does NOT exist if the DICOMDIR is user-forged !)
348  * @param  fileName file to be written to 
349  * @return false only when fail to open
350  */
351  
352 bool DicomDir::WriteDicomDir(std::string const &fileName) 
353 {  
354    int i;
355    uint16_t sq[4] = { 0x0004, 0x1220, 0xffff, 0xffff };
356    uint16_t sqt[4]= { 0xfffe, 0xe0dd, 0xffff, 0xffff };
357
358    std::ofstream *fp = new std::ofstream(fileName.c_str(),  
359                                          std::ios::out | std::ios::binary);
360    if( !fp ) 
361    {
362       gdcmVerboseMacro("Failed to open(write) File: " << fileName.c_str());
363       return false;
364    }
365
366    char filePreamble[128];
367    memset(filePreamble, 0, 128);
368    fp->write(filePreamble, 128); //FIXME
369    binary_write( *fp, "DICM");
370  
371    DicomDirMeta *ptrMeta = GetMeta();
372    ptrMeta->WriteContent(fp, ExplicitVR);
373    
374    // force writing 0004|1220 [SQ ], that CANNOT exist within DicomDirMeta
375    for(i=0;i<4;++i)
376    {
377       binary_write(*fp, sq[i]);
378    }
379         
380    for(ListDicomDirPatient::iterator cc  = Patients.begin();
381                                      cc != Patients.end();
382                                    ++cc )
383    {
384       (*cc)->WriteContent( fp, ExplicitVR );
385    }
386    
387    // force writing Sequence Delimitation Item
388    for(i=0;i<4;++i)
389    {
390       binary_write(*fp, sqt[i]);  // fffe e0dd ffff ffff 
391    }
392
393    fp->close();
394    delete fp;
395
396    return true;
397 }
398
399 //-----------------------------------------------------------------------------
400 // Protected
401
402 /**
403  * \brief create a Document-like chained list from a root Directory 
404  * @param path entry point of the tree-like structure
405  */
406 void DicomDir::CreateDicomDirChainedList(std::string const & path)
407 {
408    CallStartMethod();
409    DirList dirList(path,1); // gets recursively the file list
410    unsigned int count = 0;
411    VectDocument list;
412    File *header;
413
414    DirListType fileList = dirList.GetFilenames();
415
416    for( DirListType::iterator it  = fileList.begin();
417                               it != fileList.end();
418                               ++it )
419    {
420       Progress = (float)(count+1)/(float)fileList.size();
421       CallProgressMethod();
422       if( Abort )
423       {
424          break;
425       }
426
427       header = new File( it->c_str() );
428       if( !header )
429       {
430          gdcmVerboseMacro( "Failure in new gdcm::File " << it->c_str() );
431          continue;
432       }
433       
434       if( header->IsReadable() )
435       {
436          // Add the file to the chained list:
437          list.push_back(header);
438          gdcmVerboseMacro( "Readable " << it->c_str() );
439        }
440        else
441        {
442           delete header;
443        }
444        count++;
445    }
446    // sorts Patient/Study/Serie/
447    std::sort(list.begin(), list.end(), DicomDir::HeaderLessThan );
448    
449    std::string tmp = dirList.GetDirName();      
450    //for each File of the chained list, add/update the Patient/Study/Serie/Image info
451    SetElements(tmp, list);
452    CallEndMethod();
453
454    for(VectDocument::iterator itDoc=list.begin();
455        itDoc!=list.end();
456        ++itDoc)
457    {
458       delete dynamic_cast<File *>(*itDoc);
459    }
460 }
461
462 /**
463  * \brief   adds *the* Meta to a partially created DICOMDIR
464  */
465   
466 DicomDirMeta *DicomDir::NewMeta()
467 {
468    if( MetaElems )
469       delete MetaElems;
470
471  // friend class hunting : we miss GetLastEntry and GetPreviousEntry
472  //                  to be able to remove any direct reference to TagHT
473    DocEntry *entry = GetFirstEntry();
474    if( entry )
475    { 
476       MetaElems = new DicomDirMeta(true);
477
478       entry = GetFirstEntry();
479       while( entry )
480       {
481          if( dynamic_cast<SeqEntry *>(entry) )
482             break;
483
484          RemoveEntryNoDestroy(entry);
485          MetaElems->AddEntry(entry);
486
487          entry = GetFirstEntry();
488       }
489    }
490    else  // after root directory parsing
491    {
492       MetaElems = new DicomDirMeta(false);
493    }
494    MetaElems->SetSQItemNumber(0); // To avoid further missprinting
495    return MetaElems;  
496 }
497
498 /**
499  * \brief   adds a new Patient (with the basic elements) to a partially created
500  *          DICOMDIR
501  */
502 DicomDirPatient *DicomDir::NewPatient()
503 {
504    DicomDirPatient *p = new DicomDirPatient();
505    AddPatientToEnd( p );
506    return p;
507 }
508
509 /**
510  * \brief   Remove all Patients
511  */
512 void DicomDir::ClearPatient()
513 {
514    for(ListDicomDirPatient::iterator cc = Patients.begin();
515                                      cc!= Patients.end();
516                                    ++cc)
517    {
518       delete *cc;
519    }
520    Patients.clear();
521 }
522
523 /**
524  * \brief   adds to the HTable 
525  *          the Entries (Dicom Elements) corresponding to the given type
526  * @param   path full path file name (only used when type = GDCM_DICOMDIR_IMAGE
527  * @param   type DicomDirObject type to create (GDCM_DICOMDIR_PATIENT,
528  *          GDCM_DICOMDIR_STUDY, GDCM_DICOMDIR_SERIE ...)
529  * @param   header Header of the current file
530  */
531 void DicomDir::SetElement(std::string const &path, DicomDirType type,
532                           Document *header)
533 {
534    ListDicomDirElem elemList; //FIXME this is going to be a by copy operation
535    ListDicomDirElem::const_iterator it;
536    uint16_t tmpGr, tmpEl;
537    DictEntry *dictEntry;
538    ValEntry *entry;
539    std::string val;
540    SQItem *si;
541
542    switch( type )
543    {
544       case GDCM_DICOMDIR_IMAGE:
545          elemList = Global::GetDicomDirElements()->GetDicomDirImageElements();
546          si = new DicomDirImage(true);
547          if( !AddImageToEnd(static_cast<DicomDirImage *>(si)) )
548          {
549             delete si;
550             gdcmErrorMacro( "Add ImageToEnd failed");
551          }
552          break;
553       case GDCM_DICOMDIR_SERIE:
554          elemList = Global::GetDicomDirElements()->GetDicomDirSerieElements();
555          si = new DicomDirSerie(true);
556          if( !AddSerieToEnd(static_cast<DicomDirSerie *>(si)) )
557          {
558             delete si;
559             gdcmErrorMacro( "Add SerieToEnd failed");
560          }
561          break;
562       case GDCM_DICOMDIR_STUDY:
563          elemList = Global::GetDicomDirElements()->GetDicomDirStudyElements();
564          si = new DicomDirStudy(true);
565          if( !AddStudyToEnd(static_cast<DicomDirStudy *>(si)) )
566          {
567             delete si;
568             gdcmErrorMacro( "Add StudyToEnd failed");
569          }
570          break;
571       case GDCM_DICOMDIR_PATIENT:
572          elemList = Global::GetDicomDirElements()->GetDicomDirPatientElements();
573          si = new DicomDirPatient(true);
574          if( !AddPatientToEnd(static_cast<DicomDirPatient *>(si)) )
575          {
576             delete si;
577             gdcmErrorMacro( "Add PatientToEnd failed");
578          }
579          break;
580       case GDCM_DICOMDIR_META:
581          elemList = Global::GetDicomDirElements()->GetDicomDirMetaElements();
582          si = new DicomDirMeta(true);
583          if( MetaElems )
584          {
585             delete MetaElems;
586             gdcmErrorMacro( "MetaElements already exist, they will be destroyed");
587          }
588          MetaElems = static_cast<DicomDirMeta *>(si);
589          break;
590       default:
591          return;
592    }
593    // removed all the seems-to-be-useless stuff about Referenced Image Sequence
594    // to avoid further troubles
595    // imageElem 0008 1140 "" // Referenced Image Sequence
596    // imageElem fffe e000 "" // 'no length' item : length to be set to 0xffffffff later
597    // imageElem 0008 1150 "" // Referenced SOP Class UID    : to be set/forged later
598    // imageElem 0008 1155 "" // Referenced SOP Instance UID : to be set/forged later
599    // imageElem fffe e00d "" // Item delimitation : length to be set to ZERO later
600    // for all the relevant elements found in their own spot of the DicomDir.dic
601    // FIXME : troubles found when it's a SeqEntry
602
603    for( it = elemList.begin(); it != elemList.end(); ++it)
604    {
605       tmpGr     = it->Group;
606       tmpEl     = it->Elem;
607       dictEntry = GetPubDict()->GetEntry(tmpGr, tmpEl);
608
609       entry     = new ValEntry( dictEntry ); // Be sure it's never a BinEntry !
610
611       entry->SetOffset(0); // just to avoid further missprinting
612
613       if( header )
614       {
615          // NULL when we Build Up (ex nihilo) a DICOMDIR
616          //   or when we add the META elems
617          val = header->GetEntryValue(tmpGr, tmpEl);
618       }
619       else
620       {
621          val = GDCM_UNFOUND;
622       }
623
624       if( val == GDCM_UNFOUND) 
625       {
626          if( tmpGr == 0x0004 && tmpEl == 0x1130 ) // File-set ID
627          {
628            // force to the *end* File Name
629            val = Util::GetName( path );
630          }
631          else if( tmpGr == 0x0004 && tmpEl == 0x1500 ) // Only used for image
632          {
633             if( header->GetFileName().substr(0, path.length()) != path )
634             {
635                gdcmVerboseMacro( "The base path of file name is incorrect");
636                val = header->GetFileName();
637             }
638             else
639             {
640                val = &(header->GetFileName().c_str()[path.length()]);
641             }
642          }
643          else
644          {
645             val = it->Value;
646          }
647       }
648       else
649       {
650          if ( header->GetEntryLength(tmpGr,tmpEl) == 0 )
651             val = it->Value;
652       }
653
654       entry->SetValue( val ); // troubles expected when vr=SQ ...
655
656       if ( type == GDCM_DICOMDIR_META ) // fusible : should never print !
657       {
658          gdcmVerboseMacro("GDCM_DICOMDIR_META ?!? should never print that");
659       }
660       si->AddEntry(entry);
661    }
662 }
663
664 //-----------------------------------------------------------------------------
665 /**
666  * \brief   CallStartMethod
667  */
668 void DicomDir::CallStartMethod()
669 {
670    Progress = 0.0f;
671    Abort    = false;
672    if( StartMethod )
673    {
674       StartMethod( StartArg );
675    }
676 }
677
678 //-----------------------------------------------------------------------------
679 /**
680  * \brief   CallProgressMethod
681  */
682 void DicomDir::CallProgressMethod()
683 {
684    if( ProgressMethod )
685    {
686       ProgressMethod( ProgressArg );
687    }
688 }
689
690 //-----------------------------------------------------------------------------
691 /**
692  * \brief   CallEndMethod
693  */
694 void DicomDir::CallEndMethod()
695 {
696    Progress = 1.0f;
697    if( EndMethod )
698    {
699       EndMethod( EndArg );
700    }
701 }
702
703 //-----------------------------------------------------------------------------
704 // Private
705 /**
706  * \brief Sets all fields to NULL
707  */
708 void DicomDir::Initialize()
709 {
710    StartMethod             = NULL;
711    ProgressMethod          = NULL;
712    EndMethod               = NULL;
713    StartMethodArgDelete    = NULL;
714    ProgressMethodArgDelete = NULL;
715    EndMethodArgDelete      = NULL;
716    StartArg                = NULL;
717    ProgressArg             = NULL;
718    EndArg                  = NULL;
719
720    Progress = 0.0;
721    Abort = false;
722
723    MetaElems = NULL;   
724 }
725
726 /**
727  * \brief create a 'DicomDir' from a DICOMDIR Header 
728  */
729 void DicomDir::CreateDicomDir()
730 {
731    // The list is parsed. 
732    //  When a DicomDir tag ("PATIENT", "STUDY", "SERIE", "IMAGE") is found :
733    //  1 - we save the beginning iterator
734    //  2 - we continue to parse
735    //  3 - we find an other tag
736    //       + we create the object for the precedent tag
737    //       + loop to 1 -
738
739    // Directory record sequence
740    DocEntry *e = GetDocEntry(0x0004, 0x1220);
741    if ( !e )
742    {
743       gdcmVerboseMacro( "NO Directory record sequence (0x0004,0x1220)");
744       /// \todo FIXME: what to do when the parsed file IS NOT a DICOMDIR file ? 
745       return;         
746    }
747    
748    SeqEntry *s = dynamic_cast<SeqEntry *>(e);
749    if ( !s )
750    {
751       gdcmVerboseMacro( "No SeqEntry present");
752       // useless : (0x0004,0x1220) IS a Sequence !
753       return;
754    }
755
756    NewMeta();
757    
758    DocEntry *d;
759    std::string v;
760    SQItem *si;
761
762    SQItem *tmpSI=s->GetFirstSQItem();
763    while(tmpSI)
764    {
765       d = tmpSI->GetDocEntry(0x0004, 0x1430); // Directory Record Type
766       if ( ValEntry* valEntry = dynamic_cast<ValEntry *>(d) )
767       {
768          v = valEntry->GetValue();
769       }
770       else
771       {
772          gdcmVerboseMacro( "Not a ValEntry.");
773          continue;
774       }
775
776       if( v == "PATIENT " )
777       {
778          si = new DicomDirPatient(true);
779          if( !AddPatientToEnd( static_cast<DicomDirPatient *>(si)) )
780          {
781             delete si;
782             si = NULL;
783             gdcmErrorMacro( "Add PatientToEnd failed");
784          }
785       }
786       else if( v == "STUDY " )
787       {
788          si = new DicomDirStudy(true);
789          if( !AddStudyToEnd( static_cast<DicomDirStudy *>(si)) )
790          {
791             delete si;
792             si = NULL;
793             gdcmErrorMacro( "Add AddStudyToEnd failed");
794          }
795       }
796       else if( v == "SERIES" )
797       {
798          si = new DicomDirSerie(true);
799          if( !AddSerieToEnd( static_cast<DicomDirSerie *>(si)) )
800          {
801             delete si;
802             si = NULL;
803             gdcmErrorMacro( "Add AddSerieToEnd failed");
804          }
805       }
806       else if( v == "IMAGE " ) 
807       {
808          si = new DicomDirImage(true);
809          if( !AddImageToEnd( static_cast<DicomDirImage *>(si)) )
810          {
811             delete si;
812             si = NULL;
813             gdcmErrorMacro( "Add AddImageToEnd failed");
814          }
815       }
816       else
817       {
818          // It was not a 'PATIENT', nor a 'STUDY', nor a 'SERIE',
819          // neither an 'IMAGE' SQItem. Skip to next item.
820          continue;
821       }
822       tmpSI=s->GetNextSQItem();
823    }
824 // friend hunting : this one will be difficult to remove !
825    ClearEntry();
826 }
827
828 /**
829  * \brief  AddPatientToEnd 
830  * @param   dd SQ Item to enqueue to the DicomPatient chained List
831  */
832 bool DicomDir::AddPatientToEnd(DicomDirPatient *dd)
833 {
834    Patients.push_back(dd);
835    return true;
836 }
837
838 /**
839  * \brief  AddStudyToEnd 
840  * @param   dd SQ Item to enqueue to the DicomDirStudy chained List
841  */
842 bool DicomDir::AddStudyToEnd(DicomDirStudy *dd)
843 {
844    if( Patients.size() > 0 )
845    {
846       ListDicomDirPatient::iterator itp = Patients.end();
847       itp--;
848       (*itp)->AddStudy(dd);
849       return true;
850    }
851    return false;
852 }
853
854 /**
855  * \brief  AddSerieToEnd 
856  * @param   dd SQ Item to enqueue to the DicomDirSerie chained List
857  */
858 bool DicomDir::AddSerieToEnd(DicomDirSerie *dd)
859 {
860    if( Patients.size() > 0 )
861    {
862       ListDicomDirPatient::iterator itp = Patients.end();
863       itp--;
864
865       DicomDirStudy *study = (*itp)->GetLastStudy();
866       if( study )
867       {
868          study->AddSerie(dd);
869          return true;
870       }
871    }
872    return false;
873 }
874
875 /**
876  * \brief   AddImageToEnd
877  * @param   dd SQ Item to enqueue to the DicomDirImage chained List
878  */
879 bool DicomDir::AddImageToEnd(DicomDirImage *dd)
880 {
881    if( Patients.size() > 0 )
882    {
883       ListDicomDirPatient::iterator itp = Patients.end();
884       itp--;
885
886       DicomDirStudy *study = (*itp)->GetLastStudy();
887       if( study )
888       {
889          DicomDirSerie *serie = study->GetLastSerie();
890          if( serie )
891          {
892             serie->AddImage(dd);
893             return true;
894          }
895       }
896    }
897    return false;
898 }
899
900 /**
901  * \brief  for each Header of the chained list, add/update the Patient/Study/Serie/Image info 
902  * @param   path path of the root directory
903  * @param   list chained list of Headers
904  */
905 void DicomDir::SetElements(std::string const & path, VectDocument const &list)
906 {
907    ClearEntry();
908    ClearPatient();
909
910    std::string patPrevName         = "", patPrevID  = "";
911    std::string studPrevInstanceUID = "", studPrevID = "";
912    std::string serPrevInstanceUID  = "", serPrevID  = "";
913
914    std::string patCurName,         patCurID;
915    std::string studCurInstanceUID, studCurID;
916    std::string serCurInstanceUID,  serCurID;
917
918    bool first = true;
919    for( VectDocument::const_iterator it = list.begin();
920                                      it != list.end(); ++it )
921    {
922       // get the current file characteristics
923       patCurName         = (*it)->GetEntryValue(0x0010,0x0010);
924       patCurID           = (*it)->GetEntryValue(0x0010,0x0011);
925       studCurInstanceUID = (*it)->GetEntryValue(0x0020,0x000d);
926       studCurID          = (*it)->GetEntryValue(0x0020,0x0010);
927       serCurInstanceUID  = (*it)->GetEntryValue(0x0020,0x000e);
928       serCurID           = (*it)->GetEntryValue(0x0020,0x0011);
929
930       if( patCurName != patPrevName || patCurID != patPrevID || first )
931       {
932          SetElement(path, GDCM_DICOMDIR_PATIENT, *it);
933          first = true;
934       }
935
936       // if new Study Deal with 'STUDY' Elements   
937       if( studCurInstanceUID != studPrevInstanceUID || studCurID != studPrevID 
938          || first )
939       {
940          SetElement(path, GDCM_DICOMDIR_STUDY, *it);
941          first = true;
942       }
943
944       // if new Serie Deal with 'SERIE' Elements   
945       if( serCurInstanceUID != serPrevInstanceUID || serCurID != serPrevID
946          || first )
947       {
948          SetElement(path, GDCM_DICOMDIR_SERIE, *it);
949          first = true;
950       }
951       
952       // Always Deal with 'IMAGE' Elements  
953       SetElement(path, GDCM_DICOMDIR_IMAGE, *it);
954
955       patPrevName         = patCurName;
956       patPrevID           = patCurID;
957       studPrevInstanceUID = studCurInstanceUID;
958       studPrevID          = studCurID;
959       serPrevInstanceUID  = serCurInstanceUID;
960       serPrevID           = serCurID;
961       first = false;
962    }
963 }
964
965 /**
966  * \brief   Move the content of the source SQItem to the destination SQItem
967  *          Only DocEntry's are moved
968  * @param dst destination SQItem
969  * @param src source SQItem
970  */
971 void DicomDir::MoveSQItem(DocEntrySet *dst,DocEntrySet *src)
972 {
973    DocEntry *entry;
974
975    entry = src->GetFirstEntry();
976    while(entry)
977    {
978       src->RemoveEntryNoDestroy(entry);
979       dst->AddEntry(entry);
980       // we destroyed -> the current iterator is not longer valid
981       entry = src->GetFirstEntry();
982    }
983 }
984
985 /**
986  * \brief   compares two files
987  */
988 bool DicomDir::HeaderLessThan(Document *header1, Document *header2)
989 {
990    return *header1 < *header2;
991 }
992
993 } // end namespace gdcm
994
995 //-----------------------------------------------------------------------------