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