Skip to content

Commit 5efdd10

Browse files
committed
updated demo and made color mapping more user-friendly
1 parent 69fcef9 commit 5efdd10

File tree

3 files changed

+127
-23
lines changed

3 files changed

+127
-23
lines changed
-62 KB
Loading

examples/visualization/nglview_demo.ipynb

Lines changed: 92 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
{
1717
"data": {
1818
"application/vnd.jupyter.widget-view+json": {
19-
"model_id": "4e25adfecfbc4102b206c79c6f171915",
19+
"model_id": "318d91b0cf4f43c2b2216efa73a2d99f",
2020
"version_major": 2,
2121
"version_minor": 0
2222
},
@@ -29,7 +29,8 @@
2929
"source": [
3030
"import procaliper as pc\n",
3131
"import procaliper.view as pcv\n",
32-
"from nglview.color import ColormakerRegistry"
32+
"from nglview.color import ColormakerRegistry\n",
33+
"import matplotlib as plt"
3334
]
3435
},
3536
{
@@ -69,16 +70,16 @@
6970
}
7071
],
7172
"source": [
72-
"protein = pc.Protein.from_uniprot_id(\"A0A0B4J2F0\")\n",
73+
"protein = pc.Protein.from_uniprot_id(\"P07900\")\n",
7374
"protein.fetch_pdb(save_path=\"scratch.pdb\")"
7475
]
7576
},
7677
{
7778
"cell_type": "markdown",
7879
"metadata": {},
7980
"source": [
80-
"## Visualize SASA\n",
81-
"First we compute the sasa data and assert that it was properly created. Then, we use the sasa values to create a color scheme and nglview widget using the `view` module of `procaliper`. We remove the default representation, register our created color scheme, and add the new representation with our color scheme."
81+
"## Visualize pLDDT\n",
82+
"First, we examine the pLDDT score for the AlphaFold structure we have fetched. We extract this using the `protein.get_confidence` method. These values fall between 0 and 100, and we rescale them to fall in the interval [0,1] for coloring. Since we have manually scaled these, we set `rescale=False`. Green and yellow regions indicate a lower confidence in local structure prediction. We can view the chosen color scale using `plt.colormaps.get_cmap(\"viridis_r\")`."
8283
]
8384
},
8485
{
@@ -89,7 +90,82 @@
8990
{
9091
"data": {
9192
"application/vnd.jupyter.widget-view+json": {
92-
"model_id": "b1fe72f9609f436d90e99a1f47932183",
93+
"model_id": "d40e3b436a6f48a68bd9cd1fb17a235e",
94+
"version_major": 2,
95+
"version_minor": 0
96+
},
97+
"text/plain": [
98+
"NGLWidget()"
99+
]
100+
},
101+
"metadata": {},
102+
"output_type": "display_data"
103+
}
104+
],
105+
"source": [
106+
"_ = protein.get_confidence() # extract pLDDT values from the PDB\n",
107+
"assert protein.confidence_data is not None\n",
108+
"\n",
109+
"pLDDT_scheme = pcv.ngl_scheme(\n",
110+
" [x / 100 for x in protein.confidence_data],\n",
111+
" color_mapper=\"viridis_r\",\n",
112+
" rescale=False,\n",
113+
") # create a color scheme from the pLDDT values\n",
114+
"\n",
115+
"view = pcv.protein_to_nglview(protein) # generate an nglview widget\n",
116+
"view._remove_representation() # remove the default representation\n",
117+
"\n",
118+
"cm.add_selection_scheme(\n",
119+
" \"all_pLDDT_value\", pLDDT_scheme\n",
120+
") # add our color scheme to nglview\n",
121+
"view.add_representation(\n",
122+
" \"ribbon\", color=\"all_pLDDT_value\"\n",
123+
") # render the protein using our color scheme\n",
124+
"\n",
125+
"view"
126+
]
127+
},
128+
{
129+
"cell_type": "code",
130+
"execution_count": 5,
131+
"metadata": {},
132+
"outputs": [
133+
{
134+
"data": {
135+
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAgAAAABACAYAAABsv8+/AAAAGHRFWHRUaXRsZQB2aXJpZGlzX3IgY29sb3JtYXA0MKMeAAAAHnRFWHREZXNjcmlwdGlvbgB2aXJpZGlzX3IgY29sb3JtYXB2q0WVAAAAMHRFWHRBdXRob3IATWF0cGxvdGxpYiB2My44LjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmefc/hPAAAAMnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHYzLjguMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZ7HVZ2gAAAIqSURBVHic7dZRbqMwGIVRk7V0/9vqKrCrJhCEiUPSqE/3nIdE/jGGGbUz3zR/f7VSSplb/f0qtVyXpZbbem7der2+zO/r6+e2f17W27xbl2mZ377rul6+6zKfD/NLN788ni/rWk7m3X3bOZfu/fr963Ofv8dwfX+P6fn6cP50Mu/P6eb93/fJ9f55o+eM9rWT8+7X+/n1c7xvW5fdug3O6a8vP6YvzNcDun2ln6/r/f3bCzw+b7v+4vz+3P33NLxvf/80uD69u3/4Hmfzd99jMC+P56/e//m+9i/nTx8+Z1u3v51fn18/fc7yC3n+5+j+Qzi872j/aN/+vO15715vj+fr+51cH95f627dDvv31w/z2t/X769Pz22D59z+FQcAoggAAAgkAAAgkAAAgEACAAACCQAACCQAACCQAACAQAIAAAIJAAAIJAAAIJAAAIBAAgAAAgkAAAgkAAAgkAAAgEACAAACCQAACCQAACCQAACAQAIAAAIJAAAIJAAAIJAAAIBAAgAAAgkAAAgkAAAgkAAAgEACAAACCQAACCQAACCQAACAQAIAAAIJAAAIJAAAIJAAAIBAAgAAAgkAAAgkAAAgkAAAgEACAAACCQAACCQAACCQAACAQAIAAAIJAAAIJAAAIJAAAIBAAgAAAgkAAAgkAAAgkAAAgEACAAACCQAACCQAACCQAACAQAIAAAIJAAAIJAAAIJAAAIBAAgAAAgkAAAgkAAAg0A9PlIAn408tHAAAAABJRU5ErkJggg==",
136+
"text/html": [
137+
"<div style=\"vertical-align: middle;\"><strong>viridis_r</strong> </div><div class=\"cmap\"><img alt=\"viridis_r colormap\" title=\"viridis_r\" style=\"border: 1px solid #555;\" src=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAABACAYAAABsv8+/AAAAGHRFWHRUaXRsZQB2aXJpZGlzX3IgY29sb3JtYXA0MKMeAAAAHnRFWHREZXNjcmlwdGlvbgB2aXJpZGlzX3IgY29sb3JtYXB2q0WVAAAAMHRFWHRBdXRob3IATWF0cGxvdGxpYiB2My44LjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmefc/hPAAAAMnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHYzLjguMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZ7HVZ2gAAAIqSURBVHic7dZRbqMwGIVRk7V0/9vqKrCrJhCEiUPSqE/3nIdE/jGGGbUz3zR/f7VSSplb/f0qtVyXpZbbem7der2+zO/r6+e2f17W27xbl2mZ377rul6+6zKfD/NLN788ni/rWk7m3X3bOZfu/fr963Ofv8dwfX+P6fn6cP50Mu/P6eb93/fJ9f55o+eM9rWT8+7X+/n1c7xvW5fdug3O6a8vP6YvzNcDun2ln6/r/f3bCzw+b7v+4vz+3P33NLxvf/80uD69u3/4Hmfzd99jMC+P56/e//m+9i/nTx8+Z1u3v51fn18/fc7yC3n+5+j+Qzi872j/aN/+vO15715vj+fr+51cH95f627dDvv31w/z2t/X769Pz22D59z+FQcAoggAAAgkAAAgkAAAgEACAAACCQAACCQAACCQAACAQAIAAAIJAAAIJAAAIJAAAIBAAgAAAgkAAAgkAAAgkAAAgEACAAACCQAACCQAACCQAACAQAIAAAIJAAAIJAAAIJAAAIBAAgAAAgkAAAgkAAAgkAAAgEACAAACCQAACCQAACCQAACAQAIAAAIJAAAIJAAAIJAAAIBAAgAAAgkAAAgkAAAgkAAAgEACAAACCQAACCQAACCQAACAQAIAAAIJAAAIJAAAIJAAAIBAAgAAAgkAAAgkAAAgkAAAgEACAAACCQAACCQAACCQAACAQAIAAAIJAAAIJAAAIJAAAIBAAgAAAgkAAAgkAAAg0A9PlIAn408tHAAAAABJRU5ErkJggg==\"></div><div style=\"vertical-align: middle; max-width: 514px; display: flex; justify-content: space-between;\"><div style=\"float: left;\"><div title=\"#fde725ff\" style=\"display: inline-block; width: 1em; height: 1em; margin: 0; vertical-align: middle; border: 1px solid #555; background-color: #fde725ff;\"></div> under</div><div style=\"margin: 0 auto; display: inline-block;\">bad <div title=\"#00000000\" style=\"display: inline-block; width: 1em; height: 1em; margin: 0; vertical-align: middle; border: 1px solid #555; background-color: #00000000;\"></div></div><div style=\"float: right;\">over <div title=\"#440154ff\" style=\"display: inline-block; width: 1em; height: 1em; margin: 0; vertical-align: middle; border: 1px solid #555; background-color: #440154ff;\"></div></div>"
138+
],
139+
"text/plain": [
140+
"<matplotlib.colors.ListedColormap at 0x204323a2660>"
141+
]
142+
},
143+
"execution_count": 5,
144+
"metadata": {},
145+
"output_type": "execute_result"
146+
}
147+
],
148+
"source": [
149+
"plt.colormaps.get_cmap(\"viridis_r\")"
150+
]
151+
},
152+
{
153+
"cell_type": "markdown",
154+
"metadata": {},
155+
"source": [
156+
"## Visualize SASA\n",
157+
"We compute the sasa data and assert that it was properly created. Then, we use the sasa values to create a color scheme and nglview widget using the `view` module of `procaliper`. We remove the default representation, register our created color scheme, and add the new representation with our color scheme."
158+
]
159+
},
160+
{
161+
"cell_type": "code",
162+
"execution_count": 6,
163+
"metadata": {},
164+
"outputs": [
165+
{
166+
"data": {
167+
"application/vnd.jupyter.widget-view+json": {
168+
"model_id": "fa8aef4ce31541a58d5cdf6c3c65151a",
93169
"version_major": 2,
94170
"version_minor": 0
95171
},
@@ -132,13 +208,13 @@
132208
},
133209
{
134210
"cell_type": "code",
135-
"execution_count": 5,
211+
"execution_count": 7,
136212
"metadata": {},
137213
"outputs": [
138214
{
139215
"data": {
140216
"application/vnd.jupyter.widget-view+json": {
141-
"model_id": "8188a2d779884abda606020394d14616",
217+
"model_id": "57e8921c32824989bf0dc3249ce3bcbd",
142218
"version_major": 2,
143219
"version_minor": 0
144220
},
@@ -165,11 +241,18 @@
165241
"view.add_representation(\"surface\", color=\"all_charge_value\")\n",
166242
"view"
167243
]
244+
},
245+
{
246+
"cell_type": "code",
247+
"execution_count": null,
248+
"metadata": {},
249+
"outputs": [],
250+
"source": []
168251
}
169252
],
170253
"metadata": {
171254
"kernelspec": {
172-
"display_name": ".venv",
255+
"display_name": "alphameter-py3.12",
173256
"language": "python",
174257
"name": "python3"
175258
},

procaliper/view/nglview_utils.py

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from typing import Callable
44

5+
import matplotlib as plt
6+
57
from .._protein import Protein
68

79
"""
@@ -49,39 +51,58 @@ def _default_float_to_hex_rb(x: float) -> str:
4951
return f"#{int(1*255):02x}{int((1-x)*255):02x}{int((1-x)*255):02x}"
5052

5153

54+
def _matplotlib_cmapper(cmap: str, two_sided: bool) -> Callable[[float], str]:
55+
if two_sided:
56+
return lambda x: plt.colors.to_hex(plt.colormaps.get_cmap(cmap)(x / 2 + 0.5))
57+
58+
return lambda x: plt.colors.to_hex(plt.colormaps.get_cmap("viridis_r")(x))
59+
60+
5261
def ngl_scheme(
5362
data: list[float],
54-
float_to_hex: Callable[[float], str] | None = None,
63+
color_mapper: Callable[[float], str] | None = None,
5564
two_sided: bool = False,
65+
rescale: bool = True,
5666
) -> list[tuple[str, str]]:
5767
"""Converts a list of values to an nglview color scheme.
5868
5969
Args:
6070
data (list[float]): The list of values to convert.
61-
float_to_hex (Callable[[float], str] | None, optional): Function that
62-
converts a float to a hex color in the form `"#RRGGBB"`. If `None`,
63-
a default function is used that interpolates between white and green
64-
(one-sided) or red and blue (two-sided). Defaults to `None`.
71+
color_mapper (Callable[[float], str] | str | None, optional): Function that
72+
converts a float to a hex color in the form `"#RRGGBB"`. If a string, it
73+
should be the name of a matplotlib colormap. If `None`, a default function
74+
is used that interpolates between white and green (one-sided) or red and blue (two-sided).
75+
Defaults to `None`.
6576
two_sided (bool, optional): Whether to use a two-sided color scheme. If
6677
`False`, we assume `data` only contains positive values. Defaults to
6778
`False`.
79+
rescale (bool, optional): Whether to rescale the values to be between
80+
0 and 1 (one-sided) or -1 and 1 (two-sided). Defaults to `True`.
81+
If `False`, the values are not rescaled, but are assumed to fall within
82+
[0, 1] or [-1, 1] depending on `two_sided`.
6883
6984
Returns:
7085
list[tuple[str, str]]: A list of color and residue number tuples that
7186
are compatible with nglview.
7287
"""
73-
if float_to_hex is None:
88+
if color_mapper is None:
7489
if two_sided:
75-
float_to_hex = _default_float_to_hex_rb
90+
color_mapper = _default_float_to_hex_rb
7691
else:
77-
float_to_hex = _default_float_to_hex
92+
color_mapper = _default_float_to_hex
93+
94+
if isinstance(color_mapper, str):
95+
color_mapper = _matplotlib_cmapper(color_mapper, two_sided)
7896

79-
maxx = max(data)
80-
scale = max(min(data), abs(maxx)) if two_sided else maxx
97+
if rescale:
98+
maxx = max(data)
99+
scale = max(min(data), abs(maxx)) if two_sided else maxx
81100

82-
if scale == 0:
83-
data_scaled = [0.0] * len(data)
101+
if scale == 0:
102+
data_scaled = [0.0] * len(data)
103+
else:
104+
data_scaled = [x / maxx for x in data]
84105
else:
85-
data_scaled = [x / maxx for x in data]
106+
data_scaled = data
86107

87-
return [(float_to_hex(x), f"{i+1}") for i, x in enumerate(data_scaled)]
108+
return [(color_mapper(x), f"{i+1}") for i, x in enumerate(data_scaled)]

0 commit comments

Comments
 (0)