{"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":"import os\nimport pydicom\nfrom pydicom.pixel_data_handlers.util import apply_voi_lut\nimport numpy as np\nimport warnings\nfrom glob import glob\nimport matplotlib.pyplot as plt\nfrom matplotlib import patches\n\nimport cv2\nimport pandas as pd\nimport albumentations\nfrom PIL import Image\nfrom datetime import datetime","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"warnings.filterwarnings('ignore')\n\n# directories\ndataset_dir = '../input/vinbigdata-chest-xray-abnormalities-detection'\noutput_dir = './'","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Process Dicom to array","metadata":{"trusted":true}},{"cell_type":"code","source":"# https://www.kaggle.com/raddar/convert-dicom-to-np-array-the-correct-way\ndef dicom2array(path, voi_lut=True, fix_monochrome=True):\n    \n    dicom = pydicom.read_file(path)\n    \n    # VOI LUT (if available by DICOM device) is used to\n    # transform raw DICOM data to \"human-friendly\" view\n    if voi_lut:\n        data = apply_voi_lut(dicom.pixel_array, dicom)\n    else:\n        data = dicom.pixel_array\n        \n    # depending on this value, X-ray may look inverted - fix that:\n    if fix_monochrome and dicom.PhotometricInterpretation == \"MONOCHROME1\":\n        data = np.amax(data) - data\n    \n    data = data - np.min(data)\n    data = data / np.max(data)\n    data = (data * 255).astype(np.uint8)\n    \n    return data","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# https://www.kaggle.com/trungthanhnguyen0502/eda-vinbigdata-chest-x-ray-abnormalities#1.-Dicom-to-Numpy-array\n# we are plotting the image by not resizing the image but resizing the plot\ndef plot_img(img, title, cmap='gray'):\n    plt.figure(figsize=(7,7))\n    plt.imshow(img, cmap=cmap)\n    plt.title(title)\n    plt.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def view_DICOM_metadata(img_id, test_data=False):\n    if test_data:\n        dicom = pydicom.read_file(f'{dataset_dir}/test/{img_id}.dicom')\n    else:\n        dicom = pydicom.read_file(f'{dataset_dir}/train/{img_id}.dicom')\n    print(dicom)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# read csv data\ntrain_df = pd.read_csv(f'{dataset_dir}/train.csv')\ntrain_df.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# select only those rows which have bounding boxes\nfinding_df = train_df[train_df['class_name'] != 'No finding']\nfinding_df.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"img_ids = finding_df['image_id'].unique()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"len(img_ids)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"view_DICOM_metadata(img_ids[0])","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"(0028, 0010) Rows : 2336 <br>\n(0028, 0011) Columns: 2080\n\nIt looks like original DICOM file is **2336 x 2080** pixels","metadata":{}},{"cell_type":"code","source":"shortlisted_img_ids = img_ids[:10]\nog_imgs = [dicom2array(f'{dataset_dir}/train/{path}.dicom') for path in shortlisted_img_ids]\n\nfor img_as_arr, img_id in zip(og_imgs, shortlisted_img_ids):\n    plot_img(img_as_arr, img_id)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"shortlisted_img_ids","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"finding_df[finding_df[\"image_id\"]==\"9a5094b2563a1ef3ff50dc5c7ff71345\"]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Visualize Bounding Box with Original Image size","metadata":{}},{"cell_type":"code","source":"def get_bb_info(df, img_id):\n    bounding_boxes_info = df.loc[df[\"image_id\"]==img_id, ['x_min', 'y_min', 'x_max', 'y_max', \"class_id\"]]\n\n    bboxes = []\n    for _, row in bounding_boxes_info.astype(np.int16).iterrows():\n        bboxes.append(list(row))\n    \n    return bboxes","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# class 14:\"No finding\"\nlabel2color = { 0:(\"Aortic enlargement\",\"#2a52be\"),\n                1:(\"Atelectasis\",\"#ffa812\"),\n                2:(\"Calcification\",\"#ff8243\"),\n                3:(\"Cardiomegaly\",\"#4682b4\"),\n                4:(\"Consolidation\",\"#ddadaf\"),\n                5:(\"ILD\",\"#a3c1ad\"),\n                6:(\"Infiltration\",\"#008000\"),\n                7:(\"Lung Opacity\",\"#004953\"),\n                8:(\"Nodule/Mass\",\"#e3a857\"),\n                9:(\"Other lesion\",\"#dda0dd\"),\n               10:(\"Pleural effusion\",\"#e6e8fa\"),\n               11:(\"Pleural thickening\",\"#800020\"),\n               12:(\"Pneumothorax\",\"#918151\"),\n               13:(\"Pulmonary fibrosis\",\"#e75480\")}","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# code to plot image with bounding boxes\ndef bounding_box_plotter(img_as_arr, img_id, bounding_boxes_info):\n    fig = plt.figure(figsize=(7,7))\n    ax = fig.add_axes([0,0,1,1])\n    \n    # plot the image\n    plt.imshow(img_as_arr, cmap=\"gray\")\n    plt.title(img_id)\n\n    # add the bounding boxes\n    for row in bounding_boxes_info:\n        # each row contains 'x_min', 'y_min', 'x_max', 'y_max', \"class_id\"\n        xmin = row[0]\n        xmax = row[2]\n        ymin = row[1]\n        ymax = row[3]\n\n        width = xmax - xmin\n        height = ymax - ymin\n\n        # assign different color to different classes of objects\n        edgecolor = label2color[row[4]][1]\n        ax.annotate(label2color[row[4]][0], xy=(xmax - 40, ymin + 20))\n\n        # add bounding boxes to the image\n        rect = patches.Rectangle((xmin, ymin), width, height, edgecolor=edgecolor, facecolor='none')\n\n        ax.add_patch(rect)\n\n    plt.show()\n    ","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# plotting the original image with the original bounding boxes\nfor img_as_arr, img_id in zip(og_imgs,shortlisted_img_ids):    \n    bounding_boxes_info = get_bb_info(finding_df,    img_id)\n    bounding_box_plotter(img_as_arr, img_id, bounding_boxes_info)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"##### Resize image and adjust bounding boxes\n\nThe original X-ray image is 2336 x 2080. We will use albumentations library to rescale the image to be 512 x 512. Thus the aspect ratio of the image will be maintained. Additionally we will also take care of the bounding boxes and verify once the resizing of image is done.","metadata":{"trusted":true}},{"cell_type":"code","source":"# https://www.kaggle.com/bjoernholzhauer/eda-dicom-reading-vinbigdata-chest-x-ray#7.-Creating-fast-to-read-shelve-file\ndef resize_image(df, img_arr, image_id):\n    \n    # create resize transform pipeline\n    transform = albumentations.Compose([\n        albumentations.Resize(height=512, width=512, always_apply=True)\n    ], bbox_params=albumentations.BboxParams(format='pascal_voc')) \n    \n    # each row in bounding boxes will contain 'x_min', 'y_min', 'x_max', 'y_max', \"class_id\"\n    bboxes = get_bb_info(df, image_id)\n    \n    transformed = transform(image=img_arr, bboxes=bboxes)\n    \n    return transformed","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for img_as_arr, img_id in zip(og_imgs,shortlisted_img_ids):    \n    transformed = resize_image(finding_df, img_as_arr, img_id)\n    bounding_box_plotter(transformed[\"image\"], img_id, transformed[\"bboxes\"])\n    print(f\"Original Dimension: {img_as_arr.shape}\\nTransformed Dimension: {transformed['image'].shape}\")","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"img_as_arr.shape returns number of rows x number of columns. \n\n* number of rows --> Height\n* number of columns --> Width","metadata":{}},{"cell_type":"markdown","source":"# Handle No Finding class","metadata":{"trusted":true}},{"cell_type":"code","source":"train_df.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"values = {'x_min': 0, 'y_min': 0, 'x_max': 1, 'y_max': 1}\ntrain_df = train_df.fillna(value=values)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_df.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Resize all data and prep Bounding Box CSV file","metadata":{}},{"cell_type":"code","source":"# create train directory\ntransformed_trained_dir = f\"{output_dir}/transformed_data/train\"\nos.makedirs(transformed_trained_dir, exist_ok=True)\n\n# create test directory\ntransformed_test_dir = f\"{output_dir}/transformed_data/test\"\nos.makedirs(transformed_test_dir, exist_ok=True)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Train Data**\n\nOriginal Columns:\n* image_id\t\n* class_name\t\n* class_id\t\n* rad_id\t\n* x_min\t\n* y_min\t\n* x_max\t\n* y_max","metadata":{}},{"cell_type":"code","source":"train_df.head()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_df[\"image_id\"].unique()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_info(df, image_id, columns_arr):\n    info_row = df.loc[df[\"image_id\"]==image_id, columns_arr]\n\n    info = []\n    for _, row in info_row.iterrows():\n        info.append(list(row))\n    \n    return info","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# https://www.kaggle.com/bjoernholzhauer/eda-dicom-reading-vinbigdata-chest-x-ray#7.-Creating-fast-to-read-shelve-file\ndef generic_resize_image(df, image_id, image_path, transform_bb=True):\n        \n    # convert dicom to array\n    img_arr = dicom2array(image_path)\n    im_pil = Image.fromarray(img_arr)\n    \n    # training data\n    if transform_bb:\n        # create resize transform pipeline\n        transform = albumentations.Compose([\n            albumentations.Resize(height=512, width=512, always_apply=True)\n        ], bbox_params=albumentations.BboxParams(format='pascal_voc')) \n\n        columns = [\"x_min\", \"y_min\", \"x_max\",\"y_max\",\"class_id\",\"class_name\",\"rad_id\"]\n        bboxes = get_info(df, image_id, columns)\n\n        transformed = transform(image=img_arr, bboxes=bboxes)\n    else:\n        # create resize transform pipeline\n        transform = albumentations.Compose([\n            albumentations.Resize(height=512, width=512, always_apply=True)\n        ]) \n\n        transformed = transform(image=img_arr)\n\n    # im_pil.size[0] = width, im_pil.size[1] = height\n    return transformed, im_pil.size[0], im_pil.size[1]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# list for resized train data\nimage_id = []\nx_min = []\ny_min = []\nx_max = []\ny_max = []\nclass_id = []\nclass_name = []\nrad_id = []\noriginal_width = []\noriginal_height = []\ntransformed_width = []\ntransformed_height = []\n\n# start time\nstart = datetime.now()\n\n# conversion\nfor img_id in train_df[\"image_id\"].unique():        \n    transformed, width, height = generic_resize_image(train_df, img_id, f\"{dataset_dir}/train/{img_id}.dicom\")\n    \n    # save image array as png\n    im = Image.fromarray(transformed[\"image\"])\n    im.save(transformed_trained_dir+f\"/{img_id}.png\")\n    \n    for i in range(len(transformed[\"bboxes\"])):\n        image_id.append(img_id)\n        # each row contains \"x_min\", \"y_min\", \"x_max\",\"y_max\",\"class_id\",\"class_name\",\"rad_id\"\n        x_min.append(transformed[\"bboxes\"][i][0])\n        y_min.append(transformed[\"bboxes\"][i][1])\n        x_max.append(transformed[\"bboxes\"][i][2])\n        y_max.append(transformed[\"bboxes\"][i][3])\n        class_id.append(transformed[\"bboxes\"][i][4])\n        class_name.append(transformed[\"bboxes\"][i][5])\n        rad_id.append(transformed[\"bboxes\"][i][6])\n        original_width.append(width)\n        original_height.append(height)\n        # when using size we get width x height\n        transformed_width.append(im.size[0])\n        transformed_height.append(im.size[1])\n    \n    \n    \nupdated_csv = pd.DataFrame({\n    \"image_id\":image_id,\n    \"x_min\":x_min,\n    \"y_min\":y_min,\n    \"x_max\":x_max,\n    \"y_max\":y_max,\n    \"class_id\":class_id,\n    \"class_name\":class_name,\n    \"rad_id\":rad_id,\n    \"original_width\":original_width,\n    \"original_height\":original_height,\n    \"transformed_width\":transformed_width,\n    \"transformed_height\":transformed_height\n})\nupdated_csv.to_csv(f\"{transformed_trained_dir}/transformed_train.csv\", index=False)\n\n# end time\nprint(\"End time:\"+str(datetime.now() - start))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Verfiy train data with shortlisted ids","metadata":{"trusted":true}},{"cell_type":"code","source":"verifier_csv = pd.read_csv(f\"{transformed_trained_dir}/transformed_train.csv\")","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"shortlisted_img_ids","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for img_id in shortlisted_img_ids:    \n    bounding_boxes_info = get_bb_info(verifier_csv,img_id)\n    \n    # read image as array\n    im = Image.open(transformed_trained_dir+f\"/{img_id}.png\")\n    bounding_box_plotter(im, img_id, bounding_boxes_info)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**Test Data**","metadata":{}},{"cell_type":"code","source":"test_ids = []\nfor f in sorted(os.listdir(f\"{dataset_dir}/test/\")):\n    test_ids.append(f)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# start time\nstart = datetime.now()\n\noriginal_width = []\noriginal_height = []\ntransformed_width = []\ntransformed_height = []\nimage_id = []\n\n# conversion\nfor file_name in test_ids:        \n    transformed, width, height = generic_resize_image(None, None, f\"{dataset_dir}/test/{file_name}\", False)\n    \n    image_id.append(file_name[:-6])\n    original_width.append(width)\n    original_height.append(height)\n    \n    # save image array as png\n    im = Image.fromarray(transformed[\"image\"])\n    im.save(transformed_test_dir+f\"/{file_name[:-6]}.png\")\n    \n    # when using size we get width x height\n    transformed_width.append(im.size[0])\n    transformed_height.append(im.size[1])\n    \ntest_csv = pd.DataFrame({\n    \"image_id\":image_id,\n    \"original_width\":original_width,\n    \"original_height\":original_height,\n    \"transformed_width\":transformed_width,\n    \"transformed_height\":transformed_height\n})\ntest_csv.to_csv(f\"{transformed_test_dir}/test_original_dimension.csv\", index=False)\n    \n# end time\nprint(\"End time:\"+str(datetime.now() - start))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"view_DICOM_metadata(\"009bc039326338823ca3aa84381f17f1\", test_data=True)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}