Statistiques
| Révision :

root / src / lsm_cells.cpp @ 4

Historique | Voir | Annoter | Télécharger (16,73 ko)

1
/*
2
Level-set Method to detect cell's contours
3
- Parallel standard version -
4
       
5
       Copyright 2016 ENS de Lyon
6

7
       File author(s):
8
           Typhaine Moreau, Annamaria Kiss <annamaria.kiss@ens-lyon.fr.fr>
9
       See accompanying file LICENSE.txt
10

11
To compile :
12
 g++ -o lsm_cells lsm_cells.cpp -O2 -L/usr/X11R6/lib -lm -lpthread -lX11 -fopenmp
13
 Needs CImg.h and lsm_lib.h
14

15
To execute :
16
 ./lsm_cells img img_wat img_contour erosion a b gamma smooth type
17

18
 image in .inr or .inr.gz, save in .inr.gz
19
 img : grayscale image of cells (unsigned char)
20
 img_wat : watershed 16bits image (unsigned short) with background label 1 and cells labels >1
21
 img_contour : binary image with background=1, all cells=0 (unsigned char)
22
 erosion : amount of erosion for each cell in watershed image (int)
23
 a : area term (float) --> 0 or 0.5 or 1 (the default is 0.5)
24
             if negative, the object retracts
25
             if positive, the object inflates
26
 b : curvature term (float) --> 0 or 1 (the default is 0)
27
 gamma : scale parameter (float>0) --> 0.5 or 1 (the default is 1)
28
 smooth : gaussian blur to apply to the image (int) --> 0 or 1 (the default is 0)
29
 lsm_type : image, gradient or hessien based evolution --> 'i', 'g' or 'h' (the default is g)
30
*/
31

    
32
#include <iostream>
33
#include <math.h>
34
#include <sstream>
35
#include <vector>
36
#include <fstream>
37
#include <omp.h>
38

    
39
#include "CImg.h"
40
#include "lsm_lib.h"
41

    
42
using namespace cimg_library;
43
using namespace std;
44

    
45

    
46
//Return list of cell label
47
//---------------------------------------------------------------------
48
vector<int> index(const CImg<unsigned short> & wat, int nbcells)
49
{
50
  vector<int> list;
51
  vector<bool> test(nbcells,false);
52
  cimg_forXYZ(wat,x,y,z)
53
    {
54
      int ind=wat(x,y,z);
55
      if((test[ind]==false)and(ind!=1)and(ind!=0))
56
        {
57
          list.push_back(ind);
58
          test[ind]=true;
59
        }
60
    }
61
  return list;
62
}
63

    
64
//Return crop psi image and it's min coordinates for one cell 
65
//--------------------------------------------------------------------
66
CImg<float> box_cell(const CImg<unsigned short> & wat, int marge,int erosion, int c0,int indice, int &xmin, int &ymin, int &zmin)
67
{
68
  //search box (min and max coordinates with a marge)
69
  xmin=wat._width;
70
  ymin=wat._height;
71
  zmin=wat._depth;
72
  int xmax=0;
73
  int ymax=0;
74
  int zmax=0;
75
  cimg_forXYZ(wat,x,y,z)
76
    {
77
      if(wat(x,y,z)==indice)
78
        {
79
          if(x>xmax){xmax=x;}else if(x<xmin){xmin=x;}
80
          if(y>ymax){ymax=y;}else if(y<ymin){ymin=y;}
81
          if(z>zmax){zmax=z;}else if(z<zmin){zmin=z;}
82
        }
83
    }
84
  //marge and border conditions
85
  xmin=xmin-marge;
86
  if(xmin<0){xmin=0;}
87
  ymin=ymin-marge;
88
  if(ymin<0){ymin=0;}
89
  zmin=zmin-marge;
90
  if(zmin<0){zmin=0;}
91
  xmax=xmax+marge;
92
  if(xmax>=wat._width){xmax=wat._width-1;}
93
  ymax=ymax+marge;
94
  if(ymax>=wat._height){ymax=wat._height-1;}
95
  zmax=zmax+marge;
96
  if(zmax>=wat._depth){zmax=wat._depth-1;}
97

    
98
  //crop wat image to the size of box, make binary
99
  CImg<unsigned short>binary=wat.get_crop(xmin,ymin,zmin,0,xmax,ymax,zmax,0);
100
  cimg_forXYZ(binary,x,y,z)
101
    {
102
      if(binary(x,y,z)==indice){binary(x,y,z)=1;}
103
      else {binary(x,y,z)=0;}
104
    }
105

    
106
  //erode binary but not completely (vol stay >0)
107
  int vol=binary.sum();
108
  int nb_ero=0;
109
  while((vol>0)and(nb_ero<abs(erosion)))
110
  {
111
    CImg<unsigned char> binary_erode=binary.get_erode(3,3,3); 
112
        if (erosion<0) 
113
        {
114
                for(int i=0;i<3;i++)
115
                {
116
                binary=binary.get_dilate(2,2,2);
117
                }
118
                binary_erode=binary;
119
        }
120
      vol=binary_erode.sum();
121
      if(vol>0)
122
        {
123
          binary=binary_erode;
124
          nb_ero+=1;
125
        }
126
    }
127

    
128
  //initalize psi
129
  CImg<float>psi=binary;
130
  cimg_forXYZ(psi,x,y,z)
131
    {
132
      if(binary(x,y,z)==0){psi(x,y,z)=c0;}
133
      else {psi(x,y,z)=-c0;}
134
    }
135
  return psi;
136
}
137

    
138

    
139
//LSM segment : edge detection
140
//---------------------------------------------------------------------
141
CImg<float> lsm_segment2(CImg<float> psi,CImg<float> const & g,CImgList<float> const & gg,CImg<float> const & h, int lam, float mu,float alf, float beta, float epsilon, int dt, vector<int> min_list)
142
{
143
  int timestep_max=2000;
144
  int evolution_min=1;
145
  //int evolution_max=-1;
146
  int it=0;
147
  int it_stop=0;
148
  bool contour_evolve=true;
149
  int backsegm=0;
150
  CImg<float> psi_old=psi;
151
  cimg_forXYZ(psi,x,y,z)
152
    {
153
      if(psi(x,y,z)>=0){backsegm+=1;}
154
    }
155

    
156
  while((it<timestep_max)and(contour_evolve==true)and(backsegm>30))
157
    {
158
      psi_old=psi;
159
      //evolution
160
      psi=evolution_AK2_contour_cells(psi,g,gg,h,lam,mu,alf,beta,epsilon,dt,min_list);
161
      //new segmentation
162
      int new_backsegm=0;
163
      cimg_forXYZ(psi,x,y,z)
164
        {
165
          if(psi(x,y,z)>=0){new_backsegm+=1;}
166
        }
167
          
168
      //Stop criteria
169
      // * if the cell would disappear, we stop
170
      if(new_backsegm==0)
171
                        {psi=psi_old;
172
                     contour_evolve=false;}
173
      
174
      int bg_evolution=abs(new_backsegm-backsegm);
175
        
176
      // * if the evolution is less then evolution_min 3 consecutive times
177
      if((it>10) and (bg_evolution<=evolution_min) )
178
        {
179
          it_stop+=1;
180
          if(it_stop>3)
181
            {contour_evolve=false;}
182
        }
183
      else
184
        {
185
          it_stop=0;
186
        }
187

    
188

    
189
      it+=1;
190
      backsegm=new_backsegm;
191
    }
192
  return psi;
193
}
194

    
195

    
196
//Reconstruct full segmented image from cell's images
197
//---------------------------------------------------------------
198
CImg<unsigned short> reconstruct(const CImg<unsigned char> & background,const vector<CImg<float> > & psi_list, const vector< vector<int> > & min_list, int nbcells,const vector<int> & list)
199
{
200
  CImg<unsigned short> res=background;
201
  for(int i=0;i<nbcells;i++)
202
    {
203
      cimg_forXYZ(psi_list[i],xb,yb,zb)
204
        {
205
          int x=xb+min_list[i][0];
206
          int y=yb+min_list[i][1];
207
          int z=zb+min_list[i][2];
208
          if(psi_list[i](xb,yb,zb)>=0)
209
            {res(x,y,z)=list[i];}
210
        }
211
    }
212
  return res;
213
}
214

    
215
//Reconstruct segmented image from cell's images and treat overlap
216
//---------------------------------------------------------------
217
CImg<unsigned short> reconstruct_overlap(CImg<unsigned char> const &  background,const vector<CImg<float> > & psi_list, const vector< vector<int> > & min_list, int nbcells, CImg<unsigned char> &free, const vector<int> & list)
218
{
219
  CImg<unsigned short> res=background;
220
  free=background;
221
  for(int i=0;i<nbcells;i++)
222
    {
223
      cimg_forXYZ(psi_list[i],xb,yb,zb)
224
        {
225
          int x=xb+min_list[i][0];
226
          int y=yb+min_list[i][1];
227
          int z=zb+min_list[i][2];
228
          if(psi_list[i](xb,yb,zb)>=0)
229
            {
230
              res(x,y,z)=list[i];
231
              free(x,y,z)+=1;
232
            }
233
        }
234
    }
235
  cimg_forXYZ(free,x,y,z)
236
    {
237
      if(free(x,y,z)>1)
238
        {
239
          if(background(x,y,z)==1)
240
            {
241
              free(x,y,z)=1;
242
              res(x,y,z)=1;
243
            }
244
          else
245
            {
246
              free(x,y,z)=0;
247
              res(x,y,z)=0;
248
            }
249
        }
250
    }
251
  return res;
252
}
253

    
254

    
255
//-----------------------------------------------------------------------------------------------
256
//******************************************  MAIN **********************************************
257
//-----------------------------------------------------------------------------------------------
258

    
259
int main (int argc, char* argv[])
260
{
261
  double begin=omp_get_wtime();
262

    
263
  if(argc<5)
264
    {
265
    cout<<"!! wrong number of arguments ("<<argc<<")"<<endl;
266
    cout<<"Usage : lsm_cells img img_wat img_contour erosion [a b smooth lsm_type]"<<endl;
267
    cout<<"----------------- "<<endl;  
268
        cout<<"img : grayscale image of cells, (.inr or .inr.gz)"<<endl;
269
        cout<<"img_wat : image of seeds, (.inr or .inr.gz)"<<endl;
270
        cout<<"img_contour : mask, where cells do not evolve, (.inr or .inr.gz)"<<endl;
271
        cout<<"              if 'None', then cells can evolve on the whole image"<<endl;
272
        cout<<"erosion : amount of erosion of seeds for initialisation (uint8) --> -2, 0, 2"<<endl;
273
        cout<<"              if 0, then no erosion or dilation"<<endl;
274
        cout<<"              if negative, then a dilation is performed"<<endl;
275
        cout<<"a : area term (float) --> 0 or 0.5 or 1 (the default is 0.5)"<<endl;
276
        cout<<"              if negative, the object retracts"<<endl;
277
        cout<<"              if positive, the object inflates"<<endl;
278
        cout<<"b : curvature term (float) --> 0 or 1 (the default is 0)"<<endl;
279
        cout<<"gamma : scale parameter (float>0) --> 0.5 or 1 (the default is 1)"<<endl;
280
        cout<<"smooth : gaussian blur to apply to the image (int) --> 0 or 1 (the default is 0)"<<endl;
281
    cout<<"lsm_type : image, gradient or hessien based evolution --> 'i', 'g' or 'h' (the default is g)"<<endl;  
282
      return 0;
283
    }
284

    
285
  //----------------------------------------------read images and check the names
286
  //Original image
287
  CImg<char> description;
288
  float tailleVoxel[3] = {0}; // resolution initialisation
289
  
290
  bool gzipped=false;
291
  
292
  string filename_img=argv[1];
293
  CImg<unsigned char> img_prev;
294
  if(filename_img.compare(filename_img.size()-4,4,".inr")==0)
295
    {
296
      img_prev.load(filename_img.c_str());
297
      img_prev.get_load_inr(filename_img.c_str(),tailleVoxel); // reads resolution
298
    }
299
  else if(filename_img.compare(filename_img.size()-7,7,".inr.gz")==0)
300
    {
301
          gzipped = true;
302
      string oldname = filename_img;
303
      filename_img.erase(filename_img.size()-3);
304
      string zip="gunzip -c "+oldname+" > "+filename_img;
305
      if(system(zip.c_str())); // decompress image file
306
      img_prev.load(filename_img.c_str());
307
      img_prev.get_load_inr(filename_img.c_str(),tailleVoxel); // reads resolution
308
      zip="rm "+filename_img;
309
      if(system(zip.c_str())); //removes decompressed image  
310
    }
311
  else
312
    {cout<<"!! wrong file extension : "<<filename_img<<endl;
313
      return 0;}
314
  CImg<float> img=img_prev;
315
  img_prev.assign();
316
  cout<<"original image : "<<filename_img<<endl;
317
  cout<<"size : "<<img.size()<<endl;
318
  cout<<"size of original image : "<< img._width<<' '<< img._height <<' '<< img._depth<<' '<< endl;
319
  
320
  //Watershed
321
  string filename=argv[2];
322
  CImg<unsigned short> wat;
323
  if(filename.compare(filename.size()-4,4,".inr")==0)
324
    {
325
      wat.load(filename.c_str());
326
    }
327
  else if(filename.compare(filename.size()-7,7,".inr.gz")==0)
328
    {
329
      wat.load_gzip_external(filename.c_str());
330
      filename.erase(filename.size()-3);
331
    }
332
  else
333
    {cout<<"!! wrong file extension : "<<filename<<endl;
334
      return 0;}
335
  cout<<"watershed image : "<<filename<<endl;
336
cout<<"size : "<<wat.size()<<endl;
337
  cout<<"size of original image : "<< wat._width<<' '<< wat._height <<' '<< wat._depth<<' '<< endl;
338
  
339
  //Background
340
  string filename_bg=argv[3];
341
  CImg<unsigned char> background(img._width, img._height, img._depth,1,0);
342
   if(filename_bg.compare("None")==0)
343
    {
344
       cout<<"!! no background entered !!"<<endl;
345
    }
346
    else if(filename_bg.compare(filename_bg.size()-4,4,".inr")==0)
347
    {
348
      background.load(filename_bg.c_str());
349
    }
350
  else if(filename_bg.compare(filename_bg.size()-7,7,".inr.gz")==0)
351
    {
352
      background.load_gzip_external(filename_bg.c_str());
353
      filename_bg.erase(filename_bg.size()-3);
354
    }
355
  else
356
    {cout<<"!! wrong file extension : "<<filename_bg<<endl;
357
      return 0;}
358
  cout<<"background image : "<<filename_bg<<endl;
359
  
360
  if((wat.size()!=img.size())or(background.size()!=img.size()))
361
    {
362
      cout<<"!! images are not the same size"<<endl;
363
      return 0;
364
    }
365
  else cout<<"size : "<<img.size()<<endl;
366

    
367

    
368
  //---------------------------------------------------------------Parameters
369
  //model parameters
370
  
371
  int c0=-4;
372
  int erosion=atoi(argv[4]);
373
  int marge=10; 
374
  
375
  int lam=10;
376
  float alf=0.5;
377
  float beta=0;
378
  float gamma=1;
379
  float smooth=0;
380
  string lsm_type="g";
381
  
382
  string ar=argv[4];
383
  string insert="_cellLSM-d"+ar;
384
  if(argc>5)
385
    {
386
    alf=atof(argv[5]);
387
    ar=argv[5];
388
    insert=insert+"-a"+ar;
389
    }
390
  if(argc>6)
391
    {
392
    beta=atof(argv[6]);
393
    ar=argv[6];
394
    insert+="-b"+ar;
395
    }
396
  if(argc>7)
397
    {
398
    gamma=atof(argv[7]);
399
    ar=argv[7];
400
    insert+="-g"+ar;
401
        }
402
  if(argc>8)
403
    {
404
    smooth=atof(argv[8]);
405
    ar=argv[8];
406
    insert+="-s"+ar;
407
        }
408
  if(argc>9)
409
    {
410
        lsm_type=argv[9];
411
        ar=argv[9];
412
        insert+="-"+ar;
413
        }
414
        
415
  
416

    
417
  //numerical parameters
418
  float epsilon=0.5;
419
  int dt=1;   //it was 50, changed into 1 the 2015/10/16
420
  float mu=0.1/dt;
421
  int timestep_max=10000; // it was 1000, changed into 10000 the 2015/10/16
422

    
423

    
424
  //------------------------------------Names and directories
425
  //new name with arguments
426
  filename.insert(filename.size()-4,insert);
427

    
428
  //create directories and update names
429
  size_t test=filename.rfind("/");
430
  if(test!=filename.npos)
431
    {filename.erase(0,test+1);}
432
  string outputdir=filename;
433
  outputdir.erase(filename.size()-4);
434
  string mkdir="mkdir -p "+outputdir;
435
  if(system(mkdir.c_str())); 
436

    
437
  string filename_txt=outputdir+"/"+filename;
438
  filename_txt.erase(filename_txt.size()-4);
439
  filename=outputdir+"/"+filename;
440
  string wat_eroded_name=filename;
441
  wat_eroded_name.insert(filename.size()-4,"_eroded");
442
  string edge_detection_name=filename;
443
  edge_detection_name.insert(filename.size()-4,"_evoEdge");
444
  string edge_evolve_name=filename;
445
  edge_evolve_name.insert(filename.size()-4,"_final");
446

    
447
  //txt files 
448
  ofstream file;
449
  string txt_name=filename_txt+".txt";
450
  file.open(txt_name.c_str());
451
  file<<argv[0]<<endl;
452
  time_t t;
453
  struct tm * timeinfo;
454
  time(&t);
455
  timeinfo=localtime(&t);
456
  file<<asctime(timeinfo);
457
  file<<"image : "<<argv[1]<<endl;
458
  file<<"watershed : "<<argv[2]<<endl;
459
  file<<"background : "<<argv[3]<<endl;
460
  file<<"_________________________________"<<endl;
461
  file<<"Parameters"<<endl;
462
  file<<"lsm_type : "<<lsm_type<<endl;
463
  file<<"lambda : "<<lam<<endl;
464
  file<<"alpha : "<<alf<<endl;
465
  file<<"beta : "<<beta<<endl;
466
  file<<"gamma :"<<gamma<<endl;
467
  //file<<"alphabis : "<<alfabis<<endl;
468
  //file<<"betabis : "<<betabis<<endl;
469
  file<<"erosion : "<<erosion<<endl;
470
  file<<"marge : "<<marge<<endl;
471
  file<<"epsilon : "<<epsilon <<endl;
472
  file<<"mu : "<<mu <<endl;
473
  file<<"dt : "<<dt <<endl;
474
  file<<"timestep_max : "<<timestep_max <<endl;
475

    
476
  //-----------------------------------------Image Pre-processing
477

    
478
  //smooth image
479
  file<<"smooth : "<<smooth<<endl;
480
  if (smooth>0)
481
  {
482
  img.blur(smooth);
483
  }
484

    
485
  //-------------------------------------Initialization with erosion
486
  //compute fixed terms
487
  CImg<float> g;
488
  if(lsm_type.compare("g")==0)
489
    {
490
  g=edge_indicator1(img, gamma);
491
    }
492
  else if (lsm_type.compare("h")==0)
493
   {
494
  g=edge_indicator2s(img, gamma);
495
   }
496
  else if (lsm_type.compare("i")==0)
497
   {
498
  g=edge_indicator3(img, gamma);
499
   }
500
  else
501
  cout<<"Wrong lsm type given :'"<<lsm_type<<"'('g' for gradient-based, 'h' for Hessien-based) "<<endl;
502
 
503
  CImgList<float> gg=gradient(g);
504
  CImg<float> h=g;
505
  img.assign();
506
  
507
  //initialize psi for every cell
508
  int maxcells=wat.max()+1; //indice maximum  
509
  vector<int> list=index(wat,maxcells);
510
  int nbcells=list.size();
511
  cout<<"number of cells : "<<nbcells<<endl;
512
  file<<"number of cells : "<<nbcells<<endl;
513
  vector<CImg<float> > psi_list(nbcells);
514
  vector<vector<int> > min_list(nbcells,vector<int>(3,0));
515
  vector<vector<int> > max_list(nbcells,vector<int>(3,0));
516

    
517
#pragma omp parallel shared(wat,marge,erosion,c0,psi_list,min_list,list)
518
  {
519
  int xmin=0;
520
  int ymin=0;
521
  int zmin=0;
522
#pragma omp for schedule(dynamic)
523
  for(int i=0;i<nbcells;i++) 
524
    {
525
      int ind=list[i];
526
      psi_list[i]=box_cell(wat,marge,erosion,c0,ind,xmin,ymin,zmin);
527
      min_list[i][0]=xmin;
528
      min_list[i][1]=ymin;
529
      min_list[i][2]=zmin;
530
      cout <<"cell "<<ind<<" initialised."<<endl;
531
    }
532
  }
533
  wat.assign();
534

    
535
  //reconstruct image of eroded cells
536
  CImg<unsigned short> wat_eroded=reconstruct(background,psi_list,min_list,nbcells,list);
537
  cout <<"saving file "<<wat_eroded_name<<"..."<<endl;
538
  wat_eroded.save_inr(wat_eroded_name.c_str(),tailleVoxel);
539
  string zip="gzip -f "+wat_eroded_name;
540
  if(system(zip.c_str()));
541
  cout <<"Eroded watershed segmentation saved in file:"<<wat_eroded_name<<endl;
542

    
543
  //Segmented inital = background segmentation
544
  CImg<unsigned char> segmented=background;
545

    
546
  double end1=omp_get_wtime();
547
  double time1=double(end1-begin);
548
  cout<<"Evolving cells..... "<<endl;
549
  
550
  //---------------------------------------------------------Edge detection
551
  //evolve each cell one by one, attract to maximal gradient
552
#pragma omp parallel shared(psi_list,min_list,g,gg,h,lam,mu,alf,beta,epsilon,dt,list)
553
  {
554
#pragma omp for schedule(dynamic)
555
  for(int i=0;i<nbcells;i++) 
556
    {
557
      psi_list[i]=lsm_segment2(psi_list[i],g,gg,h,lam,mu,alf,beta,epsilon,dt,min_list[i]);
558
      cout <<"cell "<<list[i]<<" evolved."<<endl;
559
    }
560
  }
561

    
562
  //reconstruct image of edge detection, overlap=not segmented
563
  CImg<unsigned char>free=background;
564
  CImg<unsigned short> edge=reconstruct_overlap(background,psi_list,min_list,nbcells,free,list);
565
  edge.save_inr(edge_detection_name.c_str(),tailleVoxel);
566
  zip="gzip -f "+edge_detection_name;
567
  if(system(zip.c_str()));
568

    
569
// time measurements
570
  double end2=omp_get_wtime();
571
  double time2=double(end2-begin);
572
  
573
  double end=omp_get_wtime();
574
  double time=double(end-begin);
575
  cout<<"total time : "<<time<<"s"<<" (~"<<time/60<<" mn  ~"<<time/60/60<<" h)"<<endl;
576
  cout<<"-initialization with erosion : "<<time1<<"s"<<endl;
577
  cout<<"-edge detection : "<<time2<<"s"<<endl;
578
  file<<"total time : "<<time<<"s"<<" (~"<<time/60<<" mn  ~"<<time/60/60<<" h)"<<endl;
579
  file<<"-initialization with erosion : "<<time1<<"s"<<endl;
580
  file<<"-edge detection : "<<time2<<"s"<<endl;
581
  file.close();
582

    
583
  return 0;
584
}