@@ -908,7 +908,7 @@
Loading
908 908
909 909
    See Also
910 910
    --------
911 -
    props_to_image
911 +
    prop_to_image
912 912
913 913
    """
914 914
    mask = im > 0

@@ -74,7 +74,7 @@
Loading
74 74
            image = image.take(indices=ind, axis=axis)
75 75
        image = np.ma.array(image, mask=image == 0)
76 76
        fig = plt.subplot(1, len(im), i+1)
77 -
        plt.imshow(image, origin='lower')
77 +
        plt.imshow(image, origin='lower', interpolation='none')
78 78
    return fig
79 79
80 80

@@ -3,10 +3,16 @@
Loading
3 3
from tqdm import tqdm
4 4
import scipy.ndimage as spim
5 5
from porespy.tools import extract_subsection, bbox_to_slices
6 -
from skimage.measure import mesh_surface_area, marching_cubes
6 +
from skimage.measure import mesh_surface_area
7 +
try:
8 +
    from skimage.measure import marching_cubes
9 +
except ImportError:
10 +
    from skimage.measure import marching_cubes_lewiner as marching_cubes
7 11
from skimage.morphology import skeletonize_3d, ball
8 12
from skimage.measure import regionprops
13 +
from skimage.measure._regionprops import RegionProperties
9 14
from pandas import DataFrame
15 +
from edt import edt
10 16
11 17
12 18
def props_to_DataFrame(regionprops):
@@ -34,7 +40,7 @@
Loading
34 40
35 41
    See Also
36 42
    --------
37 -
    props_to_image
43 +
    prop_to_image
38 44
    regionprops_3d
39 45
    """
40 46
    # Parse the regionprops list and pull out all props with scalar values
@@ -47,19 +53,22 @@
Loading
47 53
                    metrics.append(item)
48 54
            except (TypeError, NotImplementedError, AttributeError):
49 55
                pass
50 -
    # Create a dictionary of all metrics that are simple scalar propertie
56 +
    # Create a dictionary of all metrics that are simple scalar properties
51 57
    d = {}
52 -
    for k in metrics:
58 +
    print('Processing the following properties: ', end="")
59 +
    for i, k in enumerate(metrics):
60 +
        print(k + ", ", end="")
53 61
        try:
54 62
            d[k] = np.array([r[k] for r in regionprops])
55 63
        except ValueError:
56 64
            print('Error encountered evaluating ' + k + ' so skipping it')
65 +
    print('... done')
57 66
    # Create pandas data frame an return
58 67
    df = DataFrame(d)
59 68
    return df
