Source code for mrinversion.utils

from copy import deepcopy
from itertools import combinations
from itertools import product

import csdmpy as cp
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D  # lgtm [py/unused-import]


[docs]def to_Haeberlen_grid(csdm_object, zeta, eta, n=5): """Convert the three-dimensional p(iso, x, y) to p(iso, zeta, eta) tensor distribution. Args ---- csdm_object: CSDM A CSDM object containing the 3D p(iso, x, y) distribution. zeta: CSDM.Dimension A CSDM dimension object describing the zeta dimension. eta: CSDM.Dimension A CSDM dimension object describing the eta dimension. n: int An integer used in linear interpolation of the data. The default is 5. """ [ item.to("ppm", "nmr_frequency_ratio") for item in csdm_object.x if item.origin_offset != 0 ] data = csdm_object.y[0].components[0] extra_dims = 1 if len(csdm_object.x) > 2: extra_dims = np.sum([item.coordinates.size for item in csdm_object.x[2:]]) data.shape = (extra_dims, data.shape[-2], data.shape[-1]) reg_x, reg_y = (csdm_object.x[i].coordinates.value for i in range(2)) dx = reg_x[1] - reg_x[0] dy = reg_y[1] - reg_y[0] sol = np.zeros((extra_dims, zeta.count, eta.count)) bins = [zeta.count, eta.count] d_zeta = zeta.increment.value / 2 d_eta = eta.increment.value / 2 range_ = [ [zeta.coordinates[0].value - d_zeta, zeta.coordinates[-1].value + d_zeta], [eta.coordinates[0] - d_eta, eta.coordinates[-1] + d_eta], ] avg_range_x = (np.arange(n) - (n - 1) / 2) * dx / n avg_range_y = (np.arange(n) - (n - 1) / 2) * dy / n for x_item in avg_range_x: for y_item in avg_range_y: x__ = np.abs(reg_x + x_item) y__ = np.abs(reg_y + y_item) x_, y_ = np.meshgrid(x__, y__) x_ = x_.ravel() y_ = y_.ravel() zeta_grid = np.sqrt(x_**2 + y_**2) eta_grid = np.ones(zeta_grid.shape) index = np.where(x_ < y_) eta_grid[index] = (4 / np.pi) * np.arctan(x_[index] / y_[index]) index = np.where(x_ > y_) zeta_grid[index] *= -1 eta_grid[index] = (4 / np.pi) * np.arctan(y_[index] / x_[index]) index = np.where(x_ == y_) np.append(zeta, -zeta_grid[index]) np.append(eta, np.ones(index[0].size)) for i in range(extra_dims): weight = deepcopy(data[i]).ravel() weight[index] /= 2 np.append(weight, weight[index]) sol_, _, _ = np.histogram2d( zeta_grid, eta_grid, weights=weight, bins=bins, range=range_ ) sol[i] += sol_ sol /= n * n del zeta_grid, eta_grid, index, x_, y_, avg_range_x, avg_range_y csdm_new = cp.as_csdm(np.squeeze(sol)) csdm_new.x[0] = eta csdm_new.x[1] = zeta if len(csdm_object.x) > 2: csdm_new.x[2] = csdm_object.x[2] return csdm_new
[docs]def get_polar_grids(ax, ticks=None, offset=0): """Generate a piece-wise polar grid of Haeberlen parameters, zeta and eta. Args: ax: Matplotlib Axes. ticks: Tick coordinates where radial grids are drawn. The value can be a list or a numpy array. The default value is None. offset: The grid is drawn at an offset away from the origin. """ ylim = ax.get_ylim() xlim = ax.get_xlim() if ticks is None: x = np.asarray(ax.get_xticks()) inc = x[1] - x[0] size = x.size x = np.arange(size + 5) * inc else: x = np.asarray(ticks) lw = 0.3 t1 = plt.Polygon([[0, 0], [0, x[-1]], [x[-1], x[-1]]], color="b", alpha=0.05) t2 = plt.Polygon([[0, 0], [x[-1], 0], [x[-1], x[-1]]], color="r", alpha=0.05) ax.add_artist(t1) ax.add_artist(t2) for x_ in x: if x_ - offset > 0: ax.add_artist( plt.Circle( (0, 0), x_ - offset, fill=False, color="k", linestyle="--", linewidth=lw, alpha=0.5, ) ) angle1 = np.tan(np.pi * np.asarray([0, 0.2, 0.4, 0.6, 0.8]) / 4.0) angle2 = np.tan(np.pi * np.asarray([0.8, 0.6, 0.4, 0.2, 0]) / 4.0) for ang_ in angle1: ax.plot(x, ((x - offset) * ang_) + offset, "k--", alpha=0.5, linewidth=lw) for ang_ in angle2: ax.plot(((x - offset) * ang_) + offset, x, "k--", alpha=0.5, linewidth=lw) ax.plot(x, x, "k", alpha=0.5, linewidth=2 * lw) ax.set_xlim(xlim) ax.set_ylim(ylim)
[docs]def plot_3d( ax, csdm_objects, elev=28, azim=-150, x_lim=None, y_lim=None, z_lim=None, cmap=cm.PiYG, box=False, clip_percent=0.0, linewidth=1, alpha=0.15, **kwargs, ): r"""Generate a 3D density plot with 2D contour and 1D projections. Args: ax: Matplotlib Axes to render the plot. csdm_objects: A 3D{1} CSDM object or a list of CSDM objects holding the data. elev: (optional) The 3D view angle, elevation angle in the z plane. azim: (optional) The 3D view angle, azimuth angle in the x-y plane. x_lim: (optional) The x limit given as a list, [x_min, x_max]. y_lim: (optional) The y limit given as a list, [y_min, y_max]. z_lim: (optional) The z limit given as a list, [z_min, z_max]. max_2d: (Optional) The normalization factor of the 2D contour projections. The attribute is meaningful when multiple 3D datasets are viewed on the same plot. The value is given as a list, [`yz`, `xz`, `xy`], where `ij` is the maximum of the projection onto the `ij` plane, :math:`i,j \in [x, y, z]`. max_1d: (Optional) The normalization factor of the 1D projections. The attribute is meaningful when multiple 3D datasets are viewed on the same plot. The value is given as a list, [`x`, `y`, `z`], where `i` is the maximum of the projection onto the `i` axis, :math:`i \in [x, y, z]`. cmap: (Optional) The colormap or a list of colormaps used in rendering the volumetric plot. The colormap list is applied to the ordered list of csdm_objects. The same colormap is used for the 2D contour projections. For 1D plots, the first color in the colormap scheme is used for the line color. box: (Optional) If True, draw a box around the 3D data region. clip_percent: (Optional) The amplitudes of the dataset below the given percent is made transparent for the volumetric plot. linewidth: (Optional) The linewidth of the 2D contours, 1D plots and box. alpha: (Optional) The amount of alpha(transparency) applied in rendering the 3D volume. """ csdm_object_list = csdm_objects if not isinstance(csdm_objects, list): csdm_object_list = [csdm_objects] if not isinstance(cmap, list): cmap = [cmap] fraction = np.array([abs(item).sum() for item in csdm_object_list]) fraction /= fraction.max() for index, item in enumerate(csdm_object_list): plot_3d_( ax, item, elev=elev, azim=azim, x_lim=x_lim, y_lim=y_lim, z_lim=z_lim, fraction=fraction[index], cmap=cmap[index], box=box, clip_percent=clip_percent, linewidth=linewidth, alpha=alpha, **kwargs, )
def plot_3d_( ax, csdm_object, elev=28, azim=-150, x_lim=None, y_lim=None, z_lim=None, fraction=1.0, cmap=cm.PiYG, box=False, clip_percent=0.0, linewidth=1, alpha=0.15, **kwargs, ): r"""Generate a 3D density plot with 2D contour and 1D projections. Args: ax: Matplotlib Axes to render the plot. csdm_object: A 3D{1} CSDM object holding the data. elev: (optional) The 3D view angle, elevation angle in the z plane. azim: (optional) The 3D view angle, azimuth angle in the x-y plane. x_lim: (optional) The x limit given as a list, [x_min, x_max]. y_lim: (optional) The y limit given as a list, [y_min, y_max]. z_lim: (optional) The z limit given as a list, [z_min, z_max]. max_2d: (Optional) The normalization factor of the 2D contour projections. The attribute is meaningful when multiple 3D datasets are viewed on the same plot. The value is given as a list, [`yz`, `xz`, `xy`], where `ij` is the maximum of the projection onto the `ij` plane, :math:`i,j \in [x, y, z]`. max_1d: (Optional) The normalization factor of the 1D projections. The attribute is meaningful when multiple 3D datasets are viewed on the same plot. The value is given as a list, [`x`, `y`, `z`], where `i` is the maximum of the projection onto the `i` axis, :math:`i \in [x, y, z]`. cmap: (Optional) The colormap used in rendering the volumetric plot. The same colormap is used for the 2D contour projections. For 1D plots, the first color in the colormap scheme is used for the line color. box: (Optional) If True, draw a box around the 3D data region. clip_percent: (Optional) The amplitudes of the dataset below the given percent is made transparent for the volumetric plot. linewidth: (Optional) The linewidth of the 2D contours, 1D plots and box. alpha: (Optional) The amount of alpha(transparency) applied in rendering the 3D volume. """ lw = linewidth if isinstance(csdm_object, cp.CSDM): f = csdm_object.y[0].components[0].T a_, b_, c_ = (item for item in csdm_object.x) a = a_.coordinates.value b = b_.coordinates.value c = c_.coordinates.value xlabel = f"{a_.axis_label} - 0" ylabel = f"{b_.axis_label} - 1" zlabel = f"{c_.axis_label} - 2" else: f = csdm_object a = np.arange(f.shape[0]) b = np.arange(f.shape[1]) c = np.arange(f.shape[2]) xlabel = "x" ylabel = "y" zlabel = "z" delta_a = a[1] - a[0] delta_b = b[1] - b[0] delta_c = c[1] - c[0] clr = cmap ck = cmap(0) facecolors = cmap(f) facecolors[:, :, :, -1] = f * alpha index = np.where(f < clip_percent / 100) facecolors[:, :, :, -1][index] = 0 facecolors.shape = (f.size, 4) if x_lim is None: x_lim = [a[0], a[-1]] if y_lim is None: y_lim = [b[0], b[-1]] if z_lim is None: z_lim = [c[0], c[-1]] offset_scalar = 50 height_scalar = 10 z_offset = (z_lim[1] - z_lim[0]) / offset_scalar z_scale = np.abs(z_lim[1] - z_lim[0]) / height_scalar offz_n = z_lim[0] - (delta_c / 2.0) offz_1d = z_lim[1] + z_offset y_offset = (y_lim[1] - y_lim[0]) / offset_scalar y_scale = np.abs(y_lim[1] - y_lim[0]) / height_scalar offy = y_lim[1] + (delta_b / 2.0) offy_n_1d = y_lim[0] - y_offset offx = x_lim[1] + (delta_a / 2.0) x_scale = np.abs(x_lim[1] - x_lim[0]) / height_scalar if azim > -90 and azim <= 0: offx = x_lim[0] - (delta_a / 2.0) offy_n_1d = y_lim[0] - y_offset if azim > 0 and azim <= 90: offy = y_lim[0] - (delta_b / 2.0) offy_n_1d = y_lim[1] + y_offset offx = x_lim[0] - (delta_a / 2.0) if azim > 90: offx = x_lim[1] + (delta_a / 2.0) offy_n_1d = y_lim[1] + y_offset offy = y_lim[0] - (delta_b / 2.0) ax.set_proj_type("persp") ax.view_init(elev=elev, azim=azim) # 2D x-y contour projection --------------------- levels = (np.arange(20) + 1) / 20 x1, y1 = np.meshgrid(a, b, indexing="ij") dist_xy = f.mean(axis=2) dist_xy *= fraction * z_scale / np.abs(dist_xy.max()) ax.contour( x1, y1, dist_xy, zdir="z", offset=offz_n, cmap=clr, levels=z_scale * levels, linewidths=lw, ) # 2D x-z contour projection x1, y1 = np.meshgrid(a, c, indexing="ij") dist_xz = f.mean(axis=1) dist_xz *= fraction * y_scale / np.abs(dist_xz.max()) ax.contour( x1, dist_xz, y1, zdir="y", offset=offy, cmap=clr, levels=y_scale * levels, linewidths=lw, ) # 1D x-axis projection from 2D x-z projection proj_x = dist_xz.mean(axis=1) proj_x *= fraction * z_scale / np.abs(proj_x.max()) ax.plot(a, np.sign(z_offset) * proj_x + offz_1d, offy, zdir="y", c=ck, linewidth=lw) # 1D z-axis projection from 2D x-z projection proj_z = dist_xz.mean(axis=0) proj_z *= fraction * y_scale / np.abs(proj_z.max()) ax.plot( np.sign(azim) * np.sign(y_offset) * proj_z + offy_n_1d, c, offx, zdir="x", c=ck, linewidth=lw, ) ax.set_xlim(z_lim) # 2D y-z contour projection x1, y1 = np.meshgrid(b, c, indexing="ij") dist_yz = f.mean(axis=0) dist_yz *= fraction * x_scale / np.abs(dist_yz.max()) ax.contour( dist_yz, x1, y1, zdir="x", offset=offx, cmap=clr, levels=x_scale * levels, linewidths=lw, ) # 1D y-axis projection proj_y = dist_yz.mean(axis=1) proj_y *= fraction * z_scale / np.abs(proj_y.max()) ax.plot(b, np.sign(z_offset) * proj_y + offz_1d, offx, zdir="x", c=ck, linewidth=lw) ax.set_xlim(x_lim) ax.set_ylim(y_lim) ax.set_zlim(z_lim) ax.set_xlabel(xlabel) ax.set_ylabel(ylabel) ax.set_zlabel(zlabel) x, y, z = np.meshgrid(a, b, c, indexing="ij") if "s" not in kwargs: kwargs["s"] = 300 ax.scatter(x.flat, y.flat, z.flat, marker="X", c=facecolors, **kwargs) # full box r1 = [x_lim[0] - delta_a / 2, x_lim[-1] + delta_a / 2] r2 = [y_lim[0] - delta_b / 2, y_lim[-1] + delta_b / 2] r3 = [z_lim[-1] - delta_c / 2, z_lim[0] + delta_c / 2] l_box = lw for s, e in combinations(np.array(list(product(r1, r2, r3))), 2): if np.sum(np.abs(s - e)) == r1[1] - r1[0]: ax.plot3D(*zip(s, e), color="gray", linewidth=l_box) if np.sum(np.abs(s - e)) == r2[1] - r2[0]: ax.plot3D(*zip(s, e), color="gray", linewidth=l_box) if np.sum(np.abs(s - e)) == r3[1] - r3[0]: ax.plot3D(*zip(s, e), color="gray", linewidth=l_box) # draw cube if box: r1 = [a[0] - delta_a / 2, a[-1] + delta_a / 2] r2 = [b[0] - delta_b / 2, b[-1] + delta_b / 2] r3 = [c[0] - delta_c / 2, c[-1] + delta_c / 2] for s, e in combinations(np.array(list(product(r1, r2, r3))), 2): if np.sum(np.abs(s - e)) == r1[1] - r1[0]: ax.plot3D(*zip(s, e), c="blue", linestyle="dashed", linewidth=l_box) if np.sum(np.abs(s - e)) == r2[1] - r2[0]: ax.plot3D(*zip(s, e), c="blue", linestyle="dashed", linewidth=l_box) if np.sum(np.abs(s - e)) == r3[1] - r3[0]: ax.plot3D(*zip(s, e), c="blue", linestyle="dashed", linewidth=l_box) ax.xaxis._axinfo["grid"].update({"linewidth": 0.25, "color": "gray"}) ax.yaxis._axinfo["grid"].update({"linewidth": 0.25, "color": "gray"}) ax.zaxis._axinfo["grid"].update({"linewidth": 0.25, "color": "gray"})