{"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":"### Measure bounding box sizes\n\nIn this notebook, we'll extract a bounding box and calculate the size in centimeters and draw it onto the image.\n\n- Since the image shapes aren't consistent and the pixel spacings are different, I figured I'd want to know the actual sizes of the BB's when comparing them together.\n- We can use DICOM pixel spacing to map pixel lengths to a real world measurements (millimeters in this case).\n- Typically we'd use the Pythagorean Theorem to calculate a Euclidean distance between two points. In this case, the BB's are square and their lines fall in the X and Y planes. This makes it easier to calculate their distances.\n- The basic formula is -> **mm = pixel_distance * pixel_spacing**\n\nThere are various Pixel Spacing tags that can exist. Most commonly in X-Ray, we refer to the ImagerPixelSpacing tag (x0018,x1164) for measurements. This is the calibrated amount that each pixel on the receptor is multiplied by to give a real world measurement.\n\n- Pixel Spacing tags have two values seperated with a slash character .. with one value for the X plane and one for the Y plane.\n- These values are almost always the same, but they do not have to be. Some systems create images with non-square pixels (yes .. I know that seems odd, but pixels can be non-square!).\n- DICOM Pixel Spacing Reference: http://dicom.nema.org/dicom/2013/output/chtml/part03/sect_10.7.html\n\nOf the 6443 images in the train set, 5936 images have the ImagerPixelSpacing tag present.","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport seaborn as sns\nimport pydicom\nimport matplotlib.pyplot as plt\nimport os\nfrom os import listdir\nfrom os.path import isfile, join\nimport json\nfrom pydicom.pixel_data_handlers.util import apply_voi_lut","metadata":{"execution":{"iopub.status.busy":"2021-06-22T13:54:50.456753Z","iopub.execute_input":"2021-06-22T13:54:50.457175Z","iopub.status.idle":"2021-06-22T13:54:51.721237Z","shell.execute_reply.started":"2021-06-22T13:54:50.457091Z","shell.execute_reply":"2021-06-22T13:54:51.720003Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Load the image data\nbase_path = \"/kaggle/input/siim-covid19-detection/\"\nimages_df = pd.read_csv(os.path.join(base_path,\"train_image_level.csv\"))","metadata":{"execution":{"iopub.status.busy":"2021-06-22T13:54:51.725834Z","iopub.execute_input":"2021-06-22T13:54:51.726494Z","iopub.status.idle":"2021-06-22T13:54:51.789502Z","shell.execute_reply.started":"2021-06-22T13:54:51.726454Z","shell.execute_reply":"2021-06-22T13:54:51.788422Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Function to get the first file in a study directory by study_id\ndef get_image_by_study_id(study_id):\n    study_path = base_path + \"train/\" + study_id + \"/\"\n    images = []\n    for subdir, dirs, files in os.walk(study_path):\n        for file in files:\n            images.append(os.path.join(subdir, file))\n            \n    return images","metadata":{"execution":{"iopub.status.busy":"2021-06-22T13:54:51.791195Z","iopub.execute_input":"2021-06-22T13:54:51.791547Z","iopub.status.idle":"2021-06-22T13:54:51.798419Z","shell.execute_reply.started":"2021-06-22T13:54:51.791517Z","shell.execute_reply":"2021-06-22T13:54:51.797330Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Function to get the BB data from the images DF\ndef get_boxes(image_id):\n    image = image_id.replace('.dcm','_image')\n    ti = images_df[images_df['id'] == image]\n    bx = [[],[]]\n    bx[0] = [0,0,0,0,\"\"]\n    bx[1] = [0,0,0,0,\"\"]\n    \n    if str(ti['boxes'].values[0]) != \"nan\":\n        box = str(ti['boxes'].values[0]).replace(\"'\",\"\\\"\")\n        boxes = json.loads(box)\n        lab = ti['label'].values[0].split(\" \")\n        i = 0\n        for b in boxes:\n            bx[i] = [str(b['x']), str(b['y']), str(b['width']),str(b['height']),str(lab[0])]\n            i = i+1\n    return bx","metadata":{"execution":{"iopub.status.busy":"2021-06-22T13:54:51.800256Z","iopub.execute_input":"2021-06-22T13:54:51.800593Z","iopub.status.idle":"2021-06-22T13:54:51.812060Z","shell.execute_reply.started":"2021-06-22T13:54:51.800561Z","shell.execute_reply":"2021-06-22T13:54:51.810744Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Pick a random study in the train set and get image(s) from it\nstudy_id = \"00086460a852\"\nimages = get_image_by_study_id(study_id)","metadata":{"execution":{"iopub.status.busy":"2021-06-22T13:54:51.813807Z","iopub.execute_input":"2021-06-22T13:54:51.814263Z","iopub.status.idle":"2021-06-22T13:54:51.840345Z","shell.execute_reply.started":"2021-06-22T13:54:51.814228Z","shell.execute_reply":"2021-06-22T13:54:51.839265Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"line_color = 'red'\ntext_color = 'yellow'\nplt.figure(figsize=(10,10))\n\n# Iterate through the file list\nfor img_file in images:\n    \n    # Load a file\n    img = pydicom.dcmread(img_file)\n    pixels = img.pixel_array\n    out = apply_voi_lut(pixels, img, index=0)\n    \n    # Get the ImagerPixelSpacing tag\n    if (0x0018,0x1164) in img:\n        pixel_spacing = img[0x0018,0x1164]\n        print(\"ImagerPixelSpacing: \" + str(pixel_spacing[0]) + \" / \" + str(pixel_spacing[1]))\n         \n    # Invert MONOCHROME1\n    cmap = \"gray\"\n    if (img.PhotometricInterpretation == \"MONOCHROME1\"):\n        cmap = \"gray_r\"\n\n    # Grab the bounding boxes\n    boxes = get_boxes(str(os.path.basename(img_file)))\n\n    # Iterate through the bounding boxes and draw the lines\n    if boxes:\n        for i in boxes:\n            distance_x = 0\n            distance_y = 0\n            i[0] = float(i[0])\n            i[1] = float(i[1])\n            i[2] = float(i[2])\n            i[3] = float(i[3])\n\n            # Top line\n            x = [i[0], i[0] + i[2]]\n            y = [i[1], i[1]]\n            plt.plot(x,y, color=line_color, linewidth=1)\n            plt.text(x[0], y[0]-10, str(i[4]), fontsize=\"large\", color=text_color)\n            \n            # Bottom line\n            x = [i[0], i[0] + i[2]]\n            y = [i[1]+ i[3], i[1] + i[3]]\n            distance_x = round((x[1] - x[0]) * pixel_spacing[0] / 10, 2)\n            plt.plot(x, y, color=line_color, linewidth=1)\n            plt.text(x[0] + 50, y[0] - 10, str(distance_x) + \"cm\", fontsize=\"large\", color=text_color)\n\n            # Left line\n            x = [i[0], i[0]]\n            y = [i[1], i[1] + i[3]]\n            distance_y = round((y[1] - y[0]) * pixel_spacing[0] / 10, 2)\n            plt.plot(x, y, color=line_color, linewidth=1)\n            plt.text(x[0], y[0]+ ((y[1] - y[0]) / 2), str(distance_y) + \"cm\", fontsize=\"large\", color=text_color)\n\n            # Right line\n            x = [i[0] + i[2], i[0] + i[2]]\n            y = [i[1], i[1] + i[3]]\n            plt.plot(x, y, color=line_color, linewidth=1)\n\n    \n    plt.imshow(out,cmap=cmap)\n    plt.grid(False)\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2021-06-22T13:54:51.842013Z","iopub.execute_input":"2021-06-22T13:54:51.842326Z","iopub.status.idle":"2021-06-22T13:54:53.412079Z","shell.execute_reply.started":"2021-06-22T13:54:51.842296Z","shell.execute_reply":"2021-06-22T13:54:53.410972Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"- This could be useful for extracting ROI from the BB's and scaling them for inference .. or something.\n- Likewise, other measurements on segmented masks could be useful .. possibly the Cardio-Thoracic Ratio .. or the area of a lung field.\n- Any two points on an x-ray can be measured this way. Just be sure to use Pythagorean Theorem on non-orthogonal lines and watch out for non-square pixels.","metadata":{}}]}