{"cells":[{"metadata":{"_uuid":"5796c2253aaf2ca204796d11a7fb30bed69ccd61"},"cell_type":"markdown","source":"## **Stain Normalization**\nI have read some papers  about stain normalization of histology images and would like to share some functions that perform this transformation. The basic idea is to convert train and test images to the similar color space. Here is the list of papers describing this process:\n- [Neural Stain Normalization and Unsupervised Classification of Cell Nuclei in Histopathological Breast Cancer Images](http://https://arxiv.org/abs/1811.03815)\n- [The importance of stain normalization in colorectal tissue classification with convolutional networks](http://https://arxiv.org/abs/1702.05931)\n- [Stain normalization of histopathology images using generative adversarial networks](http://https://ieeexplore.ieee.org/document/8363641)\n-[ A Nonlinear Mapping Approach to Stain Normalization in Digital Histopathology Images Using Image-Specific Color Deconvolution](http://https://w3.mi.parisdescartes.fr/~lomn/Cours/CV/BME/HistoPatho/LongPapers/DestainingLong2014.pdf)\n- [Automated Classification for Breast Cancer Histopathology Images: Is Stain Normalization Important?](http://https://www.springerprofessional.de/en/automated-classification-for-breast-cancer-histopathology-images/15030090)\n- [A METHOD FOR NORMALIZING HISTOLOGY SLIDES FOR QUANTITATIVE ANALYSIS](http://http://wwwx.cs.unc.edu/~mn/sites/default/files/macenko2009.pdf)\n\n\n"},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"from sklearn.utils import shuffle\nimport pandas as pd\nimport os\n\n# Save train labels to dataframe\ndf = pd.read_csv(\"../input/train_labels.csv\")\n\n# Save test labels to dataframe\ndf_test = pd.read_csv('../input/sample_submission.csv')\n\ndf = shuffle(df)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"89515c262b9716ead7f4902e924e4744d7d8130a"},"cell_type":"code","source":"# True positive diagnosis\ndf_true_positive = df[df[\"label\"] == 1]\nprint(\"True positive diagnosis: \" + str(len(df_true_positive)))\n\n# True negative diagnosis\ndf_true_negative = df[df[\"label\"] == 0]\nprint(\"True negative diagnosis: \" + str(len(df_true_negative)))","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"89f0b8d6439c876464b9e44062852a9eb747426f"},"cell_type":"code","source":"# Train List\ntrain_list = df['id'].tolist()\ntrain_list = ['../input/train/'+ name + \".tif\" for name in train_list]\n\n# Test list\ntest_list = df_test['id'].tolist()\ntest_list = ['../input/test/'+ name + \".tif\" for name in test_list]\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"5053f430de595e2d8d07d95a92242b557ccb1b8d"},"cell_type":"code","source":"!pip install spams","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"e32fb867eea587c9ac2aa0ba1883223d76f3325c"},"cell_type":"code","source":"# STAIN NORMALIZATION FUNCTIONS\nimport spams\nclass TissueMaskException(Exception):\n    pass\n\n######################################################################################################\n\ndef is_uint8_image(I):\n\n    if not is_image(I):\n        return False\n    if I.dtype != np.uint8:\n        return False\n    return True\n######################################################################################################\n\ndef is_image(I):\n\n    if not isinstance(I, np.ndarray):\n        return False\n    if not I.ndim == 3:\n        return False\n    return True\n######################################################################################################\n\ndef get_tissue_mask(I, luminosity_threshold=0.8):\n\n    I_LAB = cv.cvtColor(I, cv.COLOR_RGB2LAB)\n    L = I_LAB[:, :, 0] / 255.0  # Convert to range [0,1].\n    mask = L < luminosity_threshold\n\n    # Check it's not empty\n    if mask.sum() == 0:\n        raise TissueMaskException(\"Empty tissue mask computed\")\n\n    return mask\n\n######################################################################################################\n\ndef convert_RGB_to_OD(I):\n\n    mask = (I == 0)\n    I[mask] = 1\n    \n\n    #return np.maximum(-1 * np.log(I / 255), 1e-6)\n    return np.maximum(-1 * np.log(I / 255), np.zeros(I.shape) + 0.1)\n\n######################################################################################################\n\ndef convert_OD_to_RGB(OD):\n\n    assert OD.min() >= 0, \"Negative optical density.\"\n    \n    OD = np.maximum(OD, 1e-6)\n    \n    return (255 * np.exp(-1 * OD)).astype(np.uint8)\n\n######################################################################################################\n\ndef normalize_matrix_rows(A):\n\n    return A / np.linalg.norm(A, axis=1)[:, None]\n\n######################################################################################################\n\n\ndef get_concentrations(I, stain_matrix, regularizer=0.01):\n\n    OD = convert_RGB_to_OD(I).reshape((-1, 3))\n    return spams.lasso(X=OD.T, D=stain_matrix.T, mode=2, lambda1=regularizer, pos=True).toarray().T\n\n######################################################################################################\n\ndef get_stain_matrix(I, luminosity_threshold=0.8, angular_percentile=99):\n    \n    #assert is_uint8_image(I), \"Image should be RGB uint8.\"\n    # Convert to OD and ignore background\n    tissue_mask = get_tissue_mask(I, luminosity_threshold=luminosity_threshold).reshape((-1,))\n    OD = convert_RGB_to_OD(I).reshape((-1, 3))\n    \n    OD = OD[tissue_mask]\n\n    # Eigenvectors of cov in OD space (orthogonal as cov symmetric)\n    _, V = np.linalg.eigh(np.cov(OD, rowvar=False))\n\n    # The two principle eigenvectors\n    V = V[:, [2, 1]]\n\n    # Make sure vectors are pointing the right way\n    if V[0, 0] < 0: V[:, 0] *= -1\n    if V[0, 1] < 0: V[:, 1] *= -1\n\n    # Project on this basis.\n    That = np.dot(OD, V)\n\n    # Angular coordinates with repect to the prinicple, orthogonal eigenvectors\n    phi = np.arctan2(That[:, 1], That[:, 0])\n\n    # Min and max angles\n    minPhi = np.percentile(phi, 100 - angular_percentile)\n    maxPhi = np.percentile(phi, angular_percentile)\n\n    # the two principle colors\n    v1 = np.dot(V, np.array([np.cos(minPhi), np.sin(minPhi)]))\n    v2 = np.dot(V, np.array([np.cos(maxPhi), np.sin(maxPhi)]))\n\n    # Order of H and E.\n    # H first row.\n    if v1[0] > v2[0]:\n        HE = np.array([v1, v2])\n    else:\n        HE = np.array([v2, v1])\n\n    return normalize_matrix_rows(HE)\n\n######################################################################################################\n\ndef mapping(target,source):\n    \n    stain_matrix_target = get_stain_matrix(target)\n    target_concentrations = get_concentrations(target,stain_matrix_target)\n    maxC_target = np.percentile(target_concentrations, 99, axis=0).reshape((1, 2))\n    stain_matrix_target_RGB = convert_OD_to_RGB(stain_matrix_target) \n    \n    stain_matrix_source = get_stain_matrix(source)\n    source_concentrations = get_concentrations(source, stain_matrix_source)\n    maxC_source = np.percentile(source_concentrations, 99, axis=0).reshape((1, 2))\n    source_concentrations *= (maxC_target / maxC_source)\n    tmp = 255 * np.exp(-1 * np.dot(source_concentrations, stain_matrix_target))\n    return tmp.reshape(source.shape).astype(np.uint8)\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"3859fffec087e71acda3cc992290245152759829"},"cell_type":"code","source":"# Show example stain transformation\nimport cv2 as cv\nimport matplotlib.pyplot as plt\nimport numpy as np\n\ntarget = cv.imread(test_list[108])\nsource = cv.imread(train_list[555])\n\n# Convert from cv2 standard of BGR to our convention of RGB.\ntarget = cv.cvtColor(target, cv.COLOR_BGR2RGB)\nsource = cv.cvtColor(source, cv.COLOR_BGR2RGB)\n\n# Perform stain normalization\ntransformed = mapping(target,source)\n\nfig = plt.figure()\nfig, ax = plt.subplots(1,3, figsize=(20,20))\n\nax[0].imshow(source)\nax[0].set_title(\"Source Image\",fontsize=14)\nax[1].imshow(target)\nax[1].set_title(\"Target Image\",fontsize=14)\nax[2].imshow(transformed)\nax[2].set_title(\"Transformed Image\",fontsize=14)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"47f0117e296b687bd36a784865996863e72cd076"},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.6.6","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"}},"nbformat":4,"nbformat_minor":1}