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