Line data Source code
1 : //* This file is part of the MOOSE framework
2 : //* https://mooseframework.inl.gov
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 :
24 : registerMooseObject("MooseApp", ImageMeshGenerator);
25 :
26 : InputParameters
27 14281 : ImageMeshGenerator::validParams()
28 : {
29 14281 : InputParameters params = GeneratedMeshGenerator::validParams();
30 14281 : params += FileRangeBuilder::validParams();
31 14281 : params.addClassDescription("Generated mesh with the aspect ratio of a given image stack.");
32 :
33 : // Add ImageMeshGenerator-specific params
34 42843 : params.addParam<bool>(
35 28562 : "scale_to_one", true, "Whether or not to scale the image so its max dimension is 1");
36 42843 : params.addRangeCheckedParam<Real>("cells_per_pixel",
37 28562 : 1.0,
38 : "cells_per_pixel<=1.0",
39 : "The number of mesh cells per pixel, must be <=1 ");
40 :
41 14281 : return params;
42 0 : }
43 :
44 8 : ImageMeshGenerator::ImageMeshGenerator(const InputParameters & parameters)
45 : : GeneratedMeshGenerator(parameters),
46 : FileRangeBuilder(parameters),
47 8 : _scale_to_one(getParam<bool>("scale_to_one")),
48 16 : _cells_per_pixel(getParam<Real>("cells_per_pixel"))
49 : {
50 8 : declareMeshProperty("use_distributed_mesh", false);
51 8 : }
52 :
53 : std::unique_ptr<MeshBase>
54 8 : ImageMeshGenerator::generate()
55 : {
56 8 : auto mesh = buildReplicatedMesh();
57 :
58 : // A list of filenames of length 1 means we are building a 2D mesh
59 8 : if (_filenames.size() == 1)
60 8 : buildMesh2D(_filenames[0], *mesh);
61 :
62 : else
63 0 : buildMesh3D(_filenames, *mesh);
64 :
65 16 : return dynamic_pointer_cast<MeshBase>(mesh);
66 8 : }
67 :
68 : void
69 0 : ImageMeshGenerator::buildMesh3D(const std::vector<std::string> & filenames, MeshBase & mesh)
70 : {
71 : // If the user gave us a "stack" with 0 or 1 files in it, we can't
72 : // really create a 3D Mesh from that
73 0 : if (filenames.size() <= 1)
74 0 : mooseError("ImageMesh error: Cannot create a 3D ImageMesh from an image stack with ",
75 0 : filenames.size(),
76 : " images.");
77 :
78 : // For each file in the stack, process it using the 'file' command.
79 : // We want to be sure that all the images in the stack are the same
80 : // size, for example...
81 0 : int xpixels = 0, ypixels = 0, zpixels = filenames.size();
82 :
83 : // Take pixel info from the first image in the stack to determine the aspect ratio
84 0 : GetPixelInfo(filenames[0], xpixels, ypixels);
85 :
86 : // TODO: Check that all images are the same aspect ratio and have
87 : // the same number of pixels? ImageFunction does not currently do
88 : // this...
89 : // for (const auto & filename : filenames)
90 : // {
91 : // // Extract the number of pixels from the image using the file command
92 : // GetPixelInfo(filename, xpixels, ypixels);
93 : //
94 : // // Moose::out << "Image " << filename << " has size: " << xpixels << " by " << ypixels <<
95 : // std::endl;
96 : // }
97 :
98 : // Use the number of x and y pixels and the number of images to
99 : // determine the the x, y, and z dimensions of the mesh. We assume
100 : // that there is 1 pixel in the z-direction for each image in the
101 : // stack.
102 :
103 : // Set the maximum dimension to 1.0 while scaling the other
104 : // directions to maintain the aspect ratio.
105 0 : _xmax = xpixels;
106 0 : _ymax = ypixels;
107 0 : _zmax = zpixels;
108 :
109 0 : if (_scale_to_one)
110 : {
111 0 : Real max = std::max(std::max(_xmax, _ymax), _zmax);
112 0 : _xmax /= max;
113 0 : _ymax /= max;
114 0 : _zmax /= max;
115 : }
116 :
117 : // Compute the number of cells in the x and y direction based on
118 : // the user's cells_per_pixel parameter. Note: we use ints here
119 : // because the GeneratedMesh params object uses ints for these...
120 0 : _nx = static_cast<int>(_cells_per_pixel * xpixels);
121 0 : _ny = static_cast<int>(_cells_per_pixel * ypixels);
122 0 : _nz = static_cast<int>(_cells_per_pixel * zpixels);
123 :
124 : // Actually build the Mesh
125 0 : MeshTools::Generation::build_cube(dynamic_cast<UnstructuredMesh &>(mesh),
126 0 : _nx,
127 0 : _ny,
128 0 : _nz,
129 : /*xmin=*/0.,
130 0 : /*xmax=*/_xmax,
131 : /*ymin=*/0.,
132 0 : /*ymax=*/_ymax,
133 : /*zmin=*/0.,
134 0 : /*zmax=*/_zmax,
135 : HEX8);
136 0 : }
137 :
138 : void
139 8 : ImageMeshGenerator::buildMesh2D(const std::string & filename, MeshBase & mesh)
140 : {
141 8 : int xpixels = 0, ypixels = 0;
142 :
143 : // Extract the number of pixels from the image using the file command
144 8 : GetPixelInfo(filename, xpixels, ypixels);
145 :
146 : // Set the maximum dimension to 1.0 while scaling the other
147 : // direction to maintain the aspect ratio.
148 8 : _xmax = xpixels;
149 8 : _ymax = ypixels;
150 :
151 8 : if (_scale_to_one)
152 : {
153 0 : Real max = std::max(_xmax, _ymax);
154 0 : _xmax /= max;
155 0 : _ymax /= max;
156 : }
157 :
158 : // Compute the number of cells in the x and y direction based on
159 : // the user's cells_per_pixel parameter. Note: we use ints here
160 : // because the GeneratedMesh params object uses ints for these...
161 8 : _nx = static_cast<int>(_cells_per_pixel * xpixels);
162 8 : _ny = static_cast<int>(_cells_per_pixel * ypixels);
163 :
164 : // Actually build the Mesh
165 8 : MeshTools::Generation::build_square(dynamic_cast<UnstructuredMesh &>(mesh),
166 8 : _nx,
167 8 : _ny,
168 : /*xmin=*/0.,
169 8 : /*xmax=*/_xmax,
170 : /*ymin=*/0.,
171 8 : /*ymax=*/_ymax,
172 : QUAD4);
173 8 : }
174 :
175 : void
176 8 : ImageMeshGenerator::GetPixelInfo(std::string filename, int & xpixels, int & ypixels)
177 : {
178 : // For reporting possible error messages
179 8 : std::string error_message = "";
180 :
181 : // A template for creating a temporary file.
182 8 : char temp_file[] = "file_command_output.XXXXXX";
183 :
184 : // Use a do-loop so we can break out under various error conditions
185 : // while still cleaning up temporary files. Basically use goto
186 : // statements without actually using them.
187 : do
188 : {
189 : // mkstemp is not in namespace std for whatever reason...
190 8 : int fd = mkstemp(temp_file);
191 :
192 : // If mkstemp fails, we failed.
193 8 : if (fd == -1)
194 : {
195 0 : error_message = "Error creating temporary file in ImageMesh::buildMesh()";
196 0 : break;
197 : }
198 :
199 : // Construct the command string
200 8 : std::ostringstream command;
201 8 : command << "file " << filename << " 2>/dev/null 1>" << temp_file;
202 :
203 : // Make the system call, catch the return code
204 8 : int exit_status = std::system(command.str().c_str());
205 :
206 : // If the system command returned a non-zero status, we failed.
207 8 : if (exit_status != 0)
208 : {
209 0 : error_message = "Error calling 'file' command in ImageMesh::buildMesh()";
210 0 : break;
211 : }
212 :
213 : // Open the file which contains the result of the system command
214 8 : std::ifstream fin(temp_file);
215 :
216 : // Read the contents of the output file into a string
217 8 : std::string command_result;
218 8 : std::getline(fin, command_result);
219 :
220 : // A regular expression which matches "NNN x NNN" , i.e. any number
221 : // of digits, a space, an 'x', a space, and any number of digits.
222 : // The parentheses define capture groups which are stored into the
223 : // xsize and ysize integers.
224 : // Here's an example string:
225 : // sixteenth_image001_cropped3_closing_298.png: PNG image data, 115 x 99, 16-bit/color RGB,
226 : // non-interlaced
227 8 : xpixels = 0, ypixels = 0;
228 8 : pcrecpp::RE re("(\\d+) x (\\d+)");
229 8 : re.PartialMatch(command_result, &xpixels, &ypixels);
230 :
231 : // Detect failure of the regex
232 8 : if ((xpixels == 0) || (ypixels == 0))
233 : {
234 0 : error_message = "Regex failed to find a match in " + command_result;
235 0 : break;
236 : }
237 8 : } while (false);
238 :
239 : // Remove the temporary file. This will still work even if the file was never created...
240 8 : std::remove(temp_file);
241 :
242 : // Report and exit if there was an error
243 8 : if (error_message != "")
244 0 : mooseError(error_message);
245 8 : }
|