Repository: pyvista/pyvista

Issue:
Fix failing doc image tests
### Describe the bug, what's wrong, and what you expected.

### Overview
Issue to track and investigate failing tests with the documentation images.
The failing tests are initially reported in https://github.com/pyvista/pyvista/pull/5712, see the build failure [here](https://github.com/pyvista/pyvista/actions/runs/9278295702).

### Image Diff
Here's a diff of 54 failing test images:
https://github.com/pyvista/pyvista/compare/main...user27182:doc/failed_images

There appears to be two main categories of failed tests: 
1. failures from random numbers, i.e. examples that were not captured as part of #6106 but should have been, and
2. failures with MPLCharts, see #6141 

### Software Report Diff
Here's a diff between the Software Report from the successful build in https://github.com/pyvista/pyvista/pull/5718 (when the build images in the `doc_image_cache` were first generated) and the failed build linked above:
https://www.diffchecker.com/WGONBfLU/
From this diff it doesn't appear as though updates to any packages is the cause of the failures.


### Steps to reproduce the bug.

n/a

### System Information

```shell
n/a
```


### Screenshots

_No response_

Discussion:
codecov[bot]: ## [Codecov](https://app.codecov.io/gh/pyvista/pyvista/pull/6170?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pyvista) Report
Attention: Patch coverage is `80.00000%` with `2 lines` in your changes are missing coverage. Please review.
> Project coverage is 90.97%. Comparing base [(`f61ecf2`)](https://app.codecov.io/gh/pyvista/pyvista/commit/f61ecf212e88a0fc276b7ba208024777d2bdaec7?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pyvista) to head [(`aa06fa3`)](https://app.codecov.io/gh/pyvista/pyvista/commit/aa06fa381e8954196a8d30c756b89f44a530e167?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=pyvista).

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #6170      +/-   ##
==========================================
- Coverage   97.08%   90.97%   -6.12%     
==========================================
  Files         141      141              
  Lines       25935    25944       +9     
==========================================
- Hits        25180    23603    -1577     
- Misses        755     2341    +1586     
```

</details>
user27182: LGTM. The ImageNoiseSource test is no longer failing.

Patch:
diff --git a/pyvista/core/_vtk_core.py b/pyvista/core/_vtk_core.py
index 3101ea7c..d986dd12 100644
--- a/pyvista/core/_vtk_core.py
+++ b/pyvista/core/_vtk_core.py
@@ -90,6 +90,7 @@ from vtkmodules.vtkCommonCore import vtkIdList
 from vtkmodules.vtkCommonCore import vtkIdTypeArray
 from vtkmodules.vtkCommonCore import vtkLogger
 from vtkmodules.vtkCommonCore import vtkLookupTable
+from vtkmodules.vtkCommonCore import vtkMath
 from vtkmodules.vtkCommonCore import vtkOutputWindow
 from vtkmodules.vtkCommonCore import vtkPoints
 from vtkmodules.vtkCommonCore import vtkSignedCharArray
diff --git a/pyvista/core/utilities/image_sources.py b/pyvista/core/utilities/image_sources.py
index db842ca7..86a35f89 100644
--- a/pyvista/core/utilities/image_sources.py
+++ b/pyvista/core/utilities/image_sources.py
@@ -222,7 +222,7 @@ class ImageMandelbrotSource(_vtk.vtkImageMandelbrotSource):
 
 @no_new_attr
 class ImageNoiseSource(_vtk.vtkImageNoiseSource):
-    """Create a binary image of an ellipsoid class.
+    """Create an image filled with uniform noise.
 
     .. versionadded:: 0.44.0
 
@@ -237,6 +237,9 @@ class ImageNoiseSource(_vtk.vtkImageNoiseSource):
     maximum : float
         The maximum value for the generated noise.
 
+    seed : int, optional
+        Seed the random number generator with a value.
+
     Examples
     --------
     Create an image of noise.
@@ -246,13 +249,20 @@ class ImageNoiseSource(_vtk.vtkImageNoiseSource):
     ...     whole_extent=(0, 200, 0, 200, 0, 0),
     ...     minimum=0,
     ...     maximum=255,
+    ...     seed=0,
     ... )
     >>> source.output.plot(cpos="xy")
     """
 
     _new_attr_exceptions: ClassVar[List[str]] = ['_whole_extent', 'whole_extent']
 
-    def __init__(self, whole_extent=None, minimum=None, maximum=None) -> None:
+    def __init__(
+        self,
+        whole_extent=(0, 255, 0, 255, 0, 0),
+        minimum=0.0,
+        maximum=1.0,
+        seed=None,
+    ) -> None:
         super().__init__()
         if whole_extent is not None:
             self.whole_extent = whole_extent
@@ -260,6 +270,8 @@ class ImageNoiseSource(_vtk.vtkImageNoiseSource):
             self.minimum = minimum
         if maximum is not None:
             self.maximum = maximum
+        if seed is not None:
+            self.seed(seed)
 
     @property
     def whole_extent(self) -> Sequence[int]:
@@ -328,6 +340,16 @@ class ImageNoiseSource(_vtk.vtkImageNoiseSource):
         """
         self.SetMaximum(maximum)
 
+    def seed(self, value: int) -> None:
+        """Seed the random number generator with a value.
+
+        Parameters
+        ----------
+        value : int
+          The seed value for the random number generator to use.
+        """
+        _vtk.vtkMath().RandomSeed(value)
+
     @property
     def output(self):
         """Get the output image as a ImageData.


Test patch:
diff --git a/tests/core/test_image_sources.py b/tests/core/test_image_sources.py
index 53e04a60..ef1657f5 100644
--- a/tests/core/test_image_sources.py
+++ b/tests/core/test_image_sources.py
@@ -1,5 +1,7 @@
 from __future__ import annotations
 
+import numpy as np
+
 import pyvista as pv
 
 
@@ -25,8 +27,10 @@ def test_image_ellip_soid_source():
 
 def test_image_noise_source():
     whole_extent = (-10, 10, -10, 10, -10, 10)
-    source = pv.ImageNoiseSource(whole_extent=whole_extent, minimum=0, maximum=255)
+    source = pv.ImageNoiseSource(whole_extent=whole_extent, minimum=0, maximum=255, seed=42)
     assert source.whole_extent == whole_extent
+    assert source.minimum == 0
+    assert source.maximum == 255
     whole_extent = (-5, 5, -5, 5, -5, 5)
     minimum = 100
     maximum = 200
@@ -38,6 +42,17 @@ def test_image_noise_source():
     assert source.maximum == maximum
     assert isinstance(source.output, pv.ImageData)
 
+    output = pv.ImageNoiseSource().output
+    assert output.bounds == (0.0, 255.0, 0.0, 255.0, 0.0, 0.0)
+    assert output.dimensions == (256, 256, 1)
+    assert np.allclose(output.get_data_range(), (0.0, 1.0), atol=1e-4)
+
+    output_seed = pv.ImageNoiseSource(seed=0).output
+    assert not np.array_equal(output_seed.active_scalars, output.active_scalars)
+
+    output_same_seed = pv.ImageNoiseSource(seed=0).output
+    assert np.array_equal(output_same_seed.active_scalars, output_seed.active_scalars)
+
 
 def test_image_mandelbrot_source():
     whole_extent = (0, 20, 0, 20, 0, 0)