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