Showing 6 of 12 files from the diff.
Other files ignored by Codecov

@@ -1,8 +1,47 @@
Loading
 1 1 `import scipy as sp` 2 + `import scipy.ndimage as spim` 2 3 `# import matplotlib.pyplot as plt` 3 4 `# from mpl_toolkits.mplot3d.art3d import Poly3DCollection` 4 5 5 6 7 + `def show_3D(im):` 8 + ` r"""` 9 + ` Rotates a 3D image and creates an angled view for rough 2D visualization.` 10 + 11 + ` Because it rotates the image it can be slow for large image, so is` 12 + ` mostly meant for rough checking of small prototype images.` 13 + 14 + ` Parameters` 15 + ` ----------` 16 + ` im : 3D-array` 17 + ` The 3D array to be viewed from an angle` 18 + 19 + ` Returns` 20 + ` -------` 21 + ` image : 2D-array` 22 + ` A 2D veiw of the given 3D image` 23 + 24 + ` Notes` 25 + ` -----` 26 + ` This function assumes that the image contains ``True`` for void space and` 27 + ` so inverts the image to show the solid material.` 28 + 29 + ` """` 30 + ` im = ~sp.copy(im)` 31 + ` if im.ndim < 3:` 32 + ` raise Exception('show_3D only applies to 3D images')` 33 + ` im = spim.rotate(input=im, angle=22.5, axes=[0, 1], order=0)` 34 + ` im = spim.rotate(input=im, angle=45, axes=[2, 1], order=0)` 35 + ` im = spim.rotate(input=im, angle=-17, axes=[0, 1], order=0, reshape=False)` 36 + ` mask = im != 0` 37 + ` view = sp.where(mask.any(axis=2), mask.argmax(axis=2), 0)` 38 + ` view = view.max() - view` 39 + ` f = view.max()/5` 40 + ` view[view == view.max()] = -f` 41 + ` view = (view + f)**2` 42 + ` return view` 43 + 44 + 6 45 `def show_planes(im):` 7 46 ` r"""` 8 47 ` Create a quick montage showing a 3D image in all three directions`

@@ -1,6 +1,7 @@
Loading
 1 1 `from collections import namedtuple` 2 2 `import scipy as sp` 3 3 `import numpy as np` 4 + `import operator as op` 4 5 `import scipy.ndimage as spim` 5 6 `import scipy.spatial as sptl` 6 7 `import warnings`
@@ -16,6 +17,66 @@
Loading
 16 17 `from porespy.tools import _create_alias_map` 17 18 18 19 20 + `def trim_small_clusters(im, size=1):` 21 + ` r"""` 22 + ` Remove isolated voxels or clusters smaller than a given size` 23 + 24 + ` Parameters` 25 + ` ----------` 26 + ` im : ND-array` 27 + ` The binary image from which voxels are to be removed` 28 + ` size : scalar` 29 + ` The threshold size of clusters to trim. As clusters with this many` 30 + ` voxels or fewer will be trimmed. The default is 1 so only single` 31 + ` voxels are removed.` 32 + 33 + ` Returns` 34 + ` -------` 35 + ` im : ND-image` 36 + ` A copy of ``im`` with clusters of voxels smaller than the given` 37 + ` ``size`` removed.` 38 + 39 + ` """` 40 + ` if im.dims == 2:` 41 + ` strel = disk(1)` 42 + ` elif im.ndims == 3:` 43 + ` strel = ball(1)` 44 + ` else:` 45 + ` raise Exception('Only 2D or 3D images are accepted')` 46 + ` filtered_array = sp.copy(im)` 47 + ` labels, N = spim.label(filtered_array, structure=strel)` 48 + ` id_sizes = sp.array(spim.sum(im, labels, range(N + 1)))` 49 + ` area_mask = (id_sizes <= size)` 50 + ` filtered_array[area_mask[labels]] = 0` 51 + ` return filtered_array` 52 + 53 + 54 + `def hold_peaks(im, axis=-1):` 55 + ` r"""` 56 + ` Replaces each voxel with the highest value along the given axis` 57 + 58 + ` Parameters` 59 + ` ----------` 60 + ` im : ND-image` 61 + ` A greyscale image whose peaks are to` 62 + ` """` 63 + ` A = im` 64 + ` B = np.swapaxes(A, axis, -1)` 65 + ` updown = np.empty((*B.shape[:-1], B.shape[-1]+1), B.dtype)` 66 + ` updown[..., 0], updown[..., -1] = -1, -1` 67 + ` np.subtract(B[..., 1:], B[..., :-1], out=updown[..., 1:-1])` 68 + ` chnidx = np.where(updown)` 69 + ` chng = updown[chnidx]` 70 + ` pkidx, = np.where((chng[:-1] > 0) & (chng[1:] < 0) | (chnidx[-1][:-1] == 0))` 71 + ` pkidx = (*map(op.itemgetter(pkidx), chnidx),)` 72 + ` out = np.zeros_like(A)` 73 + ` aux = out.swapaxes(axis, -1)` 74 + ` aux[(*map(op.itemgetter(slice(1, None)), pkidx),)] = np.diff(B[pkidx])` 75 + ` aux[..., 0] = B[..., 0]` 76 + ` result = out.cumsum(axis=axis)` 77 + ` return result` 78 + 79 + 19 80 `def distance_transform_lin(im, axis=0, mode='both'):` 20 81 ` r"""` 21 82 ` Replaces each void voxel with the linear distance to the nearest solid`
