2 //-----------------------------------------------------------------------------
3 #include "gdcmDicomDir.h"
4 #include "gdcmDicomDirStudy.h"
5 #include "gdcmDicomDirSerie.h"
6 #include "gdcmDicomDirImage.h"
7 #include "gdcmDirList.h"
13 #include <sys/types.h>
17 //-----------------------------------------------------------------------------
18 // For full DICOMDIR description, see:
19 // PS 3.3-2003, pages 731-750
20 //-----------------------------------------------------------------------------
21 // Constructor / Destructor
23 * \ingroup gdcmDicomDir
24 * \brief Constructor : Parses recursively the directory and creates the DicomDir
25 * \ or uses an already built DICOMDIR, depending on 'parseDir' value
26 * @param Name name of the root directory (parseDir = true)
27 * name of the DICOMDIR (parseDir = false)
28 * @param parseDir - true if user passed an entry point
29 * and wants to explore recursively the directories
30 * - false if user passed an already built DICOMDIR file
32 * @param exception_on_error whether we want to throw an exception or not
34 gdcmDicomDir::gdcmDicomDir(const char *Name, bool parseDir,
35 bool exception_on_error):
36 gdcmParser(Name,exception_on_error,true) // true : enable SeQuences
38 // que l'on ai passe un root directory ou un DICOMDIR
39 // et quelle que soit la valeur de parseDir,
40 // on a lance gdcmParser
45 startMethodArgDelete= NULL;
46 progressMethodArgDelete=NULL;
47 endMethodArgDelete= NULL;
57 // gdcmParser already executed
58 // if user passed a root directory, sure we didn't get anything
60 if( GetListEntry().begin()==GetListEntry().end() )
62 // if parseDir == false, it should be tagged as an error
63 dbg.Verbose(0, "gdcmDicomDir::gdcmDicomDir : entry list empty");
65 if(strlen(Name)==1 && Name[0]=='.') { // user passed '.' as Name
66 // we get current directory name
67 char*dummy=(char*) malloc(1000); // TODO : check with Windoze
68 getcwd(dummy,(size_t)1000);
69 SetFileName(dummy); // will be converted into a string
70 free(dummy); // no longer needed
75 dbg.Verbose(0, "gdcmDicomDir::gdcmDicomDir : Parse directory and create the DicomDir");
84 * \ingroup gdcmDicomDir
85 * \brief Constructor : creates an empty gdcmDicomDir
86 * @param exception_on_error whether we want to throw an exception or not
88 gdcmDicomDir::gdcmDicomDir(bool exception_on_error):
89 gdcmParser(exception_on_error)
94 startMethodArgDelete= NULL;
95 progressMethodArgDelete=NULL;
96 endMethodArgDelete= NULL;
103 std::string pathBidon = ""; // Sorry, NULL not allowed ...
104 SetElement(pathBidon, GDCM_DICOMDIR_META, NULL); // Set the META elements
106 AddDicomDirMetaToEnd(listEntries.begin(),--listEntries.end());
111 * \ingroup gdcmDicomDir
112 * \brief Canonical destructor
114 gdcmDicomDir::~gdcmDicomDir()
116 SetStartMethod(NULL);
117 SetProgressMethod(NULL);
123 for(ListDicomDirPatient::iterator cc=patients.begin();cc!=patients.end();++cc)
129 //-----------------------------------------------------------------------------
132 * \ingroup gdcmDicomDir
133 * \brief Canonical Printer
135 void gdcmDicomDir::Print(std::ostream &os)
139 metaElems->SetPrintLevel(printLevel);
140 metaElems->Print(os);
142 for(ListDicomDirPatient::iterator cc=patients.begin();cc!=patients.end();++cc)
144 (*cc)->SetPrintLevel(printLevel);
149 //-----------------------------------------------------------------------------
152 * \ingroup gdcmDicomDir
153 * \brief This predicate, based on hopefully reasonable heuristics,
154 * decides whether or not the current header was properly parsed
155 * and contains the mandatory information for being considered as
156 * a well formed and usable DicomDir.
157 * @return true when gdcmParser is the one of a reasonable DicomDir,
160 bool gdcmDicomDir::IsReadable(void)
162 if(!gdcmParser::IsReadable())
166 if(patients.size()<=0)
173 * \ingroup gdcmDicomDir
174 * \brief fills the whole structure, starting from a root Directory
176 void gdcmDicomDir::ParseDirectory(void)
178 CreateDicomDirChainedList(GetFileName());
183 * \ingroup gdcmDicomDir
184 * \brief Set the start method to call when the parsing of the directory starts
185 * @param method Method to call
186 * @param arg Argument to pass to the method
187 * \warning In python : the arg parameter isn't considered
189 void gdcmDicomDir::SetStartMethod(gdcmMethod *method,void *arg,gdcmMethod *argDelete)
191 if((startArg)&&(startMethodArgDelete))
192 startMethodArgDelete(startArg);
196 startMethodArgDelete=argDelete;
200 * \ingroup gdcmDicomDir
201 * \brief Set the method to delete the argument
202 * The argument is destroyed when the method is changed or when the
204 * @param method Method to call to delete the argument
206 void gdcmDicomDir::SetStartMethodArgDelete(gdcmMethod *method)
208 startMethodArgDelete=method;
212 * \ingroup gdcmDicomDir
213 * \brief Set the progress method to call when the parsing of the directory progress
214 * @param method Method to call
215 * @param arg Argument to pass to the method
216 * \warning In python : the arg parameter isn't considered
218 void gdcmDicomDir::SetProgressMethod(gdcmMethod *method,void *arg,gdcmMethod *argDelete)
220 if((progressArg)&&(progressMethodArgDelete))
221 progressMethodArgDelete(progressArg);
223 progressMethod=method;
225 progressMethodArgDelete=argDelete;
229 * \ingroup gdcmDicomDir
230 * \brief Set the method to delete the argument
231 * The argument is destroyed when the method is changed or when the
233 * @param method Method to call to delete the argument
235 void gdcmDicomDir::SetProgressMethodArgDelete(gdcmMethod *method)
237 progressMethodArgDelete=method;
241 * \ingroup gdcmDicomDir
242 * \brief Set the end method to call when the parsing of the directory ends
243 * @param method Method to call
244 * @param arg Argument to pass to the method
245 * \warning In python : the arg parameter isn't considered
247 void gdcmDicomDir::SetEndMethod(gdcmMethod *method,void *arg,gdcmMethod *argDelete)
249 if((endArg)&&(endMethodArgDelete))
250 endMethodArgDelete(endArg);
254 endMethodArgDelete=argDelete;
258 * \ingroup gdcmDicomDir
259 * \brief Set the method to delete the argument
260 * The argument is destroyed when the method is changed or when the class
262 * @param method Method to call to delete the argument
264 void gdcmDicomDir::SetEndMethodArgDelete(gdcmMethod *method)
266 endMethodArgDelete=method;
270 * \ingroup gdcmDicomDir
271 * \brief writes on disc a DICOMDIR
272 * \ warning does NOT add the missing elements in the header :
273 * it's up to the user doing it !
274 * \todo : to be re-written using the DICOMDIR tree-like structure
275 * *not* the chained list
276 * (does NOT exist if the DICOMDIR is user-forged !)
277 * @param fileName file to be written to
278 * @return false only when fail to open
281 bool gdcmDicomDir::Write(std::string fileName)
285 fp1=fopen(fileName.c_str(),"wb");
288 printf("Failed to open(write) File [%s] \n",fileName.c_str());
293 filePreamble=(char*)calloc(128,1);
294 fwrite(filePreamble,128,1,fp1);
295 fwrite("DICM",4,1,fp1);
297 //UpdateDirectoryRecordSequenceLength(); // a reecrire en utilisant
298 // la structure arborescente JPR
299 WriteDicomDirEntries(fp1);
306 * \ingroup gdcmParser
307 * \brief writes on disc according to the DICOMDIR format
308 * using the tree-like structure
309 * @param _fp already open file pointer
312 void gdcmDicomDir::WriteDicomDirEntries(FILE *_fp)
314 // TODO (?) tester les echecs en ecriture
315 // (apres chaque fwrite, dans le WriteEntry)
317 gdcmDicomDirMeta *ptrMeta;
318 ListDicomDirPatient::iterator itPatient;
319 ListDicomDirStudy::iterator itStudy;
320 ListDicomDirSerie::iterator itSerie;
321 ListDicomDirImage::iterator itImage;
324 ptrMeta= GetDicomDirMeta();
325 for(i=ptrMeta->debut();i!=ptrMeta->fin();++i) {
326 WriteEntry(*i,_fp,DICOMDIR);
329 itPatient = GetDicomDirPatients().begin();
330 while ( itPatient != GetDicomDirPatients().end() ) {
331 for(i=(*itPatient)->debut();i!=(*itPatient)->fin();++i) {
332 WriteEntry(*i,_fp,DICOMDIR);
334 itStudy = ((*itPatient)->GetDicomDirStudies()).begin();
335 while (itStudy != (*itPatient)->GetDicomDirStudies().end() ) {
336 for(i=(*itStudy)->debut();i!=(*itStudy)->fin();++i) {
337 WriteEntry(*i,_fp,DICOMDIR);
339 itSerie = ((*itStudy)->GetDicomDirSeries()).begin();
340 while (itSerie != (*itStudy)->GetDicomDirSeries().end() ) {
341 for(i=(*itSerie)->debut();i!=(*itSerie)->fin();++i) {
342 WriteEntry(*i,_fp,DICOMDIR);
344 itImage = ((*itSerie)->GetDicomDirImages()).begin();
345 while (itImage != (*itSerie)->GetDicomDirImages().end() ) {
346 for(i=(*itImage)->debut();i!=(*itImage)->fin();++i) {
347 WriteEntry(*i,_fp,DICOMDIR);
359 //-----------------------------------------------------------------------------
363 * \ingroup gdcmDicomDir
364 * \brief create a gdcmHeader-like chained list from a root Directory
365 * @param path entry point of the tree-like structure
367 void gdcmDicomDir::CreateDicomDirChainedList(std::string path)
371 gdcmDirList fileList(path,1); // gets recursively the file list
372 unsigned int count=0;
379 for(gdcmDirList::iterator it=fileList.begin();
383 progress=(float)(count+1)/(float)fileList.size();
384 CallProgressMethod();
388 header=new gdcmHeader(it->c_str());
389 if(header->IsReadable())
390 list.push_back(header); // adds the file header to the chained list
396 // sorts Patient/Study/Serie/
397 std::sort(list.begin(),list.end(),gdcmDicomDir::HeaderLessThan);
399 std::string tmp=fileList.GetDirName();
400 //for each Header of the chained list, add/update the Patient/Study/Serie/Image info
401 SetElements(tmp,list);
407 * \ingroup gdcmDicomDir
408 * \brief adds a new Patient to a partially created DICOMDIR
410 gdcmDicomDirPatient * gdcmDicomDir::NewPatient(void) {
411 std::list<gdcmElement> elemList;
412 std::list<gdcmElement>::iterator it;
414 gdcmDictEntry *dictEntry;
415 gdcmHeaderEntry *entry;
417 elemList=gdcmGlobal::GetDicomDirElements()->GetDicomDirPatientElements();
418 std::list<gdcmHeaderEntry *>::iterator debInsertion, finInsertion, i,j;
420 debInsertion = metaElems->fin();
422 finInsertion=debInsertion;
424 // for all the DicomDirPatient Elements
425 for(it=elemList.begin();it!=elemList.end();++it)
429 dictEntry=GetPubDict()->GetDictEntryByNumber(tmpGr,tmpEl);
430 entry=new gdcmHeaderEntry(dictEntry);
431 entry->SetOffset(0); // just to avoid missprinting
432 entry->SetValue(it->value);
434 if(dictEntry->GetGroup()==0xfffe)
436 entry->SetLength(entry->GetValue().length());
438 else if( (dictEntry->GetVR()=="UL") || (dictEntry->GetVR()=="SL") )
442 else if( (dictEntry->GetVR()=="US") || (dictEntry->GetVR()=="SS") )
446 else if(dictEntry->GetVR()=="SQ")
448 entry->SetLength(0xffffffff);
452 entry->SetLength(entry->GetValue().length());
455 tagHT.insert( PairHT(entry->GetKey(),entry) ); // add in the (multimap) H Table
456 listEntries.insert(debInsertion ,entry); // en tete de liste des Patients
463 gdcmDicomDirPatient *p = new gdcmDicomDirPatient(i, --debInsertion,
464 &tagHT, &listEntries);
465 patients.push_front(p);
470 * \ingroup gdcmDicomDir
471 * \brief CallStartMethod
473 void gdcmDicomDir::CallStartMethod(void)
478 startMethod(startArg);
481 * \ingroup gdcmDicomDir
482 * \brief CallProgressMethod
484 void gdcmDicomDir::CallProgressMethod(void)
487 progressMethod(progressArg);
490 * \ingroup gdcmDicomDir
491 * \brief CallEndMethod
493 void gdcmDicomDir::CallEndMethod(void)
500 //-----------------------------------------------------------------------------
503 * \ingroup gdcmDicomDir
504 * \brief create a 'gdcmDicomDir' from a DICOMDIR gdcmHeader
506 void gdcmDicomDir::CreateDicomDir()
508 // The list is parsed.
509 // When a DicomDir tag ("PATIENT", "STUDY", "SERIE", "IMAGE") is found :
510 // 1 - we save the beginning iterator
511 // 2 - we continue to parse
512 // 3 - we find an other tag
513 // + we create the object for the precedent tag
516 gdcmDicomDirType type=gdcmDicomDir::GDCM_DICOMDIR_META;
517 ListTag::iterator begin;
518 ListTag::iterator end, end2;
520 begin=listEntries.begin();
522 for(ListTag::iterator i=end;i !=listEntries.end();++i)
524 std::string v=(*i)->GetValue();
528 AddObjectToEnd(type,begin,--end2);
529 type=gdcmDicomDir::GDCM_DICOMDIR_PATIENT;
536 AddObjectToEnd(type,begin,--end2);
537 type=gdcmDicomDir::GDCM_DICOMDIR_STUDY;
544 AddObjectToEnd(type,begin,--end2);
545 type=gdcmDicomDir::GDCM_DICOMDIR_SERIE;
552 AddObjectToEnd(type,begin,--end2);
553 type=gdcmDicomDir::GDCM_DICOMDIR_IMAGE;
558 end=end2=listEntries.end();
560 AddObjectToEnd(type,begin,--end2);
563 * \ingroup gdcmDicomDir
564 * \brief AddObjectToEnd
569 void gdcmDicomDir::AddObjectToEnd(gdcmDicomDirType type,ListTag::iterator begin,ListTag::iterator end)
576 case gdcmDicomDir::GDCM_DICOMDIR_META:
577 AddDicomDirMetaToEnd(begin,end);
579 case gdcmDicomDir::GDCM_DICOMDIR_PATIENT:
580 AddDicomDirPatientToEnd(begin,end);
582 case gdcmDicomDir::GDCM_DICOMDIR_STUDY:
583 AddDicomDirStudyToEnd(begin,end);
585 case gdcmDicomDir::GDCM_DICOMDIR_SERIE:
586 AddDicomDirSerieToEnd(begin,end);
588 case gdcmDicomDir::GDCM_DICOMDIR_IMAGE:
589 AddDicomDirImageToEnd(begin,end);
595 * \ingroup gdcmDicomDir
596 * \brief Well ... Not realy to end, there is only one occurence
600 void gdcmDicomDir::AddDicomDirMetaToEnd(ListTag::iterator begin,ListTag::iterator end)
604 metaElems = new gdcmDicomDirMeta(begin,end,&tagHT,&listEntries);
608 * \ingroup gdcmDicomDir
609 * \brief AddDicomDirPatientToEnd
613 void gdcmDicomDir::AddDicomDirPatientToEnd(ListTag::iterator begin,ListTag::iterator end)
615 patients.push_back(new gdcmDicomDirPatient(begin,end,&tagHT, &listEntries));
619 * \ingroup gdcmDicomDir
620 * \brief AddDicomDirStudyToEnd
624 void gdcmDicomDir::AddDicomDirStudyToEnd(ListTag::iterator begin,ListTag::iterator end)
626 if(patients.size()>0)
628 ListDicomDirPatient::iterator itp=patients.end();
630 (*itp)->AddDicomDirStudy(new gdcmDicomDirStudy(begin,end,&tagHT, &listEntries));
634 * \ingroup gdcmDicomDir
635 * \brief AddDicomDirSerieToEnd
639 void gdcmDicomDir::AddDicomDirSerieToEnd(ListTag::iterator begin,ListTag::iterator end)
641 if(patients.size()>0)
643 ListDicomDirPatient::iterator itp=patients.end();
646 if((*itp)->GetDicomDirStudies().size()>0)
648 ListDicomDirStudy::iterator itst=(*itp)->GetDicomDirStudies().end();
650 (*itst)->AddDicomDirSerie(new gdcmDicomDirSerie(begin,end,&tagHT, &listEntries));
656 * \ingroup gdcmDicomDir
657 * \brief AddDicomDirImageToEnd
661 void gdcmDicomDir::AddDicomDirImageToEnd(ListTag::iterator begin,ListTag::iterator end)
663 if(patients.size()>0)
665 ListDicomDirPatient::iterator itp=patients.end();
668 if((*itp)->GetDicomDirStudies().size()>0)
670 ListDicomDirStudy::iterator itst=(*itp)->GetDicomDirStudies().end();
673 if((*itst)->GetDicomDirSeries().size()>0)
675 ListDicomDirSerie::iterator its=(*itst)->GetDicomDirSeries().end();
677 (*its)->AddDicomDirImage(new gdcmDicomDirImage(begin,end,&tagHT, &listEntries));
684 * \ingroup gdcmDicomDir
685 * \brief for each Header of the chained list, add/update the Patient/Study/Serie/Image info
686 * @param path path of the root directory
687 * @param list chained list of Headers
689 void gdcmDicomDir::SetElements(std::string &path, ListHeader &list)
691 std::string patPrevName="", patPrevID="";
692 std::string studPrevInstanceUID="", studPrevID="";
693 std::string serPrevInstanceUID="", serPrevID="";
695 std::string patCurName, patCurID;
696 std::string studCurInstanceUID, studCurID;
697 std::string serCurInstanceUID, serCurID;
699 SetElement(path,GDCM_DICOMDIR_META,NULL);
701 ListTag::iterator debPat=listEntries.begin();
702 for(ListHeader::iterator it=list.begin();it!=list.end();++it)
704 // get the current file characteristics
705 patCurName= (*it)->GetEntryByNumber(0x0010,0x0010);
706 patCurID= (*it)->GetEntryByNumber(0x0010,0x0011);
707 studCurInstanceUID=(*it)->GetEntryByNumber(0x0020,0x000d);
708 studCurID= (*it)->GetEntryByNumber(0x0020,0x0010);
709 serCurInstanceUID= (*it)->GetEntryByNumber(0x0020,0x000e);
710 serCurID= (*it)->GetEntryByNumber(0x0020,0x0011);
712 if(patCurName!=patPrevName || patCurID!=patPrevID)
713 SetElement(path,GDCM_DICOMDIR_PATIENT,*it);
715 // if new Study Deal with 'STUDY' Elements
716 if(studCurInstanceUID!=studPrevInstanceUID || studCurID!=studPrevID)
717 SetElement(path,GDCM_DICOMDIR_STUDY,*it);
719 // if new Serie Deal with 'SERIE' Elements
720 if(serCurInstanceUID!=serPrevInstanceUID || serCurID!=serPrevID)
721 SetElement(path,GDCM_DICOMDIR_SERIE,*it);
723 // Always Deal with 'IMAGE' Elements
724 SetElement(path,GDCM_DICOMDIR_IMAGE,*it);
726 patPrevName= patCurName;
728 studPrevInstanceUID=studCurInstanceUID;
729 studPrevID= studCurID;
730 serPrevInstanceUID= serCurInstanceUID;
736 * \ingroup gdcmDicomDir
737 * \brief adds to the HTable and at the end of the Chained List
738 * the gdcmEntries (Dicom Elements) corresponding to the given type
739 * @param path full path file name(only used when type = GDCM_DICOMDIR_IMAGE
740 * @param type gdcmObject type to create (GDCM_DICOMDIR_PATIENT, GDCM_DICOMDIR_STUDY, GDCM_DICOMDIR_SERIE ...)
741 * @param header gdcmHeader of the current file
743 void gdcmDicomDir::SetElement(std::string &path,gdcmDicomDirType type,gdcmHeader *header)
745 std::list<gdcmElement> elemList;
746 std::list<gdcmElement>::iterator it;
747 guint16 tmpGr, tmpEl;
748 gdcmDictEntry *dictEntry;
749 gdcmHeaderEntry *entry;
754 case GDCM_DICOMDIR_PATIENT:
755 elemList=gdcmGlobal::GetDicomDirElements()->GetDicomDirPatientElements();
757 case GDCM_DICOMDIR_STUDY:
758 elemList=gdcmGlobal::GetDicomDirElements()->GetDicomDirStudyElements();
760 case GDCM_DICOMDIR_SERIE:
761 elemList=gdcmGlobal::GetDicomDirElements()->GetDicomDirSerieElements();
763 case GDCM_DICOMDIR_IMAGE:
764 elemList=gdcmGlobal::GetDicomDirElements()->GetDicomDirImageElements();
766 case GDCM_DICOMDIR_META:
767 elemList=gdcmGlobal::GetDicomDirElements()->GetDicomDirMetaElements();
773 for(it=elemList.begin();it!=elemList.end();++it)
777 dictEntry=GetPubDict()->GetDictEntryByNumber(tmpGr,tmpEl);
778 entry=new gdcmHeaderEntry(dictEntry);
779 entry->SetOffset(0); // just to avoid missprinting
782 val=header->GetEntryByNumber(tmpGr,tmpEl);
786 if(val==GDCM_UNFOUND)
788 if((tmpGr==0x0004) &&(tmpEl==0x1130) ) // File-set ID
790 // force to the *end* File Name
793 else if( (tmpGr==0x0004) && (tmpEl==0x1500) ) // Only used for image
795 if(header->GetFileName().substr(0,path.length())!=path)
797 dbg.Verbose(0, "gdcmDicomDir::SetElement : the base path of file name is incorrect");
798 val=header->GetFileName();
801 val=&(header->GetFileName().c_str()[path.length()]);
811 if (header->GetEntryLengthByNumber(tmpGr,tmpEl)== 0)
815 entry->SetValue(val);
819 if(dictEntry->GetGroup()==0xfffe)
821 entry->SetLength(entry->GetValue().length());
823 else if( (dictEntry->GetVR()=="UL") || (dictEntry->GetVR()=="SL") )
827 else if( (dictEntry->GetVR()=="US") || (dictEntry->GetVR()=="SS") )
831 else if(dictEntry->GetVR()=="SQ")
833 entry->SetLength(0xffffffff);
837 entry->SetLength(entry->GetValue().length());
840 //AddHeaderEntry(entry); // both in H Table and in chained list
841 tagHT.insert( PairHT( entry->GetKey(),entry) );
842 listEntries.push_back(entry);
843 //wasUpdated = 1; // is private
847 bool gdcmDicomDir::HeaderLessThan(gdcmHeader *header1,gdcmHeader *header2)
849 return(*header1<*header2);
854 void gdcmDicomDir::UpdateDirectoryRecordSequenceLength() {
856 ListTag::iterator it;
859 for(it=listEntries.begin();it!=listEntries.end();++it) {
860 gr = (*it)->GetGroup();
861 el = (*it)->GetElement();
864 if ( (vr == "OB") || (vr == "OW") || (vr == "SQ") ) {
865 offset += 4; // explicit VR AND OB, OW, SQ : 4 more bytes
867 offset += 2 + 2 + 4 + (*it)->GetLength();
869 offset += 4; // delimiters don't have a value.
872 bool res=SetEntryLengthByNumber(offset, 0x0004, 0x1220); // Hope there is no dupps.
876 //-----------------------------------------------------------------------------