# import matplotlib import numpy as np from scipy.special import expit as sigmoid from colabdesign.shared.protein import _np_kabsch, alphabet_list import matplotlib import matplotlib.pyplot as plt import matplotlib.patheffects from matplotlib import animation from matplotlib.gridspec import GridSpec from matplotlib import collections as mcoll try: import py3Dmol except: print("py3Dmol not installed") pymol_color_list = ["#33ff33","#00ffff","#ff33cc","#ffff00","#ff9999","#e5e5e5","#7f7fff","#ff7f00", "#7fff7f","#199999","#ff007f","#ffdd5e","#8c3f99","#b2b2b2","#007fff","#c4b200", "#8cb266","#00bfbf","#b27f7f","#fcd1a5","#ff7f7f","#ffbfdd","#7fffff","#ffff7f", "#00ff7f","#337fcc","#d8337f","#bfff3f","#ff7fff","#d8d8ff","#3fffbf","#b78c4c", "#339933","#66b2b2","#ba8c84","#84bf00","#b24c66","#7f7f7f","#3f3fa5","#a5512b"] jalview_color_list = {"Clustal": ["#80a0f0","#f01505","#00ff00","#c048c0","#f08080","#00ff00","#c048c0","#f09048","#15a4a4","#80a0f0","#80a0f0","#f01505","#80a0f0","#80a0f0","#ffff00","#00ff00","#00ff00","#80a0f0","#15a4a4","#80a0f0"], "Zappo": ["#ffafaf","#6464ff","#00ff00","#ff0000","#ffff00","#00ff00","#ff0000","#ff00ff","#6464ff","#ffafaf","#ffafaf","#6464ff","#ffafaf","#ffc800","#ff00ff","#00ff00","#00ff00","#ffc800","#ffc800","#ffafaf"], "Taylor": ["#ccff00","#0000ff","#cc00ff","#ff0000","#ffff00","#ff00cc","#ff0066","#ff9900","#0066ff","#66ff00","#33ff00","#6600ff","#00ff00","#00ff66","#ffcc00","#ff3300","#ff6600","#00ccff","#00ffcc","#99ff00"], "Hydrophobicity": ["#ad0052","#0000ff","#0c00f3","#0c00f3","#c2003d","#0c00f3","#0c00f3","#6a0095","#1500ea","#ff0000","#ea0015","#0000ff","#b0004f","#cb0034","#4600b9","#5e00a1","#61009e","#5b00a4","#4f00b0","#f60009","#0c00f3","#680097","#0c00f3"], "Helix Propensity": ["#e718e7","#6f906f","#1be41b","#778877","#23dc23","#926d92","#ff00ff","#00ff00","#758a75","#8a758a","#ae51ae","#a05fa0","#ef10ef","#986798","#00ff00","#36c936","#47b847","#8a758a","#21de21","#857a85","#49b649","#758a75","#c936c9"], "Strand Propensity": ["#5858a7","#6b6b94","#64649b","#2121de","#9d9d62","#8c8c73","#0000ff","#4949b6","#60609f","#ecec13","#b2b24d","#4747b8","#82827d","#c2c23d","#2323dc","#4949b6","#9d9d62","#c0c03f","#d3d32c","#ffff00","#4343bc","#797986","#4747b8"], "Turn Propensity": ["#2cd3d3","#708f8f","#ff0000","#e81717","#a85757","#3fc0c0","#778888","#ff0000","#708f8f","#00ffff","#1ce3e3","#7e8181","#1ee1e1","#1ee1e1","#f60909","#e11e1e","#738c8c","#738c8c","#9d6262","#07f8f8","#f30c0c","#7c8383","#5ba4a4"], "Buried Index": ["#00a35c","#00fc03","#00eb14","#00eb14","#0000ff","#00f10e","#00f10e","#009d62","#00d52a","#0054ab","#007b84","#00ff00","#009768","#008778","#00e01f","#00d52a","#00db24","#00a857","#00e619","#005fa0","#00eb14","#00b649","#00f10e"]} pymol_cmap = matplotlib.colors.ListedColormap(pymol_color_list) def show_pdb(pdb_str, show_sidechains=False, show_mainchains=False, color="pLDDT", chains=None, Ls=None, vmin=50, vmax=90, color_HP=False, size=(800,480), hbondCutoff=4.0, animate=False): if chains is None: chains = 1 if Ls is None else len(Ls) view = py3Dmol.view(js='https://3dmol.org/build/3Dmol.js', width=size[0], height=size[1]) if animate: view.addModelsAsFrames(pdb_str,'pdb',{'hbondCutoff':hbondCutoff}) else: view.addModel(pdb_str,'pdb',{'hbondCutoff':hbondCutoff}) if color == "pLDDT": view.setStyle({'cartoon': {'colorscheme': {'prop':'b','gradient': 'roygb','min':vmin,'max':vmax}}}) elif color == "rainbow": view.setStyle({'cartoon': {'color':'spectrum'}}) elif color == "chain": for n,chain,color in zip(range(chains),alphabet_list,pymol_color_list): view.setStyle({'chain':chain},{'cartoon': {'color':color}}) if show_sidechains: BB = ['C','O','N'] HP = ["ALA","GLY","VAL","ILE","LEU","PHE","MET","PRO","TRP","CYS","TYR"] if color_HP: view.addStyle({'and':[{'resn':HP},{'atom':BB,'invert':True}]}, {'stick':{'colorscheme':"yellowCarbon",'radius':0.3}}) view.addStyle({'and':[{'resn':HP,'invert':True},{'atom':BB,'invert':True}]}, {'stick':{'colorscheme':"whiteCarbon",'radius':0.3}}) view.addStyle({'and':[{'resn':"GLY"},{'atom':'CA'}]}, {'sphere':{'colorscheme':"yellowCarbon",'radius':0.3}}) view.addStyle({'and':[{'resn':"PRO"},{'atom':['C','O'],'invert':True}]}, {'stick':{'colorscheme':"yellowCarbon",'radius':0.3}}) else: view.addStyle({'and':[{'resn':["GLY","PRO"],'invert':True},{'atom':BB,'invert':True}]}, {'stick':{'colorscheme':f"WhiteCarbon",'radius':0.3}}) view.addStyle({'and':[{'resn':"GLY"},{'atom':'CA'}]}, {'sphere':{'colorscheme':f"WhiteCarbon",'radius':0.3}}) view.addStyle({'and':[{'resn':"PRO"},{'atom':['C','O'],'invert':True}]}, {'stick':{'colorscheme':f"WhiteCarbon",'radius':0.3}}) if show_mainchains: BB = ['C','O','N','CA'] view.addStyle({'atom':BB},{'stick':{'colorscheme':f"WhiteCarbon",'radius':0.3}}) view.zoomTo() if animate: view.animate() return view def plot_pseudo_3D(xyz, c=None, ax=None, chainbreak=5, Ls=None, cmap="gist_rainbow", line_w=2.0, cmin=None, cmax=None, zmin=None, zmax=None, shadow=0.95): def rescale(a, amin=None, amax=None): a = np.copy(a) if amin is None: amin = a.min() if amax is None: amax = a.max() a[a < amin] = amin a[a > amax] = amax return (a - amin)/(amax - amin) # make segments and colors for each segment xyz = np.asarray(xyz) if Ls is None: seg = np.concatenate([xyz[:,None],np.roll(xyz,1,0)[:,None]],axis=1) c_seg = np.arange(len(seg))[::-1] if c is None else (c + np.roll(c,1,0))/2 else: Ln = 0 seg = [] c_seg = [] for L in Ls: sub_xyz = xyz[Ln:Ln+L] seg.append(np.concatenate([sub_xyz[:,None],np.roll(sub_xyz,1,0)[:,None]],axis=1)) if c is not None: sub_c = c[Ln:Ln+L] c_seg.append((sub_c + np.roll(sub_c,1,0))/2) Ln += L seg = np.concatenate(seg,0) c_seg = np.arange(len(seg))[::-1] if c is None else np.concatenate(c_seg,0) # set colors c_seg = rescale(c_seg,cmin,cmax) if isinstance(cmap, str): if cmap == "gist_rainbow": c_seg *= 0.75 colors = matplotlib.cm.get_cmap(cmap)(c_seg) else: colors = cmap(c_seg) # remove segments that aren't connected seg_len = np.sqrt(np.square(seg[:,0] - seg[:,1]).sum(-1)) if chainbreak is not None: idx = seg_len < chainbreak seg = seg[idx] seg_len = seg_len[idx] colors = colors[idx] seg_mid = seg.mean(1) seg_xy = seg[...,:2] seg_z = seg[...,2].mean(-1) order = seg_z.argsort() # add shade/tint based on z-dimension z = rescale(seg_z,zmin,zmax)[:,None] # add shadow (make lines darker if they are behind other lines) seg_len_cutoff = (seg_len[:,None] + seg_len[None,:]) / 2 seg_mid_z = seg_mid[:,2] seg_mid_dist = np.sqrt(np.square(seg_mid[:,None] - seg_mid[None,:]).sum(-1)) shadow_mask = sigmoid(seg_len_cutoff * 2.0 - seg_mid_dist) * (seg_mid_z[:,None] < seg_mid_z[None,:]) np.fill_diagonal(shadow_mask,0.0) shadow_mask = shadow ** shadow_mask.sum(-1,keepdims=True) seg_mid_xz = seg_mid[:,:2] seg_mid_xydist = np.sqrt(np.square(seg_mid_xz[:,None] - seg_mid_xz[None,:]).sum(-1)) tint_mask = sigmoid(seg_len_cutoff/2 - seg_mid_xydist) * (seg_mid_z[:,None] < seg_mid_z[None,:]) np.fill_diagonal(tint_mask,0.0) tint_mask = 1 - tint_mask.max(-1,keepdims=True) colors[:,:3] = colors[:,:3] + (1 - colors[:,:3]) * (0.50 * z + 0.50 * tint_mask) / 3 colors[:,:3] = colors[:,:3] * (0.20 + 0.25 * z + 0.55 * shadow_mask) colors = np.clip(colors,0,1) set_lim = False if ax is None: fig, ax = plt.subplots() fig.set_figwidth(5) fig.set_figheight(5) set_lim = True else: fig = ax.get_figure() if ax.get_xlim() == (0,1): set_lim = True if set_lim: xy_min = xyz[:,:2].min() - line_w xy_max = xyz[:,:2].max() + line_w ax.set_xlim(xy_min,xy_max) ax.set_ylim(xy_min,xy_max) ax.set_aspect('equal') # determine linewidths width = fig.bbox_inches.width * ax.get_position().width linewidths = line_w * 72 * width / np.diff(ax.get_xlim()) lines = mcoll.LineCollection(seg_xy[order], colors=colors[order], linewidths=linewidths, path_effects=[matplotlib.patheffects.Stroke(capstyle="round")]) return ax.add_collection(lines) def plot_ticks(ax, Ls, Ln=None, add_yticks=False): if Ln is None: Ln = sum(Ls) L_prev = 0 for L_i in Ls[:-1]: L = L_prev + L_i L_prev += L_i ax.plot([0,Ln],[L,L],color="black") ax.plot([L,L],[0,Ln],color="black") if add_yticks: ticks = np.cumsum([0]+Ls) ticks = (ticks[1:] + ticks[:-1])/2 ax.yticks(ticks,alphabet_list[:len(ticks)]) def make_animation(seq, con=None, xyz=None, plddt=None, pae=None, losses=None, pos_ref=None, line_w=2.0, dpi=100, interval=60, color_msa="Taylor", length=None, align_xyz=True, color_by="plddt", **kwargs): def nankabsch(a,b,**kwargs): ok = np.isfinite(a).all(axis=1) & np.isfinite(b).all(axis=1) a,b = a[ok],b[ok] return _np_kabsch(a,b,**kwargs) if xyz is not None: if pos_ref is None: pos_ref = xyz[-1] if length is None: L = len(pos_ref) Ls = None elif isinstance(length, list): L = length[0] Ls = length else: L = length Ls = None # align to reference if align_xyz: pos_ref_trim = pos_ref[:L] pos_ref_trim_mu = np.nanmean(pos_ref_trim,0) pos_ref_trim = pos_ref_trim - pos_ref_trim_mu # align to reference position new_pos = [] for x in xyz: x_mu = np.nanmean(x[:L],0) aln = nankabsch(x[:L]-x_mu, pos_ref_trim, use_jax=False) new_pos.append((x-x_mu) @ aln) pos = np.array(new_pos) # rotate for best view pos_mean = np.concatenate(pos,0) m = np.nanmean(pos_mean,0) rot_mtx = nankabsch(pos_mean - m, pos_mean - m, return_v=True, use_jax=False) pos = (pos - m) @ rot_mtx pos_ref_full = ((pos_ref - pos_ref_trim_mu) - m) @ rot_mtx else: # rotate for best view pos_mean = np.concatenate(xyz,0) m = np.nanmean(pos_mean,0) aln = nankabsch(pos_mean - m, pos_mean - m, return_v=True, use_jax=False) pos = [(x - m) @ aln for x in xyz] pos_ref_full = (pos_ref - m) @ aln # initialize figure if pae is not None and len(pae) == 0: pae = None fig = plt.figure() gs = GridSpec(4,3, figure=fig) if pae is not None: ax1, ax2, ax3 = fig.add_subplot(gs[:3,:2]), fig.add_subplot(gs[3:,:]), fig.add_subplot(gs[:3,2:]) else: ax1, ax2 = fig.add_subplot(gs[:3,:]), fig.add_subplot(gs[3:,:]) fig.subplots_adjust(top=0.95,bottom=0.1,right=0.95,left=0.05,hspace=0,wspace=0) fig.set_figwidth(8); fig.set_figheight(6); fig.set_dpi(dpi) ax2.set_xlabel("positions"); ax2.set_yticks([]) if seq[0].shape[0] > 1: ax2.set_ylabel("sequences") else: ax2.set_ylabel("amino acids") if xyz is None: ax1.set_title("predicted contact map") else: ax1.set_title("N→C") if plddt is None else ax1.set_title("pLDDT") if pae is not None: ax3.set_title("pAE") ax3.set_xticks([]) ax3.set_yticks([]) # set bounderies if xyz is not None: main_pos = pos_ref_full[np.isfinite(pos_ref_full).all(1)] pred_pos = [np.isfinite(x).all(1) for x in pos] x_min,y_min,z_min = np.minimum(np.mean([x.min(0) for x in pred_pos],0),main_pos.min(0)) - 5 x_max,y_max,z_max = np.maximum(np.mean([x.max(0) for x in pred_pos],0),main_pos.max(0)) + 5 x_pad = ((y_max - y_min) * 2 - (x_max - x_min)) / 2 y_pad = ((x_max - x_min) / 2 - (y_max - y_min)) / 2 if x_pad > 0: x_min -= x_pad x_max += x_pad else: y_min -= y_pad y_max += y_pad ax1.set_xlim(x_min, x_max) ax1.set_ylim(y_min, y_max) ax1.set_xticks([]) ax1.set_yticks([]) # get animation frames ims = [] for k in range(len(seq)): ims.append([]) if xyz is not None: flags = dict(ax=ax1, line_w=line_w, zmin=z_min, zmax=z_max) if color_by == "plddt" and plddt is not None: ims[-1].append(plot_pseudo_3D(pos[k], c=plddt[k], Ls=Ls, cmin=0.5, cmax=0.9, **flags)) elif color_by == "chain": c = np.concatenate([[n]*L for n,L in enumerate(length)]) ims[-1].append(plot_pseudo_3D(pos[k], c=c, Ls=Ls, cmap=pymol_cmap, cmin=0, cmax=39, **flags)) else: L = pos[k].shape[0] ims[-1].append(plot_pseudo_3D(pos[k], c=np.arange(L)[::-1], Ls=Ls, cmin=0, cmax=L, **flags)) else: L = con[k].shape[0] ims[-1].append(ax1.imshow(con[k], animated=True, cmap="Greys",vmin=0, vmax=1, extent=(0, L, L, 0))) if seq[k].shape[0] == 1: ims[-1].append(ax2.imshow(seq[k][0].T, animated=True, cmap="bwr_r",vmin=-1, vmax=1)) else: cmap = matplotlib.colors.ListedColormap(jalview_color_list[color_msa]) vmax = len(jalview_color_list[color_msa]) - 1 ims[-1].append(ax2.imshow(seq[k].argmax(-1), animated=True, cmap=cmap, vmin=0, vmax=vmax, interpolation="none")) if pae is not None: L = pae[k].shape[0] ims[-1].append(ax3.imshow(pae[k], animated=True, cmap="bwr",vmin=0, vmax=30, extent=(0, L, L, 0))) # add lines if length is not None: Ls = length if isinstance(length, list) else [length,None] if con is not None: plot_ticks(ax1, Ls, con[0].shape[0]) if pae is not None: plot_ticks(ax3, Ls, pae[0].shape[0]) # make animation! ani = animation.ArtistAnimation(fig, ims, blit=True, interval=interval) plt.close() return ani.to_html5_video()