diffusive_size_factor_AI
#
PoreSpy’s diffusive_size_factor_AI
includes the steps for predicting the diffusive size factors of the conduit images decribed here. Note that the diffusive conductance of the conduits can be then calculated by multiplying the size factor by diffusivity of the phase. The function takes in the images of segmented porous medium and returns an array of diffusive size factors for all conduits in the image. Therefore, the framework can be applied to both one conduit image as well as a segmented image of porous medium.

Trained model and supplementary materials#
To use the diffusive_size_factor_AI
, the trained model, and training data distribution are required. The AI model files and additional files used in this example are available here. The folder contains following files:
Trained model weights: This file includes only weights of the deep learning layers. To use this file, the Resnet50 model structure must be built first.
Trained data distribution: This file will be used in denormalizing predicted values based on normalized transform applied on training data. The denormalizing step is included in
diffusive_size_factor_AI
method.Finite difference diffusive conductance: This file is used in this example to compare the prediction results with finite difference method for segmented regions
Pair of regions: This file is used in this example to compare the prediction results with finite difference method for a pair of regions
Let’s download the tensorflow
files required to run this notebook:
try:
import tensorflow as tf
except ImportError:
!pip install tensorflow
try:
import sklearn
except ImportError:
!pip install scikit-learn
import os
if not os.path.exists("sf-model-lib"):
!git clone https://github.com/PMEAL/sf-model-lib
2023-10-25 12:41:29.905143: I tensorflow/tsl/cuda/cudart_stub.cc:28] Could not find cuda drivers on your machine, GPU will not be used.
2023-10-25 12:41:29.959125: I tensorflow/tsl/cuda/cudart_stub.cc:28] Could not find cuda drivers on your machine, GPU will not be used.
2023-10-25 12:41:29.960462: I tensorflow/core/platform/cpu_feature_guard.cc:182] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations.
To enable the following instructions: AVX2 AVX512F FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.
2023-10-25 12:41:32.873161: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Could not find TensorRT
Collecting scikit-learn
Using cached scikit_learn-1.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (11 kB)
Requirement already satisfied: numpy<2.0,>=1.17.3 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from scikit-learn) (1.24.3)
Requirement already satisfied: scipy>=1.5.0 in /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages (from scikit-learn) (1.10.1)
Collecting joblib>=1.1.1 (from scikit-learn)
Using cached joblib-1.3.2-py3-none-any.whl.metadata (5.4 kB)
Collecting threadpoolctl>=2.0.0 (from scikit-learn)
Using cached threadpoolctl-3.2.0-py3-none-any.whl.metadata (10.0 kB)
Using cached scikit_learn-1.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (11.1 MB)
Using cached joblib-1.3.2-py3-none-any.whl (302 kB)
Using cached threadpoolctl-3.2.0-py3-none-any.whl (15 kB)
Installing collected packages: threadpoolctl, joblib, scikit-learn
Successfully installed joblib-1.3.2 scikit-learn-1.3.2 threadpoolctl-3.2.0
[notice] A new release of pip is available: 23.0.1 -> 23.3.1
[notice] To update, run: pip install --upgrade pip
Cloning into 'sf-model-lib'...
remote: Enumerating objects: 36, done.
remote: Counting objects: 20% (1/5)
remote: Counting objects: 40% (2/5)
remote: Counting objects: 60% (3/5)
remote: Counting objects: 80% (4/5)
remote: Counting objects: 100% (5/5)
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 20% (1/5)
remote: Compressing objects: 40% (2/5)
remote: Compressing objects: 60% (3/5)
remote: Compressing objects: 80% (4/5)
remote: Compressing objects: 100% (5/5)
remote: Compressing objects: 100% (5/5), done.
Receiving objects: 2% (1/36)
Receiving objects: 5% (2/36)
Receiving objects: 8% (3/36)
Receiving objects: 11% (4/36)
Receiving objects: 13% (5/36)
Receiving objects: 16% (6/36)
Receiving objects: 16% (6/36), 52.84 MiB | 52.83 MiB/s
Receiving objects: 19% (7/36), 80.25 MiB | 53.49 MiB/s
Receiving objects: 19% (7/36), 106.55 MiB | 53.27 MiB/s
Receiving objects: 19% (7/36), 159.57 MiB | 53.19 MiB/s
Receiving objects: 22% (8/36), 159.57 MiB | 53.19 MiB/s
Receiving objects: 25% (9/36), 159.57 MiB | 53.19 MiB/s
Receiving objects: 27% (10/36), 159.57 MiB | 53.19 MiB/s
Receiving objects: 30% (11/36), 159.57 MiB | 53.19 MiB/s
Receiving objects: 33% (12/36), 159.57 MiB | 53.19 MiB/s
Receiving objects: 36% (13/36), 159.57 MiB | 53.19 MiB/s
Receiving objects: 38% (14/36), 159.57 MiB | 53.19 MiB/s
Receiving objects: 41% (15/36), 159.57 MiB | 53.19 MiB/s
Receiving objects: 44% (16/36), 159.57 MiB | 53.19 MiB/s
Receiving objects: 47% (17/36), 159.57 MiB | 53.19 MiB/s
Receiving objects: 50% (18/36), 159.57 MiB | 53.19 MiB/s
Receiving objects: 52% (19/36), 159.57 MiB | 53.19 MiB/s
Receiving objects: 55% (20/36), 159.57 MiB | 53.19 MiB/s
Receiving objects: 58% (21/36), 159.57 MiB | 53.19 MiB/s
Receiving objects: 61% (22/36), 159.57 MiB | 53.19 MiB/s
Receiving objects: 63% (23/36), 159.57 MiB | 53.19 MiB/s
Receiving objects: 66% (24/36), 159.57 MiB | 53.19 MiB/s
Receiving objects: 69% (25/36), 159.57 MiB | 53.19 MiB/s
Receiving objects: 72% (26/36), 159.57 MiB | 53.19 MiB/s
Receiving objects: 75% (27/36), 159.57 MiB | 53.19 MiB/s
Receiving objects: 77% (28/36), 159.57 MiB | 53.19 MiB/s
Receiving objects: 77% (28/36), 212.06 MiB | 53.01 MiB/s
Receiving objects: 80% (29/36), 238.79 MiB | 53.06 MiB/s
Receiving objects: 80% (29/36), 264.75 MiB | 53.14 MiB/s
Receiving objects: 80% (29/36), 319.74 MiB | 53.22 MiB/s
remote: Total 36 (delta 0), reused 2 (delta 0), pack-reused 31
Receiving objects: 83% (30/36), 319.74 MiB | 53.22 MiB/s
Receiving objects: 86% (31/36), 319.74 MiB | 53.22 MiB/s
Receiving objects: 88% (32/36), 319.74 MiB | 53.22 MiB/s
Receiving objects: 91% (33/36), 319.74 MiB | 53.22 MiB/s
Receiving objects: 94% (34/36), 319.74 MiB | 53.22 MiB/s
Receiving objects: 97% (35/36), 319.74 MiB | 53.22 MiB/s
Receiving objects: 100% (36/36), 319.74 MiB | 53.22 MiB/s
Receiving objects: 100% (36/36), 334.57 MiB | 53.21 MiB/s, done.
Resolving deltas: 0% (0/8)
Resolving deltas: 12% (1/8)
Resolving deltas: 25% (2/8)
Resolving deltas: 37% (3/8)
Resolving deltas: 50% (4/8)
Resolving deltas: 62% (5/8)
Resolving deltas: 75% (6/8)
Resolving deltas: 87% (7/8)
Resolving deltas: 100% (8/8)
Resolving deltas: 100% (8/8), done.
Updating files: 87% (7/8)
Updating files: 100% (8/8)
Updating files: 100% (8/8), done.
Also, since the model weights have been stored in chunks, they need to be recombined first:
import importlib
h5tools = importlib.import_module("sf-model-lib.h5tools")
DIR_WEIGHTS = "sf-model-lib/diffusion"
fname_in = [f"{DIR_WEIGHTS}/model_weights_part{j}.h5" for j in [0, 1]]
h5tools.combine(fname_in, fname_out=f"{DIR_WEIGHTS}/model_weights.h5")
Note that to use diffusive_size_factor_AI
, Scikit-learn and Tensorflow must be installed. Import necessary packages and the AI model:
import inspect
import os
import warnings
import h5py
import numpy as np
import porespy as ps
import scipy as sp
from matplotlib import pyplot as plt
from sklearn.metrics import r2_score
ps.visualization.set_mpl_style()
warnings.filterwarnings("ignore")
inspect.signature(ps.networks.diffusive_size_factor_AI)
[12:41:49] ERROR PARDISO solver not installed, run `pip install pypardiso`. Otherwise, _workspace.py:56 simulations will be slow. Apple M chips not supported.
<Signature (regions, throat_conns, model, g_train, voxel_size=1)>
model
, g_train
#
Import AI model and training data from the downloaded folder:
path = "./sf-model-lib/diffusion"
path_train = os.path.join(path, 'g_train_original.hdf5')
path_weights = os.path.join(path, 'model_weights.h5')
g_train = h5py.File(path_train, 'r')['g_train'][()]
model = ps.networks.create_model()
model.load_weights(path_weights)
[12:41:52] WARNING `lr` is deprecated in Keras optimizer, please use `learning_rate` or use the optimizer.py:123 legacy optimizer, e.g.,tf.keras.optimizers.legacy.Adam.
regions
#
We can create a 3D image using PoreSpy’s poly_disperese_spheres
generator and segment the image using snow_partitioning
method. Note that find_conns
method returns the connections in the segmented region. The order of values in conns is similar to the network extraction conns. Therefore, the region with label=1 in the segmented image is mapped to indice 0 in conns.
np.random.seed(17)
shape = [120, 120, 120]
dist = sp.stats.norm(loc=7, scale=5)
im = ps.generators.polydisperse_spheres(shape=shape,
porosity=0.7,
dist=dist,
r_min=7)
results = ps.filters.snow_partitioning(im=im.astype(bool))
regions = results['regions']
fig, ax = plt.subplots(1, 1, figsize=[4, 4])
ax.imshow(regions[:, :, 20], origin='lower', interpolation='none')
ax.axis(False);

