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