trim_floating_solid#

trim_floating_solidfunction is a filter which removes solids not attached to primary solid structure.

import numpy as np
import porespy as ps
import scipy.ndimage as spim
import scipy
import matplotlib.pyplot as plt
import skimage
ps.visualization.set_mpl_style()

im#

How floating solids are removed and the result can depend on whether the image is 2D or 3D so here we investigate both cases. The 2D and 3D images are visualized but only a slice of the 3D image is shown.

im2d = ps.generators.blobs(shape=[500, 500])
im3d = ps.generators.blobs(shape=[500, 500, 500])

fig, ax = plt.subplots(1, 2, figsize=[12, 12]);
ax[0].imshow(im2d);
ax[0].axis(False);
ax[0].set_title('2D', fontdict={'fontsize': 18});
ax[1].imshow(im3d.take(indices=250, axis=2));
ax[1].axis(False);
ax[1].set_title('3D', fontdict={'fontsize': 18});
Error in callback <function flush_figures at 0x7f1f23c974c0> (for post_execute):
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
File /usr/share/miniconda/envs/test/lib/python3.8/site-packages/matplotlib_inline/backend_inline.py:126, in flush_figures()
    123 if InlineBackend.instance().close_figures:
    124     # ignore the tracking, just draw and close all figures
    125     try:
--> 126         return show(True)
    127     except Exception as e:
    128         # safely show traceback if in IPython, else raise
    129         ip = get_ipython()

File /usr/share/miniconda/envs/test/lib/python3.8/site-packages/matplotlib_inline/backend_inline.py:90, in show(close, block)
     88 try:
     89     for figure_manager in Gcf.get_all_fig_managers():
---> 90         display(
     91             figure_manager.canvas.figure,
     92             metadata=_fetch_figure_metadata(figure_manager.canvas.figure)
     93         )
     94 finally:
     95     show._to_draw = []

File /usr/share/miniconda/envs/test/lib/python3.8/site-packages/IPython/core/display_functions.py:298, in display(include, exclude, metadata, transient, display_id, raw, clear, *objs, **kwargs)
    296     publish_display_data(data=obj, metadata=metadata, **kwargs)
    297 else:
--> 298     format_dict, md_dict = format(obj, include=include, exclude=exclude)
    299     if not format_dict:
    300         # nothing to display (e.g. _ipython_display_ took over)
    301         continue

File /usr/share/miniconda/envs/test/lib/python3.8/site-packages/IPython/core/formatters.py:177, in DisplayFormatter.format(self, obj, include, exclude)
    175 md = None
    176 try:
--> 177     data = formatter(obj)
    178 except:
    179     # FIXME: log the exception
    180     raise

File /usr/share/miniconda/envs/test/lib/python3.8/site-packages/decorator.py:232, in decorate.<locals>.fun(*args, **kw)
    230 if not kwsyntax:
    231     args, kw = fix(args, kw, sig)
--> 232 return caller(func, *(extras + args), **kw)

File /usr/share/miniconda/envs/test/lib/python3.8/site-packages/IPython/core/formatters.py:221, in catch_format_error(method, self, *args, **kwargs)
    219 """show traceback on failed format call"""
    220 try:
--> 221     r = method(self, *args, **kwargs)
    222 except NotImplementedError:
    223     # don't warn on NotImplementedErrors
    224     return self._check_return(None, args[0])

File /usr/share/miniconda/envs/test/lib/python3.8/site-packages/IPython/core/formatters.py:338, in BaseFormatter.__call__(self, obj)
    336     pass
    337 else:
--> 338     return printer(obj)
    339 # Finally look for special method names
    340 method = get_real_method(obj, self.print_method)

File /usr/share/miniconda/envs/test/lib/python3.8/site-packages/IPython/core/pylabtools.py:169, in retina_figure(fig, base64, **kwargs)
    160 def retina_figure(fig, base64=False, **kwargs):
    161     """format a figure as a pixel-doubled (retina) PNG
    162 
    163     If `base64` is True, return base64-encoded str instead of raw bytes
   (...)
    167         base64 argument
    168     """