@@ -985,10 +1046,11 @@
Loading
 985 1046 986 1047 `def local_thickness(im, sizes=25, mode='hybrid'):` 987 1048 ` r"""` 988 - ` For each voxel, this functions calculates the radius of the largest sphere` 989 - ` that both engulfs the voxel and fits entirely within the foreground. This` 990 - ` is not the same as a simple distance transform, which finds the largest` 991 - ` sphere that could be *centered* on each voxel.` 1049 + ` For each voxel, this function calculates the radius of the largest sphere` 1050 + ` that both engulfs the voxel and fits entirely within the foreground.` 1051 + 1052 + ` This is not the same as a simple distance transform, which finds the` 1053 + ` largest sphere that could be *centered* on each voxel.` 992 1054 993 1055 ` Parameters` 994 1056 ` ----------`
@@ -1111,7 +1173,7 @@
Loading
 1111 1173 1112 1174 ` Notes` 1113 1175 ` -----` 1114 - ` There are many ways to perform this filter, and PoreSpy offer 3, which` 1176 + ` There are many ways to perform this filter, and PoreSpy offers 3, which` 1115 1177 ` users can choose between via the ``mode`` argument. These methods all` 1116 1178 ` work in a similar way by finding which foreground voxels can accomodate` 1117 1179 ` a sphere of a given radius, then repeating for smaller radii.`
@@ -1190,7 +1252,7 @@
Loading
 1190 1252 ` ----------` 1191 1253 ` im : ND-array` 1192 1254 ` The array to be trimmed` 1193 - ` inlets : ND-array of tuple of indices` 1255 + ` inlets : ND-array or tuple of indices` 1194 1256 ` The locations of the inlets. Any voxels *not* connected directly to` 1195 1257 ` the inlets will be trimmed` 1196 1258
@@ -1200,11 +1262,14 @@
Loading
 1200 1262 ` An array of the same shape as ``im``, but with all foreground` 1201 1263 ` voxels not connected to the ``inlets`` removed.` 1202 1264 ` """` 1203 - ` temp = sp.zeros_like(im)` 1204 - ` temp[inlets] = True` 1205 - ` labels, N = spim.label(im + temp)` 1206 - ` im = im ^ (clear_border(labels=labels) > 0)` 1207 - ` return im` 1265 + ` labels = spim.label(im)[0]` 1266 + ` keep = sp.unique(labels[inlets])` 1267 + ` keep = keep[keep > 0]` 1268 + ` if len(keep) > 0:` 1269 + ` im2 = sp.reshape(sp.in1d(labels, keep), newshape=im.shape)` 1270 + ` else:` 1271 + ` im2 = sp.zeros_like(im)` 1272 + ` return im2` 1208 1273 1209 1274 1210 1275 `def _get_axial_shifts(ndim=2, include_diagonals=False):`
