LCOV - code coverage report
Current view: top level - src/mesh - ImageMesh.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: 2bf808 Lines: 69 85 81.2 %
Date: 2025-07-17 01:28:37 Functions: 6 8 75.0 %
Legend: Lines: hit not hit

          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 : }

Generated by: LCOV version 1.14