{"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":"markdown","source":"# Preprocessing\n\nA challenge for me at the start of this challenge Additionally the original image and csv files are a both bit hard to work with.\nThe goal of this notebook is to parse the input data into a format that is easier for the variouse object detection apis.\n\nAdditionally, I think it's always a good idea to have a look at the image to see if a non-radiologist can pick out some patterns in the boxed areas.\nAt the contrast levels for the original dicome images, this was very difficult for me to do personally.\nSo I used `skimage`'s `exposure` module to change the contrast level of the input images.  I also converted all of these image into PNG files so it is easier to look at hundreds of them in a browser.\n\n\nThis notebook will primarily show a few things:\n1. How the image can be rescaled and how the contrast level can be adjusted to maximum visibility of the data. (note that we rescale instead of cropped since we are doing object detection and the bounding boxes can often sit near the edges of the image)\n2. The study-level and image-level dataframes can be combined with the image size information to arrive at a master dataframe that is a bit more verbose but much easier to work with.\n\nThis notebook will show how we arrive at the data transformations, but we will only do this for 50 images in the set since these transformations are pretty expensive, the full result of these transformations will be uploaded as a different dataset.\n\nThe processed PNG files will be saved in `train_png` and a new dataframe where each row is a specific bonding box (so the same image can have multiple rows) is saved in `train_boxes.csv`.\n\nUsing the processed PNG files we will look at the images and see if we can figure out any patterns.\n\nThe dataset containing the parsed training and test images are shown here:\nhttps://www.kaggle.com/jmmshn/siim-covid19-png1024  (need to rerun for the monochrome fix)\n\nUpdates:\nThe data must be converted so that high and low values mean the same thing across different images. \nThanks to comment by @radar.\n\n```\n    if dimg.PhotometricInterpretation == \"MONOCHROME1\":\n        img = np.amax(img) - img\n```\nDetailed explanation can be found here:\nhttps://www.kaggle.com/raddar/convert-dicom-to-np-array-the-correct-way\n","metadata":{}},{"cell_type":"code","source":"# a few of the images error out duing reading this must be installed to parse all the dicom files\n!pip install python-gdcm","metadata":{"execution":{"iopub.status.busy":"2021-06-11T00:53:23.565554Z","iopub.execute_input":"2021-06-11T00:53:23.565835Z","iopub.status.idle":"2021-06-11T00:53:33.01405Z","shell.execute_reply.started":"2021-06-11T00:53:23.565776Z","shell.execute_reply":"2021-06-11T00:53:33.013131Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%matplotlib inline\nfrom fastai.basics import *\nfrom fastai.vision.all import *\nfrom fastai.data.transforms import *\nfrom fastai.medical.imaging import *\n# import pydicom,kornia,skimage\nfrom tqdm.auto import tqdm\n\ntry:\n    import cv2\n    cv2.setNumThreads(0)\nexcept: pass\nfrom pydicom.dataset import FileDataset\n\nfrom matplotlib import patches, patheffects\nfrom itertools import chain\n\nDATA_ROOT = Path(\"../input/siim-covid19-detection/\")","metadata":{"_uuid":"195d3385-b8fb-47bb-a018-3ffa762adf9b","_cell_guid":"ad327033-3430-4544-a65c-625ee1293492","collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2021-06-11T01:35:51.902869Z","iopub.execute_input":"2021-06-11T01:35:51.903265Z","iopub.status.idle":"2021-06-11T01:35:51.913273Z","shell.execute_reply.started":"2021-06-11T01:35:51.903224Z","shell.execute_reply":"2021-06-11T01:35:51.912451Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Reading the dicom files can be done easily with the `fastai` API.\nThe `get_dicom_files` function returns a list-like object that will go through all the images in a folder recursively","metadata":{}},{"cell_type":"code","source":"dicom_files = get_dicom_files(DATA_ROOT / \"train\")\nprint(f\"There are {len(dicom_files)} images in the all of subdirectories.\")\n","metadata":{"execution":{"iopub.status.busy":"2021-06-11T01:36:51.581843Z","iopub.execute_input":"2021-06-11T01:36:51.582152Z","iopub.status.idle":"2021-06-11T01:36:57.034255Z","shell.execute_reply.started":"2021-06-11T01:36:51.582124Z","shell.execute_reply":"2021-06-11T01:36:57.032754Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"In this notebook, we will only use the first 100 files but the full set of data will be parsed and uploaded.\n","metadata":{}},{"cell_type":"code","source":"dicom_files = dicom_files[:50]","metadata":{"execution":{"iopub.status.busy":"2021-06-11T01:36:57.035736Z","iopub.execute_input":"2021-06-11T01:36:57.036079Z","iopub.status.idle":"2021-06-11T01:36:57.046006Z","shell.execute_reply.started":"2021-06-11T01:36:57.03604Z","shell.execute_reply":"2021-06-11T01:36:57.044939Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Reading the image sizes\n\nWe will need the shape of the images returned int (n_rows, n_cols) for the pixel data.\n\n\nThe normal method, commented out below, took around 20 mins on the kaggle cloud.\n\nThe multi-process method finished in around 9 mins.","metadata":{}},{"cell_type":"code","source":"# Convert DICOM to PNG via openCV\nfrom concurrent.futures import ThreadPoolExecutor, as_completed, ProcessPoolExecutor\nfrom skimage import exposure\nimport cv2\nimport os\n\ntry:\n    os.mkdir(\"./train_png\")\nexcept Exception:\n    print(\"already exists\")\n\ndef process_img(img_file):\n    img_id = str(img_file).split(\"/\")[-1].split(\".\")[0]\n    dimg = img_file.dcmread()\n    \n    img = dimg.pixel_array\n    # Convert the data so that the bones are white in all the images\n    # https://www.kaggle.com/raddar/convert-dicom-to-np-array-the-correct-way\n    # from comment by @raddar\n    if dimg.PhotometricInterpretation == \"MONOCHROME1\":\n        img = np.amax(img) - img\n    img = exposure.equalize_adapthist(img, clip_limit=0.03) # optimize the contrast\n    # resize image\n    resized = cv2.resize(img*255, (1024, 1024), interpolation = cv2.INTER_LANCZOS4)\n    cv2.imwrite(f\"./train_png/{img_id}.png\", resized)\n    return img_id, dimg.pixel_array.shape\n\nimage_sizes = dict()\n# for img_file in tqdm(dicom_files):\n#     img_id, img_shape = process_img(img_file)\n#     image_sizes[img_id] = img_shape\nwith ThreadPoolExecutor(max_workers=4) as ex:\n    results = [ex.submit(process_img, img_file) for img_file in dicom_files]\n    for f in tqdm(as_completed(results), total=len(dicom_files)):\n        img_id, img_shape = f.result()\n        image_sizes[img_id] = img_shape","metadata":{"execution":{"iopub.status.busy":"2021-06-11T01:37:03.112089Z","iopub.execute_input":"2021-06-11T01:37:03.112414Z","iopub.status.idle":"2021-06-11T01:37:51.204097Z","shell.execute_reply.started":"2021-06-11T01:37:03.112384Z","shell.execute_reply":"2021-06-11T01:37:51.203308Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"The following error message occured around the 4000 block of data\n```\n/opt/conda/lib/python3.7/site-packages/pydicom/pixel_data_handlers/numpy_handler.py:341: UserWarning: The length of the pixel data in the dataset (13262360 bytes) indicates it contains excess padding. 216296 bytes will be removed from the end of the data\n  warnings.warn(msg)\n```","metadata":{}},{"cell_type":"markdown","source":"The CSV files can be parsed into pandas, note the the `id` fields in both have suffixes indicating whether it is a study id or a image id.","metadata":{}},{"cell_type":"code","source":"train_image_level = pd.read_csv(DATA_ROOT / \"train_image_level.csv\")\ntrain_study_level = pd.read_csv(DATA_ROOT / \"train_study_level.csv\", index_col='id')","metadata":{"execution":{"iopub.status.busy":"2021-06-11T01:38:10.952854Z","iopub.execute_input":"2021-06-11T01:38:10.953178Z","iopub.status.idle":"2021-06-11T01:38:10.987107Z","shell.execute_reply.started":"2021-06-11T01:38:10.953146Z","shell.execute_reply":"2021-06-11T01:38:10.986362Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(len(train_study_level))\ntrain_image_level.head(2)","metadata":{"execution":{"iopub.status.busy":"2021-06-11T01:38:11.389524Z","iopub.execute_input":"2021-06-11T01:38:11.389859Z","iopub.status.idle":"2021-06-11T01:38:11.400373Z","shell.execute_reply.started":"2021-06-11T01:38:11.389831Z","shell.execute_reply":"2021-06-11T01:38:11.399455Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(len(train_study_level))\ntrain_study_level.head(2)","metadata":{"execution":{"iopub.status.busy":"2021-06-11T01:38:12.925118Z","iopub.execute_input":"2021-06-11T01:38:12.925476Z","iopub.status.idle":"2021-06-11T01:38:12.937129Z","shell.execute_reply.started":"2021-06-11T01:38:12.925445Z","shell.execute_reply":"2021-06-11T01:38:12.936099Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"These dataframes can be combined based on the `StudyInstanceUID` value which is just the study id with the extra suffix.\n\nThe data in `boxes` are in string format, and should be converted into (x_min, y_min, x_max, y_max) format and turned into a new row for each box.\nNote that for `boxes == NaN` we will convert to `0 0 1 1` just like in `label`.","metadata":{}},{"cell_type":"code","source":"# setting it to the index makes the id disappear after merge\ntrain_study_level['StudyInstanceUID'] = train_study_level.apply(lambda x : x.name.split('_')[0], axis = 1)\ntrain_study_level['study_outcome'] = train_study_level[['Negative for Pneumonia', 'Typical Appearance',\n       'Indeterminate Appearance', 'Atypical Appearance']].idxmax(axis=1)\n\ndf_train = pd.merge(train_image_level,train_study_level[[\"study_outcome\", \"StudyInstanceUID\"]],how='left',on='StudyInstanceUID')\n# merging idea from https://www.kaggle.com/southsakura/covid19\ndf_train['image_id'] = df_train.id.apply(lambda x: x.split('_')[0]) # remove the suffix\n# df_train.set_index('id', inplace=True)\n\n# evaluate the boxes into a normal dict if possible, else convert it to 0 0 1 1 as just like in label\ndf_train['boxes'] = df_train['boxes'].map(lambda x: eval(x) if isinstance(x, str) else eval(\"[{'x': 0, 'y': 0, 'width': 1, 'height': 1}]\"))\n\ndf_train.head(5)\n","metadata":{"execution":{"iopub.status.busy":"2021-06-11T01:38:14.083992Z","iopub.execute_input":"2021-06-11T01:38:14.084308Z","iopub.status.idle":"2021-06-11T01:38:14.300458Z","shell.execute_reply.started":"2021-06-11T01:38:14.084278Z","shell.execute_reply":"2021-06-11T01:38:14.294446Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"For this example notebook we will filter for the images where the size has been measured.","metadata":{}},{"cell_type":"code","source":"df_train = df_train[df_train.image_id.apply(lambda x: x in image_sizes)]","metadata":{"execution":{"iopub.status.busy":"2021-06-11T01:38:25.696763Z","iopub.execute_input":"2021-06-11T01:38:25.697094Z","iopub.status.idle":"2021-06-11T01:38:25.706845Z","shell.execute_reply.started":"2021-06-11T01:38:25.697066Z","shell.execute_reply":"2021-06-11T01:38:25.706038Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We can first convert the data to list of dictionaries and then `explode` that list to get one row for each box in our dataset.","metadata":{}},{"cell_type":"code","source":"df_boxes = df_train.explode('boxes') # explode is a one-to-many mapping of iterable data\n\n# apply(pd.Series) parses the dictionary in one column into multiple columns \n# https://stackoverflow.com/questions/38231591/split-explode-a-column-of-dictionaries-into-separate-columns-with-pandas \ndf_boxes = pd.concat([df_boxes.drop(['boxes'], axis=1), df_boxes['boxes'].apply(pd.Series)], axis=1) \n\ndf_boxes.head(10)","metadata":{"execution":{"iopub.status.busy":"2021-06-11T01:38:27.970147Z","iopub.execute_input":"2021-06-11T01:38:27.970466Z","iopub.status.idle":"2021-06-11T01:38:28.019358Z","shell.execute_reply.started":"2021-06-11T01:38:27.970436Z","shell.execute_reply":"2021-06-11T01:38:28.018408Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_boxes['x_min'] =  df_boxes.x\ndf_boxes['y_min'] =  df_boxes.y\ndf_boxes['x_max'] =  df_boxes.x + df_boxes.width\ndf_boxes['y_max'] =  df_boxes.y + df_boxes.height\n","metadata":{"execution":{"iopub.status.busy":"2021-06-11T01:38:32.959964Z","iopub.execute_input":"2021-06-11T01:38:32.960288Z","iopub.status.idle":"2021-06-11T01:38:32.968275Z","shell.execute_reply.started":"2021-06-11T01:38:32.960247Z","shell.execute_reply":"2021-06-11T01:38:32.967076Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Save the image sizes of each image into the dataframe\n\n","metadata":{}},{"cell_type":"code","source":"df_boxes['tp_x'] = df_boxes.image_id.apply(lambda x: image_sizes[x][1])\ndf_boxes['tp_y'] = df_boxes.image_id.apply(lambda x: image_sizes[x][0])\nfor k in ['x_min', 'x_max']:\n    df_boxes[k+\"_norm\"] = df_boxes[k] / df_boxes.tp_x\nfor k in ['y_min', 'y_max']:\n    df_boxes[k+\"_norm\"] = df_boxes[k] / df_boxes.tp_y","metadata":{"execution":{"iopub.status.busy":"2021-06-11T01:38:34.188102Z","iopub.execute_input":"2021-06-11T01:38:34.188447Z","iopub.status.idle":"2021-06-11T01:38:34.198423Z","shell.execute_reply.started":"2021-06-11T01:38:34.188415Z","shell.execute_reply":"2021-06-11T01:38:34.197583Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Save the dataframe to a csv file","metadata":{}},{"cell_type":"code","source":"df_boxes.to_csv('./train_boxes.csv')","metadata":{"execution":{"iopub.status.busy":"2021-06-11T01:43:07.626248Z","iopub.execute_input":"2021-06-11T01:43:07.62662Z","iopub.status.idle":"2021-06-11T01:43:07.638755Z","shell.execute_reply.started":"2021-06-11T01:43:07.626588Z","shell.execute_reply":"2021-06-11T01:43:07.637693Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Paranoia checks","metadata":{}},{"cell_type":"code","source":"# check for one-hot encoding\nfor j,row in train_study_level.iterrows():\n    obs = row[['Negative for Pneumonia',\n       'Typical Appearance', 'Indeterminate Appearance',\n       'Atypical Appearance']]\n    assert sum(obs.values) == 1","metadata":{"execution":{"iopub.status.busy":"2021-06-11T01:43:29.456912Z","iopub.execute_input":"2021-06-11T01:43:29.457291Z","iopub.status.idle":"2021-06-11T01:43:32.092022Z","shell.execute_reply.started":"2021-06-11T01:43:29.457241Z","shell.execute_reply":"2021-06-11T01:43:32.091229Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# the information in \"boxes\" and \"label\" agree\nfor _, row in df_train.iterrows():\n    if isinstance(row.boxes, str):\n        boxes = json.loads(row.boxes.replace(\"\\'\", \"\\\"\"))\n        labels = row.label.split('opacity')\n        labels = [*filter(lambda x: x!=\"\", labels)]\n        labels = [*map(lambda x: x.lstrip().rstrip(), labels)]\n        assert len(boxes) == len(labels)\n        for b, l in zip(boxes, labels):\n            assert l.split()[0] == \"1\"\n            assert abs(float(l.split()[3]) - (b['x'] + b['width']))<0.1\n            assert abs(float(l.split()[4]) - (b['y'] + b['height']))<0.1\n","metadata":{"execution":{"iopub.status.busy":"2021-06-11T01:43:34.550375Z","iopub.execute_input":"2021-06-11T01:43:34.55071Z","iopub.status.idle":"2021-06-11T01:43:34.563704Z","shell.execute_reply.started":"2021-06-11T01:43:34.550682Z","shell.execute_reply":"2021-06-11T01:43:34.561652Z"},"jupyter":{"source_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_boxes[df_boxes.study_outcome==\"Atypical Appearance\"]","metadata":{"execution":{"iopub.status.busy":"2021-06-11T02:28:32.78673Z","iopub.execute_input":"2021-06-11T02:28:32.787057Z","iopub.status.idle":"2021-06-11T02:28:32.806689Z","shell.execute_reply.started":"2021-06-11T02:28:32.787028Z","shell.execute_reply":"2021-06-11T02:28:32.805892Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Plotting and investigation of the images\n\nAfter the images have been processed it becomes a bit easier to look at them carefully.\n\nWe will plot both the original and scaled + enhanced image to see exactly what we are looking for.","metadata":{}},{"cell_type":"code","source":"# A simple lookup table to find the dicom files using the ids\nid_to_dicom = dict()\nfor k in dicom_files:\n    img_id = str(k).split(\"/\")[-1].split(\".\")[0]\n    id_to_dicom[img_id] = k","metadata":{"execution":{"iopub.status.busy":"2021-06-11T02:06:29.668184Z","iopub.execute_input":"2021-06-11T02:06:29.668573Z","iopub.status.idle":"2021-06-11T02:06:29.674659Z","shell.execute_reply.started":"2021-06-11T02:06:29.668541Z","shell.execute_reply":"2021-06-11T02:06:29.673665Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# drawing helper functions inspired by the fastai tutorials\ndef bb_hw(a):\n    return np.array([a[1], a[0], a[3] - a[1], a[2] - a[0]])\n\ndef draw_outline(o, lw=1):\n    o.set_path_effects(\n        [patheffects.Stroke(linewidth=lw, foreground=\"black\"), patheffects.Normal()]\n    )\n\ndef draw_rect(ax, b: list):\n    patch = ax.add_patch(\n        patches.Rectangle(b[:2], *b[-2:], fill=False, edgecolor=\"white\", lw=2)\n    )\n    draw_outline(patch, 4)\n\n\ndef draw_text(ax, xy: list, txt: str, sz=14):\n    text = ax.text(*xy, txt, va=\"top\", color=\"white\", fontsize=sz, weight=\"bold\")\n    draw_outline(text)\n\n\ndef get_boxes_scale(ax, df, image_id, px_x=1024, px_y=1024):\n    \"\"\"\n    look for image_id in a df and add all of the boxes to the plot on ax\n    \"\"\"\n    \n    for _, row in df[df.image_id == image_id].iterrows():\n        if row.x == 0 and row.y == 0:\n            continue\n        x,y = row.x_min_norm * px_x, row.y_min_norm * px_y\n        w,h = (row.x_max_norm - row.x_min_norm) * px_x, (row.y_max_norm - row.y_min_norm) * px_y\n        \n        patch = ax.add_patch(\n            patches.Rectangle((x,y), w, h, fill=False, edgecolor=\"white\", lw=2)\n        )\n        draw_outline(patch, 2)\n        draw_text(\n            ax, (x, y), row[\"study_outcome\"].split()[0]\n        )\n\ndef get_boxes_orig(ax, df, image_id):\n    \"\"\"\n    look for image_id in a df and add all of the boxes to the plot on ax\n    \"\"\"\n    for _, row in df[df.image_id == image_id].iterrows():\n        if row.x == 0 and row.y == 0:\n            continue\n    \n        patch = ax.add_patch(\n            patches.Rectangle((row.x, row.y), row.width, row.height, fill=False, edgecolor=\"white\", lw=2)\n        )\n        draw_outline(patch, 2)\n        draw_text(\n            ax, (row.x, row.y), row[\"study_outcome\"].split()[0]\n        )\n\n\ndef show_dicom(dimg: FileDataset, figsize=None, ax=None):\n    \"\"\"read the dicom data then \"\"\"\n    im = dimg.pixel_array\n    if not ax:\n        fig, ax = plt.subplots(figsize=figsize)\n    ax.imshow(im, cmap='gray')\n    ax.set_axis_off()\n    ax.set_title(\"Original\")\n    get_boxes_orig(ax, df_boxes, dimg.SOPInstanceUID)\n    \n\ndef show_png(img_id, figsize=None, ax=None):\n    im = cv2.imread(f\"./train_png/{img_id}.png\")\n    if not ax:\n        fig, ax = plt.subplots(figsize=figsize)\n    ax.imshow(im, cmap='gray')\n    ax.set_axis_off()\n    ax.set_title(\"Processed\")\n    get_boxes_scale(ax, df_boxes, img_id)\n        \ndef show_img_by_id(img_id: str, **kwargs):\n    img = id_to_dicom[img_id]\n    dimg = img.dcmread()\n    fig, (ax1, ax2) = plt.subplots(1,2, figsize=(12,6))\n    show_dicom(dimg, ax=ax1)\n    show_png(dimg.SOPInstanceUID, ax=ax2)\n    fig.suptitle(img_id)","metadata":{"execution":{"iopub.status.busy":"2021-06-11T02:59:32.498971Z","iopub.execute_input":"2021-06-11T02:59:32.499292Z","iopub.status.idle":"2021-06-11T02:59:32.517589Z","shell.execute_reply.started":"2021-06-11T02:59:32.499262Z","shell.execute_reply":"2021-06-11T02:59:32.516711Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for iid in np.unique(df_boxes[df_boxes.study_outcome==\"Typical Appearance\"].image_id.values)[:10]:\n    show_img_by_id(iid)","metadata":{"execution":{"iopub.status.busy":"2021-06-11T02:59:35.701038Z","iopub.execute_input":"2021-06-11T02:59:35.701382Z","iopub.status.idle":"2021-06-11T02:59:48.843215Z","shell.execute_reply.started":"2021-06-11T02:59:35.701327Z","shell.execute_reply":"2021-06-11T02:59:48.842251Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Multiple images from studies\nNote that for the two images `4cbc17936e7d` and `582c442e440b`  with no boxes, they belong to a study `79c3bf957d49` which has boxes in another image.\n\nSo during training it will probably make sense to drop such images from the data set.","metadata":{}},{"cell_type":"code","source":"df_train[df_train.StudyInstanceUID == \"79c3bf957d49\"]","metadata":{"execution":{"iopub.status.busy":"2021-06-11T03:01:17.943289Z","iopub.execute_input":"2021-06-11T03:01:17.943648Z","iopub.status.idle":"2021-06-11T03:01:17.962701Z","shell.execute_reply.started":"2021-06-11T03:01:17.943618Z","shell.execute_reply":"2021-06-11T03:01:17.961745Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for iid in np.unique(df_boxes[df_boxes.study_outcome==\"Indeterminate Appearance\"].image_id.values)[:10]:\n    show_img_by_id(iid)","metadata":{"execution":{"iopub.status.busy":"2021-06-11T02:38:47.787298Z","iopub.execute_input":"2021-06-11T02:38:47.787682Z","iopub.status.idle":"2021-06-11T02:38:57.067741Z","shell.execute_reply.started":"2021-06-11T02:38:47.787651Z","shell.execute_reply":"2021-06-11T02:38:57.066796Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for iid in np.unique(df_boxes[df_boxes.study_outcome==\"Negative for Pneumonia\"].image_id.values)[:10]:\n    show_img_by_id(iid)","metadata":{"execution":{"iopub.status.busy":"2021-06-11T02:39:43.749539Z","iopub.execute_input":"2021-06-11T02:39:43.749934Z","iopub.status.idle":"2021-06-11T02:39:53.515801Z","shell.execute_reply.started":"2021-06-11T02:39:43.749903Z","shell.execute_reply":"2021-06-11T02:39:53.514828Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"From these images, I think we are mostly looking for anything that obstructs our view of the bronchioles.","metadata":{}},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}