def _get_spacing(self): # Find Network spacing P12 = self["throat.conns"] C12 = self["pore.coords"][P12] mag = np.linalg.norm(np.diff(C12, axis=1), axis=2) unit_vec = np.around(np.squeeze(np.diff(C12, axis=1)) / mag, decimals=14) spacing = [0, 0, 0] dims = topotools.dimensionality(self) # Ensure vectors point in n-dims unique directions c = {tuple(row): 1 for row in unit_vec} mag = np.atleast_1d(mag.squeeze()).astype(float) if len(c.keys()) > sum(dims): raise Exception( "Spacing is undefined when throats point in more directions" " than network has dimensions." ) for ax in [0, 1, 2]: if dims[ax]: inds = np.where(unit_vec[:, ax] == unit_vec[:, ax].max())[0] temp = np.unique(mag[inds]) if not np.allclose(temp, temp[0]): raise Exception("A unique value of spacing could not be found.") spacing[ax] = temp[0] self.settings['spacing'] = spacing return np.array(spacing)
def _label_surface_pores(self): r""" """ hits = np.zeros_like(self.Ps, dtype=bool) dims = topotools.dimensionality(self) mn = np.amin(self["pore.coords"], axis=0) mx = np.amax(self["pore.coords"], axis=0) for ax in [0, 1, 2]: if dims[ax]: hits += self["pore.coords"][:, ax] <= mn[ax] hits += self["pore.coords"][:, ax] >= mx[ax] self["pore.surface"] = hits
def label_surface_nodes(network): r""" """ hits = np.zeros_like(network.Ps, dtype=bool) dims = dimensionality(network) mn = np.amin(network["vert.coords"], axis=0) mx = np.amax(network["vert.coords"], axis=0) for ax in np.where(dims)[0]: if dims[ax]: hits += network["vert.coords"][:, ax] <= mn[ax] hits += network["vert.coords"][:, ax] >= mx[ax] network["vert.surface"] = hits return network
def __init__(self, template, spacing=[1, 1, 1], **kwargs): template = np.atleast_3d(template) super().__init__(shape=template.shape, spacing=spacing, **kwargs) coords = np.unravel_index(range(template.size), template.shape) self['pore.template_coords'] = np.vstack(coords).T self['pore.template_indices'] = self.Ps topotools.trim(network=self, pores=template.flatten() == 0) # Add "internal_surface" label to "fake" surface pores! ndims = topotools.dimensionality(self).sum() max_neighbors = 6 if ndims == 3 else 4 num_neighbors = np.diff(self.get_adjacency_matrix(fmt="csr").indptr) mask_surface = self["pore.surface"] mask_internal_surface = (num_neighbors < max_neighbors) & ~mask_surface self.set_label("pore.internal_surface", pores=mask_internal_surface)
def label_faces(network, threshold=0.05): r""" Label the vertices sitting on the faces of the domain in accordance with the conventions used for cubic etc. """ dims = dimensionality(network) coords = np.around(network['vert.coords'], decimals=10) min_labels = ['front', 'left', 'bottom'] max_labels = ['back', 'right', 'top'] min_coords = np.amin(coords, axis=0) max_coords = np.amax(coords, axis=0) for ax in np.where(dims)[0]: network['vert.' + min_labels[ax]] = coords[:, ax] <= threshold*min_coords[ax] network['vert.' + max_labels[ax]] = coords[:, ax] >= (1-threshold)*max_coords[ax] return network
def get_spacing(network): r""" Determine spacing of a cubic network Parameters ---------- network : dictionary A network dictionary containing 'vert.coords' and 'edge.conns' Returns ------- spacing : ndarray An array containing the spacing between vertices in each direction Notes ----- This function only works on simple cubic networks with no boundary vertices. If a unique spacing cannot be found in each direction, and/or the edges are not all oriented perpendicularly, exceptions will be raised. """ from openpnm.topotools import dimensionality # Find Network spacing P12 = network["edge.conns"] C12 = network["vert.coords"][P12] mag = np.linalg.norm(np.diff(C12, axis=1), axis=2) unit_vec = np.around(np.squeeze(np.diff(C12, axis=1)) / mag, decimals=14) spacing = [0, 0, 0] dims = dimensionality(coords=network['vert.coords']) # Ensure vectors point in n-dims unique directions c = {tuple(row): 1 for row in unit_vec} mag = np.atleast_1d(mag.squeeze()).astype(float) if len(c.keys()) > sum(dims): raise Exception( "Spacing is undefined when throats point in more directions" " than network has dimensions." ) for ax in [0, 1, 2]: if dims[ax]: inds = np.where(unit_vec[:, ax] == unit_vec[:, ax].max())[0] temp = np.unique(mag[inds]) if not np.allclose(temp, temp[0]): raise Exception("A unique value of spacing could not be found.") spacing[ax] = temp[0] return np.array(spacing)
def _get_spacing(self): # Find Network spacing P12 = self['throat.conns'] C12 = self['pore.coords'][P12] mag = np.linalg.norm(np.diff(C12, axis=1), axis=2) unit_vec = sp.around(sp.squeeze(np.diff(C12, axis=1))/mag, decimals=14) spacing = [0, 0, 0] dims = topotools.dimensionality(self) # Ensure vectors point in n-dims unique directions c = {tuple(row): 1 for row in unit_vec} if len(c.keys()) > sum(dims): raise Exception('Spacing is undefined when throats point in ' + 'more directions than network has dimensions') mag = sp.float64(mag.squeeze()) for ax in [0, 1, 2]: if dims[ax]: inds = sp.where(unit_vec[:, ax] == unit_vec[:, ax].max())[0] temp = sp.unique(mag[inds]) if not sp.allclose(temp, temp[0]): raise Exception('A unique value of spacing could not be found') else: spacing[ax] = temp[0] return sp.array(spacing)
def plot_connections(network, throats=None, ax=None, size_by=None, color_by=None, cmap='jet', color='b', alpha=1.0, linestyle='solid', linewidth=1, **kwargs): # pragma: no cover r""" Produce a 3D plot of the network topology. This shows how throats connect for quick visualization without having to export data to veiw in Paraview. Parameters ---------- network : GenericNetwork The network whose topological connections to plot throats : array_like (optional) The list of throats to plot if only a sub-sample is desired. This is useful for inspecting a small region of the network. If no throats are specified then all throats are shown. fig : Matplotlib figure handle and line property arguments (optional) If a ``fig`` is supplied, then the topology will be overlaid on this plot. This makes it possible to combine coordinates and connections, and to color throats differently for instance. size_by : array_like (optional) An ndarray of throat values (e.g. alg['throat.rate']). These values are used to scale the ``linewidth``, so if the lines are too thin, then increase ``linewidth``. color_by : str or array_like (optional) An ndarray of throat values (e.g. alg['throat.rate']). cmap : str or cmap object (optional) The matplotlib colormap to use if specfying a throat property for ``color_by`` color : str, optional (optional) A matplotlib named color (e.g. 'r' for red). alpha : float (optional) The transparency of the lines, with 1 being solid and 0 being invisible linestyle : str (optional) Can be one of {'solid', 'dashed', 'dashdot', 'dotted'}. Default is 'solid'. linewidth : float (optional) Controls the thickness of drawn lines. Is used to scale the thickness if ``size_by`` is given. Default is 1. If a value is provided for ``size_by`` then they are used to scale the ``linewidth``. **kwargs : dict All other keyword arguments are passed on to the ``Line3DCollection`` class of matplotlib, so check their documentation for additional formatting options. Returns ------- lc : LineCollection or Line3DCollection Matplotlib object containing the lines representing the throats. Notes ----- To create a single plot containing both pore coordinates and throats, consider creating an empty figure and then pass the ``ax`` object as an argument to ``plot_connections`` and ``plot_coordinates``. Otherwise, each call to either of these methods creates a new figure. See Also -------- plot_coordinates Examples -------- >>> import openpnm as op >>> import matplotlib as mpl >>> import matplotlib.pyplot as plt >>> mpl.use('Agg') >>> pn = op.network.Cubic(shape=[10, 10, 3]) >>> pn.add_boundary_pores() >>> Ts = pn.throats('*boundary', mode='not') # find internal throats >>> fig, ax = plt.subplots() # create empty figure >>> _ = op.topotools.plot_connections(network=pn, ... throats=Ts) # plot internal throats >>> Ts = pn.throats('*boundary') # find boundary throats >>> _ = op.topotools.plot_connections(network=pn, ... throats=Ts, ... ax=ax, ... color='r') # plot boundary throats in red """ import matplotlib.pyplot as plt from matplotlib import cm from matplotlib import colors as mcolors from mpl_toolkits.mplot3d import Axes3D from matplotlib.collections import LineCollection from mpl_toolkits.mplot3d.art3d import Line3DCollection from openpnm.topotools import dimensionality Ts = network.Ts if throats is None else network._parse_indices(throats) dim = dimensionality(network) ThreeD = True if dim.sum() == 3 else False # Add a dummy axis for 1D networks if dim.sum() == 1: dim[np.argwhere(~dim)[0]] = True if "fig" in kwargs.keys(): raise Exception("'fig' argument is deprecated, use 'ax' instead.") if ax is None: fig, ax = plt.subplots() else: # The next line is necessary if ax was created using plt.subplots() fig, ax = ax.get_figure(), ax.get_figure().gca() if ThreeD and ax.name != '3d': fig.delaxes(ax) ax = fig.add_subplot(111, projection='3d') # Collect coordinates Ps = np.unique(network['throat.conns'][Ts]) X, Y, Z = network['pore.coords'][Ps].T xyz = network["pore.coords"][:, dim] P1, P2 = network["throat.conns"][Ts].T throat_pos = np.column_stack((xyz[P1], xyz[P2])).reshape((Ts.size, 2, dim.sum())) # Deal with optional style related arguments if 'c' in kwargs.keys(): color = kwargs.pop('c') color = mcolors.to_rgb(color) + tuple([alpha]) # Override colors with color_by if given if color_by is not None: color = cm.get_cmap(name=cmap)(color_by / color_by.max()) color[:, 3] = alpha if size_by is not None: linewidth = size_by / size_by.max() * linewidth if ThreeD: lc = Line3DCollection(throat_pos, colors=color, cmap=cmap, linestyles=linestyle, linewidths=linewidth, antialiaseds=np.ones_like(network.Ts), **kwargs) else: lc = LineCollection(throat_pos, colors=color, cmap=cmap, linestyles=linestyle, linewidths=linewidth, antialiaseds=np.ones_like(network.Ts), **kwargs) ax.add_collection(lc) _scale_axes(ax=ax, X=X, Y=Y, Z=Z) _label_axes(ax=ax, X=X, Y=Y, Z=Z) fig.tight_layout() return lc
def plot_networkx(network, plot_throats=True, labels=None, colors=None, scale=1, ax=None, alpha=1.0): # pragma: no cover r""" Creates a pretty 2d plot for 2d OpenPNM networks. Parameters ---------- network : GenericNetwork plot_throats : bool, optional Plots throats as well as pores, if True. labels : list, optional List of OpenPNM labels colors : list, optional List of corresponding colors to the given `labels`. scale : float, optional Scale factor for size of pores. ax : matplotlib.Axes, optional Matplotlib axes object alpha: float, optional Transparency value, 1 is opaque and 0 is transparent """ import matplotlib.pyplot as plt from matplotlib.collections import PathCollection from networkx import Graph, draw_networkx_nodes, draw_networkx_edges from openpnm.topotools import dimensionality dims = dimensionality(network) if dims.sum() > 2: raise Exception("NetworkX plotting only works for 2D networks.") temp = network['pore.coords'].T[dims].squeeze() if dims.sum() == 1: x = temp y = np.zeros_like(x) if dims.sum() == 2: x, y = temp try: node_size = scale * network['pore.diameter'] except KeyError: node_size = np.ones_like(x) * scale * 0.5 G = Graph() pos = {network.Ps[i]: [x[i], y[i]] for i in range(network.Np)} if not np.isfinite(node_size).all(): raise Exception('nan/inf values found in network["pore.diameter"]') node_color = np.array(['k'] * len(network.Ps)) if labels: if not isinstance(labels, list): labels = [labels] if not isinstance(colors, list): colors = [colors] if len(labels) != len(colors): raise Exception('len(colors) must be equal to len(labels)!') for label, color in zip(labels, colors): node_color[network.pores(label)] = color if ax is None: fig, ax = plt.subplots() ax.set_aspect('equal', adjustable='datalim') offset = node_size.max() * 0.5 ax.set_xlim((x.min() - offset, x.max() + offset)) ax.set_ylim((y.min() - offset, y.max() + offset)) ax.axis("off") # Keep track of already plotted nodes temp = [id(item) for item in ax.collections if isinstance(item, PathCollection)] # Plot pores gplot = draw_networkx_nodes(G, ax=ax, pos=pos, nodelist=network.Ps.tolist(), alpha=alpha, node_color="w", edgecolors=node_color, node_size=node_size) # (Optionally) Plot throats if plot_throats: draw_networkx_edges(G, pos=pos, edge_color='k', alpha=alpha, edgelist=network['throat.conns'].tolist(), ax=ax) spi = 2700 # 1250 was obtained by trial and error figwidth, figheight = ax.get_figure().get_size_inches() figsize_ratio = figheight / figwidth data_ratio = ax.get_data_ratio() corr = min(figsize_ratio / data_ratio, 1) xrange = np.ptp(ax.get_xlim()) markersize = np.atleast_1d((corr*figwidth)**2 / xrange**2 * node_size**2 * spi) for item in ax.collections: if isinstance(item, PathCollection) and id(item) not in temp: item.set_sizes(markersize) return gplot
def plot_coordinates(network, pores=None, ax=None, size_by=None, color_by=None, cmap='jet', color='r', alpha=1.0, marker='o', markersize=10, **kwargs): # pragma: no cover r""" Produce a 3D plot showing specified pore coordinates as markers. Parameters ---------- network : GenericNetwork The network whose topological connections to plot. pores : array_like (optional) The list of pores to plot if only a sub-sample is desired. This is useful for inspecting a small region of the network. If no pores are specified then all are shown. ax : Matplotlib axis handle If ``ax`` is supplied, then the coordinates will be overlaid. This enables the plotting of multiple different sets of pores as well as throat connections from ``plot_connections``. size_by : str or array_like An ndarray of pore values (e.g. alg['pore.concentration']). These values are normalized by scaled by ``markersize``. color_by : str or array_like An ndarray of pore values (e.g. alg['pore.concentration']). cmap : str or cmap object The matplotlib colormap to use if specfying a pore property for ``color_by`` color : str A matplotlib named color (e.g. 'r' for red). alpha : float The transparency of the lines, with 1 being solid and 0 being invisible marker : 's' The marker to use. The default is a circle. Options are explained `here <https://matplotlib.org/3.2.1/api/markers_api.html>`_ markersize : scalar Controls size of marker, default is 1.0. This value is used to scale the ``size_by`` argument if given. **kwargs All other keyword arguments are passed on to the ``scatter`` function of matplotlib, so check their documentation for additional formatting options. Returns ------- pc : PathCollection Matplotlib object containing the markers representing the pores. Notes ----- To create a single plot containing both pore coordinates and throats, consider creating an empty figure and then pass the ``ax`` object as an argument to ``plot_connections`` and ``plot_coordinates``. Otherwise, each call to either of these methods creates a new figure. See Also -------- plot_connections Examples -------- >>> import openpnm as op >>> import matplotlib as mpl >>> import matplotlib.pyplot as plt >>> mpl.use('Agg') >>> pn = op.network.Cubic(shape=[10, 10, 3]) >>> pn.add_boundary_pores() >>> Ps = pn.pores('internal') # find internal pores >>> fig, ax = plt.subplots() # create empty figure >>> _ = op.topotools.plot_coordinates(network=pn, ... pores=Ps, ... color='b', ... ax=ax) # plot internal pores >>> Ps = pn.pores('*boundary') # find boundary pores >>> _ = op.topotools.plot_coordinates(network=pn, ... pores=Ps, ... color='r', ... ax=ax) # plot boundary pores in red """ import matplotlib.pyplot as plt from matplotlib import cm from mpl_toolkits.mplot3d import Axes3D from openpnm.topotools import dimensionality Ps = network.Ps if pores is None else network._parse_indices(pores) dim = dimensionality(network) ThreeD = True if dim.sum() == 3 else False # Add a dummy axis for 1D networks if dim.sum() == 1: dim[np.argwhere(~dim)[0]] = True # Add 2 dummy axes for 0D networks (1 pore only) if dim.sum() == 0: dim[[0, 1]] = True if "fig" in kwargs.keys(): raise Exception("'fig' argument is deprecated, use 'ax' instead.") if ax is None: fig, ax = plt.subplots() else: # The next line is necessary if ax was created using plt.subplots() fig, ax = ax.get_figure(), ax.get_figure().gca() if ThreeD and ax.name != '3d': fig.delaxes(ax) ax = fig.add_subplot(111, projection='3d') # Collect specified coordinates X, Y, Z = network['pore.coords'][Ps].T # The bounding box for fig is the entire ntwork (to fix the problem with # overwriting figures' axes lim) Xl, Yl, Zl = network['pore.coords'].T # Parse formatting kwargs if 'c' in kwargs.keys(): color = kwargs.pop('c') if 's' in kwargs.keys(): markersize = kwargs.pop('s') if color_by is not None: color = cm.get_cmap(name=cmap)(color_by / color_by.max()) if size_by is not None: markersize = size_by / size_by.max() * markersize if ThreeD: sc = ax.scatter(X, Y, Z, c=color, s=markersize, marker=marker, alpha=alpha, **kwargs) _scale_axes(ax=ax, X=Xl, Y=Yl, Z=Zl) else: _X, _Y = np.column_stack((X, Y, Z))[:, dim].T sc = ax.scatter(_X, _Y, c=color, s=markersize, marker=marker, alpha=alpha, **kwargs) _scale_axes(ax=ax, X=Xl, Y=Yl, Z=np.zeros_like(Yl)) _label_axes(ax=ax, X=Xl, Y=Yl, Z=Zl) fig.tight_layout() return sc
def plot_coordinates(network, pores=None, fig=None, size_by=None, color_by=None, cmap='jet', color='r', alpha=1.0, marker='o', markersize=10, **kwargs): r""" Produce a 3D plot showing specified pore coordinates as markers. Parameters ---------- network : OpenPNM Network Object The network whose topological connections to plot. pores : array_like (optional) The list of pores to plot if only a sub-sample is desired. This is useful for inspecting a small region of the network. If no pores are specified then all are shown. fig : Matplotlib figure handle If a ``fig`` is supplied, then the coordinates will be overlaid. This enables the plotting of multiple different sets of pores as well as throat connections from ``plot_connections``. size_by : str or array_like An ND-array of pore values (e.g. alg['pore.concentration']). These values are normalized by scaled by ``markersize``. color_by : str or array_like An ND-array of pore values (e.g. alg['pore.concentration']). cmap : str or cmap object The matplotlib colormap to use if specfying a pore property for ``color_by`` color : str A matplotlib named color (e.g. 'r' for red). alpha : float The transparency of the lines, with 1 being solid and 0 being invisible marker : 's' The marker to use. The default is a circle. Options are explained `here <https://matplotlib.org/3.2.1/api/markers_api.html>`_ markersize : scalar Controls size of marker, default is 1.0. This value is used to scale the ``size_by`` argument if given. **kwargs All other keyword arguments are passed on to the ``scatter`` function of matplotlib, so check their documentation for additional formatting options. Notes ----- The figure handle returned by this method can be passed into ``plot_connections`` to create a plot that combines pore coordinates and throat connections, and vice versa. See Also -------- plot_connections Examples -------- >>> import openpnm as op >>> import matplotlib as mpl >>> mpl.use('Agg') >>> pn = op.network.Cubic(shape=[10, 10, 3]) >>> pn.add_boundary_pores() >>> Ps = pn.pores('internal') >>> # Create figure showing internal pores >>> fig = op.topotools.plot_coordinates(pn, pores=Ps, c='b') >>> Ps = pn.pores('*boundary') >>> # Pass existing fig back into function to plot boundary pores >>> fig = op.topotools.plot_coordinates(pn, pores=Ps, fig=fig, color='r') """ import matplotlib.pyplot as plt from matplotlib import cm from mpl_toolkits.mplot3d import Axes3D from openpnm.topotools import dimensionality Ps = network.Ps if pores is None else network._parse_indices(pores) dim = dimensionality(network) ThreeD = True if dim.sum() == 3 else False # Add a dummy axis for 1D networks if dim.sum() == 1: dim[np.argwhere(~dim)[0]] = True # Add 2 dummy axes for 0D networks (1 pore only) if dim.sum() == 0: dim[[0, 1]] = True fig = plt.figure() if fig is None else fig ax = fig.gca() if ThreeD and ax.name != '3d': fig.delaxes(ax) ax = fig.add_subplot(111, projection='3d') # Collect specified coordinates X, Y, Z = network['pore.coords'][Ps].T # The bounding box for fig is the entire ntwork (to fix the problem with # overwriting figures' axes lim) Xl, Yl, Zl = network['pore.coords'].T # Parse formatting kwargs if 'c' in kwargs.keys(): color = kwargs.pop('c') if 's' in kwargs.keys(): markersize = kwargs.pop('s') if color_by is not None: color = cm.get_cmap(name=cmap)(color_by / color_by.max()) if size_by is not None: markersize = size_by / size_by.max() * markersize if ThreeD: ax.scatter(X, Y, Z, c=color, s=markersize, marker=marker, alpha=alpha, **kwargs) _scale_3d_axes(ax=ax, X=Xl, Y=Yl, Z=Zl, dimen=ThreeD) else: X_temp, Y_temp = np.column_stack((X, Y, Z))[:, dim].T ax.scatter(X_temp, Y_temp, c=color, s=markersize, marker=marker, alpha=alpha, **kwargs) _scale_3d_axes(ax=ax, X=Xl, Y=Yl, Z=np.zeros_like(Yl), dimen=ThreeD) _label_axes(ax=ax, X=Xl, Y=Yl, Z=Zl) return fig