LCOV - code coverage report
Current view: top level - src/utils - MeshBaseImageSampler.C (source / functions) Hit Total Coverage
Test: idaholab/moose framework: 2bf808 Lines: 112 146 76.7 %
Date: 2025-07-17 01:28:37 Functions: 8 9 88.9 %
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             : // MOOSE includes
      11             : #include "MeshBaseImageSampler.h"
      12             : #include "MooseApp.h"
      13             : #include "ImageMeshGenerator.h"
      14             : 
      15             : #include "libmesh/mesh_tools.h"
      16             : 
      17             : InputParameters
      18       14385 : MeshBaseImageSampler::validParams()
      19             : {
      20             :   // Define the general parameters
      21       14385 :   InputParameters params = emptyInputParameters();
      22       14385 :   params += FileRangeBuilder::validParams();
      23             : 
      24       14385 :   params.addParam<Point>("origin", "Origin of the image (defaults to mesh origin)");
      25       14385 :   params.addParam<Point>("dimensions",
      26             :                          "x,y,z dimensions of the image (defaults to mesh dimensions)");
      27       14385 :   params.addParam<unsigned int>(
      28             :       "component",
      29             :       "The image RGB-component to return, leaving this blank will result in a greyscale value "
      30             :       "for the image to be created. The component number is zero based, i.e. 0 returns the first "
      31             :       "(RED) component of the image.");
      32             : 
      33             :   // Shift and Scale (application of these occurs prior to threshold)
      34       14385 :   params.addParam<double>("shift", 0, "Value to add to all pixels; occurs prior to scaling");
      35       43155 :   params.addParam<double>(
      36       28770 :       "scale", 1, "Multiplier to apply to all pixel values; occurs after shifting");
      37       14385 :   params.addParamNamesToGroup("shift scale", "Rescale");
      38             : 
      39             :   // Threshold parameters
      40       14385 :   params.addParam<double>("threshold", "The threshold value");
      41       43155 :   params.addParam<double>(
      42       28770 :       "upper_value", 1, "The value to set for data greater than the threshold value");
      43       43155 :   params.addParam<double>(
      44       28770 :       "lower_value", 0, "The value to set for data less than the threshold value");
      45       14385 :   params.addParamNamesToGroup("threshold upper_value lower_value", "Threshold");
      46             : 
      47             :   // Flip image
      48       14385 :   params.addParam<bool>("flip_x", false, "Flip the image along the x-axis");
      49       14385 :   params.addParam<bool>("flip_y", false, "Flip the image along the y-axis");
      50       14385 :   params.addParam<bool>("flip_z", false, "Flip the image along the z-axis");
      51       14385 :   params.addParamNamesToGroup("flip_x flip_y flip_z", "Flip");
      52             : 
      53       14385 :   return params;
      54           0 : }
      55             : 
      56          60 : MeshBaseImageSampler::MeshBaseImageSampler(const InputParameters & parameters)
      57             :   : FileRangeBuilder(parameters),
      58             : #ifdef LIBMESH_HAVE_VTK
      59          60 :     _data(NULL),
      60          60 :     _algorithm(NULL),
      61             : #endif
      62          60 :     _is_pars(parameters),
      63          60 :     _is_console((parameters.getCheckedPointerParam<MooseApp *>("_moose_app"))->getOutputWarehouse())
      64             : 
      65             : {
      66             : #ifndef LIBMESH_HAVE_VTK
      67             :   // This should be impossible to reach, the registration of MeshBaseImageSampler is also guarded
      68             :   // with LIBMESH_HAVE_VTK
      69             :   mooseError("libMesh must be configured with VTK enabled to utilize MeshBaseImageSampler");
      70             : #endif
      71          60 : }
      72             : 
      73             : void
      74          54 : MeshBaseImageSampler::setupImageSampler(MeshBase & mesh)
      75             : {
      76             :   // Don't warn that mesh or _is_pars are unused when VTK is not enabled.
      77          54 :   libmesh_ignore(mesh);
      78          54 :   libmesh_ignore(_is_pars);
      79             : 
      80             : #ifdef LIBMESH_HAVE_VTK
      81             :   // Get access to the Mesh object
      82          54 :   BoundingBox bbox = MeshTools::create_bounding_box(mesh);
      83             : 
      84             :   // Set the dimensions from the Mesh if not set by the User
      85          54 :   if (_is_pars.isParamValid("dimensions"))
      86           0 :     _physical_dims = _is_pars.get<Point>("dimensions");
      87             : 
      88             :   else
      89             :   {
      90          54 :     _physical_dims(0) = bbox.max()(0) - bbox.min()(0);
      91             : #if LIBMESH_DIM > 1
      92          54 :     _physical_dims(1) = bbox.max()(1) - bbox.min()(1);
      93             : #endif
      94             : #if LIBMESH_DIM > 2
      95          54 :     _physical_dims(2) = bbox.max()(2) - bbox.min()(2);
      96             : #endif
      97             :   }
      98             : 
      99             :   // Set the origin from the Mesh if not set in the input file
     100          54 :   if (_is_pars.isParamValid("origin"))
     101           0 :     _origin = _is_pars.get<Point>("origin");
     102             :   else
     103             :   {
     104          54 :     _origin(0) = bbox.min()(0);
     105             : #if LIBMESH_DIM > 1
     106          54 :     _origin(1) = bbox.min()(1);
     107             : #endif
     108             : #if LIBMESH_DIM > 2
     109          54 :     _origin(2) = bbox.min()(2);
     110             : #endif
     111             :   }
     112             : 
     113             :   // An array of filenames, to be filled in
     114          54 :   std::vector<std::string> filenames;
     115             : 
     116             :   // The file suffix, to be determined
     117          54 :   std::string file_suffix;
     118             : 
     119             :   // Try to parse our own file range parameters.  If that fails, then
     120             :   // see if the associated Mesh is an ImageMesh and use its.  If that
     121             :   // also fails, then we have to throw an error...
     122             :   //
     123             :   // The parseFileRange method sets parameters, thus a writable reference to the InputParameters
     124             :   // object must be obtained from the warehouse. Generally, this should be avoided, but
     125             :   // this is a special case.
     126          54 :   if (_status != 0)
     127             :   {
     128             :     // TO DO : enable this. It was taken from ImageSampler.C, but cn't be implemented
     129             :     // the same way.
     130             :     // We don't have parameters, so see if we can get them from ImageMesh
     131             :     /*ImageMeshgenerator * image_mesh_gen = dynamic_cast<ImageMesh *>(&mesh);
     132             :     if (!image_mesh)
     133             :       mooseError("No file range parameters were provided and the Mesh is not an ImageMesh.");
     134             : 
     135             :     // Get the ImageMesh's parameters.  This should work, otherwise
     136             :     // errors would already have been thrown...
     137             :     filenames = image_mesh->filenames();
     138             :     file_suffix = image_mesh->fileSuffix();*/
     139             :   }
     140             :   else
     141             :   {
     142             :     // Use our own parameters (using 'this' b/c of conflicts with filenames the local variable)
     143          54 :     filenames = this->filenames();
     144          54 :     file_suffix = fileSuffix();
     145             :   }
     146             : 
     147             :   // Storage for the file names
     148          54 :   _files = vtkSmartPointer<vtkStringArray>::New();
     149             : 
     150         317 :   for (const auto & filename : filenames)
     151         263 :     _files->InsertNextValue(filename);
     152             : 
     153             :   // Error if no files where located
     154          54 :   if (_files->GetNumberOfValues() == 0)
     155           0 :     mooseError("No image file(s) located");
     156             : 
     157             :   // Read the image stack.  Hurray for VTK not using polymorphism in a
     158             :   // smart way... we actually have to explicitly create the type of
     159             :   // reader based on the file extension, using an if-statement...
     160          54 :   if (file_suffix == "png")
     161          54 :     _image = vtkSmartPointer<vtkPNGReader>::New();
     162           0 :   else if (file_suffix == "tiff" || file_suffix == "tif")
     163           0 :     _image = vtkSmartPointer<vtkTIFFReader>::New();
     164             :   else
     165           0 :     mooseError("Un-supported file type '", file_suffix, "'");
     166             : 
     167             :   // Now that _image is set up, actually read the images
     168             :   // Indicate that data read has started
     169          54 :   _is_console << "Reading image(s)..." << std::endl;
     170             : 
     171             :   // Extract the data
     172          54 :   _image->SetFileNames(_files);
     173          54 :   _image->Update();
     174          54 :   _data = _image->GetOutput();
     175          54 :   _algorithm = _image->GetOutputPort();
     176             : 
     177             :   // Set the image dimensions and voxel size member variable
     178          54 :   int * dims = _data->GetDimensions();
     179         216 :   for (unsigned int i = 0; i < 3; ++i)
     180             :   {
     181         162 :     _dims.push_back(dims[i]);
     182         162 :     _voxel.push_back(_physical_dims(i) / _dims[i]);
     183             :   }
     184             : 
     185             :   // Set the dimensions of the image and bounding box
     186          54 :   _data->SetSpacing(_voxel[0], _voxel[1], _voxel[2]);
     187          54 :   _data->SetOrigin(_origin(0), _origin(1), _origin(2));
     188          54 :   _bounding_box.min() = _origin;
     189          54 :   _bounding_box.max() = _origin + _physical_dims;
     190             : 
     191             :   // Indicate data read is completed
     192          54 :   _is_console << "          ...image read finished" << std::endl;
     193             : 
     194             :   // Set the component parameter
     195             :   // If the parameter is not set then vtkMagnitude() will applied
     196          54 :   if (_is_pars.isParamValid("component"))
     197             :   {
     198           0 :     unsigned int n = _data->GetNumberOfScalarComponents();
     199           0 :     _component = _is_pars.get<unsigned int>("component");
     200           0 :     if (_component >= n)
     201           0 :       mooseError("'component' parameter must be empty or have a value of 0 to ", n - 1);
     202             :   }
     203             :   else
     204          54 :     _component = 0;
     205             : 
     206             :   // Apply filters, the toggling on and off of each filter is handled internally
     207          54 :   vtkMagnitude();
     208          54 :   vtkShiftAndScale();
     209          54 :   vtkThreshold();
     210          54 :   vtkFlip();
     211             : #endif
     212          54 : }
     213             : 
     214             : Real
     215      635411 : MeshBaseImageSampler::sample(const Point & p)
     216             : {
     217             : #ifdef LIBMESH_HAVE_VTK
     218             : 
     219             :   // Do nothing if the point is outside of the image domain
     220      635411 :   if (!_bounding_box.contains_point(p))
     221           0 :     return 0.0;
     222             : 
     223             :   // Determine pixel coordinates
     224      635411 :   std::vector<int> x(3, 0);
     225     2541644 :   for (int i = 0; i < LIBMESH_DIM; ++i)
     226             :   {
     227             :     // Compute position, only if voxel size is greater than zero
     228     1906233 :     if (_voxel[i] == 0)
     229      554611 :       x[i] = 0;
     230             : 
     231             :     else
     232             :     {
     233     1351622 :       x[i] = std::floor((p(i) - _origin(i)) / _voxel[i]);
     234             : 
     235             :       // If the point falls on the mesh extents the index needs to be decreased by one
     236     1351622 :       if (x[i] == _dims[i])
     237           0 :         x[i]--;
     238             :     }
     239             :   }
     240             : 
     241             :   // Return the image data at the given point
     242      635411 :   return _data->GetScalarComponentAsDouble(x[0], x[1], x[2], _component);
     243             : 
     244             : #else
     245             :   libmesh_ignore(p); // avoid un-used parameter warnings
     246             :   return 0.0;
     247             : #endif
     248      635411 : }
     249             : 
     250             : void
     251          54 : MeshBaseImageSampler::vtkMagnitude()
     252             : {
     253             : #ifdef LIBMESH_HAVE_VTK
     254             :   // Do nothing if 'component' is set
     255          54 :   if (_is_pars.isParamValid("component"))
     256           0 :     return;
     257             : 
     258             :   // Apply the greyscale filtering
     259          54 :   _magnitude_filter = vtkSmartPointer<vtkImageMagnitude>::New();
     260          54 :   _magnitude_filter->SetInputConnection(_algorithm);
     261          54 :   _magnitude_filter->Update();
     262             : 
     263             :   // Update the pointers
     264          54 :   _data = _magnitude_filter->GetOutput();
     265          54 :   _algorithm = _magnitude_filter->GetOutputPort();
     266             : #endif
     267             : }
     268             : 
     269             : void
     270          54 : MeshBaseImageSampler::vtkShiftAndScale()
     271             : {
     272             : #ifdef LIBMESH_HAVE_VTK
     273             :   // Capture the parameters
     274          54 :   double shift = _is_pars.get<double>("shift");
     275          54 :   double scale = _is_pars.get<double>("scale");
     276             : 
     277             :   // Do nothing if shift and scale are not set
     278          54 :   if (shift == 0 && scale == 1)
     279          54 :     return;
     280             : 
     281             :   // Perform the scaling and offset actions
     282           0 :   _shift_scale_filter = vtkSmartPointer<vtkImageShiftScale>::New();
     283           0 :   _shift_scale_filter->SetOutputScalarTypeToDouble();
     284             : 
     285           0 :   _shift_scale_filter->SetInputConnection(_algorithm);
     286           0 :   _shift_scale_filter->SetShift(shift);
     287           0 :   _shift_scale_filter->SetScale(scale);
     288           0 :   _shift_scale_filter->Update();
     289             : 
     290             :   // Update the pointers
     291           0 :   _data = _shift_scale_filter->GetOutput();
     292           0 :   _algorithm = _shift_scale_filter->GetOutputPort();
     293             : #endif
     294             : }
     295             : 
     296             : void
     297          54 : MeshBaseImageSampler::vtkThreshold()
     298             : {
     299             : #ifdef LIBMESH_HAVE_VTK
     300             :   // Do nothing if threshold not set
     301          54 :   if (!_is_pars.isParamValid("threshold"))
     302           0 :     return;
     303             : 
     304             :   // Error if both upper and lower are not set
     305          54 :   if (!_is_pars.isParamValid("upper_value") || !_is_pars.isParamValid("lower_value"))
     306           0 :     mooseError("When thresholding is applied, both the upper_value and lower_value parameters must "
     307             :                "be set");
     308             : 
     309             :   // Create the thresholding object
     310          54 :   _image_threshold = vtkSmartPointer<vtkImageThreshold>::New();
     311             : 
     312             :   // Set the data source
     313          54 :   _image_threshold->SetInputConnection(_algorithm);
     314             : 
     315             :   // Setup the thresholding options
     316          54 :   _image_threshold->ThresholdByUpper(_is_pars.get<Real>("threshold"));
     317          54 :   _image_threshold->ReplaceInOn();
     318          54 :   _image_threshold->SetInValue(_is_pars.get<Real>("upper_value"));
     319          54 :   _image_threshold->ReplaceOutOn();
     320          54 :   _image_threshold->SetOutValue(_is_pars.get<Real>("lower_value"));
     321          54 :   _image_threshold->SetOutputScalarTypeToDouble();
     322             : 
     323             :   // Perform the thresholding
     324          54 :   _image_threshold->Update();
     325             : 
     326             :   // Update the pointers
     327          54 :   _data = _image_threshold->GetOutput();
     328          54 :   _algorithm = _image_threshold->GetOutputPort();
     329             : #endif
     330             : }
     331             : 
     332             : void
     333          54 : MeshBaseImageSampler::vtkFlip()
     334             : {
     335             : #ifdef LIBMESH_HAVE_VTK
     336             :   // Convert boolean values into an integer array, then loop over it
     337             :   int mask[3] = {
     338          54 :       _is_pars.get<bool>("flip_x"), _is_pars.get<bool>("flip_y"), _is_pars.get<bool>("flip_z")};
     339             : 
     340         216 :   for (int dim = 0; dim < 3; ++dim)
     341             :   {
     342         162 :     if (mask[dim])
     343             :     {
     344           0 :       _flip_filter = imageFlip(dim);
     345             : 
     346             :       // Update pointers
     347           0 :       _data = _flip_filter->GetOutput();
     348           0 :       _algorithm = _flip_filter->GetOutputPort();
     349             :     }
     350             :   }
     351             : #endif
     352          54 : }
     353             : 
     354             : #ifdef LIBMESH_HAVE_VTK
     355             : vtkSmartPointer<vtkImageFlip>
     356           0 : MeshBaseImageSampler::imageFlip(const int & axis)
     357             : {
     358           0 :   vtkSmartPointer<vtkImageFlip> flip_image = vtkSmartPointer<vtkImageFlip>::New();
     359             : 
     360           0 :   flip_image->SetFilteredAxis(axis);
     361             : 
     362             :   // Set the data source
     363           0 :   flip_image->SetInputConnection(_algorithm);
     364             : 
     365             :   // Perform the flip
     366           0 :   flip_image->Update();
     367             : 
     368             :   // Return the flip filter pointer
     369           0 :   return flip_image;
     370           0 : }
     371             : #endif

Generated by: LCOV version 1.14