@@ -1319,3 +1384,66 @@
Loading
 1319 1384 ` return out[1:-1, 1:-1].copy()` 1320 1385 ` else:` 1321 1386 ` return out[1:-1, 1:-1, 1:-1].copy()` 1387 + 1388 + 1389 + `def prune_branches(skel, branch_points=None, iterations=1):` 1390 + ` r"""` 1391 + ` Removes all dangling ends or tails of a skeleton.` 1392 + 1393 + ` Parameters` 1394 + ` ----------` 1395 + ` skel : ND-image` 1396 + ` A image of a full or partial skeleton from which the tails should be` 1397 + ` trimmed.` 1398 + 1399 + ` branch_points : ND-image, optional` 1400 + ` An image the same size ``skel`` with True values indicating the branch` 1401 + ` points of the skeleton. If this is not provided it is calculated` 1402 + ` automatically.` 1403 + 1404 + ` Returns` 1405 + ` -------` 1406 + ` An ND-image containing the skeleton with tails removed.` 1407 + 1408 + ` """` 1409 + ` skel = skel > 0` 1410 + ` if skel.ndim == 2:` 1411 + ` from skimage.morphology import square as cube` 1412 + ` else:` 1413 + ` from skimage.morphology import cube` 1414 + ` # Create empty image to house results` 1415 + ` im_result = sp.zeros_like(skel)` 1416 + ` # If branch points are not supplied, attempt to find them` 1417 + ` if branch_points is None:` 1418 + ` branch_points = spim.convolve(skel*1.0, weights=cube(3)) > 3` 1419 + ` branch_points = branch_points*skel` 1420 + ` # Store original branch points before dilating` 1421 + ` pts_orig = branch_points` 1422 + ` # Find arcs of skeleton by deleting branch points` 1423 + ` arcs = skel*(~branch_points)` 1424 + ` # Label arcs` 1425 + ` arc_labels = spim.label(arcs, structure=cube(3))[0]` 1426 + ` # Dilate branch points so they overlap with the arcs` 1427 + ` branch_points = spim.binary_dilation(branch_points, structure=cube(3))` 1428 + ` pts_labels = spim.label(branch_points, structure=cube(3))[0]` 1429 + ` # Now scan through each arc to see if it's connected to two branch points` 1430 + ` slices = spim.find_objects(arc_labels)` 1431 + ` label_num = 0` 1432 + ` for s in slices:` 1433 + ` label_num += 1` 1434 + ` # Find branch point labels the overlap current arc` 1435 + ` hits = pts_labels[s]*(arc_labels[s] == label_num)` 1436 + ` # If image contains 2 branch points, then it's not a tail.` 1437 + ` if len(sp.unique(hits)) == 3:` 1438 + ` im_result[s] += arc_labels[s] == label_num` 1439 + ` # Add missing branch points back to arc image to make complete skeleton` 1440 + ` im_result += skel*pts_orig` 1441 + ` if iterations > 1:` 1442 + ` iterations -= 1` 1443 + ` im_temp = sp.copy(im_result)` 1444 + ` im_result = prune_branches(skel=im_result,` 1445 + ` branch_points=None,` 1446 + ` iterations=iterations)` 1447 + ` if sp.all(im_temp == im_result):` 1448 + ` iterations = 0` 1449 + ` return im_result`

