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