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