From 6f7376119cb1a7e224020e35260f3cf8ff248646 Mon Sep 17 00:00:00 2001 From: xi6th Date: Wed, 1 Jul 2026 20:19:02 +0100 Subject: [PATCH 1/2] Fix NumPy 2.0 compatibility by replacing removed np.alltrue with np.all np.alltrue was removed in NumPy 2.0; it was a plain alias for np.all with no behavior difference. This was breaking Image.__eq__ and test assertions on modern NumPy, causing 32/39 tests to fail. --- mpstool/img.py | 2 +- tests/test_connectivity.py | 26 ++++++++++++------------- tests/test_image.py | 40 +++++++++++++++++++------------------- 3 files changed, 34 insertions(+), 34 deletions(-) diff --git a/mpstool/img.py b/mpstool/img.py index a6f6170..b587d8d 100644 --- a/mpstool/img.py +++ b/mpstool/img.py @@ -104,7 +104,7 @@ def __eq__(self, other): for k in self._data.keys(): if self._data[k].shape != other._data[k].shape: return False - if not np.alltrue(self._data[k] == other._data[k]): + if not np.all(self._data[k] == other._data[k]): return False return True diff --git a/tests/test_connectivity.py b/tests/test_connectivity.py index cc681f9..5fd430a 100644 --- a/tests/test_connectivity.py +++ b/tests/test_connectivity.py @@ -56,7 +56,7 @@ def test_threshold(): thresholds = np.array([1.0, 2.0]) image.threshold(thresholds) labels = mpstool.img.labelize(image) - assert np.alltrue(labels == categorical_ref) + assert np.all(labels == categorical_ref) def test_connected_component(cube): @@ -69,7 +69,7 @@ def test_connected_component(cube): [[0, 2, 2], [0, 2, 2], [0, 0, 0]]]) - assert np.alltrue(mpstool.connectivity.get_components( + assert np.all(mpstool.connectivity.get_components( cube, background=0) == connectivity_array_cube) @@ -85,7 +85,7 @@ def test_get_map(array, image): real_map = mpstool.connectivity.get_map(ar) assert real_map.keys() == expected_map.keys() for k in expected_map.keys(): - assert np.alltrue(real_map[k] == expected_map[k]) + assert np.all(real_map[k] == expected_map[k]) def test_function_2D(array, image): @@ -97,9 +97,9 @@ def test_function_2D(array, image): axis1_result = mpstool.connectivity.get_function(ar, axis=1) for key in axis0_connectivity: - assert np.alltrue(axis0_result[key] == axis0_connectivity[key]) + assert np.all(axis0_result[key] == axis0_connectivity[key]) for key in axis1_connectivity: - assert np.alltrue(axis1_result[key] == axis1_connectivity[key]) + assert np.all(axis1_result[key] == axis1_connectivity[key]) def test_truncated_function_2D(array, image): @@ -111,9 +111,9 @@ def test_truncated_function_2D(array, image): axis1_result = mpstool.connectivity.get_function(ar, axis=1, max_lag=1) for key in axis0_connectivity: - assert np.alltrue(axis0_result[key] == axis0_connectivity[key]) + assert np.all(axis0_result[key] == axis0_connectivity[key]) for key in axis1_connectivity: - assert np.alltrue(axis1_result[key] == axis1_connectivity[key]) + assert np.all(axis1_result[key] == axis1_connectivity[key]) def test_function_3D(extruded_array): @@ -126,9 +126,9 @@ def test_function_3D(extruded_array): axis2_result = mpstool.connectivity.get_function(extruded_array, axis=2) for key in axis0_connectivity: - assert np.alltrue(axis0_result[key] == axis0_connectivity[key]) - assert np.alltrue(axis1_result[key] == axis1_connectivity[key]) - assert np.alltrue(axis2_result[key] == axis2_connectivity[key]) + assert np.all(axis0_result[key] == axis0_connectivity[key]) + assert np.all(axis1_result[key] == axis1_connectivity[key]) + assert np.all(axis2_result[key] == axis2_connectivity[key]) @pytest.fixture @@ -149,9 +149,9 @@ def test_apply_threshold(c_image): result2 = np.ones_like(c_image) - assert np.alltrue( + assert np.all( result1 == mpstool.connectivity._apply_threshold(c_image, 0.2)) - assert np.alltrue( + assert np.all( result2 == mpstool.connectivity._apply_threshold(c_image, 0.95)) @@ -168,5 +168,5 @@ def test_gamma(c_image): def test_gamma_function(c_image): - assert np.alltrue(mpstool.connectivity.gamma_function( + assert np.all(mpstool.connectivity.gamma_function( c_image, [0, 0.01, 0.92], True) == [1, 17/25, 0]) diff --git a/tests/test_image.py b/tests/test_image.py index 6ef66fb..ca54774 100644 --- a/tests/test_image.py +++ b/tests/test_image.py @@ -41,7 +41,7 @@ def test_saturate(img): [0, 0, 255], [250, 0, 0]]).reshape((3, 3, 1)) assert saturated.shape == expected.shape - assert np.alltrue(saturated == expected) + assert np.all(saturated == expected) def test_labelize(img): @@ -49,7 +49,7 @@ def test_labelize(img): [3, 1, 6], [5, 3, 0]]) labels = labelize(img) - assert np.alltrue(labels == expected) + assert np.all(labels == expected) def test_from_list(): @@ -60,7 +60,7 @@ def test_from_list(): input_data = [data1, data2] expected = np.array(input_data) img = Image.fromArray(input_data).asArray() - assert np.alltrue(img == expected) + assert np.all(img == expected) # ------ Test of conversion functions ------ @@ -76,48 +76,48 @@ def test_conversion_txt_gslib(): img = Image.fromTxt("tests/data/test_img.txt", (3, 3)) img.exportAsGslib("tests/data/test_img.gslib") img_test = Image.fromGslib("tests/data/test_img.gslib") - assert np.alltrue(img_test == img) + assert np.all(img_test == img) def test_gslib_to_vtk(): img = Image.fromGslib("tests/data/test_img.gslib") img.exportAsVtk("tests/data/test_img.vtk") img2 = img.fromVtk("tests/data/test_img.vtk") - assert np.alltrue(img == img2) + assert np.all(img == img2) def test_vtk_pgm(): img = Image.fromVtk("tests/data/test_img.vtk") img.exportAsPgm("tests/data/test_img.pgm") img2 = img.fromPgm("tests/data/test_img.pgm") - assert np.alltrue(img == img2) + assert np.all(img == img2) def test_pgm_vox(): img = Image.fromPgm("tests/data/test_img.pgm") img.exportAsVox("tests/data/test_img.vox") img_test = Image.fromVox("tests/data/test_img.vox") - assert np.alltrue(img_test == img) + assert np.all(img_test == img) def test_conversion_vox_png(): img = Image.fromVox("tests/data/test_img.vox") img.exportAsPng("tests/data/test_img.png") img_test = Image.fromPng("tests/data/test_img.png") - assert np.alltrue(img_test == img) + assert np.all(img_test == img) def test_conversion_png_txt(): img = Image.fromPng("tests/data/test_img.png") img.exportAsTxt("tests/data/test_img2.txt") img_test = Image.fromTxt("tests/data/test_img2.txt", (3, 3)) - assert np.alltrue(img_test == img) + assert np.all(img_test == img) def test_conversion_final(): a = np.loadtxt("tests/data/test_img.txt").reshape((3, 3)) b = np.loadtxt("tests/data/test_img2.txt") - return np.alltrue(a == b) + return np.all(a == b) def test_import_gslb2var(): @@ -126,8 +126,8 @@ def test_import_gslb2var(): var2 = img.asArray("V1").reshape((3, 3)) expected1 = np.array([[1, 1, 1], [1, 1, 1], [0, 0, 0]]) expected2 = np.array([[1, 0, 1], [0, 1, 0], [1, 0, 1]]) - assert np.alltrue(var1 == expected1) - assert np.alltrue(var2 == expected2) + assert np.all(var1 == expected1) + assert np.all(var2 == expected2) def test_gslib_io(): @@ -145,7 +145,7 @@ def test_conversion_color(): img.exportAsPpm("tests/data/test_color_simple.ppm") img2 = Image.fromPng("tests/data/test_color_simple.png") img3 = Image.fromPpm("tests/data/test_color_simple.ppm") - assert np.alltrue((img.asArray()-img2.asArray()) < 1e8) + assert np.all((img.asArray()-img2.asArray()) < 1e8) assert img == img3 @@ -156,7 +156,7 @@ def test_conversion_color2(): img.exportAsPpm("tests/data/test_color.ppm") img2 = Image.fromPng("tests/data/test_color.png") img3 = Image.fromPpm("tests/data/test_color.ppm") - assert np.alltrue((img.asArray()-img2.asArray()) < 1e8) + assert np.all((img.asArray()-img2.asArray()) < 1e8) assert img == img3 @@ -192,9 +192,9 @@ def test_dimension(img): assert img.zmin() == 0 assert img.zmax() == 1 coords = np.array([0.5, 1.5, 2.5]) - assert np.alltrue(img.x() == coords) - assert np.alltrue(img.y() == coords) - assert np.alltrue(img.z() == np.array([0.5])) + assert np.all(img.x() == coords) + assert np.all(img.y() == coords) + assert np.all(img.z() == np.array([0.5])) assert img.vmin() == 0 assert img.vmax() == 255 assert img.get_variables() == ["V0"] @@ -206,12 +206,12 @@ def test_variable(img): [250, 100, 0]]) img.add_variable("test", data) assert set(img.get_variables()) == {"V0", "test"} - assert np.alltrue(img._data["test"] == data) + assert np.all(img._data["test"] == data) data = np.zeros(img.shape) img.set_variable("test", data) - assert np.alltrue(img._data["test"] == data) + assert np.all(img._data["test"] == data) img.rename_variable("test", "toto") - assert np.alltrue(img._data["toto"] == data) + assert np.all(img._data["toto"] == data) img.remove_variable("toto") assert img.get_variables() == ["V0"] From afb3c8c7bfd80b7e3302980c90baedfabf25835d Mon Sep 17 00:00:00 2001 From: xi6th Date: Wed, 1 Jul 2026 20:32:51 +0100 Subject: [PATCH 2/2] Expand README features list with connectivity, FFT variogram, and cross-validation details Document mpstool.variogram and mpstool.cv_metrics, which were previously unmentioned, plus the tools/ CLI scripts and the note that these two modules require an explicit import. --- README.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 550b43c..0c96c2f 100755 --- a/README.md +++ b/README.md @@ -10,8 +10,17 @@ This python3 project provides tools for computing quality indicators for multipo The methods can also be applied to 2D or 3D images. Currently the module provides : -- An Image class, with various import/export/conversion methods to different data types -- Functions for evaluating connectivity, histograms and variograms of 2D and 3D categorical images. +- **An `Image` class** (`mpstool.img`) for handling 2D and 3D categorical or continuous grids, with: + - Import/export to GSLIB, raw text, PNG, MagicaVoxel (`.vox`), VTK, PGM and PPM formats + - Transformations: thresholding (continuous → categorical), automatic categorization (1D k-means), normalization, axis flips/permutations, and random sub-sampling + - Visualization: 2D plots and 3D orthogonal cross-section cuts +- **Spatial statistics** (`mpstool.stats`): histograms (facies proportions) and indicator/continuous variograms, computed with a spatial-shift method +- **Connectivity analysis** (`mpstool.connectivity`): connectivity functions and maps describing how categories connect across distance, plus a threshold-based connectivity index (`gamma`) for continuous fields +- **FFT-accelerated variogram maps** (`mpstool.variogram`): full 2D variogram maps computed via FFT (Marcotte, 1996), which natively handle missing data (NaN) and are much faster than the spatial-shift method on large grids; includes a variogram-comparison metric for scoring simulation quality against a reference image +- **Cross-validation metrics** (`mpstool.cv_metrics`): probabilistic scoring rules (Brier score, CRPS, 0-1 score, linear score, and their class-balanced/skill-score variants) implementing the scikit-learn scorer interface, for cross-validating spatial simulators +- **Command-line tools** (`tools/`): `gslib-plot.py` for quickly visualizing a `.gslib` file, and `geone_cv.py` for cross-validating the `geone`/DeeSSe multi-point simulator via `GridSearchCV`, driven by a JSON configuration file + +Note: `mpstool.variogram` and `mpstool.cv_metrics` are not imported automatically with `import mpstool` — import them explicitly if you need them. ## Example: connectivity function Connectivity function describes how different categories are connected depending on distance. It is given by: ![connectivity](assets/connectivity.png)