--> 169     pngdata = print_figure(fig, fmt="retina", base64=False, **kwargs)
    170     # Make sure that retina_figure acts just like print_figure and returns
    171     # None when the figure is empty.
    172     if pngdata is None:

File /usr/share/miniconda/envs/test/lib/python3.8/site-packages/IPython/core/pylabtools.py:152, in print_figure(fig, fmt, bbox_inches, base64, **kwargs)
    149     from matplotlib.backend_bases import FigureCanvasBase
    150     FigureCanvasBase(fig)
--> 152 fig.canvas.print_figure(bytes_io, **kw)
    153 data = bytes_io.getvalue()
    154 if fmt == 'svg':

File /usr/share/miniconda/envs/test/lib/python3.8/site-packages/matplotlib/backend_bases.py:2338, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
   2334 try:
   2335     # _get_renderer may change the figure dpi (as vector formats
   2336     # force the figure dpi to 72), so we need to set it again here.
   2337     with cbook._setattr_cm(self.figure, dpi=dpi):
-> 2338         result = print_method(
   2339             filename,
   2340             facecolor=facecolor,
   2341             edgecolor=edgecolor,
   2342             orientation=orientation,
   2343             bbox_inches_restore=_bbox_inches_restore,
   2344             **kwargs)
   2345 finally:
   2346     if bbox_inches and restore_bbox:

File /usr/share/miniconda/envs/test/lib/python3.8/site-packages/matplotlib/backend_bases.py:2204, in FigureCanvasBase._switch_canvas_and_return_print_method.<locals>.<lambda>(*args, **kwargs)
   2200     optional_kws = {  # Passed by print_figure for other renderers.
   2201         "dpi", "facecolor", "edgecolor", "orientation",
   2202         "bbox_inches_restore"}
   2203     skip = optional_kws - {*inspect.signature(meth).parameters}
-> 2204     print_method = functools.wraps(meth)(lambda *args, **kwargs: meth(
   2205         *args, **{k: v for k, v in kwargs.items() if k not in skip}))
   2206 else:  # Let third-parties do as they see fit.
   2207     print_method = meth

File /usr/share/miniconda/envs/test/lib/python3.8/site-packages/matplotlib/_api/deprecation.py:410, in delete_parameter.<locals>.wrapper(*inner_args, **inner_kwargs)
    400     deprecation_addendum = (
    401         f"If any parameter follows {name!r}, they should be passed as "
    402         f"keyword, not positionally.")
    403     warn_deprecated(
    404         since,
    405         name=repr(name),
   (...)
    408                  else deprecation_addendum,
    409         **kwargs)
--> 410 return func(*inner_args, **inner_kwargs)

File /usr/share/miniconda/envs/test/lib/python3.8/site-packages/matplotlib/backends/backend_agg.py:517, in FigureCanvasAgg.print_png(self, filename_or_obj, metadata, pil_kwargs, *args)
    468 @_api.delete_parameter("3.5", "args")
    469 def print_png(self, filename_or_obj, *args,
    470               metadata=None, pil_kwargs=None):
    471     """
    472     Write the figure to a PNG file.
    473 
   (...)
    515         *metadata*, including the default 'Software' key.
    516     """
--> 517     self._print_pil(filename_or_obj, "png", pil_kwargs, metadata)

File /usr/share/miniconda/envs/test/lib/python3.8/site-packages/matplotlib/backends/backend_agg.py:463, in FigureCanvasAgg._print_pil(self, filename_or_obj, fmt, pil_kwargs, metadata)
    458 def _print_pil(self, filename_or_obj, fmt, pil_kwargs, metadata=None):
    459     """
    460     Draw the canvas, then save it using `.image.imsave` (to which
    461     *pil_kwargs* and *metadata* are forwarded).
    462     """
