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