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