{"cells":[{"metadata":{},"cell_type":"markdown","source":"This notebook is a work in progress to better understand the InChI format and the image dataset. This is an interesting problem statement with the flavor of Image Captioning. \n\nThis notebook includes:\n\n* Break down of the InChI format with some insights. \n* W&B Artifacts for dataset versioning.\n* Data Visualization using interactive W&B dashboard. \n\nHope you like the work so far. "},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"%%capture\n# Install Weights and Biases.\n!pip install wandb -q","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import tensorflow as tf\nprint(tf.__version__)\n\nimport os\nos.environ[\"WANDB_SILENT\"] = \"true\"\n\nimport re\nimport cv2\nimport glob\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nimport seaborn as sns\nfrom functools import partial\nimport matplotlib.pyplot as plt\n\nfrom tqdm.auto import tqdm\ntqdm.pandas()\n\nfrom IPython.display import display\n\n%matplotlib inline","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import wandb\nfrom kaggle_secrets import UserSecretsClient\n\nuser_secrets = UserSecretsClient()\nwandb_api = user_secrets.get_secret(\"wandb_api\")\n\nwandb.login(key=wandb_api)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"WORKING_DIR = '../input/bms-molecular-translation/'\nLOG_AS_ARTIFACT = True","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# EDA Caption"},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df = pd.read_csv('../input/bms-molecular-translation/train_labels.csv')\n\nif LOG_AS_ARTIFACT:\n    # Log the raw train_labels.csv file as W&B artifact\n    run = wandb.init(project='bms', job_type='raw-dataset')\n    artifact = wandb.Artifact('raw', type='dataset')\n    artifact.add_file(WORKING_DIR+'train_labels.csv')\n    run.log_artifact(artifact)\n    run.join()\n\ndisplay(\"Description of the train_labels.csv\")\ndisplay(train_df.describe())\n\ndisplay(\"First 10 rows of the train_labels.csv\")\ndisplay(train_df.head(10))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":" 📌 There are a total of 2424186 unique images and each image got a unique InCHI indentifier. "},{"metadata":{},"cell_type":"markdown","source":"### Add path to images as a column."},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df['path'] = train_df['image_id'].progress_apply(\n    lambda x: \"../input/bms-molecular-translation/train/{}/{}/{}/{}.png\".format(\n        x[0], x[1], x[2], x))\n\ntrain_df.to_csv('train_labels_path.csv', index=False)\n\nif LOG_AS_ARTIFACT:\n    # Log the modified csv file as W&B artifact\n    run = wandb.init(project='bms', job_type='modified-dataset')\n    artifact_raw = run.use_artifact('ayush-thakur/bms/raw:v0', type='dataset')\n\n    artifact = wandb.Artifact('labels-path', type='dataset')\n    artifact.add_file('train_labels_path.csv')\n    run.log_artifact(artifact)\n    run.join()\n\ndisplay(train_df.head())\n\nDISPLAY_IMGS = 50\npaths = train_df['path'].values[:DISPLAY_IMGS]\ninchis = train_df['InChI'].values[:DISPLAY_IMGS]\n\nrun = wandb.init(project='bms', job_type='image-visualization')\nwandb.log({'Example Images': [wandb.Image(img_path, caption=inchi) for img_path, inchi in zip(paths, inchis)]})\nrun.finish()\n\nrun","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"* You can click on the pencil icon in the Media panel to change how you want to visualize the images. \n* I have turned on the \"Smooth Image\" feature. "},{"metadata":{},"cell_type":"markdown","source":"# Two Words on InChI\n\n> International Chemical Identifier (InChI) is a textual identifier for chemical substances, designed to provide a standard way to encode molecular information and to facilitate the search for such information in databases and on the web. ([Source](https://en.wikipedia.org/wiki/International_Chemical_Identifier))\n\nLet's understand the format of the label string: "},{"metadata":{},"cell_type":"markdown","source":"## 1. InChI starts with \"InChI=\"\n\n📌 Every InChI starts with the string \"InChI=\" followed by the version number, currently 1."},{"metadata":{"trusted":true},"cell_type":"code","source":"inchi_labels = train_df['InChI'].values\ncount = 0\nfor inchi_label in inchi_labels:\n    if 'InChI=1' in inchi_label:\n        count+=1\n        \nprint(f'There are {count} label strings starting with InChI= followed by version 1')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## 2. S stands for Standard InChI\n\n📌 If the InChI is standard, this is followed by the letter `S` for **standard InChIs**, which is a fully standardized InChI flavor maintaining the same level of attention to structure details and the same conventions for drawing perception."},{"metadata":{"trusted":true},"cell_type":"code","source":"inchi_labels = train_df['InChI'].values\ncount = 0\nfor inchi_label in inchi_labels:\n    if 'InChI=1S' in inchi_label:\n        count+=1\n\nprint(f'There are {count} standard InChI labels.')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"## 3. InChI got layers and sublayers\n\n📌 The remaining information in the string is structured as a sequence of layers and sub-layers, with each layer providing one specific type of information. \n\n📌 The layers and sublayers are separated by '/' and start with a prefix character. There are six important layers and we will go through each of them one by one:"},{"metadata":{"trusted":true},"cell_type":"code","source":"# Ref: https://www.kaggle.com/wineplanetary/understanding-inchi-format-and-arrange-train-label/\nprefix_list = [\"c\", \"h\", \"b\", \"t\", \"m\", \"s\", \"i\"]\nformula_list = []\nprefix_val_lists = {prefix: [] for prefix in prefix_list}\nprefix_val_lists.update({\"%s_flg\" % prefix: [] for prefix in prefix_list})\n\nfor inchi in tqdm(train_df[\"InChI\"]):\n    text_list = inchi.split(\"/\")\n    formula_list.append(text_list[1])\n    for prefix in prefix_list:\n        for text in text_list:\n            if text.startswith(prefix):\n                prefix_val_lists[prefix].append(text)\n                prefix_val_lists[\"%s_flg\" % prefix].append(1)\n                break\n            else:\n                prefix_val_lists[prefix].append(\"\")\n                prefix_val_lists[\"%s_flg\" % prefix].append(0)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### 3.1 Main Layer\n\n📌 This layer is separated into three sublayer:\n\n* Chemical formula: It is the sublayer that's avilable in every InChI. It start with no prefix character. \n* Atom connections (prefix: \"c\"). The atoms in the chemical formula (except for hydrogens) are numbered in sequence; this sublayer describes which atoms are connected by bonds to which other ones.\n* Hydrogen atoms (prefix: \"h\"). Describes how many hydrogen atoms are connected to each of the other atoms.\n"},{"metadata":{"trusted":true},"cell_type":"code","source":"INDEX = 10\n\nimage_name = train_df[\"image_id\"].loc[INDEX]\nimage_path = train_df[\"path\"].loc[INDEX]\nimg = cv2.imread(image_path)\ndisplay(Image.fromarray(img))\n\ndisplay(f'Shape of image is: {img.shape}')\n\ninchi_label = train_df[\"InChI\"].loc[INDEX]\ndisplay(f'The InChI label is: {inchi_label}')\n\ninchi_split = inchi_label.split('/')\ndisplay(f\"Chemical Formula: {inchi_split[1]}\")\ndisplay(f\"Atom Connections: {inchi_split[2]}\")\ndisplay(f\"Hydrogen Atoms: {inchi_split[3]}\")","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"> Chemical Formula"},{"metadata":{"trusted":true},"cell_type":"code","source":"count = 0\nfor a in train_df[\"InChI\"].values:\n    x = a.split('/')[1]\n    if x[0].islower(): # The atoms in formula starts with an upper case character. \n        print(a)\n    count+=1\n\nif count==len(train_df):\n    print('Every InChI label got chemical formula.')\n\n    train_df['chemical_formula'] = train_df['InChI'].progress_apply(\n        lambda x: x.split('/')[1])\n    \ndisplay(train_df.head(3))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"> Atom Connections"},{"metadata":{"trusted":true},"cell_type":"code","source":"if len(prefix_val_lists['c'])==len(train_df):\n    print('Every label got atom connections sublayer or have a sublayer with prefix c.')\n\n    train_df['atom_connection'] = train_df['InChI'].progress_apply(\n        lambda x: x.split('/')[2])\n    \ndisplay(train_df.head(3))","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"> Hydrogen Atoms"},{"metadata":{"trusted":true},"cell_type":"code","source":"count = 0\nno_hydrogen_path = []\nno_hydrogen_inchi = []\n\nfor i, a in enumerate(train_df[\"InChI\"].values):\n    try:\n        x = a.split('/')[2:]\n        if x[1][0] is 'h':\n            count+=1\n        else:\n            no_hydrogen_path.append(train_df[\"path\"].loc[i])\n            no_hydrogen_inchi.append(train_df[\"InChI\"].loc[i])\n    except:\n        no_hydrogen_path.append(train_df[\"path\"].loc[i])\n        no_hydrogen_inchi.append(train_df[\"InChI\"].loc[i])\n\nprint(f'There are {len(train_df)-count} labels with no hydrogen atoms.')\n\nprint(f'Let us look at some of these images')\n\nrun = wandb.init(project='bms', job_type='image-visualization')\nwandb.log({'No Hydrogen': [wandb.Image(img_path, caption=inchi) for img_path, inchi in zip(no_hydrogen_path, no_hydrogen_inchi)]})\nrun.finish()\n\nrun","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### 3.2 Charge Layer\n\n* charge sublayer (prefix: \"q\")\n* proton sublayer (prefix: \"p\" for \"protons\")\n\n📌 In the InChI labels that we have there are no charge and proton sublayer. "},{"metadata":{},"cell_type":"markdown","source":"### 3.3 Stereochemical Layer \n\n* double bonds and cumulenes (prefix: \"b\")\n* tetrahedral stereochemistry of atoms and allenes (prefixes: \"t\", \"m\")\n* type of stereochemistry information (prefix: \"s\")\n\n### 3.4 Isotopic Layer \n\nIt has prefixes: \"i\", \"h\", as well as \"b\", \"t\", \"m\", \"s\" for isotopic stereochemistry."},{"metadata":{"trusted":true},"cell_type":"code","source":"count = len([x for x in prefix_val_lists['b'] if x is not ''])\nprint(f'The number of occurences of prefix b: {count}')\n\ncount = len([x for x in prefix_val_lists['t'] if x is not ''])\nprint(f'The number of occurences of prefix t: {count}')\n\ncount = len([x for x in prefix_val_lists['m'] if x is not ''])\nprint(f'The number of occurences of prefix m: {count}')\n\ncount = len([x for x in prefix_val_lists['s'] if x is not ''])\nprint(f'The number of occurences of prefix s: {count}')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Final CSV File\n\nI am going to use a modified `train_labels.csv` file for training purposes. At this point of time, the `csv` file contains:\n\n* `image_id` - Name of image\n* `InChI` - Label (String)\n* `path` - Absolute path to the image\n* `chemical_formula` - Substring indicating the chemical formulae of the chemical\n* `atom_connection` - Substring starting with prefix `c`."},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df.to_csv('final_train_labels.csv', index=False)\n\nif LOG_AS_ARTIFACT:\n    # Log the modified csv file as W&B artifact\n    run = wandb.init(project='bms', job_type='final-dataset')\n    artifact_raw = run.use_artifact('ayush-thakur/bms/raw:v0', type='dataset')\n\n    artifact = wandb.Artifact('final-csv', type='dataset')\n    artifact.add_file('final_train_labels.csv')\n    run.log_artifact(artifact)\n    run.join()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"To use the final csv file in your training pipeline you can use this code snippet to download the csv file:\n\n```\nimport wandb\nrun = wandb.init()\nartifact = run.use_artifact('ayush-thakur/bms/final-csv:v0', type='dataset')\nartifact_dir = artifact.download()\n```\n\n![img](https://i.imgur.com/R14gNwT.png)"},{"metadata":{},"cell_type":"markdown","source":"# References:\n\n* https://pubs.acs.org/doi/pdf/10.1021/acs.jchemed.8b00090\n* https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3599061/\n* https://en.wikipedia.org/wiki/International_Chemical_Identifier\n* https://www.youtube.com/watch?v=rAnJ5toz26c"},{"metadata":{},"cell_type":"markdown","source":"# WORK IN PROGRESS (WIP)\n\nIf you find the work useful please considering upvoting the kernel. Share your own opinion and things to improve/add."}],"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat":4,"nbformat_minor":4}