www.mooseframework.org
ImageMeshGenerator.C
Go to the documentation of this file.
1 //* This file is part of the MOOSE framework
2 //* https://www.mooseframework.org
3 //*
4 //* All rights reserved, see COPYRIGHT for full restrictions
5 //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6 //*
7 //* Licensed under LGPL 2.1, please see LICENSE for details
8 //* https://www.gnu.org/licenses/lgpl-2.1.html
9 
10 #include "ImageMeshGenerator.h"
11 #include "pcrecpp.h"
12 #include "MooseApp.h"
13 #include "CastUniquePointer.h"
14 
15 #include "libmesh/unstructured_mesh.h"
16 #include "libmesh/replicated_mesh.h"
17 #include "libmesh/mesh_generation.h"
18 #include "libmesh/face_quad4.h"
19 #include "libmesh/cell_hex8.h"
20 
21 #include <cstdlib> // std::system, mkstemp
22 #include <fstream>
23 
25 
26 template <>
29 {
32  params.addClassDescription("Generated mesh with the aspect ratio of a given image stack.");
33 
34  // Add ImageMeshGenerator-specific params
35  params.addParam<bool>(
36  "scale_to_one", true, "Whether or not to scale the image so its max dimension is 1");
37  params.addRangeCheckedParam<Real>("cells_per_pixel",
38  1.0,
39  "cells_per_pixel<=1.0",
40  "The number of mesh cells per pixel, must be <=1 ");
41 
42  return params;
43 }
44 
46  : GeneratedMeshGenerator(parameters),
47  FileRangeBuilder(parameters),
48  _scale_to_one(getParam<bool>("scale_to_one")),
49  _cells_per_pixel(getParam<Real>("cells_per_pixel"))
50 {
51 }
52 
53 std::unique_ptr<MeshBase>
55 {
56  auto mesh = libmesh_make_unique<ReplicatedMesh>(comm());
57 
58  // A list of filenames of length 1 means we are building a 2D mesh
59  if (_filenames.size() == 1)
60  buildMesh2D(_filenames[0], mesh);
61 
62  else
63  buildMesh3D(_filenames, mesh);
64 
65  return dynamic_pointer_cast<MeshBase>(mesh);
66 }
67 
68 void
69 ImageMeshGenerator::buildMesh3D(const std::vector<std::string> & filenames,
70  std::unique_ptr<ReplicatedMesh> & mesh)
71 {
72  // If the user gave us a "stack" with 0 or 1 files in it, we can't
73  // really create a 3D Mesh from that
74  if (filenames.size() <= 1)
75  mooseError("ImageMesh error: Cannot create a 3D ImageMesh from an image stack with ",
76  filenames.size(),
77  " images.");
78 
79  // For each file in the stack, process it using the 'file' command.
80  // We want to be sure that all the images in the stack are the same
81  // size, for example...
82  int xpixels = 0, ypixels = 0, zpixels = filenames.size();
83 
84  // Take pixel info from the first image in the stack to determine the aspect ratio
85  GetPixelInfo(filenames[0], xpixels, ypixels);
86 
87  // TODO: Check that all images are the same aspect ratio and have
88  // the same number of pixels? ImageFunction does not currently do
89  // this...
90  // for (const auto & filename : filenames)
91  // {
92  // // Extract the number of pixels from the image using the file command
93  // GetPixelInfo(filename, xpixels, ypixels);
94  //
95  // // Moose::out << "Image " << filename << " has size: " << xpixels << " by " << ypixels <<
96  // std::endl;
97  // }
98 
99  // Use the number of x and y pixels and the number of images to
100  // determine the the x, y, and z dimensions of the mesh. We assume
101  // that there is 1 pixel in the z-direction for each image in the
102  // stack.
103 
104  // Set the maximum dimension to 1.0 while scaling the other
105  // directions to maintain the aspect ratio.
106  _xmax = xpixels;
107  _ymax = ypixels;
108  _zmax = zpixels;
109 
110  if (_scale_to_one)
111  {
112  Real max = std::max(std::max(_xmax, _ymax), _zmax);
113  _xmax /= max;
114  _ymax /= max;
115  _zmax /= max;
116  }
117 
118  // Compute the number of cells in the x and y direction based on
119  // the user's cells_per_pixel parameter. Note: we use ints here
120  // because the GeneratedMesh params object uses ints for these...
121  _nx = static_cast<int>(_cells_per_pixel * xpixels);
122  _ny = static_cast<int>(_cells_per_pixel * ypixels);
123  _nz = static_cast<int>(_cells_per_pixel * zpixels);
124 
125  // Actually build the Mesh
126  MeshTools::Generation::build_cube(dynamic_cast<UnstructuredMesh &>(*mesh),
127  _nx,
128  _ny,
129  _nz,
130  /*xmin=*/0.,
131  /*xmax=*/_xmax,
132  /*ymin=*/0.,
133  /*ymax=*/_ymax,
134  /*zmin=*/0.,
135  /*zmax=*/_zmax,
136  HEX8);
137 }
138 
139 void
140 ImageMeshGenerator::buildMesh2D(const std::string & filename,
141  std::unique_ptr<ReplicatedMesh> & mesh)
142 {
143  int xpixels = 0, ypixels = 0;
144 
145  // Extract the number of pixels from the image using the file command
146  GetPixelInfo(filename, xpixels, ypixels);
147 
148  // Set the maximum dimension to 1.0 while scaling the other
149  // direction to maintain the aspect ratio.
150  _xmax = xpixels;
151  _ymax = ypixels;
152 
153  if (_scale_to_one)
154  {
155  Real max = std::max(_xmax, _ymax);
156  _xmax /= max;
157  _ymax /= max;
158  }
159 
160  // Compute the number of cells in the x and y direction based on
161  // the user's cells_per_pixel parameter. Note: we use ints here
162  // because the GeneratedMesh params object uses ints for these...
163  _nx = static_cast<int>(_cells_per_pixel * xpixels);
164  _ny = static_cast<int>(_cells_per_pixel * ypixels);
165 
166  // Actually build the Mesh
167  MeshTools::Generation::build_square(dynamic_cast<UnstructuredMesh &>(*mesh),
168  _nx,
169  _ny,
170  /*xmin=*/0.,
171  /*xmax=*/_xmax,
172  /*ymin=*/0.,
173  /*ymax=*/_ymax,
174  QUAD4);
175 }
176 
177 void
178 ImageMeshGenerator::GetPixelInfo(std::string filename, int & xpixels, int & ypixels)
179 {
180  // For reporting possible error messages
181  std::string error_message = "";
182 
183  // A template for creating a temporary file.
184  char temp_file[] = "file_command_output.XXXXXX";
185 
186  // Use a do-loop so we can break out under various error conditions
187  // while still cleaning up temporary files. Basically use goto
188  // statements without actually using them.
189  do
190  {
191  // mkstemp is not in namespace std for whatever reason...
192  int fd = mkstemp(temp_file);
193 
194  // If mkstemp fails, we failed.
195  if (fd == -1)
196  {
197  error_message = "Error creating temporary file in ImageMesh::buildMesh()";
198  break;
199  }
200 
201  // Construct the command string
202  std::ostringstream command;
203  command << "file " << filename << " 2>/dev/null 1>" << temp_file;
204 
205  // Make the system call, catch the return code
206  int exit_status = std::system(command.str().c_str());
207 
208  // If the system command returned a non-zero status, we failed.
209  if (exit_status != 0)
210  {
211  error_message = "Error calling 'file' command in ImageMesh::buildMesh()";
212  break;
213  }
214 
215  // Open the file which contains the result of the system command
216  std::ifstream fin(temp_file);
217 
218  // Read the contents of the output file into a string
219  std::string command_result;
220  std::getline(fin, command_result);
221 
222  // A regular expression which matches "NNN x NNN" , i.e. any number
223  // of digits, a space, an 'x', a space, and any number of digits.
224  // The parentheses define capture groups which are stored into the
225  // xsize and ysize integers.
226  // Here's an example string:
227  // sixteenth_image001_cropped3_closing_298.png: PNG image data, 115 x 99, 16-bit/color RGB,
228  // non-interlaced
229  xpixels = 0, ypixels = 0;
230  pcrecpp::RE re("(\\d+) x (\\d+)");
231  re.PartialMatch(command_result, &xpixels, &ypixels);
232 
233  // Detect failure of the regex
234  if ((xpixels == 0) || (ypixels == 0))
235  {
236  error_message = "Regex failed to find a match in " + command_result;
237  break;
238  }
239  } while (false);
240 
241  // Remove the temporary file. This will still work even if the file was never created...
242  std::remove(temp_file);
243 
244  // Report and exit if there was an error
245  if (error_message != "")
246  mooseError(error_message);
247 }
unsigned int _nx
Number of elements in x, y, z direction.
const bool _scale_to_one
If true, forces the maximum width (height) of the mesh to be 1.0 while retaining the original aspect ...
void build_cube(UnstructuredMesh &mesh, const unsigned int nx, unsigned int ny, unsigned int nz, const Real xmin, const Real xmax, const Real ymin, const Real ymax, const Real zmin, const Real zmax, const ElemType type, bool verbose)
InputParameters validParams< GeneratedMeshGenerator >()
void GetPixelInfo(std::string filename, int &xpixels, int &ypixels)
Process a single image with the &#39;file&#39; command to find out the number of pixels in the x and y direct...
const std::vector< std::string > & filenames()
Generates a line, square, or cube mesh with uniformly spaced or biased elements.
The main MOOSE class responsible for handling user-defined parameters in almost every MOOSE system...
std::unique_ptr< T_DEST, T_DELETER > dynamic_pointer_cast(std::unique_ptr< T_SRC, T_DELETER > &src)
These are reworked from https://stackoverflow.com/a/11003103.
void mooseError(Args &&... args) const
Definition: MooseObject.h:147
nl system()
std::unique_ptr< MeshBase > generate() override
Generate / modify the mesh.
ImageMeshGenerator(const InputParameters &parameters)
void buildMesh2D(const std::string &filename, std::unique_ptr< ReplicatedMesh > &mesh)
generate() calls this helper function to build 2D ImageMeshes.
InputParameters validParams< FileRangeBuilder >()
To be called in the validParams functions of classes that need to operate on ranges of files...
InputParameters validParams< ImageMeshGenerator >()
Augments an InputParameters object with file range information.
const Real & _cells_per_pixel
A number <= 1.0 which determines the number of cells in the mesh per pixel in each direction...
registerMooseObject("MooseApp", ImageMeshGenerator)
MPI_Comm comm
void addClassDescription(const std::string &doc_string)
This method adds a description of the class that will be displayed in the input file syntax dump...
void buildMesh3D(const std::vector< std::string > &filenames, std::unique_ptr< ReplicatedMesh > &mesh)
generate() calls this helper function to build 3D ImageMeshes from stacks of images.
A 2D GeneratedMesh where xmin, xmax, etc.
std::vector< std::string > _filenames