@@ -234,45 +234,6 @@
Loading
 234 234 ` return ret` 235 235 236 236 237 - `def get_slice(im, center, size, pad=0):` 238 - ` r"""` 239 - ` Given a ``center`` location and ``radius`` of a feature, returns the slice` 240 - ` object into the ``im`` that bounds the feature but does not extend beyond` 241 - ` the image boundaries.` 242 - 243 - ` Parameters` 244 - ` ----------` 245 - ` im : ND-image` 246 - ` The image of the porous media` 247 - 248 - ` center : array_like` 249 - ` The coordinates of the center of the feature of interest` 250 - 251 - ` size : array_like or scalar` 252 - ` The size of the feature in each direction. If a scalar is supplied,` 253 - ` this implies the same size in all directions.` 254 - 255 - ` pad : scalar or array_like` 256 - ` The amount to pad onto each side of the slice. The default is 0. A` 257 - ` scalar value will increase the slice size equally in all directions,` 258 - ` while an array the same shape as ``im.shape`` can be passed to pad` 259 - ` a specified amount in each direction.` 260 - 261 - ` Returns` 262 - ` -------` 263 - ` slices : list` 264 - ` A list of slice objects, each indexing into one dimension of the image.` 265 - ` """` 266 - ` p = sp.ones(shape=im.ndim, dtype=int) * sp.array(pad)` 267 - ` s = sp.ones(shape=im.ndim, dtype=int) * sp.array(size)` 268 - ` slc = []` 269 - ` for dim in range(im.ndim):` 270 - ` lower_im = sp.amax((center[dim] - s[dim] - p[dim], 0))` 271 - ` upper_im = sp.amin((center[dim] + s[dim] + 1 + p[dim], im.shape[dim]))` 272 - ` slc.append(slice(lower_im, upper_im))` 273 - ` return slc` 274 - 275 - 276 237 `def find_outer_region(im, r=0):` 277 238 ` r"""` 278 239 ` Finds regions of the image that are outside of the solid matrix.`
@@ -648,6 +609,10 @@
Loading
 648 609 ` 3D) indices is returned. This tuple can be used directly to index into` 649 610 ` the image, such as ``im[tup] = 2``.` 650 611 612 + ` asmask : Boolean` 613 + ` If ``True`` (default) then an image of the specified ``shape`` is` 614 + ` returned, otherwise indices of the border voxels are returned.` 615 + 651 616 ` Returns` 652 617 ` -------` 653 618 ` image : ND-array`
@@ -677,6 +642,7 @@
Loading
 677 642 ` [[ True True True]` 678 643 ` [ True False True]` 679 644 ` [ True True True]]` 645 + 680 646 ` """` 681 647 ` ndims = len(shape)` 682 648 ` t = thickness`
@@ -736,9 +702,8 @@
Loading
 736 702 737 703 `def norm_to_uniform(im, scale=None):` 738 704 ` r"""` 739 - ` Take an image with normally distributed greyscale values and converts it to` 740 - ` a uniform (i.e. flat) distribution. It's also possible to specify the` 741 - ` lower and upper limits of the uniform distribution.` 705 + ` Take an image with normally distributed greyscale values and convert it to` 706 + ` a uniform (i.e. flat) distribution.` 742 707 743 708 ` Parameters` 744 709 ` ----------`