--> 463     FigureCanvasAgg.draw(self)
    464     mpl.image.imsave(
    465         filename_or_obj, self.buffer_rgba(), format=fmt, origin="upper",
    466         dpi=self.figure.dpi, metadata=metadata, pil_kwargs=pil_kwargs)

File /usr/share/miniconda/envs/test/lib/python3.8/site-packages/matplotlib/backends/backend_agg.py:405, in FigureCanvasAgg.draw(self)
    401 # Acquire a lock on the shared font cache.
    402 with RendererAgg.lock, \
    403      (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar
    404       else nullcontext()):
--> 405     self.figure.draw(self.renderer)
    406     # A GUI class may be need to update a window using this draw, so
    407     # don't forget to call the superclass.
    408     super().draw()

File /usr/share/miniconda/envs/test/lib/python3.8/site-packages/matplotlib/artist.py:74, in _finalize_rasterization.<locals>.draw_wrapper(artist, renderer, *args, **kwargs)
     72 @wraps(draw)
     73 def draw_wrapper(artist, renderer, *args, **kwargs):
---> 74     result = draw(artist, renderer, *args, **kwargs)
     75     if renderer._rasterizing:
     76         renderer.stop_rasterizing()

File /usr/share/miniconda/envs/test/lib/python3.8/site-packages/matplotlib/artist.py:51, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     48     if artist.get_agg_filter() is not None:
     49         renderer.start_filter()
---> 51     return draw(artist, renderer)
     52 finally:
     53     if artist.get_agg_filter() is not None:

File /usr/share/miniconda/envs/test/lib/python3.8/site-packages/matplotlib/figure.py:3071, in Figure.draw(self, renderer)
   3068         # ValueError can occur when resizing a window.
   3070 self.patch.draw(renderer)
-> 3071 mimage._draw_list_compositing_images(
   3072     renderer, self, artists, self.suppressComposite)
   3074 for sfig in self.subfigs:
   3075     sfig.draw(renderer)

File /usr/share/miniconda/envs/test/lib/python3.8/site-packages/matplotlib/image.py:131, in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    129 if not_composite or not has_images:
    130     for a in artists:
--> 131         a.draw(renderer)
    132 else:
    133     # Composite any adjacent images together
    134     image_group = []

File /usr/share/miniconda/envs/test/lib/python3.8/site-packages/matplotlib/artist.py:51, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     48     if artist.get_agg_filter() is not None:
     49         renderer.start_filter()
---> 51     return draw(artist, renderer)
     52 finally:
     53     if artist.get_agg_filter() is not None:

File /usr/share/miniconda/envs/test/lib/python3.8/site-packages/matplotlib/axes/_base.py:3107, in _AxesBase.draw(self, renderer)
   3104         a.draw(renderer)
   3105     renderer.stop_rasterizing()
-> 3107 mimage._draw_list_compositing_images(
   3108     renderer, self, artists, self.figure.suppressComposite)
   3110 renderer.close_group('axes')
   3111 self.stale = False

File /usr/share/miniconda/envs/test/lib/python3.8/site-packages/matplotlib/image.py:131, in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    129 if not_composite or not has_images:
    130     for a in artists:
--> 131         a.draw(renderer)
    132 else:
    133     # Composite any adjacent images together
    134     image_group = []

File /usr/share/miniconda/envs/test/lib/python3.8/site-packages/matplotlib/artist.py:51, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     48     if artist.get_agg_filter() is not None:
     49         renderer.start_filter()
---> 51     return draw(artist, renderer)
     52 finally:
     53     if artist.get_agg_filter() is not None:

File /usr/share/miniconda/envs/test/lib/python3.8/site-packages/matplotlib/image.py:641, in _ImageBase.draw(self, renderer, *args, **kwargs)
    639         renderer.draw_image(gc, l, b, im, trans)
    640 else:
--> 641     im, l, b, trans = self.make_image(
    642         renderer, renderer.get_image_magnification())
    643     if im is not None:
    644         renderer.draw_image(gc, l, b, im)

