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

Generated by: LCOV version 1.14