60 69
61 70
62 -
def props_to_image(regionprops, shape, prop):
71 +
def prop_to_image(regionprops, shape, prop):
63 72
    r"""
64 73
    Creates an image with each region colored according the specified ``prop``,
65 74
    as obtained by ``regionprops_3d``.
@@ -69,10 +78,8 @@
Loading
69 78
    regionprops : list
70 79
        This is a list of properties for each region that is computed
71 80
        by PoreSpy's ``regionprops_3D`` or Skimage's ``regionsprops``.
72 -
73 81
    shape : array_like
74 82
        The shape of the original image for which ``regionprops`` was obtained.
75 -
76 83
    prop : string
77 84
        The region property of interest.  Can be a scalar item such as 'volume'
78 85
        in which case the the regions will be colored by their respective
@@ -128,7 +135,7 @@
Loading
128 135
        The returned list contains all the metrics normally returned by
129 136
        **skimage.measure.regionprops** plus the following:
130 137
131 -
        'slice': Slice indices into the image that can be used to extract the
138 +
        'slices': Slice indices into the image that can be used to extract the
132 139
        region
133 140
134 141
        'volume': Volume of the region in number of voxels.
@@ -167,11 +174,6 @@
Loading
167 174
168 175
    Notes
169 176
    -----
170 -
    This function may seem slow compared to the skimage version, but that is
171 -
    because they defer calculation of certain properties until they are
172 -
    accessed, while this one evalulates everything (inlcuding the deferred
173 -
    properties from skimage's ``regionprops``)
174 -
175 177
    Regions can be identified using a watershed algorithm, which can be a bit
176 178
    tricky to obtain desired results.  *PoreSpy* includes the SNOW algorithm,
177 179
    which may be helpful.
@@ -181,51 +183,90 @@
Loading
181 183
    print('Calculating regionprops')
182 184
183 185
    results = regionprops(im)
184 -
    pbar = tqdm(range(len(results)), file=sys.stdout)
185 -
    for i in range(len(results)):
186 -
        pbar.update()
187 -
        mask = results[i].image
188 -
        mask_padded = np.pad(mask, pad_width=1, mode='constant')
189 -
        temp = spim.distance_transform_edt(mask_padded)
190 -
        dt = extract_subsection(temp, shape=mask.shape)
186 +
    for i, obj in enumerate(results):
187 +
        a = results[i]
188 +
        b = RegionPropertiesPS(a.slice,
189 +
                               a.label,
190 +
                               a._label_image,
191 +
                               a._intensity_image,
192 +
                               a._cache_active)
193 +
        results[i] = b
191 194
192 -
        # Slice indices
193 -
        results[i].slice = results[i]._slice
195 +
    return results
194 196
195 -
        # Volume of regions in voxels
196 -
        results[i].volume = results[i].area
197 197
198 -
        # Volume of bounding box, in voxels
199 -
        results[i].bbox_volume = np.prod(mask.shape)
198 +
class RegionPropertiesPS(RegionProperties):
200 199
201 -
        # Create an image of the border
202 -
        results[i].border = dt == 1
200 +
    @property
201 +
    def mask(self):
202 +
        return self.image
203 203
204 -
        # Create an image of the maximal inscribed sphere
205 -
        r = dt.max()
206 -
        inv_dt = spim.distance_transform_edt(dt < r)
207 -
        results[i].inscribed_sphere = inv_dt < r
204 +
    @property
205 +
    def slices(self):
206 +
        return self._slice
208 207
209 -
        # Find surface area using marching cubes and analyze the mesh
210 -
        tmp = np.pad(np.atleast_3d(mask), pad_width=1, mode='constant')
211 -
        tmp = spim.convolve(tmp, weights=ball(1)) / 5
212 -
        verts, faces, norms, vals = marching_cubes(volume=tmp, level=0)
213 -
        results[i].surface_mesh_vertices = verts
214 -
        results[i].surface_mesh_simplices = faces
215 -
        area = mesh_surface_area(verts, faces)
216 -
        results[i].surface_area = area
208 +
    @property
209 +
    def volume(self):
210 +
        return self.area
211 +
212 +
    @property
213 +
    def bbox_volume(self):
214 +
        mask = self.mask
215 +
        return np.prod(mask.shape)
217 216
218 -
        # Find sphericity
219 -
        vol = results[i].volume
217 +
    @property
218 +
    def border(self):
219 +
        return self.dt == 1
220 +
221 +
    @property
222 +
    def dt(self):
223 +
        mask = self.mask
224 +
        mask_padded = np.pad(mask, pad_width=1, mode='constant')
225 +
        temp = edt(mask_padded)
226 +
        return extract_subsection(temp, shape=mask.shape)
227 +
228 +
    @property
229 +
    def inscribed_sphere(self):
230 +
        dt = self.dt
231 +
        r = dt.max()
232 +
        inv_dt = edt(dt < r)
233 +
        return inv_dt < r
234 +
235 +
    @property
236 +
    def sphericity(self):
237 +
        vol = self.volume
220 238
        r = (3 / 4 / np.pi * vol)**(1 / 3)
221 239
        a_equiv = 4 * np.pi * r**2
222 -
        a_region = results[i].surface_area
223 -
        results[i].sphericity = a_equiv / a_region
224 -
225 -
        # Find skeleton of region
226 -
        results[i].skeleton = skeletonize_3d(mask)
240 +
        a_region = self.surface_area
241 +
        return a_equiv / a_region
227 242
228 -
        # Volume of convex image, equal to area in 2D, so just translating
229 -
        results[i].convex_volume = results[i].convex_area
243 +
    @property
244 +
    def skeleton(self):
245 +
        return skeletonize_3d(self.mask)
230 246
231 -
    return results
247 +
    @property
248 +
    def surface_area(self):
249 +
        mask = self.mask
250 +
        tmp = np.pad(np.atleast_3d(mask), pad_width=1, mode='constant')
251 +
        tmp = spim.convolve(tmp, weights=ball(1)) / 5
252 +
        verts, faces, norms, vals = marching_cubes(volume=tmp, level=0)
253 +
        self._surface_mesh_vertices = verts
254 +
        self._surface_mesh_simplices = faces
255 +
        area = mesh_surface_area(verts, faces)
256 +
        return area
257 +
258 +
    @property
259 +
    def surface_mesh_vertices(self):
260 +
        if not hasattr(self, '_surface_mesh_vertices'):
261 +
            self.surface_area
262 +
        return self._surface_mesh_vertices
263 +
264 +
    @property
265 +
    def surface_mesh_simplices(self):
266 +
        if not hasattr(self, '_surface_mesh_simplices'):
267 +
            self.surface_area
268 +
        return self._surface_mesh_simplices
269 +
270 +
    @property
271 +
    def convex_volume(self):
272 +
        return self.convex_area
Files Coverage
porespy 88.1%
Project Totals (16 files) 88.1%
codecov-umbrella
Build #324752151 -
unittests
1
codecov:
2
  branch: dev
3

4
coverage:
5
  precision: 1
6
  round: down
7
  range: "50...100"
8

9
  status:
10
    project:
11
      default:
12
        target: auto
13
        threshold: 0.5%
14
        branches: null
15

16
    patch:
17
      default:
18
        target: auto
19
        threshold: 0.5%
20
        branches: null
21

22
comment:
23
  layout: "header, diff, changes, sunburst, uncovered"
24
  branches: null
25
  behavior: default
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading