]> Creatis software - gdcm.git/blob - Example/SplitIntoDirectories.cxx
remove special chars from copied file names
[gdcm.git] / Example / SplitIntoDirectories.cxx
1 /*=========================================================================
2
3   Program:   gdcm
4   Module:    $RCSfile: SplitIntoDirectories.cxx,v $
5   Language:  C++
6   Date:      $Date: 2011/04/22 13:50:09 $
7   Version:   $Revision: 1.9 $
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 #include "gdcmDocEntry.h"
19 #include "gdcmDicomDir.h"
20 #include "gdcmDicomDirPatient.h"
21 #include "gdcmFile.h"
22 #include "gdcmFileHelper.h"
23 #include "gdcmDirList.h"
24 #include "gdcmDebug.h"
25 #include "gdcmArgMgr.h"
26 #include "gdcmUtil.h"
27 #include "gdcmSerieHelper.h"
28
29 #include <iostream>
30
31 /**
32   * \brief
33   *          - explores recursively the given directory
34   *          - keeps the requested series
35   *          - orders the gdcm-readable found Files
36   *            according to their Patient/Study/Serie/Image characteristics
37   */
38
39 typedef std::map<std::string, GDCM_NAME_SPACE::File*> SortedFiles;
40
41 int main(int argc, char *argv[])
42 {
43    START_USAGE(usage)
44    " \n SplitIntoDirectories :\n                                              ",
45    " - explores recursively the given directory,                              ",
46    " - keeps the requested series / drops the unrequested series              ",
47    " - orders the gdcm-readable found Files according to their                ",
48    "           (0x0010, 0x0010) Patient's Name                                ",
49    "           (0x0020, 0x000d) Study Instance UID                            ",
50    "           (0x0020, 0x000e) Series Instance UID                           ",
51    " - fills a tree-like structure of directories as :                        ",
52    "        - Patient                                                         ",
53    "        -- Study                                                          ",
54    "        --- Serie                                                         ",
55    "                                                                          ",
56    " usage:                                                                   ",
57    " -----                                                                    ",
58    " SplitIntoDirectories                                                     ",
59    "                  dirin=rootDirectoryName                                 ",
60    "                  dirout=outputDirectoryName                              ",
61    "                  {  [keep= list of seriesNumber to process]              ",
62    "                   | [drop= list of seriesNumber to ignore] }             ",
63    "                  [listonly]  [skel] [seriedescr]                         ",
64    "                  [noshadowseq][noshadow][noseq] [verbose] [debug]        ",
65    "                                                                          ",
66    " dirout : will be created if doesn't exist                                ",
67    " keep : if user wants to process a limited number of series               ",
68    "            he gives the list of 'SeriesNumber' (tag 0020|0011)           ",
69    " drop : if user wants to ignore a limited number of series                ",
70    "            he gives the list of 'SeriesNumber' (tag 0020|0011)           ",
71    "        SeriesNumber are short enough to be human readable                ",
72    "        e.g : 1030,1035,1043                                              ",
73    " seriedescr : SerieDescription+SerieNumber use for directory name         ",
74    "              (instead of SeriesInstanceUID)                              ",
75    " skel     : name skeleton eg : patName_1.nema -> skel=patName_            ",
76    " noshadowseq: user doesn't want to load Private Sequences                 ",
77    " noshadow : user doesn't want to load Private groups (odd number)         ",
78    " noseq    : user doesn't want to load Sequences                           ",
79    " verbose  : user wants to run the program in 'verbose mode'               ",
80    " debug    : *developer*  wants to run the program in 'debug mode'         ",
81    FINISH_USAGE
82
83
84    // VERY IMPORTANT :
85    // Respect this order while creating 'UserFileIdentifier'
86    // (mind the order of the 'AddSeriesDetail' !)
87    
88    enum Index
89    {
90       IND_PatientName,
91       IND_StudyInstanceUID,
92       IND_SerieInstanceUID,
93       IND_SerieDescription,
94       IND_SerieNumber,
95       IND_FileName
96    };
97       
98    std::cout << "... inside " << argv[0] << std::endl;
99    
100    // ----- Initialize Arguments Manager ------
101
102    GDCM_NAME_SPACE::ArgMgr *am = new GDCM_NAME_SPACE::ArgMgr(argc, argv);
103   
104    if (argc == 1 || am->ArgMgrDefined("usage")) 
105    {
106       am->ArgMgrUsage(usage); // Display 'usage'
107       delete am;
108       return 0;
109    }
110
111    const char *dirNamein;
112    dirNamein  = am->ArgMgrGetString("dirin",".");
113
114    const char *dirNameout;   
115    dirNameout  = am->ArgMgrGetString("dirout",".");
116    
117    int loadMode = GDCM_NAME_SPACE::LD_ALL;
118    if ( am->ArgMgrDefined("noshadowseq") )
119       loadMode |= GDCM_NAME_SPACE::LD_NOSHADOWSEQ;
120    else
121    {
122    if ( am->ArgMgrDefined("noshadow") )
123          loadMode |= GDCM_NAME_SPACE::LD_NOSHADOW;
124       if ( am->ArgMgrDefined("noseq") )
125          loadMode |= GDCM_NAME_SPACE::LD_NOSEQ;
126    }
127
128    if (am->ArgMgrDefined("debug"))
129       GDCM_NAME_SPACE::Debug::DebugOn();
130
131    bool verbose    = ( 0 != am->ArgMgrDefined("verbose") );
132    bool listonly   = ( 0 != am->ArgMgrDefined("listonly") );
133    bool seriedescr = ( 0 != am->ArgMgrDefined("seriedescr") );
134
135    int nbSeriesToKeep;
136    int *seriesToKeep = am->ArgMgrGetListOfInt("keep", &nbSeriesToKeep);
137    int nbSeriesToDrop;
138    int *seriesToDrop = am->ArgMgrGetListOfInt("drop", &nbSeriesToDrop);
139
140    if ( nbSeriesToKeep!=0 && nbSeriesToDrop!=0)
141    {
142       std::cout << "KEEP and DROP are mutually exclusive !" << std::endl;
143       delete am;
144       return 0;
145    }
146
147    bool hasSkel = ( 0 != am->ArgMgrDefined("hasSkel") );
148    const char *skel;
149    if (hasSkel)
150       skel = am->ArgMgrGetString("skel");
151
152
153    const char *input   = am->ArgMgrGetString("input","DCM");
154    
155    // if unused Param we give up
156    if ( am->ArgMgrPrintUnusedLabels() )
157    {
158       am->ArgMgrUsage(usage);
159       delete am;
160       return 0;
161    }
162    delete am;  // we don't need Argument Manager any longer
163
164    // ----- Begin Processing -----
165    
166      
167    // --> Check supposed-to-be-directory names
168    
169    if ( ! GDCM_NAME_SPACE::DirList::IsDirectory(dirNamein) )
170    {
171       std::cout << "KO : [" << dirNamein << "] is not a Directory."
172                 << std::endl;
173       return 0;
174
175    }
176    else
177    {
178       std::cout << "OK : [" << dirNamein << "] is a Directory." << std::endl;
179    }
180
181    std::string systemCommand;
182
183    std::cout << "Check for output directory :[" << dirNameout << "]."
184              <<std::endl;
185    if ( ! GDCM_NAME_SPACE::DirList::IsDirectory(dirNameout) )    // dirout not found
186    {
187       std::string strDirNameout(dirNameout);          // to please gcc 4
188       systemCommand = "mkdir \"" +strDirNameout + "\"";        // create it!
189       if (verbose)
190          std::cout << systemCommand << std::endl;
191       system (systemCommand.c_str());
192       if ( ! GDCM_NAME_SPACE::DirList::IsDirectory(dirNameout) ) // be sure it worked
193       {
194          std::cout << "KO : not a dir : [" << dirNameout << "] (creation failure ?)" 
195                     << std::endl;
196          return 0;
197       }
198       else
199       {
200         std::cout << "Directory [" << dirNameout << "] created." << std::endl;
201       }
202    }
203    else
204    {
205        std::cout << "Output Directory [" << dirNameout 
206                  << "] already exists; Used as is."
207                  << std::endl;
208    }
209    // --> End of checking supposed-to-be-directory names
210        
211    std::string strDirNamein(dirNamein);
212    // true ; get recursively the list of files
213    GDCM_NAME_SPACE::DirList dirList(strDirNamein, true); 
214    
215    if (listonly)
216    {
217       std::cout << "------------List of found files ------------" << std::endl;
218       dirList.Print();
219       std::cout << std::endl;
220    }
221
222
223 // ======================================= The job starts here =========================
224    
225    GDCM_NAME_SPACE::DirListType fileNames;
226    fileNames = dirList.GetFilenames();
227
228    GDCM_NAME_SPACE::SerieHelper *s;     // Needed to use SerieHelper::AddSeriesDetail()
229    s = GDCM_NAME_SPACE::SerieHelper::New();
230
231    std::string token = "%%%"; // Hope it's enough!
232   
233    GDCM_NAME_SPACE::File *f;
234    std::vector<std::string> tokens;
235    std::vector<std::string> tokensForFileName;
236    
237    if (verbose)
238       std::cout << "------------------Print Break levels-----------------" << std::endl;
239
240    std::string userFileIdentifier;
241    SortedFiles sf;
242
243
244    // VERY IMPORTANT :
245    // While coding the various AddSeriesDetail,
246    // respect the order you choosed in 'enum Index' !
247  
248 /*
249    enum Index
250    {
251       IND_PatientName,
252       IND_StudyInstanceUID,
253       IND_SerieInstanceUID,
254       IND_SerieDescription,
255       IND_SerieNumber,
256       IND_FileName
257    }; 
258 */     
259    s->AddSeriesDetail(0x0010, 0x0010, false); // Patient's Name (false : no convert)
260    
261    // You may prefer 0020 0010  Study ID
262    // use :
263    // s->AddSeriesDetail(0x0020, 0x0010, true); 
264    // Avoid using 0008 0020 Study Date, 
265    // since you may have more than one study, for a given Patient, at a given Date!
266    // or the field may be empty!   
267    s->AddSeriesDetail(0x0020, 0x000d, false); // Study Instance UID (false : no convert)
268
269    // You may prefer 0020 0011 Series Number
270    // use :
271    // s->AddSeriesDetail(0x0020, 0x0011, true);    
272    s->AddSeriesDetail(0x0020, 0x000e, false); // Series Instance UID (false : no convert)
273
274    s->AddSeriesDetail(0x0008, 0x103e, false); // Serie Description
275    s->AddSeriesDetail(0x0020, 0x0011, false); // Serie Number (more than 1 serie may have the same Ser.Nbr don't 'convert!)
276
277    // Feel free to add more fields, if they can help a suitable (for you)
278    // image sorting
279
280    // Loop on all the gdcm-readable files
281    for (GDCM_NAME_SPACE::DirListType::iterator it = fileNames.begin();
282                                     it != fileNames.end();
283                                   ++it)
284    {
285       f = GDCM_NAME_SPACE::File::New();
286       f->SetLoadMode(loadMode);
287       f->SetFileName( *it );
288       if (verbose)
289          std::cout << "Try[" << *it << "]\n";
290       f->Load();
291       if (!f->Document::IsReadable())
292       {
293          if (verbose)
294             std::cout << "File : [" << *it << "] not gdcm-readable -> skipped !" << std::endl;
295          continue;
296       }
297       if (verbose)
298          std::cout << "Loaded!\n";
299       std::string strSeriesNumber;
300       int seriesNumber;
301       int j;
302
303       // keep only requested Series
304       bool keep = false;
305       if (nbSeriesToKeep != 0)
306       {
307          strSeriesNumber = f->GetEntryString(0x0020, 0x0011 );
308          seriesNumber = atoi( strSeriesNumber.c_str() );
309          for (j=0; j<nbSeriesToKeep; j++)
310          {
311             if(seriesNumber == seriesToKeep[j])
312             {
313                keep = true;
314                break;
315             }
316          }
317          if ( !keep)
318          {
319             f->Delete();
320             continue;
321          }
322       }
323       // drop all unrequested Series
324       bool drop = false;
325       if (nbSeriesToDrop != 0)
326       {
327          strSeriesNumber = f->GetEntryString(0x0020, 0x0011 );
328          seriesNumber = atoi( strSeriesNumber.c_str() );
329          for (j=0;j<nbSeriesToDrop; j++)
330          {
331             if(seriesNumber == seriesToDrop[j])
332             {
333                drop = true;
334                break;
335             }
336         }
337         if (drop)
338         {
339            f->Delete();
340            continue;
341         }
342       }
343
344       userFileIdentifier=s->CreateUserDefinedFileIdentifier(f);
345       if (verbose)
346          std::cout << "userFileIdentifier [" << userFileIdentifier << "]" << std::endl; 
347       tokens.clear();
348       GDCM_NAME_SPACE::Util::Tokenize (userFileIdentifier, tokens, token);
349
350       char newName[1024];
351
352       ///this is a trick to build up a lexicographical compliant name :
353       ///     eg : fich001.ima vs fich100.ima as opposed to fich1.ima vs fich100.ima
354       std::string name = GDCM_NAME_SPACE::Util::GetName( *it );
355       if (verbose)
356          std::cout << "name :[" << name << "]\n";
357
358       if (hasSkel)
359       {
360          int imageNum; // Within FileName
361          GDCM_NAME_SPACE::Util::Tokenize (name, tokensForFileName, skel);
362          imageNum = atoi ( tokensForFileName[0].c_str() );
363          // probabely we could write something much more complicated using C++ !
364          sprintf (newName, "%s%06d.dcm", skel, imageNum);
365          tokens[IND_FileName] = newName;
366          tokensForFileName.clear();
367        }
368        else
369        {
370          tokens[IND_FileName] = name;
371        }
372
373          // Patient's Name
374          // Study Instance UID
375          // Series Instance UID
376          // SerieDescription
377          // Serie Number
378          // file Name
379
380       userFileIdentifier = tokens[IND_PatientName]      + token +
381                            tokens[IND_StudyInstanceUID] + token + 
382                            tokens[IND_SerieInstanceUID] + token +
383
384                            tokens[IND_SerieDescription] + token +
385                            tokens[IND_SerieNumber]      + token +
386                            tokens[IND_FileName];
387       if (verbose) 
388          std::cout << "[" << userFileIdentifier  << "] : " << *it << std::endl;
389
390       // storing in a map ensures automatic sorting !
391       sf[userFileIdentifier] = f;
392    }
393    
394    if (verbose)
395       std::cout << " ==== " << std::endl;
396       
397    std::string fullFilename, lastFilename;
398    std::string previousPatientName, currentPatientName;
399    std::string previousStudyInstanceUID, currentStudyInstanceUID;   
400    std::string previousSerieInstanceUID, currentSerieInstanceUID;
401    
402    std::string currentSerieDescription, currentSerieNumber;   
403       
404    std::string writeDir, currentWriteDir;
405    std::string currentPatientWriteDir;
406    std::string currentStudyWriteDir;
407    std::string currentSerieWriteDir; 
408
409    std::string fullWriteFilename;
410            
411    writeDir = GDCM_NAME_SPACE::Util::NormalizePath(dirNameout);     
412    SortedFiles::iterator it2;
413  
414    previousPatientName            = "";
415    previousStudyInstanceUID       = "";    
416    previousSerieInstanceUID       = "";   
417        
418    GDCM_NAME_SPACE::File *currentFile;
419    std::string replaceChar("_"); 
420    for (it2 = sf.begin() ; it2 != sf.end(); ++it2)
421    {  
422       currentFile = it2->second;
423        
424       fullFilename =  currentFile->GetFileName();
425       lastFilename =  GDCM_NAME_SPACE::Util::GetName( fullFilename );
426       if (verbose) 
427       std::cout <<" ------------------------------------------------------------------------------" 
428                 << std::endl << " Deal with [" << it2->first << "] : [" <<fullFilename << "]" 
429                 << std::endl;
430      
431       tokens.clear();
432       GDCM_NAME_SPACE::Util::Tokenize (it2->first, tokens, token);
433       
434       currentPatientName            = tokens[IND_PatientName];
435       currentStudyInstanceUID       = tokens[IND_StudyInstanceUID];     
436       currentSerieInstanceUID       = tokens[IND_SerieInstanceUID];
437       currentSerieDescription       = tokens[IND_SerieDescription];
438       currentSerieNumber            = tokens[IND_SerieNumber];
439
440       if (previousPatientName != currentPatientName)
441       {  
442          previousPatientName = currentPatientName;
443          if (verbose)   
444             std::cout << "==== new Patient  [" << currentPatientName  << "]" << std::endl;
445
446          previousPatientName            = currentPatientName;
447          previousStudyInstanceUID       = ""; 
448          previousSerieInstanceUID       = "";
449
450          currentPatientWriteDir = writeDir + currentPatientName;
451
452          systemCommand   = "mkdir \"" + currentPatientWriteDir + "\"";
453          if (verbose || listonly)
454             std::cout << "[" << systemCommand << "]" << std::endl;
455          if (!listonly)
456             system ( systemCommand.c_str() );
457       }
458
459       if (previousStudyInstanceUID != currentStudyInstanceUID)
460       {
461          previousStudyInstanceUID       = currentStudyInstanceUID;
462          if (verbose)
463             std::cout << "==== === new Study [" << currentStudyInstanceUID << "]"
464                       << std::endl;
465
466          currentStudyWriteDir  = currentPatientWriteDir + GDCM_NAME_SPACE::GDCM_FILESEPARATOR
467                              + currentStudyInstanceUID;
468          systemCommand   = "mkdir \"" + currentStudyWriteDir + "\"";
469  
470          if (verbose)
471             std::cout << "Directory [" << currentStudyWriteDir << "] created" << std::endl;
472  
473          if (listonly)
474             std::cout << "[" << systemCommand << "]" << std::endl;         
475          else            
476             system (systemCommand.c_str());
477       }  
478
479       if (previousSerieInstanceUID != currentSerieInstanceUID)
480       {        
481          previousSerieInstanceUID       = currentSerieInstanceUID;
482          if (verbose)   
483             std::cout << "=== ==== === new Serie [" << currentSerieInstanceUID << "]"
484                       << std::endl;
485                             
486          if (seriedescr) // more human readable!
487             currentSerieWriteDir  = currentStudyWriteDir + GDCM_NAME_SPACE::GDCM_FILESEPARATOR
488                                   + currentSerieDescription + "_" + currentSerieNumber
489                                   /*+ "_" + currentSerieInstanceUID */
490                                   ;
491
492          else
493             currentSerieWriteDir  = currentStudyWriteDir + GDCM_NAME_SPACE::GDCM_FILESEPARATOR
494                                   + currentSerieInstanceUID;         
495    
496          systemCommand   = "mkdir \"" + currentSerieWriteDir + "\"";
497          
498          if (listonly)
499             std::cout << "[" << systemCommand << "]" << std::endl;         
500          else             
501             system (systemCommand.c_str());
502
503          if (verbose)
504             std::cout << "Directory [" << currentSerieWriteDir << "] created" << std::endl;
505       }            
506    
507       if ( GDCM_NAME_SPACE::Debug::GetDebugFlag())
508          std::cout << "--- --- --- --- --- " << it2->first << "  " 
509                    << (it2->second)->GetFileName() << " " 
510                    << GDCM_NAME_SPACE::Util::GetName( fullFilename ) << std::endl;
511  
512       // If you want to create file names of your own, here is the place!
513       // Just replace 'lastFilename' by anything that's better for you.
514       GDCM_NAME_SPACE:: Util::ReplaceSpecChar(lastFilename, replaceChar);
515                    
516       fullWriteFilename = currentSerieWriteDir + GDCM_NAME_SPACE::GDCM_FILESEPARATOR 
517                                          + lastFilename; 
518
519       systemCommand   = "cp \"" + fullFilename + "\"  \"" + fullWriteFilename + "\"";
520       
521       if (listonly)
522          std::cout << "[" << systemCommand << "]" << std::endl;         
523       else             
524          system (systemCommand.c_str());
525
526    }
527    return 0;
528  }