DGtalTools  1.2.0
at-u2-v0.cpp
1 
32 #include <iostream>
33 
34 #include <sstream>
35 #include <string>
36 #include <functional>
37 #include <boost/format.hpp>
38 
39 
40 #include "CLI11.hpp"
41 
42 
43 #include "DGtal/base/Common.h"
44 #include "DGtal/helpers/StdDefs.h"
45 #include "DGtal/io/readers/GenericReader.h"
46 #include "DGtal/io/writers/GenericWriter.h"
47 
48 #include "ATu2v0.h"
49 
197 using namespace std;
198 using namespace DGtal;
199 
200 int main( int argc, char* argv[] )
201 {
202  using namespace Z2i;
203 
204  // parse command line ----------------------------------------------
205  CLI::App app;
206  string f1;
207  string f2 {"AT"};
208  string inpainting_mask;
209  double l;
210  double l1 {0.3125};
211  double l2 {0.0005};
212  double lr {sqrt(2)};
213  double a {1.0};
214  double epsilon;
215 
216  double e1 {2.0};
217  double e2 {0.25};
218  double er {2.0};
219  int verb {0};
220  int nbiter = {10};
221  int pix_sz = {1};
222  string scv {"0xff0000"};
223  string isnr;
224  bool metric;
225 
226 
227  stringstream ssDescr;
228  ssDescr << "Computes a piecewise smooth approximation of a grey-level or color image, by optimizing the Ambrosio-Tortorelli functional (with u a 2-form and v a 0-form).";
229  ssDescr << "Usage: " << argv[0] << " -i toto.pgm\n"
230  << "Computes the Ambrosio-Tortorelli reconstruction/segmentation of an input image."
231  << "It outputs 2 or 3 images (of basename given by option --output) giving the"
232  << " reconstructed image u, and other images superposing u and the discontinuities v."
233  << endl << endl
234  << " / "
235  << endl
236  << " | a.(u-g)^2 + v^2 |grad u|^2 + le.|grad v|^2 + (l/4e).(1-v)^2 "
237  << endl
238  << " / "
239  << endl
240  << "Discretized as (u 2-form, v 0-form, A vertex-edge bdry, B edge-face bdy, M vertex-edge average)" << endl
241  << "E(u,v) = a(u-g)^t (u-g) + u^t B diag(M v)^2 B^t u + l e v^t A^t A v + l/(4e) (1-v)^t (1-v)" << endl
242  << endl
243  << "Example: ./at-u2-v0 -i ../Images/cerclesTriangle64b02.pgm -o tmp -a 0.05 -e 1 --lambda-1 0.1 --lambda-2 0.00001 -g"
244  << endl;
245 
246  app.description(ssDescr.str());
247 
248 
249  app.add_option("-i,--input,1", f1, "the input image PPM filename." )
250  ->required()
251  ->check(CLI::ExistingFile);
252  app.add_option("--inpainting-mask,-m", inpainting_mask, "the input inpainting mask filename." );
253  app.add_option("--output,-o", f2, "the output image basename.", true);
254  auto lambdaOpt = app.add_option("--lambda,-l",l, "the parameter lambda.");
255  app.add_flag("--metric-average,-M",metric, "use metric average to smooth L1-metric." );
256 
257  app.add_option("--lambda-1,-1",l1, "the initial parameter lambda (l1).", true);
258  app.add_option("--lambda-2,-2",l2, "the final parameter lambda (l2).", true );
259  app.add_option("--lambda-ratio,-q",lr, "the division ratio for lambda from l1 to l2.", true);
260  app.add_option("--alpha,-a",a, "the parameter alpha.", true);
261  auto epsOpt = app.add_option("--epsilon,-e", "the initial and final parameter epsilon of AT functional at the same time.");
262 
263  app.add_option("--epsilon-1",e1, "the initial parameter epsilon.", true);
264  app.add_option("--epsilon-2",e2, "the final parameter epsilon.", true);
265  app.add_option("--epsilon-r",er, "sets the ratio between two consecutive epsilon values of AT functional.", true);
266 
267  app.add_option("--nbiter,-n",nbiter, "the maximum number of iterations.", true );
268  auto snrOpt = app.add_option("--image-snr", isnr, "the input image without deterioration if you wish to compute the SNR.");
269  app.add_option("--pixel-size,-p", pix_sz, "the pixel size for outputing images (useful when one wants to see the discontinuities v on top of u).", true);
270  app.add_option("--color-v,-c",scv, "the color chosen for displaying the singularities v (e.g. red is 0xff0000).", true );
271  app.add_option("--verbose,-v", verb, "the verbose level (0: silent, 1: less silent, etc).", true );
272 
273  app.get_formatter()->column_width(40);
274  CLI11_PARSE(app, argc, argv);
275  // END parse comm"metric-average,Mand line using CLI ----------------------------------------------
276 
277 
278 
279  Color color_v( (unsigned int) std::stoul( scv, nullptr, 16 ), 255 );
280  if ( lambdaOpt->count()) l1 = l2 = l;
281  if ( l2 > l1 ) l2 = l1;
282  if ( lr <= 1.0 ) lr = sqrt(2);
283  if ( epsOpt->count() > 0 ){
284  e1 = e2 = epsilon;
285 
286  }
287  bool snr = snrOpt->count() > 0;
288 
289 
290  bool color_image = f1.size() > 4 && f1.compare( f1.size() - 4, 4, ".ppm" ) == 0;
291  bool grey_image = f1.size() > 4 && f1.compare( f1.size() - 4, 4, ".pgm" ) == 0;
292  if ( ! color_image && ! grey_image )
293  {
294  trace.error() << "Input image file must be either a PGM (grey-level) or a PPM (color) image with these extensions."
295  << endl;
296  return 2;
297  }
298 
299  KSpace K;
300  ATu2v0< KSpace > AT( verb );
301  Domain domain;
302  AT.setMetricAverage( metric );
303 
304  typedef ATu2v0<KSpace>::Calculus Calculus;
305  typedef ImageContainerBySTLVector<Domain, Color> ColorImage;
306  typedef ImageContainerBySTLVector<Domain, unsigned char> GreyLevelImage;
307  //---------------------------------------------------------------------------
308  if ( color_image )
309  {
310  trace.beginBlock("Reading PPM image");
311  ColorImage image = PPMReader<ColorImage>::importPPM( f1 );
312  trace.endBlock();
313  trace.beginBlock("Building AT");
314  domain = image.domain();
315  K.init( domain.lowerBound(), domain.upperBound(), true );
316  AT.init( K );
317  AT.addInput( image, [] ( Color c ) -> double { return ((double) c.red()) / 255.0; } );
318  AT.addInput( image, [] ( Color c ) -> double { return ((double) c.green()) / 255.0; } );
319  AT.addInput( image, [] ( Color c ) -> double { return ((double) c.blue()) / 255.0; } );
320  trace.endBlock();
321  }
322  else if ( grey_image )
323  {
324  trace.beginBlock("Reading PGM image");
325  GreyLevelImage image = PGMReader<GreyLevelImage>::importPGM( f1 );
326  trace.endBlock();
327  trace.beginBlock("Building AT");
328  domain = image.domain();
329  K.init( domain.lowerBound(), domain.upperBound(), true );
330  AT.init( K );
331  AT.addInput( image, [] (unsigned char c ) { return ((double) c) / 255.0; } );
332  trace.endBlock();
333  }
334 
335  //---------------------------------------------------------------------------
336  if ( snr && color_image )
337  {
338  trace.beginBlock("Reading ideal PPM image");
339  ColorImage image = PPMReader<ColorImage>::importPPM( isnr );
340  trace.endBlock();
341  AT.addInput( image, [] ( Color c ) -> double { return ((double) c.red()) / 255.0; }, true );
342  AT.addInput( image, [] ( Color c ) -> double { return ((double) c.green()) / 255.0; }, true );
343  AT.addInput( image, [] ( Color c ) -> double { return ((double) c.blue()) / 255.0; }, true );
344  }
345  else if ( snr && grey_image )
346  {
347  trace.beginBlock("Reading ideal PGM image");
348  GreyLevelImage image = PGMReader<GreyLevelImage>::importPGM( isnr );
349  trace.endBlock();
350  AT.addInput( image, [] (unsigned char c ) { return ((double) c) / 255.0; }, true );
351  }
352 
353  //---------------------------------------------------------------------------
354  // Prepare zoomed output domain
355  Domain out_domain( pix_sz * domain.lowerBound(),
356  pix_sz * domain.upperBound() + Point::diagonal( pix_sz ) );
357  //---------------------------------------------------------------------------
358  AT.setUFromInput();
359  double g_snr = snr ? AT.computeSNR() : 0.0;
360 
361  if ( inpainting_mask.size() > 0 )
362  {
363  string fm = inpainting_mask;
364  trace.beginBlock("Reading inpainting mask");
365  GreyLevelImage mask = GenericReader<GreyLevelImage>::import( fm );
366  trace.endBlock();
367  Calculus::PrimalForm2 m( AT.calculus );
368  for ( Calculus::Index index = 0; index < m.myContainer.rows(); index++)
369  {
370  auto cell = m.getSCell( index );
371  double col = ((double) mask( K.sCoords( cell ) )) / 255.0;
372  m.myContainer( index ) = col > 0.0 ? 1.0 : 0.0;
373  }
374  AT.setAlpha( a, m );
375  AT.setUFromInputAndMask();
376  if ( grey_image )
377  {
378  ostringstream ossGM;
379  ossGM << boost::format("%s-g-mask.pgm") %f2;
380  GreyLevelImage image_mg( domain );
381  const Calculus::PrimalForm2 mg = functions::dec::diagonal( m ) * AT.getG( 0 );
382  functions::dec::form2ToGreyLevelImage
383  ( AT.calculus, mg, image_mg, 0.0, 1.0, 1 );
384  PGMWriter<GreyLevelImage>::exportPGM( ossGM.str(), image_mg );
385  }
386  else if ( color_image )
387  {
388  ostringstream ossGM;
389  ossGM << boost::format("%s-g-mask.ppm") %f2;
390  ColorImage image_mg( domain );
391  const Calculus::PrimalForm2 mg0 = functions::dec::diagonal( m ) * AT.getG( 0 );
392  const Calculus::PrimalForm2 mg1 = functions::dec::diagonal( m ) * AT.getG( 1 );
393  const Calculus::PrimalForm2 mg2 = functions::dec::diagonal( m ) * AT.getG( 2 );
394  functions::dec::threeForms2ToRGBColorImage
395  ( AT.calculus, mg0, mg1, mg2, image_mg, 0.0, 1.0, 1 );
396  PPMWriter<ColorImage, functors::Identity >::exportPPM( ossGM.str(), image_mg );
397  }
398  }
399  else
400  AT.setAlpha( a );
401 
402  trace.info() << AT << std::endl;
403  double n_v = 0.0;
404  double eps = 0.0;
405  while ( l1 >= l2 )
406  {
407  trace.info() << "************ lambda = " << l1 << " **************" << endl;
408  AT.setLambda( l1 );
409  for ( eps = e1; eps >= e2; eps /= er )
410  {
411  trace.info() << " ======= epsilon = " << eps << " ========" << endl;
412  AT.setEpsilon( eps );
413  int n = 0;
414  do {
415  trace.progressBar( n, nbiter );
416  AT.solveU();
417  AT.solveV();
418  AT.checkV();
419  n_v = AT.computeVariation();
420  } while ( ( n_v > 0.0001 ) && ( ++n < nbiter ) );
421  trace.progressBar( n, nbiter );
422  trace.info() << "[#### last variation = " << n_v << " " << endl;
423  }
424  if ( grey_image )
425  {
426  if ( verb > 0 ) trace.beginBlock("Writing u[0] as PGM image");
427  ostringstream ossU, ossV, ossW;
428  ossU << boost::format("%s-a%.5f-l%.7f-u.pgm") % f2 % a % l1;
429  ossV << boost::format("%s-a%.5f-l%.7f-u-v.pgm") % f2 % a % l1;
430  ossW << boost::format("%s-a%.5f-l%.7f-u-v.ppm") % f2 % a % l1;
431  const Calculus::PrimalForm2 u = AT.getU( 0 );
432  const Calculus::PrimalForm1 v = AT.M01 * AT.getV();
433  // Restored image
434  GreyLevelImage image_u( domain );
435  functions::dec::form2ToGreyLevelImage
436  ( AT.calculus, u, image_u, 0.0, 1.0, 1 );
437  PGMWriter<GreyLevelImage>::exportPGM( ossU.str(), image_u );
438  // Zoomed restored image with discontinuities (in black).
439  GreyLevelImage image_uv( out_domain );
440  functions::dec::form2ToGreyLevelImage
441  ( AT.calculus, u, image_uv, 0.0, 1.0, pix_sz );
442  functions::dec::primalForm1ToGreyLevelImage
443  ( AT.calculus, v, image_uv, 0.0, 1.0, pix_sz );
444  PGMWriter<GreyLevelImage>::exportPGM( ossV.str(), image_uv );
445  // Zoomed restored image with discontinuities (in specified color).
446  ColorImage cimage( out_domain );
447  functions::dec::threeForms2ToRGBColorImage
448  ( AT.calculus, u, u, u, cimage, 0.0, 1.0, pix_sz );
449  functions::dec::primalForm1ToRGBColorImage
450  ( AT.calculus, v, cimage, color_v, 0.0, 1.0, pix_sz );
451  PPMWriter<ColorImage, functors::Identity >::exportPPM( ossW.str(), cimage );
452  if ( verb > 0 ) trace.endBlock();
453  }
454  else if ( color_image )
455  {
456  if ( verb > 0 ) trace.beginBlock("Writing u[0,1,2] as PGM image");
457  ostringstream ossU, ossV;
458  ossU << boost::format("%s-a%.5f-l%.7f-u.ppm") % f2 % a % l1;
459  ossV << boost::format("%s-a%.5f-l%.7f-u-v.ppm") % f2 % a % l1;
460  const Calculus::PrimalForm2 u0 = AT.getU( 0 );
461  const Calculus::PrimalForm2 u1 = AT.getU( 1 );
462  const Calculus::PrimalForm2 u2 = AT.getU( 2 );
463  const Calculus::PrimalForm1 v = AT.M01 * AT.getV();
464  // Restored image
465  ColorImage image_u( domain );
466  functions::dec::threeForms2ToRGBColorImage
467  ( AT.calculus, u0, u1, u2, image_u, 0.0, 1.0, 1 );
468  PPMWriter<ColorImage, functors::Identity >::exportPPM( ossU.str(), image_u );
469  ColorImage image_uv( out_domain );
470  functions::dec::threeForms2ToRGBColorImage
471  ( AT.calculus, u0, u1, u2, image_uv, 0.0, 1.0, pix_sz );
472  functions::dec::primalForm1ToRGBColorImage
473  ( AT.calculus, v, image_uv, color_v, 0.0, 1.0, pix_sz );
474  PPMWriter<ColorImage, functors::Identity >::exportPPM( ossV.str(), image_uv );
475  if ( verb > 0 ) trace.endBlock();
476  }
477  // Compute SNR if possible
478  if ( snr )
479  {
480  double u_snr = AT.computeSNR();
481  trace.info() << "- SNR of u = " << u_snr << " SNR of g = " << g_snr << endl;
482  }
483  l1 /= lr;
484  }
485  return 0;
486 }
Definition: ATu0v1.h:57
Aim: This class solves Ambrosio-Tortorelli functional in a plane for u a (vector of) 2-form(s) and v ...
Definition: ATu2v0.h:75
DiscreteExteriorCalculus< 2, 2, LinearAlgebra > Calculus