LCOV - code coverage report
Current view: top level - src/meshgenerators - ImageMeshGenerator.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: 2bf808 Lines: 54 91 59.3 %
Date: 2025-07-17 01:28:37 Functions: 5 6 83.3 %
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 "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 : }

Generated by: LCOV version 1.14