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