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