]> Creatis software - gdcm.git/blob - src/gdcmSerieHelper.cxx
19f82fe3e6588ab1784657cad76111030b296bd8
[gdcm.git] / src / gdcmSerieHelper.cxx
1 /*=========================================================================
2                                                                                 
3   Program:   gdcm
4   Module:    $RCSfile: gdcmSerieHelper.cxx,v $
5   Language:  C++
6   Date:      $Date: 2005/07/30 18:13:24 $
7   Version:   $Revision: 1.17 $
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 that may already exist within 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; // remove entry
50       }
51       l->clear();
52       delete l;     // remove the list
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; // remove entry
73       }
74       l->clear();
75       delete l;  // remove the list
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 Elementary coherence checking of the files with the same Serie UID
301  * Only sizes and pixel type are checked right now ...
302  */ 
303 bool SerieHelper::IsCoherent(FileList *coherentFileList)
304 {
305    if(coherentFileList->size() == 1)
306    return true;
307
308    FileList::const_iterator it = coherentFileList->begin();
309
310    int nX = (*it)->GetXSize();
311    int nY = (*it)->GetYSize();
312    int pixelSize = (*it)->GetPixelSize();
313
314    it ++;
315    for ( ;
316          it != coherentFileList->end();
317        ++it)
318    {
319       if ( (*it)->GetXSize() != nX )
320          return false;
321       if ( (*it)->GetYSize() != nY )
322          return false;
323       if ( (*it)->GetPixelSize() != pixelSize )
324          return false;
325       // probabely more is to be checked (?)
326    }
327    return true;
328 }
329 /**
330  * \brief   Get the first List while visiting the CoherentFileListHT
331  * @return  The first FileList if found, otherwhise NULL
332  */
333 FileList *SerieHelper::GetFirstCoherentFileList()
334 {
335    ItListHt = CoherentFileListHT.begin();
336    if ( ItListHt != CoherentFileListHT.end() )
337       return ItListHt->second;
338    return NULL;
339 }
340
341 /**
342  * \brief   Get the next List while visiting the CoherentFileListHT
343  * \note : meaningfull only if GetFirstCoherentFileList() already called
344  * @return  The next FileList if found, otherwhise NULL
345  */
346 FileList *SerieHelper::GetNextCoherentFileList()
347 {
348    gdcmAssertMacro (ItListHt != CoherentFileListHT.end());
349   
350    ++ItListHt;
351    if ( ItListHt != CoherentFileListHT.end() )
352       return ItListHt->second;
353    return NULL;
354 }
355
356 /**
357  * \brief   Get the Coherent Files list according to its Serie UID
358  * @param SerieUID SerieUID
359  * \return  pointer to the Coherent Files list if found, otherwhise NULL
360  */
361 FileList *SerieHelper::GetCoherentFileList(std::string SerieUID)
362 {
363    if ( CoherentFileListHT.count(SerieUID) == 0 )
364       return 0;     
365    return CoherentFileListHT[SerieUID];
366 }
367
368 //-----------------------------------------------------------------------------
369 // Protected
370
371 //-----------------------------------------------------------------------------
372 // Private
373 /**
374  * \brief sorts the images, according to their Patient Position
375  *  We may order, considering :
376  *   -# Image Position Patient
377  *   -# Image Number
378  *   -# More to come :-)
379  * WARNING : FileList = std::vector<File* >
380  * @param fileList Coherent File list (same Serie UID) to sort
381  * @return false only if the header is bugged !
382  */
383 bool SerieHelper::ImagePositionPatientOrdering( FileList *fileList )
384 //based on Jolinda Smith's algorithm
385 {
386    //iop is calculated based on the file file
387    float cosines[6];
388    float normal[3];
389    float ipp[3];
390    float dist;
391    float min = 0, max = 0;
392    bool first = true;
393    int n=0;
394    std::vector<float> distlist;
395
396    //!\todo rewrite this for loop.
397    for ( FileList::const_iterator 
398          it = fileList->begin();
399          it != fileList->end(); ++it )
400    {
401       if ( first ) 
402       {
403          (*it)->GetImageOrientationPatient( cosines );
404       
405          // You only have to do this once for all slices in the volume. Next, 
406          // for each slice, calculate the distance along the slice normal 
407          // using the IPP tag ("dist" is initialized to zero before reading 
408          // the first slice) :
409          normal[0] = cosines[1]*cosines[5] - cosines[2]*cosines[4];
410          normal[1] = cosines[2]*cosines[3] - cosines[0]*cosines[5];
411          normal[2] = cosines[0]*cosines[4] - cosines[1]*cosines[3];
412   
413          ipp[0] = (*it)->GetXOrigin();
414          ipp[1] = (*it)->GetYOrigin();
415          ipp[2] = (*it)->GetZOrigin();
416
417          dist = 0;
418          for ( int i = 0; i < 3; ++i )
419          {
420             dist += normal[i]*ipp[i];
421          }
422     
423          distlist.push_back( dist );
424
425          max = min = dist;
426          first = false;
427       }
428       else 
429       {
430          ipp[0] = (*it)->GetXOrigin();
431          ipp[1] = (*it)->GetYOrigin();
432          ipp[2] = (*it)->GetZOrigin();
433   
434          dist = 0;
435          for ( int i = 0; i < 3; ++i )
436          {
437             dist += normal[i]*ipp[i];
438          }
439
440          distlist.push_back( dist );
441
442          min = (min < dist) ? min : dist;
443          max = (max > dist) ? max : dist;
444       }
445       ++n;
446    }
447
448    // Then I order the slices according to the value "dist". Finally, once
449    // I've read in all the slices, I calculate the z-spacing as the difference
450    // between the "dist" values for the first two slices.
451    FileVector CoherentFileVector(n);
452    // CoherentFileVector.reserve( n );
453    CoherentFileVector.resize( n );
454    // gdcmAssertMacro( CoherentFileVector.capacity() >= n );
455
456    // Find out if min/max are coherent
457    if ( min == max )
458      {
459      gdcmWarningMacro( "Looks like all images have the exact same image position."
460                        << "No PositionPatientOrdering sort performed" );
461      return false;
462      }
463
464    float step = (max - min)/(n - 1);
465    int pos;
466    n = 0;
467     
468    //VC++ don't understand what scope is !! it -> it2
469    for (FileList::const_iterator it2  = fileList->begin();
470         it2 != fileList->end(); ++it2, ++n)
471    {
472       //2*n sort algo !!
473       //Assumption: all files are present (no one missing)
474       pos = (int)( fabs( (distlist[n]-min)/step) + .5 );
475
476       // a Dicom 'Serie' may contain scout views
477       // and images may have differents directions
478       // -> More than one may have the same 'pos'
479       // Sorting has then NO meaning !
480       if (CoherentFileVector[pos]==NULL)
481          CoherentFileVector[pos] = *it2;
482       else
483       {
484          gdcmWarningMacro( "At least 2 files with same position. No PositionPatientOrdering sort performed");
485          return false;
486       }
487    }
488
489    fileList->clear();  // doesn't delete list elements, only nodes
490
491    if (DirectOrder)
492    {  
493       //VC++ don't understand what scope is !! it -> it3
494       for (FileVector::const_iterator it3  = CoherentFileVector.begin();
495            it3 != CoherentFileVector.end(); ++it3)
496       {
497          fileList->push_back( *it3 );
498       }
499    }
500    else // user asked for reverse order
501    {
502       FileVector::const_iterator it4;
503       it4 = CoherentFileVector.end();
504       do
505       {
506          it4--;
507          fileList->push_back( *it4 );
508       } while (it4 != CoherentFileVector.begin() );
509    } 
510
511    distlist.clear();
512    CoherentFileVector.clear();
513
514    return true;
515 }
516
517 bool SerieHelper::ImageNumberLessThan(File *file1, File *file2)
518 {
519   return file1->GetImageNumber() < file2->GetImageNumber();
520 }
521
522 bool SerieHelper::ImageNumberGreaterThan(File *file1, File *file2)
523 {
524   return file1->GetImageNumber() > file2->GetImageNumber();
525 }
526 /**
527  * \brief sorts the images, according to their Image Number
528  * \note Works only on bona fide files  (i.e image number is a character string
529  *                                      corresponding to an integer)
530  *             within a bona fide serie (i.e image numbers are consecutive)
531  * @param fileList Coherent File list (same Serie UID) to sort 
532  * @return false if non bona fide stuff encountered
533  */
534 bool SerieHelper::ImageNumberOrdering(FileList *fileList) 
535 {
536    int min, max, pos;
537    int n = fileList->size();
538
539    FileList::const_iterator it = fileList->begin();
540    min = max = (*it)->GetImageNumber();
541
542    for (; it != fileList->end(); ++it, ++n)
543    {
544       pos = (*it)->GetImageNumber();
545       min = (min < pos) ? min : pos;
546       max = (max > pos) ? max : pos;
547    }
548
549    // Find out if image numbers are coherent (consecutive)
550    if ( min == max || max == 0 || max >= (n+min) )
551    {
552       gdcmWarningMacro( " 'Image numbers' not coherent. No ImageNumberOrdering sort performed.");
553       return false;
554    }
555    if (DirectOrder) 
556       std::sort(fileList->begin(), fileList->end(), SerieHelper::ImageNumberLessThan );
557    else
558       std::sort(fileList->begin(), fileList->end(), SerieHelper::ImageNumberGreaterThan );
559
560    return true;
561 }
562
563 bool SerieHelper::FileNameLessThan(File *file1, File *file2)
564 {
565   return file1->GetFileName() < file2->GetFileName();
566 }
567
568 bool SerieHelper::FileNameGreaterThan(File *file1, File *file2)
569 {
570   return file1->GetFileName() > file2->GetFileName();
571 }
572 /**
573  * \brief sorts the images, according to their File Name
574  * @param fileList Coherent File list (same Serie UID) to sort
575  * @return false only if the header is bugged !
576  */
577 bool SerieHelper::FileNameOrdering(FileList *fileList)
578 {
579    if (DirectOrder) 
580       std::sort(fileList->begin(), fileList->end(), SerieHelper::FileNameLessThan);
581    else
582       std::sort(fileList->begin(), fileList->end(), SerieHelper::FileNameGreaterThan);
583
584    return true;
585 }
586
587 //-----------------------------------------------------------------------------
588 // Print
589 /**
590  * \brief   Canonical printer.
591  */
592 void SerieHelper::Print(std::ostream &os, std::string const &indent)
593 {
594    // For all the Coherent File lists of the gdcm::Serie
595    CoherentFileListmap::iterator itl = CoherentFileListHT.begin();
596    if ( itl == CoherentFileListHT.end() )
597    {
598       gdcmWarningMacro( "No Coherent File list found" );
599       return;
600    }
601    while (itl != CoherentFileListHT.end())
602    { 
603       os << "Serie UID :[" << itl->first << "]" << std::endl;
604
605       // For all the files of a Coherent File list
606       for (FileList::iterator it =  (itl->second)->begin();
607                                   it != (itl->second)->end(); 
608                                 ++it)
609       {
610          os << indent << " --- " << (*it)->GetFileName() << std::endl;
611       }
612       ++itl;
613    }
614 }
615
616 //-----------------------------------------------------------------------------
617 } // end namespace gdcm