{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# sPyRMSD: A symmetry-corrected Python tool for RMSD calculations"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"%load_ext autoreload\n",
"%autoreload 2\n",
"%matplotlib inline "
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"import pandas as pd\n",
"import numpy as np\n",
"\n",
"import os\n",
"import warnings\n",
"\n",
"import seaborn as sns"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [],
"source": [
"np.random.seed(42)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Utility Functions"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The following function is an helper function to create a `pd.DataFrame` for every each system, containing the RMSD for each rank."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"def systemdata(RMSD: np.ndarray, system: str, name: str):\n",
" dfs = pd.DataFrame()\n",
" \n",
" n = len(RMSD)\n",
" \n",
" dfs[name] = RMSD\n",
" dfs[\"system\"] = [system] * n\n",
" dfs[\"rank\"] = range(1,n+1)\n",
" \n",
" return dfs"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"def load_to_df(col_name, systems, fname, path=\"docking\"):\n",
" df = pd.DataFrame(columns=[\"system\", \"rank\", col_name])\n",
" \n",
" for system in systems:\n",
" \n",
" infname = os.path.join(path, system, fname)\n",
"\n",
" with warnings.catch_warnings(): # Avoid UserWarning for empty file\n",
" warnings.simplefilter(\"ignore\")\n",
"\n",
" try:\n",
" RMSD = np.loadtxt(infname)\n",
" except:\n",
" print(system)\n",
" raise\n",
" \n",
" if RMSD.shape == tuple(): # Check if file is empty\n",
" continue\n",
" \n",
" dfs = systemdata(RMSD, system, col_name)\n",
" \n",
" df = df.append(dfs, sort=False, ignore_index=True)\n",
" \n",
" return df"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Systems"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"All ligand crystal structures and docking poses are stored in `pdbbind/PDBID` folders, where `PDBID` it the [PDB](https://www.rcsb.org/) identifier for a particular protein-ligand complex. Let's list all the systems:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {
"tags": [
"outputPrepend"
]
},
"outputs": [],
"source": [
"allsystems = [s for s in os.listdir(\"docking\") if not s.startswith('.')]"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"tags": [
"outputPrepend"
]
},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": "['2v25', '2xxx', '4jpx', '4io2', '1laf', '4io5', '3zi8', '4oeu', '2p2a', '4jpy', '4xmr', '2a5s', '1lan', '4io4', '1lag', '3s9e', '4io3', '6h2t', '4zv2', '3ip6', '4kqp', '6h1u', '2cht', '3dln', '3lir', '3ff3', '4bkt', '4g4p', '4ts1', '6gg4', '4crf', '1lst', '5vja', '5mby', '3rv4', '5tcj', '5vih', '1usk', '4ymx', '6hke', '3q1x', '3mi3', '3lp4', '4igt', '4us3', '3f3e', '2pyy', '5dex', '3f6g', '1qaw', '3tk2', '3fuz', '3f3d', '2a4m', '5ey0', '3b3w', '5ave', '2pvu', '4io6', '3f48', '2xxr', '1o86', '4n07', '3u93', '4io7', '5fhm', '3b7i', '4uc5', '1hsl', '1phw', '4zv1', '3ip5', '5u8c', '3a9i', '2xii', '2ypo', '1ii5', '2rio', '4x48', '4ykj', '4o3c', '4yb5', '1usi', '2y7i', '5vij', '3lmk', '3fas', '4ykk', '1xff', '2q2a', '1ssq', '6mj7', '5l8a', '1xt8', '1t7d', '3b3s', '5gs9', '1wdn']\n"
}
],
"source": [
"systems = []\n",
"missing = [] # Systems with missing data\n",
"for system in allsystems:\n",
" allfiles = True\n",
" for f in [\"obrms.dat\", \"obrms-min.dat\", \"spyrmsd.dat\", \"spyrmsd-min.dat\"]:\n",
" fname = os.path.join(\"docking\", system, f)\n",
" if os.path.getsize(fname) == 0:\n",
" missing.append(system)\n",
" allfiles = False\n",
" break\n",
" \n",
" if allfiles:\n",
" systems.append(system)\n",
"\n",
"print(missing)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## OpenBabel Data"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"In order to test `pyRMSD`, we compare our results with [OpenBabel](http://openbabel.org) `obrms` tool. RMSD calculations performed with `obrms` on a series of docking calculations are stored in `obrms.dat` (for standard RMSD calculations) and in `obrms-min.dat` (for minimised RMSD calculations)."
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": " system rank obrms\n0 4rdn 1 3.385690\n1 4rdn 2 0.600406\n2 4rdn 3 6.571630\n3 4rdn 4 7.721690\n4 4rdn 5 6.976840\n... ... ... ...\n40434 5umx 6 5.077780\n40435 5umx 7 8.807000\n40436 5umx 8 6.399600\n40437 5umx 9 8.682270\n40438 5umx 10 5.332780\n\n[40439 rows x 3 columns]",
"text/html": "
\n\n
\n \n \n | \n system | \n rank | \n obrms | \n
\n \n \n \n 0 | \n 4rdn | \n 1 | \n 3.385690 | \n
\n \n 1 | \n 4rdn | \n 2 | \n 0.600406 | \n
\n \n 2 | \n 4rdn | \n 3 | \n 6.571630 | \n
\n \n 3 | \n 4rdn | \n 4 | \n 7.721690 | \n
\n \n 4 | \n 4rdn | \n 5 | \n 6.976840 | \n
\n \n ... | \n ... | \n ... | \n ... | \n
\n \n 40434 | \n 5umx | \n 6 | \n 5.077780 | \n
\n \n 40435 | \n 5umx | \n 7 | \n 8.807000 | \n
\n \n 40436 | \n 5umx | \n 8 | \n 6.399600 | \n
\n \n 40437 | \n 5umx | \n 9 | \n 8.682270 | \n
\n \n 40438 | \n 5umx | \n 10 | \n 5.332780 | \n
\n \n
\n
40439 rows × 3 columns
\n
"
},
"metadata": {},
"execution_count": 8
}
],
"source": [
"df_obrms = load_to_df(\"obrms\", systems, \"obrms.dat\")\n",
"df_obrms"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": " system rank obrmsm\n0 4rdn 1 0.864598\n1 4rdn 2 0.421085\n2 4rdn 3 0.525745\n3 4rdn 4 1.385680\n4 4rdn 5 0.615616\n... ... ... ...\n40434 5umx 6 2.018260\n40435 5umx 7 0.491282\n40436 5umx 8 1.385560\n40437 5umx 9 1.395160\n40438 5umx 10 1.441860\n\n[40439 rows x 3 columns]",
"text/html": "\n\n
\n \n \n | \n system | \n rank | \n obrmsm | \n
\n \n \n \n 0 | \n 4rdn | \n 1 | \n 0.864598 | \n
\n \n 1 | \n 4rdn | \n 2 | \n 0.421085 | \n
\n \n 2 | \n 4rdn | \n 3 | \n 0.525745 | \n
\n \n 3 | \n 4rdn | \n 4 | \n 1.385680 | \n
\n \n 4 | \n 4rdn | \n 5 | \n 0.615616 | \n
\n \n ... | \n ... | \n ... | \n ... | \n
\n \n 40434 | \n 5umx | \n 6 | \n 2.018260 | \n
\n \n 40435 | \n 5umx | \n 7 | \n 0.491282 | \n
\n \n 40436 | \n 5umx | \n 8 | \n 1.385560 | \n
\n \n 40437 | \n 5umx | \n 9 | \n 1.395160 | \n
\n \n 40438 | \n 5umx | \n 10 | \n 1.441860 | \n
\n \n
\n
40439 rows × 3 columns
\n
"
},
"metadata": {},
"execution_count": 9
}
],
"source": [
"df_obrmsm = load_to_df(\"obrmsm\", systems, \"obrms-min.dat\")\n",
"df_obrmsm"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can marge `df_obrms` and `df_obrmsm` in a single dataframe:"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"scrolled": true
},
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": " system rank obrms obrmsm\n0 4rdn 1 3.385690 0.864598\n1 4rdn 2 0.600406 0.421085\n2 4rdn 3 6.571630 0.525745\n3 4rdn 4 7.721690 1.385680\n4 4rdn 5 6.976840 0.615616\n... ... ... ... ...\n40434 5umx 6 5.077780 2.018260\n40435 5umx 7 8.807000 0.491282\n40436 5umx 8 6.399600 1.385560\n40437 5umx 9 8.682270 1.395160\n40438 5umx 10 5.332780 1.441860\n\n[40439 rows x 4 columns]",
"text/html": "\n\n
\n \n \n | \n system | \n rank | \n obrms | \n obrmsm | \n
\n \n \n \n 0 | \n 4rdn | \n 1 | \n 3.385690 | \n 0.864598 | \n
\n \n 1 | \n 4rdn | \n 2 | \n 0.600406 | \n 0.421085 | \n
\n \n 2 | \n 4rdn | \n 3 | \n 6.571630 | \n 0.525745 | \n
\n \n 3 | \n 4rdn | \n 4 | \n 7.721690 | \n 1.385680 | \n
\n \n 4 | \n 4rdn | \n 5 | \n 6.976840 | \n 0.615616 | \n
\n \n ... | \n ... | \n ... | \n ... | \n ... | \n
\n \n 40434 | \n 5umx | \n 6 | \n 5.077780 | \n 2.018260 | \n
\n \n 40435 | \n 5umx | \n 7 | \n 8.807000 | \n 0.491282 | \n
\n \n 40436 | \n 5umx | \n 8 | \n 6.399600 | \n 1.385560 | \n
\n \n 40437 | \n 5umx | \n 9 | \n 8.682270 | \n 1.395160 | \n
\n \n 40438 | \n 5umx | \n 10 | \n 5.332780 | \n 1.441860 | \n
\n \n
\n
40439 rows × 4 columns
\n
"
},
"metadata": {},
"execution_count": 10
}
],
"source": [
"df = pd.merge(df_obrms, df_obrmsm, left_on=[\"system\", \"rank\"], right_on=[\"system\", \"rank\"], how=\"outer\")\n",
"df"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## sPyRMSD Data"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {
"tags": [
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend"
]
},
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": " system rank rmsd\n0 4rdn 1 3.38569\n1 4rdn 2 0.60041\n2 4rdn 3 6.57163\n3 4rdn 4 7.72169\n4 4rdn 5 6.97684\n... ... ... ...\n40434 5umx 6 5.07778\n40435 5umx 7 8.80700\n40436 5umx 8 6.39960\n40437 5umx 9 8.68227\n40438 5umx 10 5.33278\n\n[40439 rows x 3 columns]",
"text/html": "\n\n
\n \n \n | \n system | \n rank | \n rmsd | \n
\n \n \n \n 0 | \n 4rdn | \n 1 | \n 3.38569 | \n
\n \n 1 | \n 4rdn | \n 2 | \n 0.60041 | \n
\n \n 2 | \n 4rdn | \n 3 | \n 6.57163 | \n
\n \n 3 | \n 4rdn | \n 4 | \n 7.72169 | \n
\n \n 4 | \n 4rdn | \n 5 | \n 6.97684 | \n
\n \n ... | \n ... | \n ... | \n ... | \n
\n \n 40434 | \n 5umx | \n 6 | \n 5.07778 | \n
\n \n 40435 | \n 5umx | \n 7 | \n 8.80700 | \n
\n \n 40436 | \n 5umx | \n 8 | \n 6.39960 | \n
\n \n 40437 | \n 5umx | \n 9 | \n 8.68227 | \n
\n \n 40438 | \n 5umx | \n 10 | \n 5.33278 | \n
\n \n
\n
40439 rows × 3 columns
\n
"
},
"metadata": {},
"execution_count": 11
}
],
"source": [
"df_rmsd = load_to_df(\"rmsd\", systems, \"spyrmsd.dat\")\n",
"df_rmsd"
]
},
{
"cell_type": "code",
"execution_count": 12,
"metadata": {
"tags": [
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend",
"outputPrepend"
]
},
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": " system rank rmsdm\n0 4rdn 1 0.86460\n1 4rdn 2 0.42108\n2 4rdn 3 0.52575\n3 4rdn 4 1.38568\n4 4rdn 5 0.61562\n... ... ... ...\n40434 5umx 6 2.01826\n40435 5umx 7 0.49128\n40436 5umx 8 1.38556\n40437 5umx 9 1.39516\n40438 5umx 10 1.44186\n\n[40439 rows x 3 columns]",
"text/html": "\n\n
\n \n \n | \n system | \n rank | \n rmsdm | \n
\n \n \n \n 0 | \n 4rdn | \n 1 | \n 0.86460 | \n
\n \n 1 | \n 4rdn | \n 2 | \n 0.42108 | \n
\n \n 2 | \n 4rdn | \n 3 | \n 0.52575 | \n
\n \n 3 | \n 4rdn | \n 4 | \n 1.38568 | \n
\n \n 4 | \n 4rdn | \n 5 | \n 0.61562 | \n
\n \n ... | \n ... | \n ... | \n ... | \n
\n \n 40434 | \n 5umx | \n 6 | \n 2.01826 | \n
\n \n 40435 | \n 5umx | \n 7 | \n 0.49128 | \n
\n \n 40436 | \n 5umx | \n 8 | \n 1.38556 | \n
\n \n 40437 | \n 5umx | \n 9 | \n 1.39516 | \n
\n \n 40438 | \n 5umx | \n 10 | \n 1.44186 | \n
\n \n
\n
40439 rows × 3 columns
\n
"
},
"metadata": {},
"execution_count": 12
}
],
"source": [
"df_rmsdm = load_to_df(\"rmsdm\", systems, \"spyrmsd-min.dat\")\n",
"df_rmsdm"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
"df = pd.merge(df, df_rmsd, left_on=[\"system\", \"rank\"], right_on=[\"system\", \"rank\"])\n",
"df = pd.merge(df, df_rmsdm, left_on=[\"system\", \"rank\"], right_on=[\"system\", \"rank\"])"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": " system rank obrms obrmsm rmsd rmsdm\n0 4rdn 1 3.385690 0.864598 3.38569 0.86460\n1 4rdn 2 0.600406 0.421085 0.60041 0.42108\n2 4rdn 3 6.571630 0.525745 6.57163 0.52575\n3 4rdn 4 7.721690 1.385680 7.72169 1.38568\n4 4rdn 5 6.976840 0.615616 6.97684 0.61562",
"text/html": "\n\n
\n \n \n | \n system | \n rank | \n obrms | \n obrmsm | \n rmsd | \n rmsdm | \n
\n \n \n \n 0 | \n 4rdn | \n 1 | \n 3.385690 | \n 0.864598 | \n 3.38569 | \n 0.86460 | \n
\n \n 1 | \n 4rdn | \n 2 | \n 0.600406 | \n 0.421085 | \n 0.60041 | \n 0.42108 | \n
\n \n 2 | \n 4rdn | \n 3 | \n 6.571630 | \n 0.525745 | \n 6.57163 | \n 0.52575 | \n
\n \n 3 | \n 4rdn | \n 4 | \n 7.721690 | \n 1.385680 | \n 7.72169 | \n 1.38568 | \n
\n \n 4 | \n 4rdn | \n 5 | \n 6.976840 | \n 0.615616 | \n 6.97684 | \n 0.61562 | \n
\n \n
\n
"
},
"metadata": {},
"execution_count": 14
}
],
"source": [
"df.head()"
]
},
{
"cell_type": "code",
"execution_count": 15,
"metadata": {},
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": " obrms obrmsm rmsd rmsdm\ncount 40439.000000 40439.000000 40439.000000 40439.000000\nmean 4.770393 1.478122 4.770393 1.478123\nstd 2.707172 1.060373 2.707172 1.060373\nmin 0.070833 0.000000 0.070830 0.000000\n25% 2.716360 0.660768 2.716360 0.660765\n50% 4.422400 1.306030 4.422400 1.306030\n75% 6.525090 2.077525 6.525090 2.077525\nmax 21.327900 7.729380 21.327900 7.729380",
"text/html": "\n\n
\n \n \n | \n obrms | \n obrmsm | \n rmsd | \n rmsdm | \n
\n \n \n \n count | \n 40439.000000 | \n 40439.000000 | \n 40439.000000 | \n 40439.000000 | \n
\n \n mean | \n 4.770393 | \n 1.478122 | \n 4.770393 | \n 1.478123 | \n
\n \n std | \n 2.707172 | \n 1.060373 | \n 2.707172 | \n 1.060373 | \n
\n \n min | \n 0.070833 | \n 0.000000 | \n 0.070830 | \n 0.000000 | \n
\n \n 25% | \n 2.716360 | \n 0.660768 | \n 2.716360 | \n 0.660765 | \n
\n \n 50% | \n 4.422400 | \n 1.306030 | \n 4.422400 | \n 1.306030 | \n
\n \n 75% | \n 6.525090 | \n 2.077525 | \n 6.525090 | \n 2.077525 | \n
\n \n max | \n 21.327900 | \n 7.729380 | \n 21.327900 | \n 7.729380 | \n
\n \n
\n
"
},
"metadata": {},
"execution_count": 15
}
],
"source": [
"df.describe()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## spyRMSD vs OpenBabel"
]
},
{
"cell_type": "code",
"execution_count": 16,
"metadata": {},
"outputs": [],
"source": [
"from matplotlib import pyplot as plt\n",
"from sklearn.metrics import mean_squared_error\n",
"from scipy.stats import pearsonr"
]
},
{
"cell_type": "code",
"execution_count": 17,
"metadata": {},
"outputs": [],
"source": [
"epsilon = 1e-4"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Standard RMSD"
]
},
{
"cell_type": "code",
"execution_count": 18,
"metadata": {},
"outputs": [],
"source": [
"x = df[\"rmsd\"].to_numpy()\n",
"y = df[\"obrms\"].to_numpy()"
]
},
{
"cell_type": "code",
"execution_count": 19,
"metadata": {},
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": "