{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":90095,"databundleVersionId":10627580,"sourceType":"competition"}],"dockerImageVersionId":30822,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport matplotlib.pyplot as plt \nimport os\nimport json\nimport cv2\nimport numpy as np\nfrom PIL import Image \n\nbase_path = \"/kaggle/input/glioma-mcd-2025/Data_122824/\"\ntr_folder_path = os.path.join(base_path, \"Glioma_MDC_2025_training\")\nte_folder_path = os.path.join(base_path, \"Glioma_MDC_2025_test\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-01-11T16:23:28.253274Z","iopub.execute_input":"2025-01-11T16:23:28.253595Z","iopub.status.idle":"2025-01-11T16:23:28.258538Z","shell.execute_reply.started":"2025-01-11T16:23:28.253569Z","shell.execute_reply":"2025-01-11T16:23:28.257477Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"I drafted this notebook because the problem with the unmatching dataset and JSON is not clear and presents a barrier to the participants. To those who did not understand or how to solve the problem may have experienced very bad performances on the submissions.\n\nWe are provided with JPG file and JSON file \n1. We can directly load JPG file and determine image dimensions\n2. JSON has 'imageHeight' or 'imageWidth' which tells us indirectly about JSON coordinates.","metadata":{}},{"cell_type":"markdown","source":"### Checking Image Sizes directly from JPG files","metadata":{}},{"cell_type":"code","source":"# Function to check image sizes\ndef check_image_sizes(folder_path):\n    sizes = {}\n    for file_name in os.listdir(folder_path):\n        if file_name.endswith(\".jpg\"):\n            file_path = os.path.join(folder_path, file_name)\n            with Image.open(file_path) as img:\n                size = img.size  # (width, height)\n                sizes[file_name] = size\n    return sizes\n\n# Check image sizes in each folder\ntraining_image_sizes = check_image_sizes(tr_folder_path)\ntesting_image_sizes = check_image_sizes(te_folder_path)\n\ntraining_image_sizes_summary = {size: list(training_image_sizes.values()).count(size) for size in set(training_image_sizes.values())}\ntesting_image_sizes_summary = {size: list(testing_image_sizes.values()).count(size) for size in set(testing_image_sizes.values())}\n\ntraining_image_sizes_summary, testing_image_sizes_summary\n\n# All images are of 512 by 512 and need to be resized to 1015x1015 to fit JSON format","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-11T16:23:30.400464Z","iopub.execute_input":"2025-01-11T16:23:30.400773Z","iopub.status.idle":"2025-01-11T16:23:38.410925Z","shell.execute_reply.started":"2025-01-11T16:23:30.400748Z","shell.execute_reply":"2025-01-11T16:23:38.409917Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Checking 'imageHeight' from the JSON file","metadata":{}},{"cell_type":"code","source":"# Function to check 'imageHeight' from JSON files\ndef check_image_heights(folder_path):\n    heights = {}\n    for file_name in os.listdir(folder_path):\n        if file_name.endswith(\".json\"):\n            file_path = os.path.join(folder_path, file_name)\n            with open(file_path, 'r') as f:\n                data = json.load(f)\n                if 'imageHeight' in data:\n                    heights[file_name] = data['imageHeight']\n                else:\n                    heights[file_name] = None  # In case 'imageHeight' is missing\n    return heights\n\n# Check 'imageHeight' in each folder\ntraining_image_heights = check_image_heights(tr_folder_path)\ntesting_image_heights = check_image_heights(te_folder_path)\n\n# Summarize 'imageHeight' values\ntraining_image_heights_summary = {height: list(training_image_heights.values()).count(height) for height in set(training_image_heights.values())}\ntesting_image_heights_summary = {height: list(testing_image_heights.values()).count(height) for height in set(testing_image_heights.values())}\n\ntraining_image_heights_summary, testing_image_heights_summary","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-11T16:24:41.314478Z","iopub.execute_input":"2025-01-11T16:24:41.314825Z","iopub.status.idle":"2025-01-11T16:24:49.519599Z","shell.execute_reply.started":"2025-01-11T16:24:41.314795Z","shell.execute_reply":"2025-01-11T16:24:49.518587Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## All JPG files are in 512x512. But 'imageHeight' in JSON files vary","metadata":{}},{"cell_type":"markdown","source":"### If 'imageHeight' are 1015, either you resize the image from 512 --> 1015, or rescale JSON coordinates from 1015 --> 512 \n\n### Below shows a unique case in 'training0002' where both JPG image size and 'imageHeight' are 512, but JSON coordinates are actually with respect to 1015. ","metadata":{}},{"cell_type":"code","source":"# Function to check for mismatched points in JSON and image pairs\ndef find_mismatched_json_image_pairs(folder_path):\n    mismatched_pairs = []\n    for file_name in os.listdir(folder_path):\n        if file_name.endswith(\".json\"):\n            json_path = os.path.join(folder_path, file_name)\n            image_path = json_path.replace(\".json\", \".jpg\")\n            \n            if not os.path.exists(image_path):\n                continue\n            \n            # Open JSON and image\n            with open(json_path, 'r') as f:\n                data = json.load(f)\n                image_height = data.get('imageHeight', None)\n                image_width = data.get('imageWidth', None)\n\n            with Image.open(image_path) as img:\n                img_width, img_height = img.size\n\n            # Check if both are 512x512\n            if image_height == 512 and image_width == 512 and img_width == 512 and img_height == 512:\n                for shape in data.get('shapes', []):\n                    points = shape.get('points', [])\n                    for x, y in points:\n                        if x > 512 or y > 512:  # Check if any points exceed 512\n                            mismatched_pairs.append(json_path)\n                            break\n\n    return mismatched_pairs\n\n# Find mismatched pairs in training and testing folders\ntraining_mismatched_pairs = find_mismatched_json_image_pairs(tr_folder_path)\ntesting_mismatched_pairs = find_mismatched_json_image_pairs(te_folder_path)\n\ntraining_mismatched_pairs, testing_mismatched_pairs","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-11T16:29:44.894832Z","iopub.execute_input":"2025-01-11T16:29:44.895264Z","iopub.status.idle":"2025-01-11T16:29:48.265935Z","shell.execute_reply.started":"2025-01-11T16:29:44.895233Z","shell.execute_reply":"2025-01-11T16:29:48.265167Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Function to rescale coordinates if needed\ndef rescale_coordinates(points, original_size, target_size):\n    \"\"\"\n    Rescale the points from the original size to the target size.\n    \"\"\"\n    if original_size != target_size:\n        scale_factor = target_size / original_size\n        return [(int(x * scale_factor), int(y * scale_factor)) for x, y in points]\n    else:\n        return [(int(x), int(y)) for x, y in points]\n\n# Function to overlay bounding boxes and extract crops\ndef overlay_points_and_bboxes_with_crops(image, rois):\n    \"\"\"\n    Overlay points and bounding boxes and return crops.\n    \"\"\"\n    overlay_image = image.copy()\n    crops = [] \n    for points in rois:\n        points = np.array(points, dtype=np.int32)\n        cv2.polylines(overlay_image, [points], isClosed=True, color=(255, 0, 0), thickness=2)\n\n        x_min, y_min = np.min(points, axis=0)\n        x_max, y_max = np.max(points, axis=0)\n        cv2.rectangle(overlay_image, (x_min, y_min), (x_max, y_max), color=(0, 0, 255), thickness=2)\n\n        crop = image[y_min:y_max, x_min:x_max]\n        crops.append(crop)\n\n    return overlay_image, crops\n\n# Plotting function to include cropped regions\ndef plot_image_and_crops(image_path, json_path):\n    \"\"\"\n    Plot the original image, overlayed image with bounding boxes, and cropped regions.\n    \"\"\"\n    image = cv2.imread(image_path)\n    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)  # Convert to RGB\n\n    with open(json_path, 'r') as f:\n        data = json.load(f)\n\n    # Determine if rescaling is needed\n    json_image_height = data['imageHeight']\n    json_image_width = data['imageWidth']\n    actual_image_height, actual_image_width, _ = image.shape\n\n    # Rescale coordinates if the JSON dimensions differ from the actual image dimensions\n    shapes = data['shapes']\n    rois = [\n        rescale_coordinates(shape['points'], json_image_height, actual_image_height) \n        for shape in shapes\n    ]\n\n    # Overlay the ROIs and bounding boxes on the image, and get cropped regions\n    overlay_image, crops = overlay_points_and_bboxes_with_crops(image, rois)\n\n    # Plot the original, overlayed, and cropped images\n    plt.figure(figsize=(16,12))\n\n    # Original image\n    plt.subplot(2, 4, 1)\n    plt.imshow(image)\n    plt.title(\"Original Image\")\n    plt.axis(\"off\")\n\n    # Image with overlayed points and bounding boxes\n    plt.subplot(2, 4, 2)\n    plt.imshow(overlay_image)\n    plt.title(\"Overlayed Image with Bounding Boxes\")\n    plt.axis(\"off\")\n\n    # Cropped regions\n    for i, crop in enumerate(crops[:6]):  # Display up to 6 cropped regions\n        plt.subplot(2, 4, i + 3)\n        plt.imshow(crop)\n        plt.title(f\"Crop {i+1}\")\n        plt.axis(\"off\")\n\n    plt.tight_layout()\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-11T16:32:19.264034Z","iopub.execute_input":"2025-01-11T16:32:19.264366Z","iopub.status.idle":"2025-01-11T16:32:19.27535Z","shell.execute_reply.started":"2025-01-11T16:32:19.264339Z","shell.execute_reply":"2025-01-11T16:32:19.274337Z"},"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"You will see above rescaling works for most cases but fails for 'training0002' because of incorrectly-matched data.","metadata":{}},{"cell_type":"code","source":"example_image_path = \"/kaggle/input/glioma-mcd-2025/Data_122824/Glioma_MDC_2025_training/training0001.jpg\"\nexample_json_path = \"/kaggle/input/glioma-mcd-2025/Data_122824/Glioma_MDC_2025_training/training0001.json\"\n\nplot_image_and_crops(example_image_path, example_json_path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-11T16:32:34.91637Z","iopub.execute_input":"2025-01-11T16:32:34.916721Z","iopub.status.idle":"2025-01-11T16:32:35.342145Z","shell.execute_reply.started":"2025-01-11T16:32:34.916687Z","shell.execute_reply":"2025-01-11T16:32:35.340997Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"example_image_path = \"/kaggle/input/glioma-mcd-2025/Data_122824/Glioma_MDC_2025_training/training0004.jpg\"\nexample_json_path = \"/kaggle/input/glioma-mcd-2025/Data_122824/Glioma_MDC_2025_training/training0004.json\"\n\nplot_image_and_crops(example_image_path, example_json_path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-11T16:32:02.197653Z","iopub.execute_input":"2025-01-11T16:32:02.19805Z","iopub.status.idle":"2025-01-11T16:32:02.709348Z","shell.execute_reply.started":"2025-01-11T16:32:02.198017Z","shell.execute_reply":"2025-01-11T16:32:02.708299Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"My code luckily found this problem, because one or more coordinates had value larger than 512, which contradicts. But this type of algorithm does not guarantee, it found all un-matching image/JSON data","metadata":{}},{"cell_type":"code","source":"example_image_path = \"/kaggle/input/glioma-mcd-2025/Data_122824/Glioma_MDC_2025_training/training0002.jpg\"\nexample_json_path = \"/kaggle/input/glioma-mcd-2025/Data_122824/Glioma_MDC_2025_training/training0002.json\"\n\nplot_image_and_crops(example_image_path, example_json_path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-11T16:34:04.836039Z","iopub.execute_input":"2025-01-11T16:34:04.836379Z","iopub.status.idle":"2025-01-11T16:34:05.429836Z","shell.execute_reply.started":"2025-01-11T16:34:04.836351Z","shell.execute_reply":"2025-01-11T16:34:05.428352Z"}},"outputs":[],"execution_count":null}]}