File /usr/share/miniconda/envs/test/lib/python3.8/site-packages/matplotlib/image.py:949, in AxesImage.make_image(self, renderer, magnification, unsampled)
    946 transformed_bbox = TransformedBbox(bbox, trans)
    947 clip = ((self.get_clip_box() or self.axes.bbox) if self.get_clip_on()
    948         else self.figure.bbox)
--> 949 return self._make_image(self._A, bbox, transformed_bbox, clip,
    950                         magnification, unsampled=unsampled)

File /usr/share/miniconda/envs/test/lib/python3.8/site-packages/matplotlib/image.py:515, in _ImageBase._make_image(self, A, in_bbox, out_bbox, clip_bbox, magnification, unsampled, round_to_pixel_border)
    510 if isinstance(self.norm, mcolors.NoNorm):
    511     A_resampled = A_resampled.astype(A.dtype)
    513 mask = (np.where(A.mask, np.float32(np.nan), np.float32(1))
    514         if A.mask.shape == A.shape  # nontrivial mask
--> 515         else np.ones_like(A, np.float32))
    516 # we always have to interpolate the mask to account for
    517 # non-affine transformations
    518 out_alpha = _resample(self, mask, out_shape, t, resample=True)

File <__array_function__ internals>:180, in ones_like(*args, **kwargs)

File /usr/share/miniconda/envs/test/lib/python3.8/site-packages/numpy/core/numeric.py:280, in ones_like(a, dtype, order, subok, shape)
    218 @array_function_dispatch(_ones_like_dispatcher)
    219 def ones_like(a, dtype=None, order='K', subok=True, shape=None):
    220     """
    221     Return an array of ones with the same shape and type as a given array.
    222 
   (...)
    278 
    279     """
--> 280     res = empty_like(a, dtype=dtype, order=order, subok=subok, shape=shape)
    281     multiarray.copyto(res, 1, casting='unsafe')
    282     return res

File <__array_function__ internals>:180, in empty_like(*args, **kwargs)

File /usr/share/miniconda/envs/test/lib/python3.8/site-packages/numpy/ma/core.py:3043, in MaskedArray.__array_finalize__(self, obj)
   3040         _mask = _mask.astype(_mask_dtype, order)
   3041     else:
   3042         # Take a view so shape changes, etc., do not propagate back.
-> 3043         _mask = _mask.view()
   3044 else:
   3045     _mask = nomask

KeyboardInterrupt: 

2D conn#

conn options for 2D images are 4 and 8 for square and diagonal neighbours respectively.

x1 = ps.filters.trim_floating_solid(im=im2d, conn=4)
x2 = ps.filters.trim_floating_solid(im=im2d, conn=8)

fig, ax = plt.subplots(1, 2, figsize=[12, 12]);
ax[0].imshow(x1);
ax[0].axis(False);
ax[0].set_title('conn = 4', fontdict={'fontsize': 18});
ax[1].imshow(x2);
ax[1].axis(False);
ax[1].set_title('conn = 8', fontdict={'fontsize': 18});
../../../_images/89d795806bea8a2f022c78fd42a2a2b367f68f5c58b7b3212434947cd8b60dcf.svg

conn options for 3D images are 6 and 26 for similarly square and diagonal neighbours. Apparent floating solids in the visualized 2D slice may actually be attached to solid in adjacent 2D slice and therefore may not actually be considered to be a floating solid.

x3 = ps.filters.trim_floating_solid(im=im3d, conn=6)
x4 = ps.filters.trim_floating_solid(im=im3d, conn=26)

fig, ax = plt.subplots(1, 2, figsize=[12, 12]);
ax[0].imshow(x3.take(indices=250, axis=2));
ax[0].axis(False);
ax[0].set_title('conn = 6', fontdict={'fontsize': 18});
ax[1].imshow(x4.take(indices=250, axis=2));
ax[1].axis(False);
ax[1].set_title('conn = 26', fontdict={'fontsize': 18});
../../../_images/91b70550a01d0d179d6dea6743a0df6e2e70ef8ac076fdb5056ff52cbdb2cd6d.svg