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