@@ -1117,3 +1082,45 @@
Loading
 1117 1082 ` raise Exception('Alias labels does not match with image labels '` 1118 1083 ` 'please provide correct image labels')` 1119 1084 ` return al` 1085 + 1086 + 1087 + `def extract_regions(regions, labels: list, trim=True):` 1088 + ` r"""` 1089 + ` Combine given regions into a single boolean mask` 1090 + 1091 + ` Parameters` 1092 + ` -----------` 1093 + ` regions : ND-array` 1094 + ` An image containing an arbitrary number of labeled regions` 1095 + ` labels : array_like or scalar` 1096 + ` A list of labels indicating which region or regions to extract` 1097 + ` trim : bool` 1098 + ` If ``True`` then image shape will trimmed to a bounding box around the` 1099 + ` given regions.` 1100 + 1101 + ` Returns` 1102 + ` -------` 1103 + ` im : ND-array` 1104 + ` A boolean mask with ``True`` values indicating where the given labels` 1105 + ` exist` 1106 + 1107 + ` """` 1108 + ` if type(labels) is int:` 1109 + ` labels = [labels]` 1110 + ` s = spim.find_objects(regions)` 1111 + ` im_new = sp.zeros_like(regions)` 1112 + ` x_min, y_min, z_min = sp.inf, sp.inf, sp.inf` 1113 + ` x_max, y_max, z_max = 0, 0, 0` 1114 + ` for i in labels:` 1115 + ` im_new[s[i-1]] = regions[s[i-1]] == i` 1116 + ` x_min, x_max = min(s[i-1][0].start, x_min), max(s[i-1][0].stop, x_max)` 1117 + ` y_min, y_max = min(s[i-1][1].start, y_min), max(s[i-1][1].stop, y_max)` 1118 + ` if regions.ndim == 3:` 1119 + ` z_min, z_max = min(s[i-1][2].start, z_min), max(s[i-1][2].stop, z_max)` 1120 + ` if trim:` 1121 + ` if regions.ndim == 3:` 1122 + ` bbox = bbox_to_slices([x_min, y_min, z_min, x_max, y_max, z_max])` 1123 + ` else:` 1124 + ` bbox = bbox_to_slices([x_min, y_min, x_max, y_max])` 1125 + ` im_new = im_new[bbox]` 1126 + ` return im_new`

@@ -704,7 +704,7 @@
Loading
 704 704 705 705 706 706 `def cylinders(shape: List[int], radius: int, ncylinders: int,` 707 - ` phi_max: float = 0, theta_max: float = 90):` 707 + ` phi_max: float = 0, theta_max: float = 90, length: float = None):` 708 708 ` r"""` 709 709 ` Generates a binary image of overlapping cylinders. This is a good` 710 710 ` approximation of a fibrous mat.`
@@ -722,14 +722,21 @@
Loading
 722 722 ` cylinders overlap and intersect different fractions of the domain.` 723 723 ` theta_max : scalar` 724 724 ` A value between 0 and 90 that controls the amount of rotation *in the*` 725 - ` XY plane, with 0 meaning all fibers point in the X-direction, and` 725 + ` XY plane, with 0 meaning all cylinders point in the X-direction, and` 726 726 ` 90 meaning they are randomly rotated about the Z axis by as much` 727 727 ` as +/- 90 degrees.` 728 728 ` phi_max : scalar` 729 - ` A value between 0 and 90 that controls the amount that the fibers` 730 - ` lie *out of* the XY plane, with 0 meaning all fibers lie in the XY` 731 - ` plane, and 90 meaning that fibers are randomly oriented out of the` 729 + ` A value between 0 and 90 that controls the amount that the cylinders` 730 + ` lie *out of* the XY plane, with 0 meaning all cylinders lie in the XY` 731 + ` plane, and 90 meaning that cylinders are randomly oriented out of the` 732 732 ` plane by as much as +/- 90 degrees.` 733 + ` length : scalar` 734 + ` The length of the cylinders to add. If ``None`` (default) then the` 735 + ` cylinders will extend beyond the domain in both directions so no ends` 736 + ` will exist. If a scalar value is given it will be interpreted as the` 737 + ` Euclidean distance between the two ends of the cylinder. Note that` 738 + ` one or both of the ends *may* still lie outside the domain, depending` 739 + ` on the randomly chosen center point of the cylinder.` 733 740 734 741 ` Returns` 735 742 ` -------`
@@ -741,7 +748,10 @@
Loading
 741 748 ` shape = sp.full((3, ), int(shape))` 742 749 ` elif sp.size(shape) == 2:` 743 750 ` raise Exception("2D cylinders don't make sense")` 744 - ` R = sp.sqrt(sp.sum(sp.square(shape))).astype(int)` 751 + ` if length is None:` 752 + ` R = sp.sqrt(sp.sum(sp.square(shape))).astype(int)` 753 + ` else:` 754 + ` R = length/2` 745 755 ` im = sp.zeros(shape)` 746 756 ` # Adjust max angles to be between 0 and 90` 747 757 ` if (phi_max > 90) or (phi_max < 0):`

