]> Creatis software - gdcm.git/blob - src/gdcmSerieHelper.cxx
Extend 'Restriction' syntax :
[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 05:00:15 $
7   Version:   $Revision: 1.14 $
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 : 
249  */
250 void SerieHelper::AddRestriction(TagKey const &key, std::string const &value)
251 {
252    Rule r;
253    r.first = key;
254    r.second = value;
255    Restrictions.push_back( r ); 
256 }
257 #endif
258
259 /**
260  * \brief Sets the root Directory
261  * @param   dir Name of the directory to deal with
262  * @param recursive whether we want explore recursively the Directory
263  */
264 void SerieHelper::SetDirectory(std::string const &dir, bool recursive)
265 {
266    DirList dirList(dir, recursive); // OS specific
267   
268    DirListType filenames_list = dirList.GetFilenames();
269    for( DirListType::const_iterator it = filenames_list.begin(); 
270         it != filenames_list.end(); ++it)
271    {
272       AddFileName( *it );
273    }
274 }
275
276 /**
277  * \brief Sorts the given File List
278  * \warning This could be implemented in a 'Strategy Pattern' approach
279  *          But as I don't know how to do it, I leave it this way
280  *          BTW, this is also a Strategy, I don't know this is the best approach :)
281  */
282 void SerieHelper::OrderFileList(FileList *coherentFileList)
283 {
284    if ( ImagePositionPatientOrdering( coherentFileList ) )
285    {
286       return ;
287    }
288    else if ( ImageNumberOrdering(coherentFileList ) )
289    {
290       return ;
291    }
292    else  
293    {
294       FileNameOrdering(coherentFileList );
295    }
296 }
297
298 /**
299  * \brief   Get the first List while visiting the CoherentFileListHT
300  * @return  The first FileList if found, otherwhise NULL
301  */
302 FileList *SerieHelper::GetFirstCoherentFileList()
303 {
304    ItListHt = CoherentFileListHT.begin();
305    if ( ItListHt != CoherentFileListHT.end() )
306       return ItListHt->second;
307    return NULL;
308 }
309
310 /**
311  * \brief   Get the next List while visiting the CoherentFileListHT
312  * \note : meaningfull only if GetFirstCoherentFileList() already called
313  * @return  The next FileList if found, otherwhise NULL
314  */
315 FileList *SerieHelper::GetNextCoherentFileList()
316 {
317    gdcmAssertMacro (ItListHt != CoherentFileListHT.end());
318   
319    ++ItListHt;
320    if ( ItListHt != CoherentFileListHT.end() )
321       return ItListHt->second;
322    return NULL;
323 }
324
325 /**
326  * \brief   Get the Coherent Files list according to its Serie UID
327  * @param SerieUID SerieUID
328  * \return  pointer to the Coherent Files list if found, otherwhise NULL
329  */
330 FileList *SerieHelper::GetCoherentFileList(std::string SerieUID)
331 {
332    if ( CoherentFileListHT.count(SerieUID) == 0 )
333       return 0;     
334    return CoherentFileListHT[SerieUID];
335 }
336
337 //-----------------------------------------------------------------------------
338 // Protected
339
340 //-----------------------------------------------------------------------------
341 // Private
342 /**
343  * \brief sorts the images, according to their Patient Position
344  *  We may order, considering :
345  *   -# Image Position Patient
346  *   -# Image Number
347  *   -# More to come :-)
348  * WARNING : FileList = std::vector<File* >
349  * @param fileList Coherent File list (same Serie UID) to sort
350  * @return false only if the header is bugged !
351  */
352 bool SerieHelper::ImagePositionPatientOrdering( FileList *fileList )
353 //based on Jolinda Smith's algorithm
354 {
355    //iop is calculated based on the file file
356    float cosines[6];
357    float normal[3];
358    float ipp[3];
359    float dist;
360    float min = 0, max = 0;
361    bool first = true;
362    int n=0;
363    std::vector<float> distlist;
364
365    //!\todo rewrite this for loop.
366    for ( FileList::const_iterator 
367          it = fileList->begin();
368          it != fileList->end(); ++it )
369    {
370       if ( first ) 
371       {
372          (*it)->GetImageOrientationPatient( cosines );
373       
374          // You only have to do this once for all slices in the volume. Next, 
375          // for each slice, calculate the distance along the slice normal 
376          // using the IPP tag ("dist" is initialized to zero before reading 
377          // the first slice) :
378          normal[0] = cosines[1]*cosines[5] - cosines[2]*cosines[4];
379          normal[1] = cosines[2]*cosines[3] - cosines[0]*cosines[5];
380          normal[2] = cosines[0]*cosines[4] - cosines[1]*cosines[3];
381   
382          ipp[0] = (*it)->GetXOrigin();
383          ipp[1] = (*it)->GetYOrigin();
384          ipp[2] = (*it)->GetZOrigin();
385
386          dist = 0;
387          for ( int i = 0; i < 3; ++i )
388          {
389             dist += normal[i]*ipp[i];
390          }
391     
392          distlist.push_back( dist );
393
394          max = min = dist;
395          first = false;
396       }
397       else 
398       {
399          ipp[0] = (*it)->GetXOrigin();
400          ipp[1] = (*it)->GetYOrigin();
401          ipp[2] = (*it)->GetZOrigin();
402   
403          dist = 0;
404          for ( int i = 0; i < 3; ++i )
405          {
406             dist += normal[i]*ipp[i];
407          }
408
409          distlist.push_back( dist );
410
411          min = (min < dist) ? min : dist;
412          max = (max > dist) ? max : dist;
413       }
414       ++n;
415    }
416
417    // Then I order the slices according to the value "dist". Finally, once
418    // I've read in all the slices, I calculate the z-spacing as the difference
419    // between the "dist" values for the first two slices.
420    FileVector CoherentFileVector(n);
421    // CoherentFileVector.reserve( n );
422    CoherentFileVector.resize( n );
423    // gdcmAssertMacro( CoherentFileVector.capacity() >= n );
424
425    // Find out if min/max are coherent
426    if ( min == max )
427      {
428      gdcmWarningMacro( "Looks like all images have the exact same image position."
429                        << "No PositionPatientOrdering sort performed" );
430      return false;
431      }
432
433    float step = (max - min)/(n - 1);
434    int pos;
435    n = 0;
436     
437    //VC++ don't understand what scope is !! it -> it2
438    for (FileList::const_iterator it2  = fileList->begin();
439         it2 != fileList->end(); ++it2, ++n)
440    {
441       //2*n sort algo !!
442       //Assumption: all files are present (no one missing)
443       pos = (int)( fabs( (distlist[n]-min)/step) + .5 );
444
445       // a Dicom 'Serie' may contain scout views
446       // and images may have differents directions
447       // -> More than one may have the same 'pos'
448       // Sorting has then NO meaning !
449       if (CoherentFileVector[pos]==NULL)
450          CoherentFileVector[pos] = *it2;
451       else
452       {
453          gdcmWarningMacro( "At least 2 files with same position. No PositionPatientOrdering sort performed");
454          return false;
455       }
456    }
457
458    fileList->clear();  // doesn't delete list elements, only nodes
459   
460    //VC++ don't understand what scope is !! it -> it3
461    for (FileVector::const_iterator it3  = CoherentFileVector.begin();
462         it3 != CoherentFileVector.end(); ++it3)
463    {
464       fileList->push_back( *it3 );
465    }
466
467    distlist.clear();
468    CoherentFileVector.clear();
469
470    return true;
471 }
472
473 bool SerieHelper::ImageNumberLessThan(File *file1, File *file2)
474 {
475   return file1->GetImageNumber() < file2->GetImageNumber();
476 }
477
478 /**
479  * \brief sorts the images, according to their Image Number
480  * \note Works only on bona fide files  (i.e image number is a character string
481  *                                      corresponding to an integer)
482  *             within a bona fide serie (i.e image numbers are consecutive)
483  * @param fileList Coherent File list (same Serie UID) to sort 
484  * @return false if non bona fide stuff encountered
485  */
486 bool SerieHelper::ImageNumberOrdering(FileList *fileList) 
487 {
488    int min, max, pos;
489    int n = fileList->size();
490
491    FileList::const_iterator it = fileList->begin();
492    min = max = (*it)->GetImageNumber();
493
494    for (; it != fileList->end(); ++it, ++n)
495    {
496       pos = (*it)->GetImageNumber();
497       min = (min < pos) ? min : pos;
498       max = (max > pos) ? max : pos;
499    }
500
501    // Find out if image numbers are coherent (consecutive)
502    if ( min == max || max == 0 || max >= (n+min) )
503    {
504       gdcmWarningMacro( " 'Image numbers' not coherent. No ImageNumberOrdering sort performed.");
505       return false;
506    }
507    std::sort(fileList->begin(), fileList->end(), SerieHelper::ImageNumberLessThan );
508
509    return true;
510 }
511
512 bool SerieHelper::FileNameLessThan(File *file1, File *file2)
513 {
514   return file1->GetFileName() < file2->GetFileName();
515 }
516
517 /**
518  * \brief sorts the images, according to their File Name
519  * @param fileList Coherent File list (same Serie UID) to sort
520  * @return false only if the header is bugged !
521  */
522 bool SerieHelper::FileNameOrdering(FileList *fileList)
523 {
524    std::sort(fileList->begin(), fileList->end(), SerieHelper::FileNameLessThan);
525    return true;
526 }
527
528 //-----------------------------------------------------------------------------
529 // Print
530 /**
531  * \brief   Canonical printer.
532  */
533 void SerieHelper::Print(std::ostream &os, std::string const &indent)
534 {
535    // For all the Coherent File lists of the gdcm::Serie
536    CoherentFileListmap::iterator itl = CoherentFileListHT.begin();
537    if ( itl == CoherentFileListHT.end() )
538    {
539       gdcmWarningMacro( "No Coherent File list found" );
540       return;
541    }
542    while (itl != CoherentFileListHT.end())
543    { 
544       os << "Serie UID :[" << itl->first << "]" << std::endl;
545
546       // For all the files of a Coherent File list
547       for (FileList::iterator it =  (itl->second)->begin();
548                                   it != (itl->second)->end(); 
549                                 ++it)
550       {
551          os << indent << " --- " << (*it)->GetFileName() << std::endl;
552       }
553       ++itl;
554    }
555 }
556
557 //-----------------------------------------------------------------------------
558 } // end namespace gdcm