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