{"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_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install pycocotools","metadata":{"_kg_hide-input":true,"_kg_hide-output":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import os\nimport re\nimport json\nfrom glob import glob\nfrom tqdm.notebook import tqdm\nfrom tqdm.contrib.concurrent import process_map\nfrom multiprocessing import Manager\n\nimport numpy as np\nimport pandas as pd\npd.set_option('display.max_rows', 500)\npd.set_option('display.max_columns', 500)\npd.set_option('display.width', 1000)\nimport cv2\nimport scipy.ndimage\nfrom pycocotools import _mask\n\nimport matplotlib.pyplot as plt\nimport seaborn as sns","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_kg_hide-input":true,"execution":{"iopub.status.busy":"2021-11-19T11:01:52.356654Z","iopub.execute_input":"2021-11-19T11:01:52.357072Z","iopub.status.idle":"2021-11-19T11:01:53.358950Z","shell.execute_reply.started":"2021-11-19T11:01:52.356948Z","shell.execute_reply":"2021-11-19T11:01:53.358262Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Sartorius - Cell Instance Segmentation","metadata":{}},{"cell_type":"markdown","source":"## 1. Introduction\n\nThis competition's objective is detecting masks of different cell objects in phase contrast microscopy images. There are 606 images, 73585 annotations in training set, and there are roughly 240 images in hidden test set. Average annotations per image is 121.42 in training set and same ratio is expected in the hidden test set. In addition to that, there are 1972 images without annotations in train_semi_supervised directory. Their metadata isn't listed in train.csv file.\n\nThere are 9 columns in image metadata file. elapsed_timedelta is initially dropped because it has the same values with plate_time.\n\n* `id` - Unique ID of the image\n* `annotation` - Run length encoded segmentation masks\n* `width` - Width of the image\n* `height` - Height of the image\n* `cell_type` - Type of the cell line\n* `plate_time` - Plate creation time\n* `sample_date` - Timestamp of the sample\n* `sample_id` - Unique ID of the sample\n* `elapsed_timedelta` - Time since first image taken of sample","metadata":{}},{"cell_type":"code","source":"df_train = pd.read_csv('../input/sartorius-cell-instance-segmentation/train.csv')\ndf_train.drop(columns=['elapsed_timedelta'], inplace=True)\nprint(f'Training Set Shape: {df_train.shape} - {df_train[\"id\"].nunique()} Images - Memory Usage: {df_train.memory_usage().sum() / 1024 ** 2:.2f} MB')","metadata":{"execution":{"iopub.status.busy":"2021-11-19T11:01:53.360355Z","iopub.execute_input":"2021-11-19T11:01:53.360690Z","iopub.status.idle":"2021-11-19T11:01:54.075294Z","shell.execute_reply.started":"2021-11-19T11:01:53.360661Z","shell.execute_reply":"2021-11-19T11:01:54.074111Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Files in train_semi_supervised directory can be appended to metadata file for further analysis. Their filenames contain all the information in metadata except width and height. Fortunately, width and height of all images are already known, and they are 704 and 520 respectively.","metadata":{}},{"cell_type":"code","source":"def parse_filename(filename):\n    \n    image_id = filename.split('.')[0]\n    cell_type = filename.split('[')[0]\n    filename_split = filename.split('_')\n    plate_time = filename_split[-3]\n    sample_date = filename_split[-4]\n    sample_id = '_'.join(filename_split[:3]) + '_' + '_'.join(filename_split[-2:]).split('.')[0]\n    \n    return image_id, cell_type, plate_time, sample_date, sample_id\n\n\ntrain_semi_supervised_images = os.listdir('../input/sartorius-cell-instance-segmentation/train_semi_supervised/')\nfor filename in tqdm(train_semi_supervised_images):\n    image_id, cell_type, plate_time, sample_date, sample_id = parse_filename(filename)\n    sample = {\n        'id': image_id,\n        'annotation': np.nan,\n        'width': 704,\n        'height': 520,\n        'cell_type': cell_type,\n        'plate_time': plate_time,\n        'sample_date': sample_date,\n        'sample_id': sample_id\n    }\n    df_train = df_train.append(sample, ignore_index=True)\n    \ndf_train['cell_type'] = df_train['cell_type'].str.rstrip('s')\nprint(f'Training Set Shape: {df_train.shape} - {df_train[\"id\"].nunique()} Images - Memory Usage: {df_train.memory_usage().sum() / 1024 ** 2:.2f} MB')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 2. Metadata\n\nFor advanced data analysis and feature extraction, masks have to be decoded into 2 dimensional arrays. Since the training annotations are provided as run length encoded strings, they can be decoded with the function defined below. ","metadata":{}},{"cell_type":"code","source":"def decode_rle_mask(rle_mask, shape):\n\n    \"\"\"\n    Decode run-length encoded segmentation mask string into 2d array\n\n    Parameters\n    ----------\n    rle_mask (str): Run-length encoded segmentation mask string\n    shape (tuple): Height and width of the mask\n\n    Returns\n    -------\n    mask [numpy.ndarray of shape (height, width)]: Decoded 2d segmentation mask\n    \"\"\"\n\n    rle_mask = rle_mask.split()\n    starts, lengths = [np.asarray(x, dtype=int) for x in (rle_mask[0:][::2], rle_mask[1:][::2])]\n    starts -= 1\n    ends = starts + lengths\n\n    mask = np.zeros((shape[0] * shape[1]), dtype=np.uint8)\n    for start, end in zip(starts, ends):\n        mask[start:end] = 1\n\n    mask = mask.reshape(shape[0], shape[1])\n    return mask\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Additional metadata features like image mean, image standard deviation are extracted from both annotated and unannotated images. Mask area and annotation count are also extracted for only annotated files. Final dataframe with unannotated images and extracted metadata is saved as a csv file.","metadata":{}},{"cell_type":"code","source":"for image_id in tqdm(df_train.loc[~df_train['annotation'].isnull(), 'id'].unique()):\n    \n    image = cv2.imread(f'../input/sartorius-cell-instance-segmentation/train/{image_id}.png')\n    image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)\n\n    df_train.loc[df_train['id'] == image_id, 'image_mean'] = np.mean(image)\n    df_train.loc[df_train['id'] == image_id, 'image_std'] = np.std(image)\n    \n    for rle_mask in df_train.loc[df_train['id'] == image_id, 'annotation']:\n        \n        mask = decode_rle_mask(rle_mask, (520, 704))\n        df_train.loc[(df_train['id'] == image_id) & (df_train['annotation'] == rle_mask), 'mask_area'] = np.sum(mask)\n\n\nfor image_id in tqdm(df_train.loc[df_train['annotation'].isnull(), 'id'].unique()):\n    \n    image = cv2.imread(f'../input/sartorius-cell-instance-segmentation/train_semi_supervised/{image_id}.png')\n    image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)\n    \n    df_train.loc[df_train['id'] == image_id, 'image_mean'] = np.mean(image)\n    df_train.loc[df_train['id'] == image_id, 'image_std'] = np.std(image)\n\n\nannotation_counts = df_train.loc[~df_train['annotation'].isnull()].groupby('id')['annotation'].count()\ndf_train['annotation_count'] = df_train['id'].map(annotation_counts)\ndf_train.to_csv('train_processed.csv', index=False)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 3. Cell Types\n\nThere are 3 types of cell lines in images and each image contains only a single cell type. Those types are cort (neurons), shsy5y (neuroblastoma) and astro (astrocytes). Each cell lines is different from each other in terms of characteristics and statistics, so each type might require its own unique processing techniques.\n\nDistributions of cell types are different in annotated and unannotated training set. Annotated training set has higher number of cort but unannotated training set has higher number astro cell lines.","metadata":{}},{"cell_type":"code","source":"def visualize_cell_type_distributions(df, title):\n    \n    fig, ax = plt.subplots(figsize=(24, 5), dpi=100)\n\n    sns.barplot(\n        x=df['cell_type'].value_counts().index,\n        y=df['cell_type'].value_counts().values,\n        ax=ax\n    )\n\n    ax.set_xlabel('')\n    ax.set_ylabel('')\n    ax.set_xticklabels([f'{target} ({value_count:,})' for value_count, target in zip(df['cell_type'].value_counts().values, df['cell_type'].value_counts().index)])\n    ax.tick_params(axis='x', labelsize=15, pad=10)\n    ax.tick_params(axis='y', labelsize=15, pad=10)\n    ax.set_title(title, size=20, pad=15)\n\n    plt.show()\n\n\ndf_train_supervised_cell_types = df_train[~df_train['annotation'].isnull()].groupby('id')['cell_type'].first().reset_index()\nvisualize_cell_type_distributions(df=df_train_supervised_cell_types, title='Cell Type Distribution in Annotated Training Set')\ndf_train_unsupervised_cell_types = df_train[df_train['annotation'].isnull()].groupby('id')['cell_type'].first().reset_index()\nvisualize_cell_type_distributions(df=df_train_unsupervised_cell_types, title='Cell Type Distribution in Unannotated Training Set')\ndf_train_all_cell_types = df_train.groupby('id')['cell_type'].first().reset_index()\nvisualize_cell_type_distributions(df=df_train_all_cell_types, title='Cell Type Distribution in All Training Set')","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Metadata distributions among cell types are quite different as expected. Number of annotations distribution is in a small range for cort cell lines, but the range increases in astro cell lines and it increases even more in shsy5y cell lines. Area of annotations distributions are quite similar in cort and shsy5y cell lines since the cells from both of those cell lines are very alike in terms of size. Average size of astrocytes are larger than those two cell types but size of an astrocyte can be between 37 and 13327 pixels. Image mean and image standard deviation distributions are slightly similar in cort and shsy5y cell lines because of their similar cell sizes but astro cell line has a very different distribution in those features. Based on those observations, cutoff points for discarding small detected objects or number of masks to predict can be identified for different cell types.","metadata":{}},{"cell_type":"code","source":"def visualize_feature_distribution_for_cell_types(df, feature, title):\n    \n    if feature == 'annotation_count' or feature == 'image_mean' or feature == 'image_std':\n        df = df.groupby('id').first()\n        \n    print(f'{feature}\\n{\"-\" * len(feature)}')\n    print(f'cort Mean: {df.loc[df[\"cell_type\"] == \"cort\"][feature].mean():.4f}  -  Median: {df.loc[df[\"cell_type\"] == \"cort\"][feature].median():.4f}  -  Std: {df.loc[df[\"cell_type\"] == \"cort\"][feature].std():.4f} - Min: {df.loc[df[\"cell_type\"] == \"cort\"][feature].min():.4f} -  Max: {df.loc[df[\"cell_type\"] == \"cort\"][feature].max():.4f}')\n    print(f'shsy5y Mean: {df.loc[df[\"cell_type\"] == \"shsy5y\"][feature].mean():.4f}  -  Median: {df.loc[df[\"cell_type\"] == \"shsy5y\"][feature].median():.4f}  -  Std: {df.loc[df[\"cell_type\"] == \"shsy5y\"][feature].std():.4f} - Min: {df.loc[df[\"cell_type\"] == \"shsy5y\"][feature].min():.4f} -  Max: {df.loc[df[\"cell_type\"] == \"shsy5y\"][feature].max():.4f}')\n    print(f'astro Mean: {df.loc[df[\"cell_type\"] == \"astro\"][feature].mean():.4f}  -  Median: {df.loc[df[\"cell_type\"] == \"astro\"][feature].median():.4f}  -  Std: {df.loc[df[\"cell_type\"] == \"astro\"][feature].std():.4f} - Min: {df.loc[df[\"cell_type\"] == \"astro\"][feature].min():.4f} -  Max: {df.loc[df[\"cell_type\"] == \"astro\"][feature].max():.4f}')\n\n    fig, ax = plt.subplots(figsize=(24, 8), dpi=100)\n    sns.kdeplot(df.loc[df['cell_type'] == 'cort'][feature], label='cort', fill=True)\n    sns.kdeplot(df.loc[df['cell_type'] == 'shsy5y'][feature], label='shsy5y', fill=True)\n    sns.kdeplot(df.loc[df['cell_type'] == 'astro'][feature], label='astro', fill=True)\n    \n    ax.legend(prop={'size': 16})\n    ax.tick_params(axis='x', labelsize=12.5, pad=10)\n    ax.tick_params(axis='y', labelsize=12.5, pad=10)\n    ax.set_xlabel('')\n    ax.set_ylabel('')\n    ax.set_title(title, fontsize=20, pad=15)\n    \n    plt.show()\n    \n\nfeatures = ['annotation_count', 'mask_area', 'image_mean', 'image_std']\nfor feature in features:\n    visualize_feature_distribution_for_cell_types(df=df_train, feature=feature, title=f'{feature} Distribution in Training Set')","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2021-11-19T11:49:56.015143Z","iopub.execute_input":"2021-11-19T11:49:56.015941Z","iopub.status.idle":"2021-11-19T11:49:56.231429Z","shell.execute_reply.started":"2021-11-19T11:49:56.015894Z","shell.execute_reply":"2021-11-19T11:49:56.230072Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 4. Images\n\nImage mean and image standard deviation distributions are slightly different between images in train and train_semi_supervised directories. This doesn't necessarily mean the images are completely different and can't be used. It can be explained with number of objects being different in those two datasets and images in train_semi_supervised directory can still be useful.","metadata":{}},{"cell_type":"code","source":"def visualize_feature_distribution_for_images(df, feature):\n    \n    if feature == 'image_mean' or feature == 'image_std':\n        df = df.groupby('id').first()\n        \n    condition = df['annotation'].isnull()\n        \n    print(f'{feature}\\n{\"-\" * len(feature)}')\n    print(f'train Mean: {df.loc[~condition, feature].mean():.4f}  -  Median: {df.loc[~condition, feature].median():.4f}  -  Std: {df.loc[~condition, feature].std():.4f} - Min: {df.loc[~condition, feature].min():.4f} -  Max: {df.loc[~condition, feature].max():.4f}')\n    print(f'train_semi_supervised Mean: {df.loc[condition, feature].mean():.4f}  -  Median: {df.loc[condition, feature].median():.4f}  -  Std: {df.loc[condition, feature].std():.4f} - Min: {df.loc[condition, feature].min():.4f} -  Max: {df.loc[condition, feature].max():.4f}')\n\n    fig, ax = plt.subplots(figsize=(24, 8), dpi=100)\n    sns.kdeplot(df.loc[~condition, feature], label='train', fill=True)\n    sns.kdeplot(df.loc[condition, feature], label='train_semi_supervised', fill=True)\n    \n    ax.legend(prop={'size': 16})\n    ax.tick_params(axis='x', labelsize=12.5, pad=10)\n    ax.tick_params(axis='y', labelsize=12.5, pad=10)\n    ax.set_xlabel('')\n    ax.set_ylabel('')\n    ax.set_title(f'{feature} Distribution in Training Set', fontsize=20, pad=15)\n    \n    plt.show()\n    \n\nfeatures = ['image_mean', 'image_std']\nfor feature in features:\n    visualize_feature_distribution_for_images(df=df_train, feature=feature)","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Since the number of annotated images is very few, all of them can be visualized with their annotations. In the next sections, cort, shsy5y and astro images from train directory are visualized with their annotations and metadata with the function defined below. ","metadata":{}},{"cell_type":"code","source":"def visualize_image(df, image_id):\n\n    \"\"\"\n    Visualize image along with segmentation masks\n\n    Parameters\n    ----------\n    df [pandas.DataFrame of shape (73585, 9)]: Training dataframe\n    image_id (str): Image ID (filename)\n    \"\"\"\n    \n    image_path = df.loc[df['id'] == image_id, 'id'].values[0]\n    cell_type = df.loc[df['id'] == image_id, 'cell_type'].values[0]\n    annotation_count = df.loc[df['id'] == image_id, 'annotation_count'].values[0]\n    plate_time = df.loc[df['id'] == image_id, 'plate_time'].values[0]\n    sample_date = df.loc[df['id'] == image_id, 'sample_date'].values[0]\n    sample_id = df.loc[df['id'] == image_id, 'sample_id'].values[0]\n\n    image = cv2.imread(f'../input/sartorius-cell-instance-segmentation/train/{image_path}.png')\n    image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)\n    \n    print(f'{image_id}\\n{\"-\" * len(image_id)}')\n    print(f'Image Mean: {np.mean(image):.4f}  -  Median: {np.median(image):.4f}  -  Std: {np.std(image):.4f} - Min: {np.min(image):.4f} -  Max: {np.max(image):.4f}')\n\n    fig, axes = plt.subplots(figsize=(20, 20), ncols=2)\n    fig.tight_layout(pad=5.0)\n    \n    axes[0].imshow(image, cmap='gray')\n    masks = []\n    for mask in df.loc[df['id'] == image_id, 'annotation'].values:\n        decoded_mask = decode_rle_mask(rle_mask=mask, shape=image.shape)\n        masks.append(decoded_mask)\n    mask = np.stack(masks)\n    mask = np.any(mask == 1, axis=0)\n    axes[1].imshow(image, cmap='gray')\n    axes[1].imshow(mask, alpha=0.4)\n\n    for i in range(2):\n        axes[i].set_xlabel('')\n        axes[i].set_ylabel('')\n        axes[i].tick_params(axis='x', labelsize=15, pad=10)\n        axes[i].tick_params(axis='y', labelsize=15, pad=10)\n        \n    axes[0].set_title(f'{image_path} - {cell_type} - {int(annotation_count)} Annotations\\n{plate_time} - {sample_date} - {sample_id}', fontsize=20, pad=15)\n    axes[1].set_title('Segmentation Mask', fontsize=20, pad=15)\n    plt.show()\n    plt.close(fig)\n","metadata":{"execution":{"iopub.status.busy":"2021-11-19T12:54:17.584502Z","iopub.execute_input":"2021-11-19T12:54:17.585261Z","iopub.status.idle":"2021-11-19T12:54:17.602860Z","shell.execute_reply.started":"2021-11-19T12:54:17.585210Z","shell.execute_reply":"2021-11-19T12:54:17.601900Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 5. Neurons (cort) Images","metadata":{}},{"cell_type":"code","source":"for image_id in df_train.loc[(df_train['cell_type'] == 'cort') & (~df_train['annotation'].isnull()), 'id'].unique():\n    visualize_image(df=df_train, image_id=image_id)","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 6. Neuroblastoma (shsy5y) Images","metadata":{}},{"cell_type":"code","source":"for image_id in df_train.loc[(df_train['cell_type'] == 'shsy5y') & (~df_train['annotation'].isnull()), 'id'].unique():\n    visualize_image(df=df_train, image_id=image_id)","metadata":{"_kg_hide-input":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 7. Astrocytes (astro) Images","metadata":{}},{"cell_type":"code","source":"for image_id in df_train.loc[(df_train['cell_type'] == 'astro') & (~df_train['annotation'].isnull()), 'id'].unique():\n    visualize_image(df=df_train, image_id=image_id)","metadata":{"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## 8. LIVECell Dataset\n\n[LIVECell](https://github.com/sartorius-research/LIVECell) is the predecessor dataset to this competition. It is a high-quality, manually annotated and expert-validated dataset with 1.6 million annotations of 8 different cell types. LIVECell dataset is provided in json format but it is converted to a csv file for convenience.\n\nThere are 5239 images and 1662447 annotations in LIVECell dataset. Average annotations per image is 317.32 which is much higher than average annotations per image in competition dataset since the cell cultures are more confluent in LIVECell dataset.\n\nAs the LIVECell dataset is the predecessor of the competition dataset, they are very similar. Images have width of 704 and height of 520 pixels in both datasets and they are probably taken from the same source.","metadata":{}},{"cell_type":"code","source":"df_livecell = pd.read_csv('../input/sartorius-cell-instance-segmentation-dataset/livecell.csv')\nprint(f'LIVECell Dataset Shape: {df_livecell.shape} - {df_livecell[\"id\"].nunique()} Images - Memory Usage: {df_livecell.memory_usage().sum() / 1024 ** 2:.2f} MB')","metadata":{"execution":{"iopub.status.busy":"2021-11-19T11:02:25.680845Z","iopub.execute_input":"2021-11-19T11:02:25.682095Z","iopub.status.idle":"2021-11-19T11:02:30.802576Z","shell.execute_reply.started":"2021-11-19T11:02:25.682043Z","shell.execute_reply":"2021-11-19T11:02:30.801666Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"There are 8 types of cell lines in LIVECell dataset and each image contains only a single cell type. Only shsy5y cell line exists in both competition and LIVECell dataset. Other 7 cell lines are completely different.","metadata":{}},{"cell_type":"code","source":"df_livecell_cell_types = df_livecell.groupby('id')['cell_type'].first().reset_index()\nvisualize_cell_type_distributions(df=df_livecell_cell_types, title='Cell Type Distribution in LIVECell Dataset')","metadata":{"execution":{"iopub.status.busy":"2021-11-19T11:28:44.109931Z","iopub.execute_input":"2021-11-19T11:28:44.110462Z","iopub.status.idle":"2021-11-19T11:28:44.642988Z","shell.execute_reply.started":"2021-11-19T11:28:44.110416Z","shell.execute_reply":"2021-11-19T11:28:44.642063Z"},"_kg_hide-input":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"shsy5y cell line is the second most common cell line after mcf7. There are 704 shsy5y images in LIVECell dataset but their annotation counts are much higher than the ones in competition dataset because there are lots of overconfluent images in it. ","metadata":{}},{"cell_type":"code","source":"def visualize_shsy5y_annotation_count_in_datasets():\n    \n    df_train_images = df_train.groupby('id').first()\n    train_annotation_counts = df_train_images.loc[df_train_images['cell_type'] == 'shsy5y']['annotation_count']\n    df_livecell_images = df_livecell.groupby('id').first()\n    livecell_annotation_counts = df_livecell_images.loc[df_livecell_images['cell_type'] == 'shsy5y']['annotation_count']\n    \n    print(f'shsy5y Annotation Count\\n{\"-\" * 24}')\n    print(f'Competition Mean: {train_annotation_counts.mean():.4f}  -  Median: {train_annotation_counts.median():.4f}  -  Std: {train_annotation_counts.std():.4f} - Min: {train_annotation_counts.min():.4f} -  Max: {train_annotation_counts.max():.4f}')\n    print(f'LIVECell Mean: {livecell_annotation_counts.mean():.4f}  -  Median: {livecell_annotation_counts.median():.4f}  -  Std: {livecell_annotation_counts.std():.4f} - Min: {livecell_annotation_counts.min():.4f} -  Max: {livecell_annotation_counts.max():.4f}')\n\n    fig, ax = plt.subplots(figsize=(24, 8), dpi=100)\n    sns.kdeplot(train_annotation_counts, label='Competition Dataset', fill=True)\n    sns.kdeplot(livecell_annotation_counts, label='LIVECell Dataset', fill=True)\n    \n    ax.legend(prop={'size': 16})\n    ax.tick_params(axis='x', labelsize=12.5, pad=10)\n    ax.tick_params(axis='y', labelsize=12.5, pad=10)\n    ax.set_xlabel('')\n    ax.set_ylabel('')\n    ax.set_title(f'Annotation Count Distribution of shsy5y Cell Lines', fontsize=20, pad=15)\n    \n    plt.show()\n\n\nannotation_counts = df_livecell.groupby('id')['annotation'].count()\ndf_livecell['annotation_count'] = df_livecell['id'].map(annotation_counts)\nvisualize_shsy5y_annotation_count_in_datasets()","metadata":{"_kg_hide-input":true,"execution":{"iopub.status.busy":"2021-11-19T12:47:43.782885Z","iopub.execute_input":"2021-11-19T12:47:43.783346Z","iopub.status.idle":"2021-11-19T12:47:45.110224Z","shell.execute_reply.started":"2021-11-19T12:47:43.783299Z","shell.execute_reply":"2021-11-19T12:47:45.109337Z"},"trusted":true},"execution_count":null,"outputs":[]}]}