]> Creatis software - gdcm.git/blob - src/gdcmSerieHelper.cxx
the 'FileIdentifier' is now tokenizable on %%%.
[gdcm.git] / src / gdcmSerieHelper.cxx
1 /*=========================================================================
2                                                                                 
3   Program:   gdcm
4   Module:    $RCSfile: gdcmSerieHelper.cxx,v $
5   Language:  C++
6   Date:      $Date: 2006/01/31 11:29:41 $
7   Version:   $Revision: 1.45 $
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 <map>
29 #include <algorithm>
30 #include <stdio.h>  //for sscanf
31
32 namespace gdcm 
33 {
34 //-----------------------------------------------------------------------------
35
36 //-----------------------------------------------------------------------------
37 // Constructor / Destructor
38 /**
39  * \brief   Constructor from a given SerieHelper
40  */
41 SerieHelper::SerieHelper()
42 {
43    m_UseSeriesDetails = false;
44    ClearAll();
45    UserLessThanFunction = 0;
46    DirectOrder = true;
47 }
48
49 /**
50  * \brief   Canonical destructor.
51  */
52 SerieHelper::~SerieHelper()
53 {
54    ClearAll();
55 }
56
57 /**
58  * \brief  Preventively, clear everything at constructor time.
59  *         ( use it at destructor time.)
60  */
61 void SerieHelper::ClearAll()
62 {
63    // For all the 'Single SerieUID' Filesets that may already exist 
64    FileList *l = GetFirstSingleSerieUIDFileSet();
65    while (l)
66    { 
67       // For all the gdcm::File of a File set
68       for (gdcm::FileList::iterator it  = l->begin();
69                                     it != l->end(); 
70                                   ++it)
71       {
72          (*it)->Delete(); // remove each entry
73       }
74       l->clear();
75       delete l;     // remove the container
76       l = GetNextSingleSerieUIDFileSet();
77    }
78 }
79
80 //-----------------------------------------------------------------------------
81
82 //-----------------------------------------------------------------------------
83
84 // Public
85 /**
86  * \brief add a gdcm::File to the Fileset 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 = File::New();
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 defined a set of rules for the DICOM file
101       // he is looking for.
102       // Make sure the file corresponds to his set of rules:
103
104       std::string s;
105       for(SerieExRestrictions::iterator it2 = ExRestrictions.begin();
106           it2 != ExRestrictions.end();
107           ++it2)
108       {
109          const ExRule &r = *it2;
110          s = header->GetEntryString( r.group, r.elem );
111          if ( !Util::CompareDicomString(s, r.value.c_str(), r.op) )
112          {
113            // Argh ! This rule is unmatched; let's just quit
114
115            allrules = 0;
116            break;
117          }
118       }
119
120       if ( allrules ) // all rules are respected:
121       {
122          // Allright! we have a found a DICOM that matches the user expectation. 
123          // Let's add it!
124
125          // 0020 000e UI REL Series Instance UID
126          const std::string &uid = header->GetEntryString(0x0020, 0x000e);
127          // if uid == GDCM_UNFOUND then consistently we should find GDCM_UNFOUND
128          // no need here to do anything special
129
130
131          if ( SingleSerieUIDFileSetHT.count(uid) == 0 )
132          {
133             gdcmDebugMacro(" New Serie UID :[" << uid << "]");
134             // create a std::list in 'uid' position
135             SingleSerieUIDFileSetHT[uid] = new FileList;
136          }
137          // Current Serie UID and DICOM header seems to match; add the file:
138          SingleSerieUIDFileSetHT[uid]->push_back( header );
139       }
140       else
141       {
142          // at least one rule was unmatched we need to deallocate the file:
143          header->Delete();
144       }
145    }
146    else
147    {
148       gdcmWarningMacro("Could not read file: " << filename );
149       header->Delete();
150    }
151 }
152
153 /**
154  * \brief add a gdcm::File to the first (and supposed to be unique) file set
155  *        of the gdcm::SerieHelper.
156  * \warning : this method should be used by aware users only!
157  *           Passing a gdcm::File* has the same effect than passing a file name!
158  * \todo : decide which one is wrong (the method, or the commentary)!
159  *           the following comment doesn't match the method :-(
160  *            User is supposed to know the files he want to deal with
161  *           and consider them they belong to the same Serie
162  *           (even if their Serie UID is different)
163  *           user will probabely OrderFileList() this list (actually, ordering
164  *           user choosen gdm::File is the sole interest of this method)
165  *           Moreover, using vtkGdcmReader::SetCoherentFileList() will avoid
166  *           vtkGdcmReader parsing twice the same files. 
167  *           *no* coherence check is performed, but those specified
168  *           by SerieHelper::AddRestriction()
169  * @param   header gdcm::File* of the file to deal with
170  */
171 void SerieHelper::AddGdcmFile(File *header)
172 {
173       int allrules = 1;
174       // First step the user has defined a set of rules for the DICOM 
175       // he is looking for.
176       // make sure the file correspond to his set of rules:
177       for(SerieRestrictions::iterator it =  Restrictions.begin();
178                                       it != Restrictions.end();
179                                     ++it)
180       {
181          const Rule &r = *it;
182          const std::string s;// = header->GetEntryString( r.first );
183          if ( !Util::DicomStringEqual(s, r.second.c_str()) )
184          {
185            // Argh ! This rule is unmatch let's just quit
186            allrules = 0;
187            break;
188          }
189       }
190       if ( allrules ) // all rules are respected:
191       {
192          // Allright ! we have a found a DICOM that match the user expectation. 
193          // Let's add it !
194
195          const std::string &uid = "0";
196          // Serie UID of the gdcm::File* may be different.
197          // User is supposed to know what he wants
198
199          if ( SingleSerieUIDFileSetHT.count(uid) == 0 )
200          {
201             gdcmDebugMacro(" New Serie UID :[" << uid << "]");
202             // create a std::list in 'uid' position
203             SingleSerieUIDFileSetHT[uid] = new FileList;
204          }
205          // Current Serie UID and DICOM header seems to match; add the file:
206          SingleSerieUIDFileSetHT[uid]->push_back( header );
207       }
208          // Even if a rule was unmatch we don't deallocate the gdcm::File:
209 }
210
211 /**
212  * \brief add a rule for restricting a DICOM file to be in the serie we are
213  * trying to find. For example you can select only the DICOM files from a
214  * directory which would have a particular EchoTime==4.0.
215  * This method is a user level, value is not required to be formatted as a DICOM
216  * string
217  * \todo find a trick to allow user to say if he wants the Rectrictions 
218  *       to be *ored* (and not only *anded*)
219  * @param   key  Target tag we want restrict on a given value
220  * @param value value to be checked to exclude File
221  * @param op  operator we want to use to check
222  */
223 void SerieHelper::AddRestriction(TagKey const &key, 
224                                  std::string const &value, int op)
225 {
226    ExRule r;
227    r.group = key[0];
228    r.elem  = key[1];
229    r.value = value;
230    r.op    = op;
231    ExRestrictions.push_back( r ); 
232 }
233
234 /**
235  * \brief add a rule for restricting a DICOM file to be in the serie we are
236  * trying to find. For example you can select only the DICOM file from a
237  * directory which would have a particular EchoTime==4.0.
238  * This method is a user level, value is not required to be formatted as a DICOM
239  * string
240  * \todo find a trick to allow user if he wants the Rectrictions to be *ored*
241  *       (and not only *anded*)
242  * @param   group tag group number we want restrict on a given value
243  * @param   elem  tag element number we want restrict on a given value 
244  * @param value value to be checked to exclude File
245  * @param op  operator we want to use to check
246  */
247 void SerieHelper::AddRestriction(uint16_t group, uint16_t elem, 
248                                  std::string const &value, int op)
249 {
250    ExRule r;
251    r.group = group;
252    r.elem  = elem;
253    r.value = value;
254    r.op    = op;
255    ExRestrictions.push_back( r ); 
256 }
257
258 /**
259  * \brief add an extra  'SerieDetail' for building a 'Serie Identifier'
260  *        that ensures (hope so) File constistency (Series Instance UID doesn't.
261  * @param   group tag group number we want restrict on a given value
262  * @param   elem  tag element number we want restrict on a given value
263  * @param  convert wether we want 'convertion', to allow further ordering
264  *         e.g : 100 would be *before* 20; 000020.00 vs 00100.00 : OK 
265  */
266 void SerieHelper::AddSeriesDetail(uint16_t group, uint16_t elem, bool convert)
267 {
268    
269    ExDetail d;
270    d.group   = group;
271    d.elem    = elem;
272    d.convert = convert;
273    ExDetails.push_back( d ); 
274 }
275 /**
276  * \brief Sets the root Directory
277  * @param   dir Name of the directory to deal with
278  * @param recursive whether we want explore recursively the root Directory
279  */
280 void SerieHelper::SetDirectory(std::string const &dir, bool recursive)
281 {
282    DirList dirList(dir, recursive); // OS specific
283   
284    DirListType filenames_list = dirList.GetFilenames();
285    for( DirListType::const_iterator it = filenames_list.begin(); 
286         it != filenames_list.end(); ++it)
287    {
288       AddFileName( *it );
289    }
290 }
291
292 /**
293  * \brief Sorts the given Fileset
294  * \warning This could be implemented in a 'Strategy Pattern' approach
295  *          But as I don't know how to do it, I leave it this way
296  *          BTW, this is also a Strategy, I don't know this is 
297  *          the best approach :)
298  */
299 void SerieHelper::OrderFileList(FileList *fileSet)
300 {
301
302    if ( SerieHelper::UserLessThanFunction )
303    {
304       UserOrdering( fileSet );
305       return; 
306    }
307    else if ( ImagePositionPatientOrdering( fileSet ) )
308    {
309       return ;
310    }
311    else if ( ImageNumberOrdering(fileSet ) )
312    {
313       return ;
314    }
315    else  
316    {
317       FileNameOrdering(fileSet );
318    }
319 }
320
321 /**
322  * \brief Elementary coherence checking of the files with the same Serie UID
323  * Only sizes and pixel type are checked right now ...
324  */ 
325 bool SerieHelper::IsCoherent(FileList *fileSet)
326 {
327    if(fileSet->size() == 1)
328    return true;
329
330    FileList::const_iterator it = fileSet->begin();
331
332    int nX =               (*it)->GetXSize();
333    int nY =               (*it)->GetYSize();
334    int pixelSize =        (*it)->GetPixelSize();
335    bool signedPixelData = (*it)->IsSignedPixelData();
336    it ++;
337    for ( ;
338          it != fileSet->end();
339        ++it)
340    {
341       if ( (*it)->GetXSize() != nX )
342          return false;
343       if ( (*it)->GetYSize() != nY )
344          return false;
345       if ( (*it)->GetPixelSize() != pixelSize )
346          return false;
347       if ( (*it)->IsSignedPixelData() != signedPixelData )
348          return false;
349       // probabely more is to be checked (?)      
350    }
351    return true;
352 }
353
354 #ifndef GDCM_LEGACY_REMOVE
355
356 FileList *SerieHelper::GetFirstCoherentFileList()
357 {
358    ItFileSetHt = SingleSerieUIDFileSetHT.begin();
359    if ( ItFileSetHt != SingleSerieUIDFileSetHT.end() )
360       return ItFileSetHt->second;
361    return NULL;
362 }
363
364
365 FileList *SerieHelper::GetNextCoherentFileList()
366 {
367    gdcmAssertMacro (ItFileSetHt != SingleSerieUIDFileSetHT.end());
368   
369    ++ItFileSetHt;
370    if ( ItFileSetHt != SingleSerieUIDFileSetHT.end() )
371       return ItFileSetHt->second;
372    return NULL;
373 }
374
375
376 FileList *SerieHelper::GetCoherentFileList(std::string SerieUID)
377 {
378    if ( SingleSerieUIDFileSetHT.count(SerieUID) == 0 )
379       return 0;     
380    return SingleSerieUIDFileSetHT[SerieUID];
381 }
382 #endif
383
384
385 /**
386  * \brief   Get the first Fileset while visiting the SingleSerieUIDFileSetmap
387  * @return  The first FileList (SingleSerieUIDFileSet) if found, otherwhise 0
388  */
389 FileList *SerieHelper::GetFirstSingleSerieUIDFileSet()
390 {
391    ItFileSetHt = SingleSerieUIDFileSetHT.begin();
392    if ( ItFileSetHt != SingleSerieUIDFileSetHT.end() )
393       return ItFileSetHt->second;
394    return NULL;
395 }
396
397 /**
398  * \brief   Get the next Fileset while visiting the SingleSerieUIDFileSetmap
399  * \note : meaningfull only if GetNextSingleSerieUIDFileSet() already called 
400  * @return  The next FileList (SingleSerieUIDFileSet) if found, otherwhise 0
401  */
402 FileList *SerieHelper::GetNextSingleSerieUIDFileSet()
403 {
404    gdcmAssertMacro (ItFileSetHt != SingleSerieUIDFileSetHT.end());
405   
406    ++ItFileSetHt;
407    if ( ItFileSetHt != SingleSerieUIDFileSetHT.end() )
408       return ItFileSetHt->second;
409    return NULL;
410 }
411
412 /**
413  * \brief   Get the SingleSerieUIDFileSet according to its Serie UID
414  * @param SerieUID SerieUID to retrieve
415  * \return pointer to the FileList (SingleSerieUIDFileSet) if found, otherwhise 0
416  */
417 FileList *SerieHelper::GetSingleSerieUIDFileSet(std::string SerieUID)
418 {
419    if ( SingleSerieUIDFileSetHT.count(SerieUID) == 0 )
420       return 0;     
421    return SingleSerieUIDFileSetHT[SerieUID];
422 }
423
424 /**
425  * \brief   Splits a Single SerieUID Fileset according to the Orientations
426  * @param fileSet File Set to be splitted
427  * \return  std::map of 'Xcoherent' File sets
428  */
429
430 XCoherentFileSetmap SerieHelper::SplitOnOrientation(FileList *fileSet)
431 {
432    XCoherentFileSetmap CoherentFileSet;
433
434    int nb = fileSet->size();
435    if (nb == 0 )
436       return CoherentFileSet;
437    float iop[6];
438
439    std::string strOrient;
440    std::ostringstream ossOrient;   
441    FileList::const_iterator it = fileSet->begin();
442    it ++;
443    for ( ;
444          it != fileSet->end();
445        ++it)
446    {     
447       // Information is in :      
448       // 0020 0037 : Image Orientation (Patient) or
449       // 0020 0035 : Image Orientation (RET)
450
451       // Let's build again the 'cosines' string, to be sure of it's format      
452       (*it)->GetImageOrientationPatient(iop);
453
454       ossOrient << iop[0];      
455       for (int i = 1; i < 6; i++)
456       {
457         ossOrient << "\\";
458         ossOrient << iop[i]; 
459       }      
460       strOrient = ossOrient.str();
461       ossOrient.str("");
462       // FIXME : is it a 'cleaner' way to initialize an ostringstream? 
463
464       if ( CoherentFileSet.count(strOrient) == 0 )
465       {
466          gdcmDebugMacro(" New Orientation :[" << strOrient << "]");
467          // create a File set in 'orientation' position
468          CoherentFileSet[strOrient] = new FileList;
469       }
470       // Current Orientation and DICOM header match; add the file:
471       CoherentFileSet[strOrient]->push_back( (*it) );
472    } 
473    return CoherentFileSet;
474 }
475
476 /**
477  * \brief   Splits a 'Single SerieUID' Fileset according to the Positions
478  * @param fileSet File Set to be splitted
479  * \return  std::map of 'Xcoherent' File sets
480  */
481
482 XCoherentFileSetmap SerieHelper::SplitOnPosition(FileList *fileSet)
483 {
484    XCoherentFileSetmap CoherentFileSet;
485
486    int nb = fileSet->size();
487    if (nb == 0 )
488       return CoherentFileSet;
489    float pos[3];
490    std::string strImPos;  // read on disc
491    std::ostringstream ossPosition;
492    std::string strPosition; // re computed
493    FileList::const_iterator it = fileSet->begin();
494    it ++;
495    for ( ;
496          it != fileSet->end();
497        ++it)
498    {     
499       // Information is in :      
500       // 0020,0032 : Image Position Patient
501       // 0020,0030 : Image Position (RET)
502
503       strImPos = (*it)->GetEntryString(0x0020,0x0032);
504       if ( strImPos == GDCM_UNFOUND)
505       {
506          gdcmWarningMacro( "Unfound Image Position Patient (0020,0032)");
507          strImPos = (*it)->GetEntryString(0x0020,0x0030); // For ACR-NEMA images
508          if ( strImPos == GDCM_UNFOUND )
509          {
510             gdcmWarningMacro( "Unfound Image Position (RET) (0020,0030)");
511             // User wants to split on the 'Position'
512             // No 'Position' info found.
513             // We return an empty Htable !
514             return CoherentFileSet;
515          }  
516       }
517
518       if ( sscanf( strImPos.c_str(), "%f \\%f \\%f ", 
519                                               &pos[0], &pos[1], &pos[2]) != 3 )
520       {
521             gdcmWarningMacro( "Wrong number for Position : ["
522                        << strImPos << "]" );
523              return CoherentFileSet;
524       }
525
526       // Let's build again the 'position' string, to be sure of it's format      
527
528       ossPosition << pos[0];      
529       for (int i = 1; i < 3; i++)
530       {
531         ossPosition << "\\";
532         ossPosition << pos[i]; 
533       }      
534       strPosition = ossPosition.str();
535       ossPosition.str("");
536             
537       if ( CoherentFileSet.count(strPosition) == 0 )
538       {
539          gdcmDebugMacro(" New Position :[" << strPosition << "]");
540          // create a File set in 'position' position
541          CoherentFileSet[strPosition] = new FileList;
542       }
543       // Current Position and DICOM header match; add the file:
544       CoherentFileSet[strPosition]->push_back( (*it) );
545    }   
546    return CoherentFileSet;
547 }
548
549 /**
550  * \brief   Splits a 'Single SerieUID' File set Coherent according to the
551  *          value of a given Tag
552  * @param fileSet File Set to be splitted
553  * @param   group  group number of the target Element
554  * @param   elem element number of the target Element
555  * \return  std::map of 'Xcoherent' File sets
556  */
557
558 XCoherentFileSetmap SerieHelper::SplitOnTagValue(FileList *fileSet, 
559                                                uint16_t group, uint16_t elem)
560 {
561    XCoherentFileSetmap CoherentFileSet;
562
563    int nb = fileSet->size();
564    if (nb == 0 )
565       return CoherentFileSet;
566
567    std::string strTagValue;  // read on disc
568
569    FileList::const_iterator it = fileSet->begin();
570    it ++;
571    for ( ;
572          it != fileSet->end();
573        ++it)
574    {     
575       // Information is in :      
576       // 0020,0032 : Image Position Patient
577       // 0020,0030 : Image Position (RET)
578
579       strTagValue = (*it)->GetEntryString(group,elem);
580       
581       if ( CoherentFileSet.count(strTagValue) == 0 )
582       {
583          gdcmDebugMacro(" New Tag Value :[" << strTagValue << "]");
584          // create a File set in 'position' position
585          CoherentFileSet[strTagValue] = new FileList;
586       }
587       // Current Tag value and DICOM header match; add the file:
588       CoherentFileSet[strTagValue]->push_back( (*it) );
589    }
590    return CoherentFileSet;
591 }
592
593 //-----------------------------------------------------------------------------
594 // Protected
595
596 //-----------------------------------------------------------------------------
597 // Private
598 /**
599  * \brief sorts the images, according to their Patient Position.
600  *
601  *  We may order, considering :
602  *   -# Image Position Patient
603  *   -# Image Number
604  *   -# file name
605  *   -# More to come :-)
606  * \note : FileList = std::vector<File* >
607  * @param fileList Coherent File list (same Serie UID) to sort
608  * @return false only if the header is bugged !
609  */
610 bool SerieHelper::ImagePositionPatientOrdering( FileList *fileList )
611 //based on Jolinda Smith's algorithm
612 {
613    //iop is calculated based on the file file
614    float cosines[6];
615    double normal[3];
616    double ipp[3];
617    double dist;
618    double min = 0, max = 0;
619    bool first = true;
620
621    std::multimap<double,File *> distmultimap;
622    // Use a multimap to sort the distances from 0,0,0
623    for ( FileList::const_iterator 
624          it = fileList->begin();
625          it != fileList->end(); ++it )
626    {
627       if ( first ) 
628       {
629          (*it)->GetImageOrientationPatient( cosines );
630       
631          // You only have to do this once for all slices in the volume. Next, 
632          // for each slice, calculate the distance along the slice normal 
633          // using the IPP ("Image Position Patient") tag.
634          // ("dist" is initialized to zero before reading the first slice) :
635          normal[0] = cosines[1]*cosines[5] - cosines[2]*cosines[4];
636          normal[1] = cosines[2]*cosines[3] - cosines[0]*cosines[5];
637          normal[2] = cosines[0]*cosines[4] - cosines[1]*cosines[3];
638   
639          ipp[0] = (*it)->GetXOrigin();
640          ipp[1] = (*it)->GetYOrigin();
641          ipp[2] = (*it)->GetZOrigin();
642
643          dist = 0;
644          for ( int i = 0; i < 3; ++i )
645          {
646             dist += normal[i]*ipp[i];
647          }
648     
649          distmultimap.insert(std::pair<const double,File *>(dist, *it));
650
651          max = min = dist;
652          first = false;
653       }
654       else 
655       {
656          ipp[0] = (*it)->GetXOrigin();
657          ipp[1] = (*it)->GetYOrigin();
658          ipp[2] = (*it)->GetZOrigin();
659   
660          dist = 0;
661          for ( int i = 0; i < 3; ++i )
662          {
663             dist += normal[i]*ipp[i];
664          }
665
666          distmultimap.insert(std::pair<const double,File *>(dist, *it));
667
668          min = (min < dist) ? min : dist;
669          max = (max > dist) ? max : dist;
670       }
671
672    }
673
674    // Find out if min/max are coherent
675    if ( min == max )
676    {
677      gdcmWarningMacro("Looks like all images have the exact same image position"
678                       << ". No PositionPatientOrdering sort performed" );
679      return false;
680    }
681
682    // Check to see if image shares a common position
683     bool ok = true;
684     for (std::multimap<double, File *>::iterator it2 = distmultimap.begin();
685                                                  it2 != distmultimap.end();
686                                                  ++it2)
687     {
688        if (distmultimap.count((*it2).first) != 1)
689        {
690           gdcmErrorMacro("File: "
691                << ((*it2).second->GetFileName())
692                << " Distance: "
693                << (*it2).first
694                << " position is not unique");
695           ok = false;
696        } 
697     }
698     if (!ok)
699     {
700        return false;
701     }
702   
703    fileList->clear();  // doesn't delete list elements, only nodes
704
705    if (DirectOrder)
706    {  
707       for (std::multimap<double, File *>::iterator it3 = distmultimap.begin();
708                                                    it3 != distmultimap.end();
709                                                  ++it3)
710       {
711          fileList->push_back( (*it3).second );
712       }
713    }
714    else // user asked for reverse order
715    {
716       std::multimap<double, File *>::const_iterator it4;
717       it4 = distmultimap.end();
718       do
719       {
720          it4--;
721          fileList->push_back( (*it4).second );
722       } while (it4 != distmultimap.begin() );
723    } 
724
725    distmultimap.clear();
726
727    return true;
728 }
729
730 bool SerieHelper::ImageNumberLessThan(File *file1, File *file2)
731 {
732   return file1->GetImageNumber() < file2->GetImageNumber();
733 }
734
735 bool SerieHelper::ImageNumberGreaterThan(File *file1, File *file2)
736 {
737   return file1->GetImageNumber() > file2->GetImageNumber();
738 }
739
740 /**
741  * \brief sorts the images, according to their Image Number
742  * \note Works only on bona fide files  (i.e image number is a character string
743  *                                      corresponding to an integer)
744  *             within a bona fide serie (i.e image numbers are consecutive)
745  * @param fileList  File set (same Serie UID) to sort 
746  * @return false if non bona fide stuff encountered
747  */
748 bool SerieHelper::ImageNumberOrdering(FileList *fileList) 
749 {
750    int min, max, pos;
751    int n = fileList->size();
752
753    FileList::const_iterator it = fileList->begin();
754    min = max = (*it)->GetImageNumber();
755
756    for (; it != fileList->end(); ++it, ++n)
757    {
758       pos = (*it)->GetImageNumber();
759       min = (min < pos) ? min : pos;
760       max = (max > pos) ? max : pos;
761    }
762
763    // Find out if image numbers are coherent (consecutive)
764    if ( min == max || max == 0 || max >= (n+min) )
765    {
766       gdcmWarningMacro( " 'Image numbers' not coherent. "
767                         << " No ImageNumberOrdering sort performed.");
768       return false;
769    }
770    if (DirectOrder) 
771         Sort(fileList,SerieHelper::ImageNumberLessThan);
772 //      std::sort(fileList->begin(), fileList->end(), 
773 //                                          SerieHelper::ImageNumberLessThan );
774    else
775         Sort(fileList,SerieHelper::ImageNumberGreaterThan);
776 //      std::sort(fileList->begin(), fileList->end(),
777 //                                          SerieHelper::ImageNumberGreaterThan );
778
779    return true;
780 }
781
782 bool SerieHelper::FileNameLessThan(File *file1, File *file2)
783 {
784    return file1->GetFileName() < file2->GetFileName();
785 }
786
787 bool SerieHelper::FileNameGreaterThan(File *file1, File *file2)
788 {
789    return file1->GetFileName() > file2->GetFileName();
790 }
791 /**
792  * \brief sorts the images, according to their File Name
793  * @param fileList Coherent File list (same Serie UID) to sort
794  * @return false only if the header is bugged !
795  */
796 bool SerieHelper::FileNameOrdering(FileList *fileList)
797 {
798    if (DirectOrder) 
799         Sort(fileList,SerieHelper::FileNameLessThan);
800 //      std::sort(fileList->begin(), fileList->end(), 
801 //                                       SerieHelper::FileNameLessThan);
802    else
803         Sort(fileList,SerieHelper::FileNameGreaterThan);   
804 //      std::sort(fileList->begin(), fileList->end(), 
805 //                                       SerieHelper::FileNameGreaterThan);
806
807    return true;
808 }
809
810 /**
811  * \brief sorts the images, according to user supplied function
812  * @param fileList Coherent File list (same Serie UID) to sort
813  * @return false only if the header is bugged !
814  */
815 bool SerieHelper::UserOrdering(FileList *fileList)
816 {
817         Sort(fileList,SerieHelper::UserLessThanFunction);   
818 //   std::sort(fileList->begin(), fileList->end(), 
819 //                                             SerieHelper::UserLessThanFunction);
820    if (!DirectOrder) 
821    {
822       std::reverse(fileList->begin(), fileList->end());
823    }
824    return true;
825 }
826
827 /**
828  * \brief Heuristics to *try* to build a Serie Identifier that would ensure
829  *        all the images are coherent.
830  *
831  * By default, uses the SeriesUID.  If UseSeriesDetails(true) has been called,
832  *         then additional identifying information is used.
833  *  We allow user to add his own critierions, using AddSeriesDetail
834  *        (he knows more than we do about his images!)
835  *        ex : in tagging series, the only pertnent tag is
836  *        0018|1312 [In-plane Phase Encoding Direction] value : ROW/COLUMN
837  * @param inFile gdcm::File we want to build a Serie Identifier for.
838  * @return the SeriesIdentifier
839  */
840 std::string SerieHelper::CreateUniqueSeriesIdentifier( File *inFile )
841 {
842    if( inFile->IsReadable() )
843    {
844      // 0020 000e UI REL Series Instance UID
845     std::string uid =  inFile->GetEntryString (0x0020, 0x000e);
846     std::string id = uid.c_str();
847     if(m_UseSeriesDetails)
848     {
849     // If the user requests, additional information can be appended
850     // to the SeriesUID to further differentiate volumes in the DICOM
851     // objects being processed.
852  
853    // 0020 0011 Series Number
854    // A scout scan prior to a CT volume scan can share the same
855    // SeriesUID, but they will sometimes have a different Series Number
856        std::string sNum = inFile->GetEntryString(0x0020, 0x0011);
857        if( sNum == gdcm::GDCM_UNFOUND )
858        {
859            sNum = "";
860        }
861  // 0018 0024 Sequence Name
862  // For T1-map and phase-contrast MRA, the different flip angles and
863  // directions are only distinguished by the Sequence Name
864          std::string sName = inFile->GetEntryString(0x0018, 0x0024);
865          if( sName == gdcm::GDCM_UNFOUND )
866          {
867            sName = "";
868          }
869  
870   // You can think on checking Image Orientation (0020,0037), as well.
871   
872
873   // 0018 0050 Slice Thickness
874   // On some CT systems, scout scans and subsequence volume scans will
875   // have the same SeriesUID and Series Number - YET the slice
876   // thickness will differ from the scout slice and the volume slices.
877          std::string sThick = inFile->GetEntryString (0x0018, 0x0050);
878          if( sThick == gdcm::GDCM_UNFOUND )
879          {
880            sThick = "";
881          }
882   // 0028 0010 Rows
883   // If the 2D images in a sequence don't have the same number of rows,
884   // then it is difficult to reconstruct them into a 3D volume.
885          std::string sRows = inFile->GetEntryString (0x0028, 0x0010);
886          if( sRows == gdcm::GDCM_UNFOUND )
887          {
888            sRows = "";
889          }
890   // 0028 0011 Columns
891   // If the 2D images in a sequence don't have the same number of columns,
892   // then it is difficult to reconstruct them into a 3D volume.
893          std::string sColumns = inFile->GetEntryString (0x0028, 0x0011);
894          if( sColumns == gdcm::GDCM_UNFOUND )
895          {
896            sColumns = "";
897          }
898
899   // Concat the new info
900          std::string num = sNum.c_str();
901          num += sName.c_str();
902          num += sThick.c_str();
903          num += sRows.c_str();
904          num += sColumns.c_str();
905  
906   // Add a loop, here, to deal with any extra user supplied tag.
907   //      We allow user to add his own critierions 
908   //      (he knows more than we do about his images!)
909   //      ex : in tagging series, the only pertinent tag is
910   //          0018|1312 [In-plane Phase Encoding Direction] values : ROW/COLUMN
911   
912       std::string s; 
913       for(SeriesExDetails::iterator it2 = ExDetails.begin();
914           it2 != ExDetails.end();
915           ++it2)
916       {
917          const ExDetail &r = *it2;
918          s = inFile->GetEntryString( r.group, r.elem );      
919          num += s.c_str();
920       }
921    
922   // Append the new info to the SeriesUID
923          id += ".";
924          id += num.c_str();
925        }
926    
927   // Eliminate non-alphanum characters, including whitespace...
928   // that may have been introduced by concats.
929        for(unsigned int i=0; i<id.size(); i++)
930        {
931          while(i<id.size()
932                && !( id[i] == '.' || id[i] == '-' || id[i] == '_'
933                     || (id[i] >= 'a' && id[i] <= 'z')
934                     || (id[i] >= '0' && id[i] <= '9')
935                     || (id[i] >= 'A' && id[i] <= 'Z')))
936          {
937            id.erase(i, 1);
938          }
939        }
940        return id;
941      }
942      else // Could not open inFile
943      {
944        gdcmWarningMacro("Could not parse series info.");
945        std::string id = gdcm::GDCM_UNFOUND;
946        return id;
947      }
948 }
949
950 /**
951  * \brief Allow user to build is own File Identifier (to be able to sort
952  *        temporal series just as he wants)
953  *        Criterions will be set with AddSeriesDetail.
954  *        (Maybe the method should be moved elsewhere 
955  *       -File class? FileHelper class?-
956  * @return FileIdentifier (Tokenizable on '%%%'. Hope it's enough !)
957  */
958 std::string SerieHelper::CreateUserDefinedFileIdentifier( File * inFile )
959 {
960   //     Deal with all user supplied tags.
961   //      (user knows more than we do about his images!)
962   
963    double converted;
964    std::string id;
965    std::string s; 
966    char charConverted[17]; 
967    
968    for(SeriesExDetails::iterator it2 = ExDetails.begin();
969       it2 != ExDetails.end();
970       ++it2)
971    {
972       const ExDetail &r = *it2;
973       s = inFile->GetEntryString( r.group, r.elem );
974       
975       // User is allowed to ask 'convertion', to allow further ordering
976       // e.g : 100 would be *before* 20; 000020.00 vs 00100.00 : OK
977       if (it2->convert)
978       {
979          if ( s != GDCM_UNFOUND) // Don't convert unfound fields !
980          {
981             converted = atof(s.c_str());
982             // probabely something much more complicated is possible, 
983             // using C++ features
984             /// \todo check the behaviour when there are >0 and <0 numbers
985             sprintf(charConverted, "%016.6f",converted);
986             s = charConverted;
987          }
988       }
989       // Eliminate non-alphanum characters, including whitespace.
990       for(unsigned int i=0; i<s.size(); i++)
991       {
992          while(i<s.size()
993             && !( s[i] == '.' || s[i] == '%'
994                     || (s[i] >= 'a' && s[i] <= 'z')
995                     || (s[i] >= '0' && s[i] <= '9')
996                     || (s[i] >= 'A' && s[i] <= 'Z')))
997          {
998             s.erase(i, 1);
999          }
1000       }
1001       
1002       id += s.c_str();
1003       id += "%%%"; // make the FileIdentifier Tokenizable
1004    }
1005    
1006    return id;             
1007 }
1008 //-----------------------------------------------------------------------------
1009 // Print
1010 /**
1011  * \brief   Canonical printer.
1012  */
1013 void SerieHelper::Print(std::ostream &os, std::string const &indent)
1014 {
1015    // For all the Coherent File lists of the gdcm::Serie
1016    SingleSerieUIDFileSetmap::iterator itl = SingleSerieUIDFileSetHT.begin();
1017    if ( itl == SingleSerieUIDFileSetHT.end() )
1018    {
1019       gdcmWarningMacro( "No SingleSerieUID File set found" );
1020       return;
1021    }
1022    while (itl != SingleSerieUIDFileSetHT.end())
1023    { 
1024       os << "Serie UID :[" << itl->first << "]" << std::endl;
1025
1026       // For all the files of a SingleSerieUID File set
1027       for (FileList::iterator it =  (itl->second)->begin();
1028                                   it != (itl->second)->end(); 
1029                                 ++it)
1030       {
1031          os << indent << " --- " << (*it)->GetFileName() << std::endl;
1032       }
1033       ++itl;
1034    }
1035 }
1036
1037 //-----------------------------------------------------------------------------
1038 // Sort
1039 /**
1040  * \brief   Sort FileList.
1041  */
1042 void SerieHelper::Sort(FileList *fileList, bool (*pt2Func)( File *file1, File *file2) )
1043 {
1044  std::sort(fileList->begin(), fileList->end(), pt2Func );
1045 }
1046
1047 //-----------------------------------------------------------------------------
1048 } // end namespace gdcm