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