throat_conns
#
PoreSpy’s diffusive_size_factor_AI
method takes in the segmented image, model, training ground truth values, and the conncetions of regions in the segmented image (throat conns). In this example we have created an image with voxel_size=1. For a different voxel size, the voxel_size argument needs to be passed to the method.
conns = ps.networks.find_conns(regions)
size_factors = ps.networks.diffusive_size_factor_AI(regions,
model=model,
g_train=g_train,
throat_conns=conns)
2023-10-25 12:42:14.041617: W tensorflow/tsl/framework/cpu_allocator_impl.cc:83] Allocation of 780140544 exceeds 10% of free system memory.
2023-10-25 12:42:14.647370: W tensorflow/tsl/framework/cpu_allocator_impl.cc:83] Allocation of 780140544 exceeds 10% of free system memory.
1/47 [..............................] - ETA: 2:34
2/47 [>.............................] - ETA: 1:27
3/47 [>.............................] - ETA: 1:28
4/47 [=>............................] - ETA: 1:26
5/47 [==>...........................] - ETA: 1:25
6/47 [==>...........................] - ETA: 1:23
7/47 [===>..........................] - ETA: 1:21
---------------------------------------------------------------------------
KeyboardInterrupt Traceback (most recent call last)
Cell In[6], line 2
1 conns = ps.networks.find_conns(regions)
----> 2 size_factors = ps.networks.diffusive_size_factor_AI(regions,
3 model=model,
4 g_train=g_train,
5 throat_conns=conns)
File ~/work/porespy/porespy/porespy/networks/_size_factors.py:81, in diffusive_size_factor_AI(regions, throat_conns, model, g_train, voxel_size)
79 batch_size = 16
80 test_steps = math.ceil(len(throat_conns) / batch_size)
---> 81 predictions = model.predict(test_data, steps=test_steps)
82 predictions = np.squeeze(predictions)
83 denorm_size_factor = _denorm_predict(predictions, g_train)
File /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/keras/src/utils/traceback_utils.py:65, in filter_traceback.<locals>.error_handler(*args, **kwargs)
63 filtered_tb = None
64 try:
---> 65 return fn(*args, **kwargs)
66 except Exception as e:
67 filtered_tb = _process_traceback_frames(e.__traceback__)
File /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/keras/src/engine/training.py:2554, in Model.predict(self, x, batch_size, verbose, steps, callbacks, max_queue_size, workers, use_multiprocessing)
2552 for step in data_handler.steps():
2553 callbacks.on_predict_batch_begin(step)
-> 2554 tmp_batch_outputs = self.predict_function(iterator)
2555 if data_handler.should_sync:
2556 context.async_wait()
File /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/tensorflow/python/util/traceback_utils.py:150, in filter_traceback.<locals>.error_handler(*args, **kwargs)
148 filtered_tb = None
149 try:
--> 150 return fn(*args, **kwargs)
151 except Exception as e:
152 filtered_tb = _process_traceback_frames(e.__traceback__)
File /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/tensorflow/python/eager/polymorphic_function/polymorphic_function.py:825, in Function.__call__(self, *args, **kwds)
822 compiler = "xla" if self._jit_compile else "nonXla"
824 with OptionalXlaContext(self._jit_compile):
--> 825 result = self._call(*args, **kwds)
827 new_tracing_count = self.experimental_get_tracing_count()
828 without_tracing = (tracing_count == new_tracing_count)
File /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/tensorflow/python/eager/polymorphic_function/polymorphic_function.py:864, in Function._call(self, *args, **kwds)
861 self._lock.release()
862 # In this case we have not created variables on the first call. So we can
863 # run the first trace but we should fail if variables are created.
--> 864 results = self._variable_creation_fn(*args, **kwds)
865 if self._created_variables and not ALLOW_DYNAMIC_VARIABLE_CREATION:
866 raise ValueError("Creating variables on a non-first call to a function"
867 " decorated with tf.function.")
File /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/tensorflow/python/eager/polymorphic_function/tracing_compiler.py:148, in TracingCompiler.__call__(self, *args, **kwargs)
145 with self._lock:
146 (concrete_function,
147 filtered_flat_args) = self._maybe_define_function(args, kwargs)
--> 148 return concrete_function._call_flat(
149 filtered_flat_args, captured_inputs=concrete_function.captured_inputs)
File /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/tensorflow/python/eager/polymorphic_function/monomorphic_function.py:1349, in ConcreteFunction._call_flat(self, args, captured_inputs)
1345 possible_gradient_type = gradients_util.PossibleTapeGradientTypes(args)
1346 if (possible_gradient_type == gradients_util.POSSIBLE_GRADIENT_TYPES_NONE
1347 and executing_eagerly):
1348 # No tape is watching; skip to running the function.
-> 1349 return self._build_call_outputs(self._inference_function(*args))
1350 forward_backward = self._select_forward_and_backward_functions(
1351 args,
1352 possible_gradient_type,
1353 executing_eagerly)
1354 forward_function, args_with_tangents = forward_backward.forward()
File /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/tensorflow/python/eager/polymorphic_function/atomic_function.py:196, in AtomicFunction.__call__(self, *args)
194 with record.stop_recording():
195 if self._bound_context.executing_eagerly():
--> 196 outputs = self._bound_context.call_function(
197 self.name,
198 list(args),
199 len(self.function_type.flat_outputs),
200 )
201 else:
202 outputs = make_call_op_in_graph(self, list(args))
File /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/tensorflow/python/eager/context.py:1457, in Context.call_function(self, name, tensor_inputs, num_outputs)
1455 cancellation_context = cancellation.context()
1456 if cancellation_context is None:
-> 1457 outputs = execute.execute(
1458 name.decode("utf-8"),
1459 num_outputs=num_outputs,
1460 inputs=tensor_inputs,
1461 attrs=attrs,
1462 ctx=self,
1463 )
1464 else:
1465 outputs = execute.execute_with_cancellation(
1466 name.decode("utf-8"),
1467 num_outputs=num_outputs,
(...)
1471 cancellation_manager=cancellation_context,
1472 )
File /opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/tensorflow/python/eager/execute.py:53, in quick_execute(op_name, num_outputs, inputs, attrs, ctx, name)
51 try:
52 ctx.ensure_initialized()
---> 53 tensors = pywrap_tfe.TFE_Py_Execute(ctx._handle, device_name, op_name,
54 inputs, attrs, num_outputs)
55 except core._NotOkStatusException as e:
56 if name is not None:
KeyboardInterrupt:
Compare with finite difference#
Assuming a diffusivity of 1, the diffusive conductance of the conduits will be equal to their diffusive size factors. Now let’s compare the AI-based diffusive conductances with the conductance values calculated by finite difference method. The finite difference method results are found using the steps explained here.
Note: The finite difference-based diffusive size factors were calculated using PoreSpy’s size factor method diffusive_size_factor_DNS
. However, due to the long runtime of the DNS function the results were saved in the example data folder and used in this example. The Following code was used to estimate the finite difference-based values using PoreSpy:
g_FD = ps.networks.diffusive_size_factor_DNS(regions, conns)
fname = os.path.join(path, 'g_finite_difference120-phi7.hdf5')
g_FD = h5py.File(fname, 'r')['g_finite_difference'][()]
g_AI = size_factors
max_val = np.max([g_FD, g_AI])
plt.figure(figsize=[4, 4])
plt.plot(g_FD, g_AI, '*', [0, max_val], [0, max_val], 'r')
plt.xlabel('g reference')
plt.ylabel('g prediction')
r2 = r2_score(g_FD, g_AI)
print(f"The R^2 prediction accuracy is {r2:.3}")
The R^2 prediction accuracy is 0.977

Note on runtime: A larger part of AI_size_factors
runtime is related to extracting the pairs of conduits, which is the common step required for both AI and finite difference method. Once the data is prepared, AI Prediction on the tensor takes a smaller amount of time in contrast to finite difference method, as it was shown here.
Apply on one conduit#
PoreSpy’s diffusive_size_factor_AI
method can take in an image of a pair of regions. Let’s predict the diffusice size factor for a pair of image using both AI and DNS methods:
fname = os.path.join(path, 'pair.hdf5')
pair_in = h5py.File(fname, 'r')
im_pair = pair_in['pair'][()]
conns = ps.networks.find_conns(im_pair)
sf_FD = ps.networks.diffusive_size_factor_DNS(im_pair, throat_conns=conns)
sf_AI = ps.networks.diffusive_size_factor_AI(im_pair,
model=model,
g_train=g_train,
throat_conns=conns)
print(f"Diffusive size factor from FD: {sf_FD[0]:.2f}, and AI: {sf_AI[0]:.2f}")
1/1 [==============================] - 1s 1s/step
Diffusive size factor from FD: 8.39, and AI: 8.47