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