@@ -81,7 +81,7 @@
Loading
 81 81 ` p_label[pore] = i` 82 82 ` p_coords[pore, :] = spim.center_of_mass(pore_im) + s_offset` 83 83 ` p_volume[pore] = sp.sum(pore_im)` 84 - ` p_dia_local[pore] = 2*sp.amax(pore_dt)` 84 + ` p_dia_local[pore] = (2*sp.amax(pore_dt)) - sp.sqrt(3)` 85 85 ` p_dia_global[pore] = 2*sp.amax(sub_dt)` 86 86 ` p_area_surf[pore] = sp.sum(pore_dt == 1)` 87 87 ` im_w_throats = spim.binary_dilation(input=pore_im, structure=struc_elem(1))`

@@ -197,12 +197,12 @@
Loading
 197 197 ` solid phase.` 198 198 199 199 ` All other values are ignored, so this can also return the relative` 200 - ` fraction of a phase of interest.` 200 + ` fraction of a phase of interest in trinary or multiphase images.` 201 201 202 202 ` Parameters` 203 203 ` ----------` 204 204 ` im : ND-array` 205 - ` Image of the void space with 1's indicating void space (or True) and` 205 + ` Image of the void space with 1's indicating void phase (or True) and` 206 206 ` 0's indicating the solid phase (or False).` 207 207 208 208 ` Returns`
@@ -387,7 +387,10 @@
Loading
 387 387 ` be automatically generated that span the data range.` 388 388 ` log : boolean` 389 389 ` If ``True`` (default) the size data is converted to log (base-10)` 390 - ` values before processing. This can help` 390 + ` values before processing. This can help to plot wide size` 391 + ` distributions or to better visualize the in the small size region.` 392 + ` Note that you can anti-log the radii values in the retunred ``tuple``,` 393 + ` but the binning is performed on the logged radii values.` 391 394 ` voxel_size : scalar` 392 395 ` The size of a voxel side in preferred units. The default is 1, so the` 393 396 ` user can apply the scaling to the returned results after the fact.`
@@ -416,8 +419,8 @@
Loading
 416 419 417 420 ` Notes` 418 421 ` -----` 419 - ` (1) To ensure the returned values represent actual sizes be sure to scale` 420 - ` the distance transform by the voxel size first (``dt *= voxel_size``)` 422 + ` (1) To ensure the returned values represent actual sizes you can manually` 423 + ` scale the input image by the voxel size first (``im *= voxel_size``)` 421 424 422 425 ` plt.bar(psd.R, psd.satn, width=psd.bin_widths, edgecolor='k')` 423 426
Files Coverage
porespy 85.2%
docs/conf.py 100.0%
examples.py 100.0%
Project Totals (17 files) 85.6%
1257.2
```TRAVIS_PYTHON_VERSION=3.7
TRAVIS_OS_NAME=linux
```
1257.1
```TRAVIS_PYTHON_VERSION=3.6
TRAVIS_OS_NAME=linux
```
1260.2
```TRAVIS_PYTHON_VERSION=3.7
TRAVIS_OS_NAME=linux
```
1260.1
```TRAVIS_PYTHON_VERSION=3.6
TRAVIS_OS_NAME=linux
```
 1 ```codecov: ``` 2 ``` branch: master ``` 3 4 ```coverage: ``` 5 ``` precision: 2 ``` 6 ``` round: down ``` 7 ``` range: "50...100" ``` 8 9 ``` status: ``` 10 ``` project: ``` 11 ``` default: ``` 12 ``` target: auto ``` 13 ``` threshold: null ``` 14 ``` branches: null ``` 15 16 ``` patch: ``` 17 ``` default: ``` 18 ``` target: auto ``` 19 ``` branches: null ``` 20 21 ```comment: ``` 22 ``` layout: "header, diff, changes, sunburst, uncovered" ``` 23 ``` branches: null ``` 24 ``` 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