]> Creatis software - gdcm.git/blob - src/gdcmSerieHelper.cxx
New method SerieHelper::AddSeriesDetail() to allow user to specifiy
[gdcm.git] / src / gdcmSerieHelper.cxx
1 /*=========================================================================
2                                                                                 
3   Program:   gdcm
4   Module:    $RCSfile: gdcmSerieHelper.cxx,v $
5   Language:  C++
6   Date:      $Date: 2005/12/16 13:48:46 $
7   Version:   $Revision: 1.40 $
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 if he wants the Rectrictions to be *ored*
218  *       (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  */
264 void SerieHelper::AddSeriesDetail(uint16_t group, uint16_t elem)
265 {
266    m_UseSeriesDetails = true;
267    
268    ExDetail d;
269    d.group = group;
270    d.elem  = elem;
271    ExDetails.push_back( d ); 
272 }
273 /**
274  * \brief Sets the root Directory
275  * @param   dir Name of the directory to deal with
276  * @param recursive whether we want explore recursively the root Directory
277  */
278 void SerieHelper::SetDirectory(std::string const &dir, bool recursive)
279 {
280    DirList dirList(dir, recursive); // OS specific
281   
282    DirListType filenames_list = dirList.GetFilenames();
283    for( DirListType::const_iterator it = filenames_list.begin(); 
284         it != filenames_list.end(); ++it)
285    {
286       AddFileName( *it );
287    }
288 }
289
290 /**
291  * \brief Sorts the given Fileset
292  * \warning This could be implemented in a 'Strategy Pattern' approach
293  *          But as I don't know how to do it, I leave it this way
294  *          BTW, this is also a Strategy, I don't know this is 
295  *          the best approach :)
296  */
297 void SerieHelper::OrderFileList(FileList *fileSet)
298 {
299
300    if ( SerieHelper::UserLessThanFunction )
301    {
302       UserOrdering( fileSet );
303       return; 
304    }
305    else if ( ImagePositionPatientOrdering( fileSet ) )
306    {
307       return ;
308    }
309    else if ( ImageNumberOrdering(fileSet ) )
310    {
311       return ;
312    }
313    else  
314    {
315       FileNameOrdering(fileSet );
316    }
317 }
318
319 /**
320  * \brief Elementary coherence checking of the files with the same Serie UID
321  * Only sizes and pixel type are checked right now ...
322  */ 
323 bool SerieHelper::IsCoherent(FileList *fileSet)
324 {
325    if(fileSet->size() == 1)
326    return true;
327
328    FileList::const_iterator it = fileSet->begin();
329
330    int nX =               (*it)->GetXSize();
331    int nY =               (*it)->GetYSize();
332    int pixelSize =        (*it)->GetPixelSize();
333    bool signedPixelData = (*it)->IsSignedPixelData();
334    it ++;
335    for ( ;
336          it != fileSet->end();
337        ++it)
338    {
339       if ( (*it)->GetXSize() != nX )
340          return false;
341       if ( (*it)->GetYSize() != nY )
342          return false;
343       if ( (*it)->GetPixelSize() != pixelSize )
344          return false;
345       if ( (*it)->IsSignedPixelData() != signedPixelData )
346          return false;
347       // probabely more is to be checked (?)      
348    }
349    return true;
350 }
351
352 #ifndef GDCM_LEGACY_REMOVE
353
354 FileList *SerieHelper::GetFirstCoherentFileList()
355 {
356    ItFileSetHt = SingleSerieUIDFileSetHT.begin();
357    if ( ItFileSetHt != SingleSerieUIDFileSetHT.end() )
358       return ItFileSetHt->second;
359    return NULL;
360 }
361
362
363 FileList *SerieHelper::GetNextCoherentFileList()
364 {
365    gdcmAssertMacro (ItFileSetHt != SingleSerieUIDFileSetHT.end());
366   
367    ++ItFileSetHt;
368    if ( ItFileSetHt != SingleSerieUIDFileSetHT.end() )
369       return ItFileSetHt->second;
370    return NULL;
371 }
372
373
374 FileList *SerieHelper::GetCoherentFileList(std::string SerieUID)
375 {
376    if ( SingleSerieUIDFileSetHT.count(SerieUID) == 0 )
377       return 0;     
378    return SingleSerieUIDFileSetHT[SerieUID];
379 }
380 #endif
381
382
383 /**
384  * \brief   Get the first Fileset while visiting the SingleSerieUIDFileSetmap
385  * @return  The first FileList (SingleSerieUIDFileSet) if found, otherwhise 0
386  */
387 FileList *SerieHelper::GetFirstSingleSerieUIDFileSet()
388 {
389    ItFileSetHt = SingleSerieUIDFileSetHT.begin();
390    if ( ItFileSetHt != SingleSerieUIDFileSetHT.end() )
391       return ItFileSetHt->second;
392    return NULL;
393 }
394
395 /**
396  * \brief   Get the next Fileset while visiting the SingleSerieUIDFileSetmap
397  * \note : meaningfull only if GetNextSingleSerieUIDFileSet() already called 
398  * @return  The next FileList (SingleSerieUIDFileSet) if found, otherwhise 0
399  */
400 FileList *SerieHelper::GetNextSingleSerieUIDFileSet()
401 {
402    gdcmAssertMacro (ItFileSetHt != SingleSerieUIDFileSetHT.end());
403   
404    ++ItFileSetHt;
405    if ( ItFileSetHt != SingleSerieUIDFileSetHT.end() )
406       return ItFileSetHt->second;
407    return NULL;
408 }
409
410 /**
411  * \brief   Get the SingleSerieUIDFileSet according to its Serie UID
412  * @param SerieUID SerieUID to retrieve
413  * \return pointer to the FileList (SingleSerieUIDFileSet) if found, otherwhise 0
414  */
415 FileList *SerieHelper::GetSingleSerieUIDFileSet(std::string SerieUID)
416 {
417    if ( SingleSerieUIDFileSetHT.count(SerieUID) == 0 )
418       return 0;     
419    return SingleSerieUIDFileSetHT[SerieUID];
420 }
421
422 /**
423  * \brief   Splits a Single SerieUID Fileset according to the Orientations
424  * @param fileSet File Set to be splitted
425  * \return  std::map of 'Xcoherent' File sets
426  */
427
428 XCoherentFileSetmap SerieHelper::SplitOnOrientation(FileList *fileSet)
429 {
430    XCoherentFileSetmap CoherentFileSet;
431
432    int nb = fileSet->size();
433    if (nb == 0 )
434       return CoherentFileSet;
435    float iop[6];
436
437    std::string strOrient;
438    std::ostringstream ossOrient;   
439    FileList::const_iterator it = fileSet->begin();
440    it ++;
441    for ( ;
442          it != fileSet->end();
443        ++it)
444    {     
445       // Information is in :      
446       // 0020 0037 : Image Orientation (Patient) or
447       // 0020 0035 : Image Orientation (RET)
448
449       // Let's build again the 'cosines' string, to be sure of it's format      
450       (*it)->GetImageOrientationPatient(iop);
451
452       ossOrient << iop[0];      
453       for (int i = 1; i < 6; i++)
454       {
455         ossOrient << "\\";
456         ossOrient << iop[i]; 
457       }      
458       strOrient = ossOrient.str();
459       ossOrient.str("");
460       // FIXME : is it a 'cleaner' way to initialize an ostringstream? 
461
462       if ( CoherentFileSet.count(strOrient) == 0 )
463       {
464          gdcmDebugMacro(" New Orientation :[" << strOrient << "]");
465          // create a File set in 'orientation' position
466          CoherentFileSet[strOrient] = new FileList;
467       }
468       // Current Orientation and DICOM header match; add the file:
469       CoherentFileSet[strOrient]->push_back( (*it) );
470    } 
471    return CoherentFileSet;
472 }
473
474 /**
475  * \brief   Splits a 'Single SerieUID' Fileset according to the Positions
476  * @param fileSet File Set to be splitted
477  * \return  std::map of 'Xcoherent' File sets
478  */
479
480 XCoherentFileSetmap SerieHelper::SplitOnPosition(FileList *fileSet)
481 {
482    XCoherentFileSetmap CoherentFileSet;
483
484    int nb = fileSet->size();
485    if (nb == 0 )
486       return CoherentFileSet;
487    float pos[3];
488    std::string strImPos;  // read on disc
489    std::ostringstream ossPosition;
490    std::string strPosition; // re computed
491    FileList::const_iterator it = fileSet->begin();
492    it ++;
493    for ( ;
494          it != fileSet->end();
495        ++it)
496    {     
497       // Information is in :      
498       // 0020,0032 : Image Position Patient
499       // 0020,0030 : Image Position (RET)
500
501       strImPos = (*it)->GetEntryString(0x0020,0x0032);
502       if ( strImPos == GDCM_UNFOUND)
503       {
504          gdcmWarningMacro( "Unfound Image Position Patient (0020,0032)");
505          strImPos = (*it)->GetEntryString(0x0020,0x0030); // For ACR-NEMA images
506          if ( strImPos == GDCM_UNFOUND )
507          {
508             gdcmWarningMacro( "Unfound Image Position (RET) (0020,0030)");
509             // User wants to split on the 'Position'
510             // No 'Position' info found.
511             // We return an empty Htable !
512             return CoherentFileSet;
513          }  
514       }
515
516       if ( sscanf( strImPos.c_str(), "%f \\%f \\%f ", 
517                                               &pos[0], &pos[1], &pos[2]) != 3 )
518       {
519             gdcmWarningMacro( "Wrong number for Position : ["
520                        << strImPos << "]" );
521              return CoherentFileSet;
522       }
523
524       // Let's build again the 'position' string, to be sure of it's format      
525
526       ossPosition << pos[0];      
527       for (int i = 1; i < 3; i++)
528       {
529         ossPosition << "\\";
530         ossPosition << pos[i]; 
531       }      
532       strPosition = ossPosition.str();
533       ossPosition.str("");
534             
535       if ( CoherentFileSet.count(strPosition) == 0 )
536       {
537          gdcmDebugMacro(" New Position :[" << strPosition << "]");
538          // create a File set in 'position' position
539          CoherentFileSet[strPosition] = new FileList;
540       }
541       // Current Position and DICOM header match; add the file:
542       CoherentFileSet[strPosition]->push_back( (*it) );
543    }   
544    return CoherentFileSet;
545 }
546
547 /**
548  * \brief   Splits a 'Single SerieUID' File set Coherent according to the
549  *          value of a given Tag
550  * @param fileSet File Set to be splitted
551  * @param   group  group number of the target Element
552  * @param   elem element number of the target Element
553  * \return  std::map of 'Xcoherent' File sets
554  */
555
556 XCoherentFileSetmap SerieHelper::SplitOnTagValue(FileList *fileSet, 
557                                                uint16_t group, uint16_t elem)
558 {
559    XCoherentFileSetmap CoherentFileSet;
560
561    int nb = fileSet->size();
562    if (nb == 0 )
563       return CoherentFileSet;
564
565    std::string strTagValue;  // read on disc
566
567    FileList::const_iterator it = fileSet->begin();
568    it ++;
569    for ( ;
570          it != fileSet->end();
571        ++it)
572    {     
573       // Information is in :      
574       // 0020,0032 : Image Position Patient
575       // 0020,0030 : Image Position (RET)
576
577       strTagValue = (*it)->GetEntryString(group,elem);
578       
579       if ( CoherentFileSet.count(strTagValue) == 0 )
580       {
581          gdcmDebugMacro(" New Tag Value :[" << strTagValue << "]");
582          // create a File set in 'position' position
583          CoherentFileSet[strTagValue] = new FileList;
584       }
585       // Current Tag value and DICOM header match; add the file:
586       CoherentFileSet[strTagValue]->push_back( (*it) );
587    }
588    return CoherentFileSet;
589 }
590
591 //-----------------------------------------------------------------------------
592 // Protected
593
594 //-----------------------------------------------------------------------------
595 // Private
596 /**
597  * \brief sorts the images, according to their Patient Position.
598  *
599  *  We may order, considering :
600  *   -# Image Position Patient
601  *   -# Image Number
602  *   -# file name
603  *   -# More to come :-)
604  * \note : FileList = std::vector<File* >
605  * @param fileList Coherent File list (same Serie UID) to sort
606  * @return false only if the header is bugged !
607  */
608 bool SerieHelper::ImagePositionPatientOrdering( FileList *fileList )
609 //based on Jolinda Smith's algorithm
610 {
611    //iop is calculated based on the file file
612    float cosines[6];
613    double normal[3];
614    double ipp[3];
615    double dist;
616    double min = 0, max = 0;
617    bool first = true;
618
619    std::multimap<double,File *> distmultimap;
620    // Use a multimap to sort the distances from 0,0,0
621    for ( FileList::const_iterator 
622          it = fileList->begin();
623          it != fileList->end(); ++it )
624    {
625       if ( first ) 
626       {
627          (*it)->GetImageOrientationPatient( cosines );
628       
629          // You only have to do this once for all slices in the volume. Next, 
630          // for each slice, calculate the distance along the slice normal 
631          // using the IPP ("Image Position Patient") tag.
632          // ("dist" is initialized to zero before reading the first slice) :
633          normal[0] = cosines[1]*cosines[5] - cosines[2]*cosines[4];
634          normal[1] = cosines[2]*cosines[3] - cosines[0]*cosines[5];
635          normal[2] = cosines[0]*cosines[4] - cosines[1]*cosines[3];
636   
637          ipp[0] = (*it)->GetXOrigin();
638          ipp[1] = (*it)->GetYOrigin();
639          ipp[2] = (*it)->GetZOrigin();
640
641          dist = 0;
642          for ( int i = 0; i < 3; ++i )
643          {
644             dist += normal[i]*ipp[i];
645          }
646     
647          distmultimap.insert(std::pair<const double,File *>(dist, *it));
648
649          max = min = dist;
650          first = false;
651       }
652       else 
653       {
654          ipp[0] = (*it)->GetXOrigin();
655          ipp[1] = (*it)->GetYOrigin();
656          ipp[2] = (*it)->GetZOrigin();
657   
658          dist = 0;
659          for ( int i = 0; i < 3; ++i )
660          {
661             dist += normal[i]*ipp[i];
662          }
663
664          distmultimap.insert(std::pair<const double,File *>(dist, *it));
665
666          min = (min < dist) ? min : dist;
667          max = (max > dist) ? max : dist;
668       }
669
670    }
671
672    // Find out if min/max are coherent
673    if ( min == max )
674    {
675      gdcmWarningMacro("Looks like all images have the exact same image position"
676                       << ". No PositionPatientOrdering sort performed" );
677      return false;
678    }
679
680    // Check to see if image shares a common position
681     bool ok = true;
682     for (std::multimap<double, File *>::iterator it2 = distmultimap.begin();
683                                                  it2 != distmultimap.end();
684                                                  ++it2)
685     {
686        if (distmultimap.count((*it2).first) != 1)
687        {
688           gdcmErrorMacro("File: "
689                << ((*it2).second->GetFileName())
690                << " Distance: "
691                << (*it2).first
692                << " position is not unique");
693           ok = false;
694        } 
695     }
696     if (!ok)
697     {
698        return false;
699     }
700   
701    fileList->clear();  // doesn't delete list elements, only nodes
702
703    if (DirectOrder)
704    {  
705       for (std::multimap<double, File *>::iterator it3 = distmultimap.begin();
706                                                    it3 != distmultimap.end();
707                                                  ++it3)
708       {
709          fileList->push_back( (*it3).second );
710       }
711    }
712    else // user asked for reverse order
713    {
714       std::multimap<double, File *>::const_iterator it4;
715       it4 = distmultimap.end();
716       do
717       {
718          it4--;
719          fileList->push_back( (*it4).second );
720       } while (it4 != distmultimap.begin() );
721    } 
722
723    distmultimap.clear();
724
725    return true;
726 }
727
728 bool SerieHelper::ImageNumberLessThan(File *file1, File *file2)
729 {
730   return file1->GetImageNumber() < file2->GetImageNumber();
731 }
732
733 bool SerieHelper::ImageNumberGreaterThan(File *file1, File *file2)
734 {
735   return file1->GetImageNumber() > file2->GetImageNumber();
736 }
737
738 /**
739  * \brief sorts the images, according to their Image Number
740  * \note Works only on bona fide files  (i.e image number is a character string
741  *                                      corresponding to an integer)
742  *             within a bona fide serie (i.e image numbers are consecutive)
743  * @param fileList  File set (same Serie UID) to sort 
744  * @return false if non bona fide stuff encountered
745  */
746 bool SerieHelper::ImageNumberOrdering(FileList *fileList) 
747 {
748    int min, max, pos;
749    int n = fileList->size();
750
751    FileList::const_iterator it = fileList->begin();
752    min = max = (*it)->GetImageNumber();
753
754    for (; it != fileList->end(); ++it, ++n)
755    {
756       pos = (*it)->GetImageNumber();
757       min = (min < pos) ? min : pos;
758       max = (max > pos) ? max : pos;
759    }
760
761    // Find out if image numbers are coherent (consecutive)
762    if ( min == max || max == 0 || max >= (n+min) )
763    {
764       gdcmWarningMacro( " 'Image numbers' not coherent. "
765                         << " No ImageNumberOrdering sort performed.");
766       return false;
767    }
768    if (DirectOrder) 
769       std::sort(fileList->begin(), fileList->end(), 
770                                           SerieHelper::ImageNumberLessThan );
771    else
772       std::sort(fileList->begin(), fileList->end(),
773                                           SerieHelper::ImageNumberGreaterThan );
774
775    return true;
776 }
777
778 bool SerieHelper::FileNameLessThan(File *file1, File *file2)
779 {
780    return file1->GetFileName() < file2->GetFileName();
781 }
782
783 bool SerieHelper::FileNameGreaterThan(File *file1, File *file2)
784 {
785    return file1->GetFileName() > file2->GetFileName();
786 }
787 /**
788  * \brief sorts the images, according to their File Name
789  * @param fileList Coherent File list (same Serie UID) to sort
790  * @return false only if the header is bugged !
791  */
792 bool SerieHelper::FileNameOrdering(FileList *fileList)
793 {
794    if (DirectOrder) 
795       std::sort(fileList->begin(), fileList->end(), 
796                                        SerieHelper::FileNameLessThan);
797    else
798       std::sort(fileList->begin(), fileList->end(), 
799                                        SerieHelper::FileNameGreaterThan);
800
801    return true;
802 }
803
804 /**
805  * \brief sorts the images, according to user supplied function
806  * @param fileList Coherent File list (same Serie UID) to sort
807  * @return false only if the header is bugged !
808  */
809 bool SerieHelper::UserOrdering(FileList *fileList)
810 {
811    std::sort(fileList->begin(), fileList->end(), 
812                                              SerieHelper::UserLessThanFunction);
813    if (!DirectOrder) 
814    {
815       std::reverse(fileList->begin(), fileList->end());
816    }
817    return true;
818 }
819
820 /**
821  * \brief Heuritics to *try* to build a Serie Identifier that would ensure
822  *        all the images are coherent.
823  *
824  *  We allow user to add his own critierions, using AddSerieDetail
825  *        (he knows more than we do about his images!)
826  *        ex : in tagging series, the only pertnent tag is
827  *        0018|1312 [In-plane Phase Encoding Direction] value : ROW/COLUMN
828  * @param inFile gdcm::File we want to build a Serie Identifier for.
829  * @return the SeriesIdentifier
830  */
831 std::string SerieHelper::CreateUniqueSeriesIdentifier( File *inFile )
832 {
833    if( inFile->IsReadable() )
834    {
835      // 0020 000e UI REL Series Instance UID
836     std::string uid =  inFile->GetEntryString (0x0020, 0x000e);
837     std::string id = uid.c_str();
838     if(m_UseSeriesDetails)
839     {
840     // If the user requests, additional information can be appended
841     // to the SeriesUID to further differentiate volumes in the DICOM
842     // objects being processed.
843  
844    // 0020 0011 Series Number
845    // A scout scan prior to a CT volume scan can share the same
846    // SeriesUID, but they will sometimes have a different Series Number
847        std::string sNum = inFile->GetEntryString(0x0020, 0x0011);
848        if( sNum == gdcm::GDCM_UNFOUND )
849        {
850            sNum = "";
851        }
852  // 0018 0024 Sequence Name
853  // For T1-map and phase-contrast MRA, the different flip angles and
854  // directions are only distinguished by the Sequence Name
855          std::string sName = inFile->GetEntryString(0x0018, 0x0024);
856          if( sName == gdcm::GDCM_UNFOUND )
857          {
858            sName = "";
859          }
860   // 0018 0050 Slice Thickness
861   // On some CT systems, scout scans and subsequence volume scans will
862   // have the same SeriesUID and Series Number - YET the slice
863   // thickness will differ from the scout slice and the volume slices.
864          std::string sThick = inFile->GetEntryString (0x0018, 0x0050);
865          if( sThick == gdcm::GDCM_UNFOUND )
866          {
867            sThick = "";
868          }
869   // 0028 0010 Rows
870   // If the 2D images in a sequence don't have the same number of rows,
871   // then it is difficult to reconstruct them into a 3D volume.
872          std::string sRows = inFile->GetEntryString (0x0028, 0x0010);
873          if( sRows == gdcm::GDCM_UNFOUND )
874          {
875            sRows = "";
876          }
877   // 0028 0011 Columns
878   // If the 2D images in a sequence don't have the same number of columns,
879   // then it is difficult to reconstruct them into a 3D volume.
880          std::string sColumns = inFile->GetEntryString (0x0028, 0x0011);
881          if( sColumns == gdcm::GDCM_UNFOUND )
882          {
883            sColumns = "";
884          }
885
886   // Concat the new info
887          std::string num = sNum.c_str();
888          num += sName.c_str();
889          num += sThick.c_str();
890          num += sRows.c_str();
891          num += sColumns.c_str();
892  
893   // Add a loop, here, to deal with any extra user supplied tag.
894   //      We allow user to add his own critierions 
895   //      (he knows more than we do about his images!)
896   //      ex : in tagging series, the only pertinent tag is
897   //          0018|1312 [In-plane Phase Encoding Direction] value : ROW/COLUMN
898   
899       std::string s;  
900       for(SeriesExDetails::iterator it2 = ExDetails.begin();
901           it2 != ExDetails.end();
902           ++it2)
903       {
904          const ExDetail &r = *it2;
905          s = inFile->GetEntryString( r.group, r.elem );        
906          num += s.c_str();
907       }
908    
909   // Append the new info to the SeriesUID
910          id += ".";
911          id += num.c_str();
912        }
913    
914   // Eliminate non-alnum characters, including whitespace...
915   // that may have been introduced by concats.
916        for(unsigned int i=0; i<id.size(); i++)
917        {
918          while(i<id.size()
919                && !( id[i] == '.' || id[i] == '-'
920                     || (id[i] >= 'a' && id[i] <= 'z')
921                     || (id[i] >= '0' && id[i] <= '9')
922                     || (id[i] >= 'A' && id[i] <= 'Z')))
923          {
924            id.erase(i, 1);
925          }
926        }
927        return id;
928      }
929      else // Could not open inFile
930      {
931        gdcmWarningMacro("Could not parse series info.");
932        std::string id = gdcm::GDCM_UNFOUND;
933        return id;
934      }
935 }
936
937
938 //-----------------------------------------------------------------------------
939 // Print
940 /**
941  * \brief   Canonical printer.
942  */
943 void SerieHelper::Print(std::ostream &os, std::string const &indent)
944 {
945    // For all the Coherent File lists of the gdcm::Serie
946    SingleSerieUIDFileSetmap::iterator itl = SingleSerieUIDFileSetHT.begin();
947    if ( itl == SingleSerieUIDFileSetHT.end() )
948    {
949       gdcmWarningMacro( "No SingleSerieUID File set found" );
950       return;
951    }
952    while (itl != SingleSerieUIDFileSetHT.end())
953    { 
954       os << "Serie UID :[" << itl->first << "]" << std::endl;
955
956       // For all the files of a SingleSerieUID File set
957       for (FileList::iterator it =  (itl->second)->begin();
958                                   it != (itl->second)->end(); 
959                                 ++it)
960       {
961          os << indent << " --- " << (*it)->GetFileName() << std::endl;
962       }
963       ++itl;
964    }
965 }
966
967 //-----------------------------------------------------------------------------
968 } // end namespace gdcm