]> Creatis software - gdcm.git/blob - src/gdcmSerieHelper.cxx
User may now use
[gdcm.git] / src / gdcmSerieHelper.cxx
1 /*=========================================================================
2                                                                                 
3   Program:   gdcm
4   Module:    $RCSfile: gdcmSerieHelper.cxx,v $
5   Language:  C++
6   Date:      $Date: 2005/07/29 15:07:16 $
7   Version:   $Revision: 1.16 $
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    DirectOrder = true;
56 }
57
58 /**
59  * \brief   Canonical destructor.
60  */
61 SerieHelper::~SerieHelper()
62 {
63    // For all the Coherent File lists of the gdcm::Serie
64    FileList *l = GetFirstCoherentFileList();
65    while (l)
66    { 
67       // For all the files of a Coherent File list
68       for (FileList::iterator it  = l->begin();
69                               it != l->end(); 
70                             ++it)
71       {
72          delete *it;
73       }
74       l->clear();
75       delete l;
76       l = GetNextCoherentFileList();
77    }
78 }
79
80 //-----------------------------------------------------------------------------
81
82 //-----------------------------------------------------------------------------
83
84 // Public
85 /**
86  * \brief add a gdcm::File to the list corresponding to its Serie UID
87  * @param   filename Name of the file to deal with
88  */
89 void SerieHelper::AddFileName(std::string const &filename)
90 {
91    // Create a DICOM file
92    File *header = new File ();
93    header->SetLoadMode(LoadMode);
94    header->SetFileName( filename ); 
95    header->Load();
96
97    if ( header->IsReadable() )
98    {
99       int allrules = 1;
100       // First step the user has defined a set of rules for the DICOM file
101       // he is looking for.
102       // make sure the file correspond to his set of rules:
103
104 /*
105       for(SerieRestrictions::iterator it = Restrictions.begin();
106           it != Restrictions.end();
107           ++it)
108       {
109          const Rule &r = *it;
110          // doesn't compile (no matching function...).
111          const std::string s;// = header->GetEntryValue( r.first );
112          if ( !Util::DicomStringEqual(s, r.second.c_str()) )
113          {
114            // Argh ! This rule is unmatch let's just quit
115            allrules = 0;
116            break;
117          }
118       }
119 */
120       // Just keep 'new style' for Rules
121       std::string s;
122       for(SerieExRestrictions::iterator it2 = ExRestrictions.begin();
123           it2 != ExRestrictions.end();
124           ++it2)
125       {
126          const ExRule &r = *it2;
127          s = header->GetEntryValue( r.group, r.elem );
128          if ( !Util::CompareDicomString(s, r.value.c_str(), r.op) )
129          {
130            // Argh ! This rule is unmatch let's just quit
131            allrules = 0;
132            break;
133          }
134       }
135
136       if ( allrules ) // all rules are respected:
137       {
138          // Allright ! we have a found a DICOM that match the user expectation. 
139          // Let's add it !
140
141          // 0020 000e UI REL Series Instance UID
142          const std::string &uid = header->GetEntryValue (0x0020, 0x000e);
143          // if uid == GDCM_UNFOUND then consistently we should find GDCM_UNFOUND
144          // no need here to do anything special
145
146          if ( CoherentFileListHT.count(uid) == 0 )
147          {
148             gdcmDebugMacro(" New Serie UID :[" << uid << "]");
149             // create a std::list in 'uid' position
150             CoherentFileListHT[uid] = new FileList;
151          }
152          // Current Serie UID and DICOM header seems to match; add the file:
153          CoherentFileListHT[uid]->push_back( header );
154       }
155       else
156       {
157          // at least one rule was unmatch we need to deallocate the file:
158          delete header;
159       }
160    }
161    else
162    {
163       gdcmWarningMacro("Could not read file: " << filename );
164       delete header;
165    }
166 }
167
168 /**
169  * \brief add a gdcm::File to the first (and supposed to be unique) list
170  *        of the gdcm::SerieHelper.
171  * \warning : this method should be used by aware users only!
172  *            User is supposed to know the files he want to deal with
173  *           and consider them they belong to the same Serie
174  *           (even if their Serie UID is different)
175  *           user will probabely OrderFileList() this list (actually, ordering
176  *           user choosen gdm::File is the sole interest of this method)
177  *           Moreover, using vtkGdcmReader::SetCoherentFileList() will avoid
178  *           vtkGdcmReader parsing twice the same files. 
179  *           *no* coherence check is performed, but those specified
180  *           by SerieHelper::AddRestriction()
181  * @param   header gdcm::File* of the file to deal with
182  */
183 void SerieHelper::AddGdcmFile(File *header)
184 {
185       int allrules = 1;
186       // First step the user has defined a set of rules for the DICOM 
187       // he is looking for.
188       // make sure the file correspond to his set of rules:
189       for(SerieRestrictions::iterator it = Restrictions.begin();
190           it != Restrictions.end();
191           ++it)
192       {
193          const Rule &r = *it;
194          const std::string s;// = header->GetEntryValue( r.first );
195          if ( !Util::DicomStringEqual(s, r.second.c_str()) )
196          {
197            // Argh ! This rule is unmatch let's just quit
198            allrules = 0;
199            break;
200          }
201       }
202       if ( allrules ) // all rules are respected:
203       {
204          // Allright ! we have a found a DICOM that match the user expectation. 
205          // Let's add it !
206
207          const std::string &uid = "0";
208          // Serie UID of the gdcm::File* may be different.
209          // User is supposed to know what he wants
210
211          if ( CoherentFileListHT.count(uid) == 0 )
212          {
213             gdcmDebugMacro(" New Serie UID :[" << uid << "]");
214             // create a std::list in 'uid' position
215             CoherentFileListHT[uid] = new FileList;
216          }
217          // Current Serie UID and DICOM header seems to match; add the file:
218          CoherentFileListHT[uid]->push_back( header );
219       }
220          // Even if a rule was unmatch we don't deallocate the gdcm::File:
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    if (DirectOrder)
462    {  
463       //VC++ don't understand what scope is !! it -> it3
464       for (FileVector::const_iterator it3  = CoherentFileVector.begin();
465            it3 != CoherentFileVector.end(); ++it3)
466       {
467          fileList->push_back( *it3 );
468       }
469    }
470    else // user asked for reverse order
471    {
472       FileVector::const_iterator it4;
473       it4 = CoherentFileVector.end();
474       do
475       {
476          it4--;
477          fileList->push_back( *it4 );
478       } while (it4 != CoherentFileVector.begin() );
479    } 
480
481    distlist.clear();
482    CoherentFileVector.clear();
483
484    return true;
485 }
486
487 bool SerieHelper::ImageNumberLessThan(File *file1, File *file2)
488 {
489   return file1->GetImageNumber() < file2->GetImageNumber();
490 }
491
492 bool SerieHelper::ImageNumberGreaterThan(File *file1, File *file2)
493 {
494   return file1->GetImageNumber() > file2->GetImageNumber();
495 }
496 /**
497  * \brief sorts the images, according to their Image Number
498  * \note Works only on bona fide files  (i.e image number is a character string
499  *                                      corresponding to an integer)
500  *             within a bona fide serie (i.e image numbers are consecutive)
501  * @param fileList Coherent File list (same Serie UID) to sort 
502  * @return false if non bona fide stuff encountered
503  */
504 bool SerieHelper::ImageNumberOrdering(FileList *fileList) 
505 {
506    int min, max, pos;
507    int n = fileList->size();
508
509    FileList::const_iterator it = fileList->begin();
510    min = max = (*it)->GetImageNumber();
511
512    for (; it != fileList->end(); ++it, ++n)
513    {
514       pos = (*it)->GetImageNumber();
515       min = (min < pos) ? min : pos;
516       max = (max > pos) ? max : pos;
517    }
518
519    // Find out if image numbers are coherent (consecutive)
520    if ( min == max || max == 0 || max >= (n+min) )
521    {
522       gdcmWarningMacro( " 'Image numbers' not coherent. No ImageNumberOrdering sort performed.");
523       return false;
524    }
525    if (DirectOrder) 
526       std::sort(fileList->begin(), fileList->end(), SerieHelper::ImageNumberLessThan );
527    else
528       std::sort(fileList->begin(), fileList->end(), SerieHelper::ImageNumberGreaterThan );
529
530    return true;
531 }
532
533 bool SerieHelper::FileNameLessThan(File *file1, File *file2)
534 {
535   return file1->GetFileName() < file2->GetFileName();
536 }
537
538 bool SerieHelper::FileNameGreaterThan(File *file1, File *file2)
539 {
540   return file1->GetFileName() > file2->GetFileName();
541 }
542 /**
543  * \brief sorts the images, according to their File Name
544  * @param fileList Coherent File list (same Serie UID) to sort
545  * @return false only if the header is bugged !
546  */
547 bool SerieHelper::FileNameOrdering(FileList *fileList)
548 {
549    if (DirectOrder) 
550       std::sort(fileList->begin(), fileList->end(), SerieHelper::FileNameLessThan);
551    else
552       std::sort(fileList->begin(), fileList->end(), SerieHelper::FileNameGreaterThan);
553
554    return true;
555 }
556
557 //-----------------------------------------------------------------------------
558 // Print
559 /**
560  * \brief   Canonical printer.
561  */
562 void SerieHelper::Print(std::ostream &os, std::string const &indent)
563 {
564    // For all the Coherent File lists of the gdcm::Serie
565    CoherentFileListmap::iterator itl = CoherentFileListHT.begin();
566    if ( itl == CoherentFileListHT.end() )
567    {
568       gdcmWarningMacro( "No Coherent File list found" );
569       return;
570    }
571    while (itl != CoherentFileListHT.end())
572    { 
573       os << "Serie UID :[" << itl->first << "]" << std::endl;
574
575       // For all the files of a Coherent File list
576       for (FileList::iterator it =  (itl->second)->begin();
577                                   it != (itl->second)->end(); 
578                                 ++it)
579       {
580          os << indent << " --- " << (*it)->GetFileName() << std::endl;
581       }
582       ++itl;
583    }
584 }
585
586 //-----------------------------------------------------------------------------
587 } // end namespace gdcm