// Copyright (c) 2010-2023, Lawrence Livermore National Security, LLC. Produced // at the Lawrence Livermore National Laboratory. All Rights reserved. See files // LICENSE and NOTICE for details. LLNL-CODE-806117. // // This file is part of the MFEM library. For more information and source code // availability visit https://mfem.org. // // MFEM is free software; you can redistribute it and/or modify it under the // terms of the BSD-3 license. We welcome feedback and contributions, see file // CONTRIBUTING.md for details. // // ------------------------------------------------- // Mondrian Miniapp: Convert an image to an AMR mesh // ------------------------------------------------- // // This miniapp is a specialized version of the Shaper miniapp that converts an // input image to an AMR mesh. It allows the fast approximate meshing of any // domain for which there is an image. // // The input to image should be in 8-bit grayscale PGM format. You can use a // number of image manipulation tools, such as GIMP (gimp.org) and ImageMagick's // convert utility (imagemagick.org/script/convert.php) to convert your image to // this format as a pre-processing step, e.g.: // // /usr/bin/convert australia.svg -compress none -depth 8 australia.pgm // // Compile with: make mondrian // // Sample runs: mondrian -i australia.pgm // mondrian -i australia.pgm -m ../../data/inline-tri.mesh // mondrian -i australia.pgm -m ../../data/disc-nurbs.mesh // mondrian -i australia.pgm -sd 3 -a -ncl -1 #include "mfem.hpp" #include #include using namespace mfem; using namespace std; // Simple class to parse portable graymap format (PGM) image files, see // http://netpbm.sourceforge.net/doc/pgm.html class ParsePGM { public: ParsePGM(const char *filename); ~ParsePGM(); int Height() const { return N; } int Width() const { return M; } int operator()(int i, int j) const { return int((pgm8) ? pgm8[M*i+j] : pgm16[M*i+j]); } private: int M, N; int depth; char *pgm8; unsigned short int *pgm16; void ReadMagicNumber(istream &in); void ReadComments(istream &in); void ReadDimensions(istream &in); void ReadDepth(istream &in); void ReadPGM(istream &in); }; // Given a point x, return its "material" specification defined by the grayscale // pixel values from the pgm image using NC different colors. int material(const ParsePGM &pgm, int NC, Vector &x, Vector &xmin, Vector &xmax); int main(int argc, char *argv[]) { const char *mesh_file = "../../data/inline-quad.mesh"; const char *img_file = "australia.pgm"; int sd = 2; int nclimit = 1; int ncolors = 3; bool aniso = false; bool visualization = 1; // Parse command line OptionsParser args(argc, argv); args.AddOption(&mesh_file, "-m", "--mesh", "Input mesh file to shape materials in."); args.AddOption(&img_file, "-i", "--img", "Input image."); args.AddOption(&sd, "-sd", "--sub-divisions", "Number of element subdivisions for interface detection."); args.AddOption(&nclimit, "-ncl", "--nc-limit", "Level of hanging nodes allowed (-1 = unlimited)."); args.AddOption(&ncolors, "-nc", "--num-colors", "Number of colors considered (1-256, based on binning)."); args.AddOption(&aniso, "-a", "--aniso", "-i", "--iso", "Enable anisotropic refinement of quads and hexes."); args.AddOption(&visualization, "-vis", "--visualization", "-no-vis", "--no-visualization", "Enable or disable GLVis visualization."); args.Parse(); if (!args.Good()) { args.PrintUsage(cout); return 1; } args.PrintOptions(cout); // Read the image ParsePGM pgm(img_file); // Read initial mesh, get dimensions and bounding box Mesh mesh(mesh_file, 1, 1); int dim = mesh.Dimension(); int sdim = mesh.SpaceDimension(); Vector xmin, xmax; mesh.GetBoundingBox(xmin, xmax); // NURBS meshes don't support non-conforming refinement for now if (mesh.NURBSext) { mesh.SetCurvature(2); } // Anisotropic refinement not supported for simplex meshes. if (mesh.MeshGenerator() & 1) { aniso = false; } // Mesh attributes will be visualized as piece-wise constants L2_FECollection attr_fec(0, dim); FiniteElementSpace attr_fespace(&mesh, &attr_fec); GridFunction attr(&attr_fespace); // GLVis server to visualize to socketstream sol_sock; if (visualization) { char vishost[] = "localhost"; int visport = 19916; sol_sock.open(vishost, visport); sol_sock.precision(8); } // Shaping loop for (int iter = 0; 1; iter++) { Array refs; for (int i = 0; i < mesh.GetNE(); i++) { bool refine = false; // Sample materials in each element using "sd" sub-divisions Vector pt; Geometry::Type geom = mesh.GetElementBaseGeometry(i); ElementTransformation *T = mesh.GetElementTransformation(i); RefinedGeometry *RefG = GlobGeometryRefiner.Refine(geom, sd, 1); IntegrationRule &ir = RefG->RefPts; // Refine any element where different materials are detected. A more // sophisticated logic can be implemented here -- e.g. don't refine // the interfaces between certain materials. Array mat(ir.GetNPoints()); double matsum = 0.0; for (int j = 0; j < ir.GetNPoints(); j++) { T->Transform(ir.IntPoint(j), pt); int m = material(pgm, 256/ncolors, pt, xmin, xmax); mat[j] = m; matsum += m; if ((int)matsum != m*(j+1)) { refine = true; } } // Set the element attribute as the "average". Other choices are // possible here too, e.g. attr(i) = mat; attr(i) = round(matsum/ir.GetNPoints()); // Mark the element for refinement if (refine) { int type = 7; if (aniso) { // Determine the XYZ bitmask for anisotropic refinement. int dx = 0, dy = 0, dz = 0; const int s = sd+1; if (dim == 2) { for (int jj = 0; jj <= sd; jj++) for (int ii = 0; ii < sd; ii++) { dx += abs(mat[jj*s + ii+1] - mat[jj*s + ii]); dy += abs(mat[(ii+1)*s + jj] - mat[ii*s + jj]); } } else if (dim == 3) { for (int kk = 0; kk <= sd; kk++) for (int jj = 0; jj <= sd; jj++) for (int ii = 0; ii < sd; ii++) { dx += abs(mat[(kk*s + jj)*s + ii+1] - mat[(kk*s + jj)*s + ii]); dy += abs(mat[(kk*s + ii+1)*s + jj] - mat[(kk*s + ii)*s + jj]); dz += abs(mat[((ii+1)*s + jj)*s + kk] - mat[(ii*s + jj)*s + kk]); } } type = 0; const int tol = mat.Size() / 10; if (dx > tol) { type |= 1; } if (dy > tol) { type |= 2; } if (dz > tol) { type |= 4; } if (!type) { type = 7; } // because of tol } refs.Append(Refinement(i, type)); } } // Visualization if (visualization) { sol_sock << "solution\n" << mesh << attr; if (iter == 0 && sdim == 2) { sol_sock << "keys 'RjlmpppppppppppppA*************'\n"; } if (iter == 0 && sdim == 3) { sol_sock << "keys 'YYYYYYYYYXXXXXXXmA********8888888pppttt"; if (dim == 3) { sol_sock << "iiM"; } sol_sock << "'\n"; } sol_sock << flush; } // Ask the user if we should continue refining cout << "Iteration " << iter+1 << ": mesh has " << mesh.GetNE() << " elements. \n"; if ((iter+1) % 3 == 0) { if (!visualization) { break; } char yn; cout << "Continue shaping? --> "; cin >> yn; if (yn == 'n' || yn == 'q') { break; } } // Perform refinement, update spaces and grid functions mesh.GeneralRefinement(refs, -1, nclimit); attr_fespace.Update(); attr.Update(); } // Set element attributes in the mesh object before saving for (int i = 0; i < mesh.GetNE(); i++) { mesh.SetAttribute(i, static_cast(attr(i))); } mesh.SetAttributes(); // Save the final mesh ofstream mesh_ofs("mondrian.mesh"); mesh_ofs.precision(8); mesh.Print(mesh_ofs); } ParsePGM::ParsePGM(const char *filename) : M(-1), N(-1), depth(-1), pgm8(NULL), pgm16(NULL) { ifstream in(filename); if (!in) { // Abort with an error message MFEM_ABORT("Image file not found: " << filename << '\n'); } ReadMagicNumber(in); ReadDimensions(in); ReadDepth(in); ReadPGM(in); in.close(); } ParsePGM::~ParsePGM() { if (pgm8 != NULL) { delete [] pgm8; } if (pgm16 != NULL) { delete [] pgm16; } } void ParsePGM::ReadMagicNumber(istream &in) { char c; int p; in >> c >> p; // Read magic number which should be P2 or P5 MFEM_VERIFY(c == 'P' && (p == 2 || p == 5), "Invalid PGM file! Unrecognized magic number\"" << c << p << "\"."); ReadComments(in); } void ParsePGM::ReadComments(istream &in) { string buf; in >> std::ws; // absorb any white space while (in.peek() == '#') { std::getline(in,buf); } in >> std::ws; // absorb any white space } void ParsePGM::ReadDimensions(istream &in) { in >> M; ReadComments(in); in >> N; ReadComments(in); } void ParsePGM::ReadDepth(istream &in) { in >> depth; ReadComments(in); } void ParsePGM::ReadPGM(istream &in) { if (depth < 16) { pgm8 = new char[M*N]; } else { pgm16 = new unsigned short int[M*N]; } if (pgm8) { for (int i=0; i> pgm8[i]; } } else { for (int i=0; i> pgm16[i]; } } } int material(const ParsePGM &pgm, int NC, Vector &x, Vector &xmin, Vector &xmax) { // Rescaling to [0,1]^sdim for (int i = 0; i < x.Size(); i++) { x(i) = (x(i)-xmin(i))/(xmax(i)-xmin(i)); } int M = pgm.Width(); int N = pgm.Height(); int i = (int)(x(1)*N), j = (int)(x(0)*M); if (i == N) { i = N-1; } if (j == M) { j = M-1; } i = N-1-i; return pgm(i,j)/NC+1; }