]> Creatis software - gdcm.git/blob - src/gdcmSerieHelper.cxx
According to some requierements, if user knows more about his images
[gdcm.git] / src / gdcmSerieHelper.cxx
1 /*=========================================================================
2                                                                                 
3   Program:   gdcm
4   Module:    $RCSfile: gdcmSerieHelper.cxx,v $
5   Language:  C++
6   Date:      $Date: 2005/08/30 08:12:40 $
7   Version:   $Revision: 1.18 $
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 #include "gdcmSerieHelper.h"
20 #include "gdcmDirList.h"
21 #include "gdcmFile.h"
22 #include "gdcmDictEntry.h" // for TranslateToKey
23 #include "gdcmDebug.h"
24 #include "gdcmUtil.h"
25
26 #include <math.h>
27 #include <vector>
28 #include <algorithm>
29
30 namespace gdcm 
31 {
32
33 //-----------------------------------------------------------------------------
34 // Constructor / Destructor
35 /**
36  * \brief   Constructor from a given SerieHelper
37  */
38 SerieHelper::SerieHelper()
39 {
40    UserLessThanFunction = 0;
41
42    // For all the File lists that may already exist within the gdcm::Serie
43    FileList *l = GetFirstCoherentFileList();
44    while (l)
45    { 
46       // For all the files of a File list
47       for (gdcm::FileList::iterator it  = l->begin();
48                               it != l->end(); 
49                             ++it)
50       {
51          delete *it; // remove entry
52       }
53       l->clear();
54       delete l;     // remove the list
55       l = GetNextCoherentFileList();
56    }
57    DirectOrder = true;
58 }
59
60 /**
61  * \brief   Canonical destructor.
62  */
63 SerieHelper::~SerieHelper()
64 {
65    // For all the Coherent File lists of the gdcm::Serie
66    FileList *l = GetFirstCoherentFileList();
67    while (l)
68    { 
69       // For all the files of a Coherent File list
70       for (FileList::iterator it  = l->begin();
71                               it != l->end(); 
72                             ++it)
73       {
74          delete *it; // remove entry
75       }
76       l->clear();
77       delete l;  // remove the list
78       l = GetNextCoherentFileList();
79    }
80 }
81
82 //-----------------------------------------------------------------------------
83
84 //-----------------------------------------------------------------------------
85
86 // Public
87 /**
88  * \brief add a gdcm::File to the list corresponding to its Serie UID
89  * @param   filename Name of the file to deal with
90  */
91 void SerieHelper::AddFileName(std::string const &filename)
92 {
93    // Create a DICOM file
94    File *header = new File ();
95    header->SetLoadMode(LoadMode);
96    header->SetFileName( filename ); 
97    header->Load();
98
99    if ( header->IsReadable() )
100    {
101       int allrules = 1;
102       // First step the user has defined a set of rules for the DICOM file
103       // he is looking for.
104       // make sure the file correspond to his set of rules:
105
106 /*
107       for(SerieRestrictions::iterator it = Restrictions.begin();
108           it != Restrictions.end();
109           ++it)
110       {
111          const Rule &r = *it;
112          // doesn't compile (no matching function...).
113          const std::string s;// = header->GetEntryValue( r.first );
114          if ( !Util::DicomStringEqual(s, r.second.c_str()) )
115          {
116            // Argh ! This rule is unmatch let's just quit
117            allrules = 0;
118            break;
119          }
120       }
121 */
122       // Just keep 'new style' for Rules
123       std::string s;
124       for(SerieExRestrictions::iterator it2 = ExRestrictions.begin();
125           it2 != ExRestrictions.end();
126           ++it2)
127       {
128          const ExRule &r = *it2;
129          s = header->GetEntryValue( r.group, r.elem );
130          if ( !Util::CompareDicomString(s, r.value.c_str(), r.op) )
131          {
132            // Argh ! This rule is unmatch let's just quit
133            allrules = 0;
134            break;
135          }
136       }
137
138       if ( allrules ) // all rules are respected:
139       {
140          // Allright ! we have a found a DICOM that match the user expectation. 
141          // Let's add it !
142
143          // 0020 000e UI REL Series Instance UID
144          const std::string &uid = header->GetEntryValue (0x0020, 0x000e);
145          // if uid == GDCM_UNFOUND then consistently we should find GDCM_UNFOUND
146          // no need here to do anything special
147
148          if ( CoherentFileListHT.count(uid) == 0 )
149          {
150             gdcmDebugMacro(" New Serie UID :[" << uid << "]");
151             // create a std::list in 'uid' position
152             CoherentFileListHT[uid] = new FileList;
153          }
154          // Current Serie UID and DICOM header seems to match; add the file:
155          CoherentFileListHT[uid]->push_back( header );
156       }
157       else
158       {
159          // at least one rule was unmatch we need to deallocate the file:
160          delete header;
161       }
162    }
163    else
164    {
165       gdcmWarningMacro("Could not read file: " << filename );
166       delete header;
167    }
168 }
169
170 /**
171  * \brief add a gdcm::File to the first (and supposed to be unique) list
172  *        of the gdcm::SerieHelper.
173  * \warning : this method should be used by aware users only!
174  *            User is supposed to know the files he want to deal with
175  *           and consider them they belong to the same Serie
176  *           (even if their Serie UID is different)
177  *           user will probabely OrderFileList() this list (actually, ordering
178  *           user choosen gdm::File is the sole interest of this method)
179  *           Moreover, using vtkGdcmReader::SetCoherentFileList() will avoid
180  *           vtkGdcmReader parsing twice the same files. 
181  *           *no* coherence check is performed, but those specified
182  *           by SerieHelper::AddRestriction()
183  * @param   header gdcm::File* of the file to deal with
184  */
185 void SerieHelper::AddGdcmFile(File *header)
186 {
187       int allrules = 1;
188       // First step the user has defined a set of rules for the DICOM 
189       // he is looking for.
190       // make sure the file correspond to his set of rules:
191       for(SerieRestrictions::iterator it = Restrictions.begin();
192           it != Restrictions.end();
193           ++it)
194       {
195          const Rule &r = *it;
196          const std::string s;// = header->GetEntryValue( r.first );
197          if ( !Util::DicomStringEqual(s, r.second.c_str()) )
198          {
199            // Argh ! This rule is unmatch let's just quit
200            allrules = 0;
201            break;
202          }
203       }
204       if ( allrules ) // all rules are respected:
205       {
206          // Allright ! we have a found a DICOM that match the user expectation. 
207          // Let's add it !
208
209          const std::string &uid = "0";
210          // Serie UID of the gdcm::File* may be different.
211          // User is supposed to know what he wants
212
213          if ( CoherentFileListHT.count(uid) == 0 )
214          {
215             gdcmDebugMacro(" New Serie UID :[" << uid << "]");
216             // create a std::list in 'uid' position
217             CoherentFileListHT[uid] = new FileList;
218          }
219          // Current Serie UID and DICOM header seems to match; add the file:
220          CoherentFileListHT[uid]->push_back( header );
221       }
222          // Even if a rule was unmatch we don't deallocate the gdcm::File:
223 }
224
225 /**
226  * \brief add a rules for restricting a DICOM file to be in the serie we are
227  * trying to find. For example you can select only the DICOM file from a
228  * directory which would have a particular EchoTime==4.0.
229  * This method is a user level, value is not required to be formatted as a DICOM
230  * string
231  */
232 void SerieHelper::AddRestriction(uint16_t group, uint16_t elem, 
233                                  std::string const &value, int op)
234 {
235    ExRule r;
236    r.group = group;
237    r.elem  = elem;
238    r.value = value;
239    r.op    = op;
240    ExRestrictions.push_back( r ); 
241 }
242
243 #ifndef GDCM_LEGACY_REMOVE
244 /**
245  * \brief add a rules for restricting a DICOM file to be in the serie we are
246  * trying to find. For example you can select only the DICOM file from a
247  * directory which would have a particular EchoTime==4.0.
248  * This method is a user level, value is not required to be formatted as a DICOM
249  * string
250  * @deprecated use : AddRestriction(uint16_t group, uint16_t elem, 
251  *                                 std::string const &value, int op);
252  */
253 void SerieHelper::AddRestriction(TagKey const &key, std::string const &value)
254 {
255    Rule r;
256    r.first = key;
257    r.second = value;
258    Restrictions.push_back( r ); 
259 }
260 #endif
261
262 /**
263  * \brief Sets the root Directory
264  * @param   dir Name of the directory to deal with
265  * @param recursive whether we want explore recursively the Directory
266  */
267 void SerieHelper::SetDirectory(std::string const &dir, bool recursive)
268 {
269    DirList dirList(dir, recursive); // OS specific
270   
271    DirListType filenames_list = dirList.GetFilenames();
272    for( DirListType::const_iterator it = filenames_list.begin(); 
273         it != filenames_list.end(); ++it)
274    {
275       AddFileName( *it );
276    }
277 }
278
279 /**
280  * \brief Sorts the given File List
281  * \warning This could be implemented in a 'Strategy Pattern' approach
282  *          But as I don't know how to do it, I leave it this way
283  *          BTW, this is also a Strategy, I don't know this is the best approach :)
284  */
285 void SerieHelper::OrderFileList(FileList *coherentFileList)
286 {
287
288    if ( SerieHelper::UserLessThanFunction )
289    {
290       UserOrdering( coherentFileList );
291       return; 
292    }
293    else if ( ImagePositionPatientOrdering( coherentFileList ) )
294    {
295       return ;
296    }
297    else if ( ImageNumberOrdering(coherentFileList ) )
298    {
299       return ;
300    }
301    else  
302    {
303       FileNameOrdering(coherentFileList );
304    }
305 }
306
307 /**
308  * \brief Elementary coherence checking of the files with the same Serie UID
309  * Only sizes and pixel type are checked right now ...
310  */ 
311 bool SerieHelper::IsCoherent(FileList *coherentFileList)
312 {
313    if(coherentFileList->size() == 1)
314    return true;
315
316    FileList::const_iterator it = coherentFileList->begin();
317
318    int nX = (*it)->GetXSize();
319    int nY = (*it)->GetYSize();
320    int pixelSize = (*it)->GetPixelSize();
321
322    it ++;
323    for ( ;
324          it != coherentFileList->end();
325        ++it)
326    {
327       if ( (*it)->GetXSize() != nX )
328          return false;
329       if ( (*it)->GetYSize() != nY )
330          return false;
331       if ( (*it)->GetPixelSize() != pixelSize )
332          return false;
333       // probabely more is to be checked (?)
334    }
335    return true;
336 }
337 /**
338  * \brief   Get the first List while visiting the CoherentFileListHT
339  * @return  The first FileList if found, otherwhise NULL
340  */
341 FileList *SerieHelper::GetFirstCoherentFileList()
342 {
343    ItListHt = CoherentFileListHT.begin();
344    if ( ItListHt != CoherentFileListHT.end() )
345       return ItListHt->second;
346    return NULL;
347 }
348
349 /**
350  * \brief   Get the next List while visiting the CoherentFileListHT
351  * \note : meaningfull only if GetFirstCoherentFileList() already called
352  * @return  The next FileList if found, otherwhise NULL
353  */
354 FileList *SerieHelper::GetNextCoherentFileList()
355 {
356    gdcmAssertMacro (ItListHt != CoherentFileListHT.end());
357   
358    ++ItListHt;
359    if ( ItListHt != CoherentFileListHT.end() )
360       return ItListHt->second;
361    return NULL;
362 }
363
364 /**
365  * \brief   Get the Coherent Files list according to its Serie UID
366  * @param SerieUID SerieUID
367  * \return  pointer to the Coherent Files list if found, otherwhise NULL
368  */
369 FileList *SerieHelper::GetCoherentFileList(std::string SerieUID)
370 {
371    if ( CoherentFileListHT.count(SerieUID) == 0 )
372       return 0;     
373    return CoherentFileListHT[SerieUID];
374 }
375
376 //-----------------------------------------------------------------------------
377 // Protected
378
379 //-----------------------------------------------------------------------------
380 // Private
381 /**
382  * \brief sorts the images, according to their Patient Position
383  *  We may order, considering :
384  *   -# Image Position Patient
385  *   -# Image Number
386  *   -# More to come :-)
387  * WARNING : FileList = std::vector<File* >
388  * @param fileList Coherent File list (same Serie UID) to sort
389  * @return false only if the header is bugged !
390  */
391 bool SerieHelper::ImagePositionPatientOrdering( FileList *fileList )
392 //based on Jolinda Smith's algorithm
393 {
394    //iop is calculated based on the file file
395    float cosines[6];
396    float normal[3];
397    float ipp[3];
398    float dist;
399    float min = 0, max = 0;
400    bool first = true;
401    int n=0;
402    std::vector<float> distlist;
403
404    //!\todo rewrite this for loop.
405    for ( FileList::const_iterator 
406          it = fileList->begin();
407          it != fileList->end(); ++it )
408    {
409       if ( first ) 
410       {
411          (*it)->GetImageOrientationPatient( cosines );
412       
413          // You only have to do this once for all slices in the volume. Next, 
414          // for each slice, calculate the distance along the slice normal 
415          // using the IPP tag ("dist" is initialized to zero before reading 
416          // the first slice) :
417          normal[0] = cosines[1]*cosines[5] - cosines[2]*cosines[4];
418          normal[1] = cosines[2]*cosines[3] - cosines[0]*cosines[5];
419          normal[2] = cosines[0]*cosines[4] - cosines[1]*cosines[3];
420   
421          ipp[0] = (*it)->GetXOrigin();
422          ipp[1] = (*it)->GetYOrigin();
423          ipp[2] = (*it)->GetZOrigin();
424
425          dist = 0;
426          for ( int i = 0; i < 3; ++i )
427          {
428             dist += normal[i]*ipp[i];
429          }
430     
431          distlist.push_back( dist );
432
433          max = min = dist;
434          first = false;
435       }
436       else 
437       {
438          ipp[0] = (*it)->GetXOrigin();
439          ipp[1] = (*it)->GetYOrigin();
440          ipp[2] = (*it)->GetZOrigin();
441   
442          dist = 0;
443          for ( int i = 0; i < 3; ++i )
444          {
445             dist += normal[i]*ipp[i];
446          }
447
448          distlist.push_back( dist );
449
450          min = (min < dist) ? min : dist;
451          max = (max > dist) ? max : dist;
452       }
453       ++n;
454    }
455
456    // Then I order the slices according to the value "dist". Finally, once
457    // I've read in all the slices, I calculate the z-spacing as the difference
458    // between the "dist" values for the first two slices.
459    FileVector CoherentFileVector(n);
460    // CoherentFileVector.reserve( n );
461    CoherentFileVector.resize( n );
462    // gdcmAssertMacro( CoherentFileVector.capacity() >= n );
463
464    // Find out if min/max are coherent
465    if ( min == max )
466      {
467      gdcmWarningMacro( "Looks like all images have the exact same image position."
468                        << "No PositionPatientOrdering sort performed" );
469      return false;
470      }
471
472    float step = (max - min)/(n - 1);
473    int pos;
474    n = 0;
475     
476    //VC++ don't understand what scope is !! it -> it2
477    for (FileList::const_iterator it2  = fileList->begin();
478         it2 != fileList->end(); ++it2, ++n)
479    {
480       //2*n sort algo !!
481       //Assumption: all files are present (no one missing)
482       pos = (int)( fabs( (distlist[n]-min)/step) + .5 );
483
484       // a Dicom 'Serie' may contain scout views
485       // and images may have differents directions
486       // -> More than one may have the same 'pos'
487       // Sorting has then NO meaning !
488       if (CoherentFileVector[pos]==NULL)
489          CoherentFileVector[pos] = *it2;
490       else
491       {
492          gdcmWarningMacro( "At least 2 files with same position. No PositionPatientOrdering sort performed");
493          return false;
494       }
495    }
496
497    fileList->clear();  // doesn't delete list elements, only nodes
498
499    if (DirectOrder)
500    {  
501       //VC++ don't understand what scope is !! it -> it3
502       for (FileVector::const_iterator it3  = CoherentFileVector.begin();
503            it3 != CoherentFileVector.end(); ++it3)
504       {
505          fileList->push_back( *it3 );
506       }
507    }
508    else // user asked for reverse order
509    {
510       FileVector::const_iterator it4;
511       it4 = CoherentFileVector.end();
512       do
513       {
514          it4--;
515          fileList->push_back( *it4 );
516       } while (it4 != CoherentFileVector.begin() );
517    } 
518
519    distlist.clear();
520    CoherentFileVector.clear();
521
522    return true;
523 }
524
525 bool SerieHelper::ImageNumberLessThan(File *file1, File *file2)
526 {
527   return file1->GetImageNumber() < file2->GetImageNumber();
528 }
529
530 bool SerieHelper::ImageNumberGreaterThan(File *file1, File *file2)
531 {
532   return file1->GetImageNumber() > file2->GetImageNumber();
533 }
534 /**
535  * \brief sorts the images, according to their Image Number
536  * \note Works only on bona fide files  (i.e image number is a character string
537  *                                      corresponding to an integer)
538  *             within a bona fide serie (i.e image numbers are consecutive)
539  * @param fileList Coherent File list (same Serie UID) to sort 
540  * @return false if non bona fide stuff encountered
541  */
542 bool SerieHelper::ImageNumberOrdering(FileList *fileList) 
543 {
544    int min, max, pos;
545    int n = fileList->size();
546
547    FileList::const_iterator it = fileList->begin();
548    min = max = (*it)->GetImageNumber();
549
550    for (; it != fileList->end(); ++it, ++n)
551    {
552       pos = (*it)->GetImageNumber();
553       min = (min < pos) ? min : pos;
554       max = (max > pos) ? max : pos;
555    }
556
557    // Find out if image numbers are coherent (consecutive)
558    if ( min == max || max == 0 || max >= (n+min) )
559    {
560       gdcmWarningMacro( " 'Image numbers' not coherent. No ImageNumberOrdering sort performed.");
561       return false;
562    }
563    if (DirectOrder) 
564       std::sort(fileList->begin(), fileList->end(), SerieHelper::ImageNumberLessThan );
565    else
566       std::sort(fileList->begin(), fileList->end(), SerieHelper::ImageNumberGreaterThan );
567
568    return true;
569 }
570
571 bool SerieHelper::FileNameLessThan(File *file1, File *file2)
572 {
573   return file1->GetFileName() < file2->GetFileName();
574 }
575
576 bool SerieHelper::FileNameGreaterThan(File *file1, File *file2)
577 {
578   return file1->GetFileName() > file2->GetFileName();
579 }
580 /**
581  * \brief sorts the images, according to their File Name
582  * @param fileList Coherent File list (same Serie UID) to sort
583  * @return false only if the header is bugged !
584  */
585 bool SerieHelper::FileNameOrdering(FileList *fileList)
586 {
587    if (DirectOrder) 
588       std::sort(fileList->begin(), fileList->end(), SerieHelper::FileNameLessThan);
589    else
590       std::sort(fileList->begin(), fileList->end(), SerieHelper::FileNameGreaterThan);
591
592    return true;
593 }
594
595 /**
596  * \brief sorts the images, according to user supplied function
597  * \note Only Direct ordering is allowed
598  * @param fileList Coherent File list (same Serie UID) to sort
599  * @return false only if the header is bugged !
600  */
601 bool SerieHelper::UserOrdering(FileList *fileList)
602 {
603    if (DirectOrder) 
604       std::sort(fileList->begin(), fileList->end(), SerieHelper::UserLessThanFunction);
605    else
606       gdcmWarningMacro( " Only Direct ordering allowed "
607                      << "when user function is supplied");
608    return true;
609 }
610 //-----------------------------------------------------------------------------
611 // Print
612 /**
613  * \brief   Canonical printer.
614  */
615 void SerieHelper::Print(std::ostream &os, std::string const &indent)
616 {
617    // For all the Coherent File lists of the gdcm::Serie
618    CoherentFileListmap::iterator itl = CoherentFileListHT.begin();
619    if ( itl == CoherentFileListHT.end() )
620    {
621       gdcmWarningMacro( "No Coherent File list found" );
622       return;
623    }
624    while (itl != CoherentFileListHT.end())
625    { 
626       os << "Serie UID :[" << itl->first << "]" << std::endl;
627
628       // For all the files of a Coherent File list
629       for (FileList::iterator it =  (itl->second)->begin();
630                                   it != (itl->second)->end(); 
631                                 ++it)
632       {
633          os << indent << " --- " << (*it)->GetFileName() << std::endl;
634       }
635       ++itl;
636    }
637 }
638
639 //-----------------------------------------------------------------------------
640 } // end namespace gdcm