MFEM v4.8.0
Finite element discretization library
Loading...
Searching...
No Matches
mondrian.cpp
Go to the documentation of this file.
1// Copyright (c) 2010-2025, Lawrence Livermore National Security, LLC. Produced
2// at the Lawrence Livermore National Laboratory. All Rights reserved. See files
3// LICENSE and NOTICE for details. LLNL-CODE-806117.
4//
5// This file is part of the MFEM library. For more information and source code
6// availability visit https://mfem.org.
7//
8// MFEM is free software; you can redistribute it and/or modify it under the
9// terms of the BSD-3 license. We welcome feedback and contributions, see file
10// CONTRIBUTING.md for details.
11//
12// -------------------------------------------------
13// Mondrian Miniapp: Convert an image to an AMR mesh
14// -------------------------------------------------
15//
16// This miniapp is a specialized version of the Shaper miniapp that converts an
17// input image to an AMR mesh. It allows the fast approximate meshing of any
18// domain for which there is an image.
19//
20// The input to image should be in 8-bit grayscale PGM format. You can use a
21// number of image manipulation tools, such as GIMP (gimp.org) and ImageMagick's
22// convert utility (imagemagick.org/script/convert.php) to convert your image to
23// this format as a pre-processing step, e.g.:
24//
25// /usr/bin/convert australia.svg -compress none -depth 8 australia.pgm
26//
27// Compile with: make mondrian
28//
29// Sample runs: mondrian -i australia.pgm
30// mondrian -i australia.pgm -m ../../data/inline-tri.mesh
31// mondrian -i australia.pgm -m ../../data/disc-nurbs.mesh
32// mondrian -i australia.pgm -sd 3 -a -ncl -1
33
34#include "mfem.hpp"
35#include <fstream>
36#include <iostream>
37
38using namespace mfem;
39using namespace std;
40
41// Simple class to parse portable graymap format (PGM) image files, see
42// http://netpbm.sourceforge.net/doc/pgm.html
43class ParsePGM
44{
45public:
46 ParsePGM(const char *filename);
47 ~ParsePGM();
48
49 int Height() const { return N; }
50 int Width() const { return M; }
51
52 int operator()(int i, int j) const
53 { return int((pgm8) ? pgm8[M*i+j] : pgm16[M*i+j]); }
54
55private:
56 int M, N;
57 int depth;
58
59 char *pgm8;
60 unsigned short int *pgm16;
61
62 void ReadMagicNumber(istream &in);
63 void ReadComments(istream &in);
64 void ReadDimensions(istream &in);
65 void ReadDepth(istream &in);
66 void ReadPGM(istream &in);
67};
68
69// Given a point x, return its "material" specification defined by the grayscale
70// pixel values from the pgm image using NC different colors.
71int material(const ParsePGM &pgm, int NC,
72 Vector &x, Vector &xmin, Vector &xmax);
73
74int main(int argc, char *argv[])
75{
76 const char *mesh_file = "../../data/inline-quad.mesh";
77 const char *img_file = "australia.pgm";
78 int sd = 2;
79 int nclimit = 1;
80 int ncolors = 3;
81 bool aniso = false;
82 bool visualization = 1;
83 int visport = 19916;
84
85 // Parse command line
86 OptionsParser args(argc, argv);
87 args.AddOption(&mesh_file, "-m", "--mesh",
88 "Input mesh file to shape materials in.");
89 args.AddOption(&img_file, "-i", "--img",
90 "Input image.");
91 args.AddOption(&sd, "-sd", "--sub-divisions",
92 "Number of element subdivisions for interface detection.");
93 args.AddOption(&nclimit, "-ncl", "--nc-limit",
94 "Level of hanging nodes allowed (-1 = unlimited).");
95 args.AddOption(&ncolors, "-nc", "--num-colors",
96 "Number of colors considered (1-256, based on binning).");
97 args.AddOption(&aniso, "-a", "--aniso", "-i", "--iso",
98 "Enable anisotropic refinement of quads and hexes.");
99 args.AddOption(&visualization, "-vis", "--visualization", "-no-vis",
100 "--no-visualization",
101 "Enable or disable GLVis visualization.");
102 args.AddOption(&visport, "-p", "--send-port", "Socket for GLVis.");
103 args.Parse();
104 if (!args.Good()) { args.PrintUsage(cout); return 1; }
105 args.PrintOptions(cout);
106
107 // Read the image
108 ParsePGM pgm(img_file);
109
110 // Read initial mesh, get dimensions and bounding box
111 Mesh mesh(mesh_file, 1, 1);
112 int dim = mesh.Dimension();
113 int sdim = mesh.SpaceDimension();
114 Vector xmin, xmax;
115 mesh.GetBoundingBox(xmin, xmax);
116
117 // NURBS meshes don't support non-conforming refinement for now
118 if (mesh.NURBSext) { mesh.SetCurvature(2); }
119
120 // Anisotropic refinement not supported for simplex meshes.
121 if (mesh.MeshGenerator() & 1) { aniso = false; }
122
123 // Mesh attributes will be visualized as piece-wise constants
124 L2_FECollection attr_fec(0, dim);
125 FiniteElementSpace attr_fespace(&mesh, &attr_fec);
126 GridFunction attr(&attr_fespace);
127
128 // GLVis server to visualize to
129 socketstream sol_sock;
130 if (visualization)
131 {
132 char vishost[] = "localhost";
133 sol_sock.open(vishost, visport);
134 sol_sock.precision(8);
135 }
136
137 // Shaping loop
138 for (int iter = 0; 1; iter++)
139 {
141 for (int i = 0; i < mesh.GetNE(); i++)
142 {
143 bool refine = false;
144
145 // Sample materials in each element using "sd" sub-divisions
146 Vector pt;
149 RefinedGeometry *RefG = GlobGeometryRefiner.Refine(geom, sd, 1);
150 IntegrationRule &ir = RefG->RefPts;
151
152 // Refine any element where different materials are detected. A more
153 // sophisticated logic can be implemented here -- e.g. don't refine
154 // the interfaces between certain materials.
155 Array<int> mat(ir.GetNPoints());
156 real_t matsum = 0.0;
157 for (int j = 0; j < ir.GetNPoints(); j++)
158 {
159 T->Transform(ir.IntPoint(j), pt);
160 int m = material(pgm, 256/ncolors, pt, xmin, xmax);
161 mat[j] = m;
162 matsum += m;
163 if ((int)matsum != m*(j+1))
164 {
165 refine = true;
166 }
167 }
168
169 // Set the element attribute as the "average". Other choices are
170 // possible here too, e.g. attr(i) = mat;
171 attr(i) = round(matsum/ir.GetNPoints());
172
173 // Mark the element for refinement
174 if (refine)
175 {
176 int type = 7;
177 if (aniso)
178 {
179 // Determine the XYZ bitmask for anisotropic refinement.
180 int dx = 0, dy = 0, dz = 0;
181 const int s = sd+1;
182 if (dim == 2)
183 {
184 for (int jj = 0; jj <= sd; jj++)
185 for (int ii = 0; ii < sd; ii++)
186 {
187 dx += abs(mat[jj*s + ii+1] - mat[jj*s + ii]);
188 dy += abs(mat[(ii+1)*s + jj] - mat[ii*s + jj]);
189 }
190 }
191 else if (dim == 3)
192 {
193 for (int kk = 0; kk <= sd; kk++)
194 for (int jj = 0; jj <= sd; jj++)
195 for (int ii = 0; ii < sd; ii++)
196 {
197 dx += abs(mat[(kk*s + jj)*s + ii+1] - mat[(kk*s + jj)*s + ii]);
198 dy += abs(mat[(kk*s + ii+1)*s + jj] - mat[(kk*s + ii)*s + jj]);
199 dz += abs(mat[((ii+1)*s + jj)*s + kk] - mat[(ii*s + jj)*s + kk]);
200 }
201 }
202 type = 0;
203 const int tol = mat.Size() / 10;
204 if (dx > tol) { type |= 1; }
205 if (dy > tol) { type |= 2; }
206 if (dz > tol) { type |= 4; }
207 if (!type) { type = 7; } // because of tol
208 }
209
210 refs.Append(Refinement(i, type));
211 }
212 }
213
214 // Visualization
215 if (visualization)
216 {
217 sol_sock << "solution\n" << mesh << attr;
218 if (iter == 0 && sdim == 2)
219 {
220 sol_sock << "keys 'RjlmpppppppppppppA*************'\n";
221 }
222 if (iter == 0 && sdim == 3)
223 {
224 sol_sock << "keys 'YYYYYYYYYXXXXXXXmA********8888888pppttt";
225 if (dim == 3) { sol_sock << "iiM"; }
226 sol_sock << "'\n";
227 }
228 sol_sock << flush;
229 }
230
231 // Ask the user if we should continue refining
232 cout << "Iteration " << iter+1 << ": mesh has " << mesh.GetNE() <<
233 " elements. \n";
234 if ((iter+1) % 3 == 0)
235 {
236 if (!visualization) { break; }
237 char yn;
238 cout << "Continue shaping? --> ";
239 cin >> yn;
240 if (yn == 'n' || yn == 'q') { break; }
241 }
242
243 // Perform refinement, update spaces and grid functions
244 mesh.GeneralRefinement(refs, -1, nclimit);
245 attr_fespace.Update();
246 attr.Update();
247 }
248
249 // Set element attributes in the mesh object before saving
250 for (int i = 0; i < mesh.GetNE(); i++)
251 {
252 mesh.SetAttribute(i, static_cast<int>(attr(i)));
253 }
254 mesh.SetAttributes();
255
256 // Save the final mesh
257 ofstream mesh_ofs("mondrian.mesh");
258 mesh_ofs.precision(8);
259 mesh.Print(mesh_ofs);
260}
261
262ParsePGM::ParsePGM(const char *filename)
263 : M(-1), N(-1), depth(-1), pgm8(NULL), pgm16(NULL)
264{
265 ifstream in(filename);
266 if (!in)
267 {
268 // Abort with an error message
269 MFEM_ABORT("Image file not found: " << filename << '\n');
270 }
271
272 ReadMagicNumber(in);
273 ReadDimensions(in);
274 ReadDepth(in);
275 ReadPGM(in);
276
277 in.close();
278}
279
280ParsePGM::~ParsePGM()
281{
282 if (pgm8 != NULL) { delete [] pgm8; }
283 if (pgm16 != NULL) { delete [] pgm16; }
284}
285
286void ParsePGM::ReadMagicNumber(istream &in)
287{
288 char c;
289 int p;
290 in >> c >> p; // Read magic number which should be P2 or P5
291 MFEM_VERIFY(c == 'P' && (p == 2 || p == 5),
292 "Invalid PGM file! Unrecognized magic number\""
293 << c << p << "\".");
294 ReadComments(in);
295}
296
297void ParsePGM::ReadComments(istream &in)
298{
299 string buf;
300 in >> std::ws; // absorb any white space
301 while (in.peek() == '#')
302 {
303 std::getline(in,buf);
304 }
305 in >> std::ws; // absorb any white space
306}
307
308void ParsePGM::ReadDimensions(istream &in)
309{
310 in >> M;
311 ReadComments(in);
312 in >> N;
313 ReadComments(in);
314}
315
316void ParsePGM::ReadDepth(istream &in)
317{
318 in >> depth;
319 ReadComments(in);
320}
321
322void ParsePGM::ReadPGM(istream &in)
323{
324 if (depth < 16)
325 {
326 pgm8 = new char[M*N];
327 }
328 else
329 {
330 pgm16 = new unsigned short int[M*N];
331 }
332
333 if (pgm8)
334 {
335 for (int i=0; i<M*N; i++)
336 {
337 in >> pgm8[i];
338 }
339 }
340 else
341 {
342 for (int i=0; i<M*N; i++)
343 {
344 in >> pgm16[i];
345 }
346 }
347}
348
349int material(const ParsePGM &pgm, int NC, Vector &x, Vector &xmin, Vector &xmax)
350{
351 // Rescaling to [0,1]^sdim
352 for (int i = 0; i < x.Size(); i++)
353 {
354 x(i) = (x(i)-xmin(i))/(xmax(i)-xmin(i));
355 }
356
357 int M = pgm.Width();
358 int N = pgm.Height();
359
360 int i = (int)(x(1)*N), j = (int)(x(0)*M);
361 if (i == N) { i = N-1; }
362 if (j == M) { j = M-1; }
363 i = N-1-i;
364
365 return pgm(i,j)/NC+1;
366}
int Size() const
Return the logical size of the array.
Definition array.hpp:147
int Append(const T &el)
Append element 'el' to array, resize if necessary.
Definition array.hpp:830
virtual void Transform(const IntegrationPoint &, Vector &)=0
Transform integration point from reference coordinates to physical coordinates and store them in the ...
Class FiniteElementSpace - responsible for providing FEM view of the mesh, mainly managing the set of...
Definition fespace.hpp:244
virtual void Update(bool want_transform=true)
Reflect changes in the mesh: update number of DOFs, etc. Also, calculate GridFunction transformation ...
Definition fespace.cpp:4157
RefinedGeometry * Refine(Geometry::Type Geom, int Times, int ETimes=1)
Definition geom.cpp:1136
Class for grid function - Vector with associated FE space.
Definition gridfunc.hpp:31
virtual void Update()
Transform by the Space UpdateMatrix (e.g., on Mesh change).
Definition gridfunc.cpp:167
Class for an integration rule - an Array of IntegrationPoint.
Definition intrules.hpp:100
int GetNPoints() const
Returns the number of the points in the integration rule.
Definition intrules.hpp:256
IntegrationPoint & IntPoint(int i)
Returns a reference to the i-th integration point.
Definition intrules.hpp:259
Arbitrary order "L2-conforming" discontinuous finite elements.
Definition fe_coll.hpp:346
Mesh data type.
Definition mesh.hpp:64
NURBSExtension * NURBSext
Optional NURBS mesh extension.
Definition mesh.hpp:298
void GeneralRefinement(const Array< Refinement > &refinements, int nonconforming=-1, int nc_limit=0)
Definition mesh.cpp:10883
void SetAttribute(int i, int attr)
Set the attribute of element i.
Definition mesh.cpp:7629
virtual void Print(std::ostream &os=mfem::out, const std::string &comments="") const
Definition mesh.hpp:2433
int GetNE() const
Returns number of elements.
Definition mesh.hpp:1282
void GetBoundingBox(Vector &min, Vector &max, int ref=2)
Returns the minimum and maximum corners of the mesh bounding box.
Definition mesh.cpp:138
int Dimension() const
Dimension of the reference space used within the elements.
Definition mesh.hpp:1216
void GetElementTransformation(int i, IsoparametricTransformation *ElTr) const
Builds the transformation defining the i-th element in ElTr. ElTr must be allocated in advance and wi...
Definition mesh.cpp:357
int SpaceDimension() const
Dimension of the physical space containing the mesh.
Definition mesh.hpp:1219
int MeshGenerator() const
Get the mesh generator/type.
Definition mesh.hpp:1243
virtual void SetCurvature(int order, bool discont=false, int space_dim=-1, int ordering=1)
Set the curvature of the mesh nodes using the given polynomial degree.
Definition mesh.cpp:6484
Geometry::Type GetElementBaseGeometry(int i) const
Definition mesh.hpp:1455
virtual void SetAttributes()
Determine the sets of unique attribute values in domain and boundary elements.
Definition mesh.cpp:1819
void Parse()
Parse the command-line options. Note that this function expects all the options provided through the ...
void PrintUsage(std::ostream &out) const
Print the usage message.
void PrintOptions(std::ostream &out) const
Print the options.
void AddOption(bool *var, const char *enable_short_name, const char *enable_long_name, const char *disable_short_name, const char *disable_long_name, const char *description, bool required=false)
Add a boolean option and set 'var' to receive the value. Enable/disable tags are used to set the bool...
Definition optparser.hpp:82
bool Good() const
Return true if the command line options were parsed successfully.
IntegrationRule RefPts
Definition geom.hpp:321
Vector data type.
Definition vector.hpp:82
int Size() const
Returns the size of the vector.
Definition vector.hpp:226
int open(const char hostname[], int port)
Open the socket stream on 'port' at 'hostname'.
int dim
Definition ex24.cpp:53
int main()
int material(const ParsePGM &pgm, int NC, Vector &x, Vector &xmin, Vector &xmax)
Definition mondrian.cpp:349
GeometryRefiner GlobGeometryRefiner
Definition geom.cpp:2014
float real_t
Definition config.hpp:43
const char vishost[]
real_t p(const Vector &x, real_t t)