├── Annotations └── .gitkeep ├── DataPostprocessing ├── ReversePreprocessing │ ├── back_chexpert.py │ ├── back_mimic.py │ ├── back_padchest.py │ └── back_vin.py └── utils.py ├── DataPreparation ├── chexpert.py ├── mimic.py ├── padchest.py └── vindr-cxr.py ├── Example.ipynb ├── Example ├── H_landmarks.txt ├── H_mask.png ├── LL_landmarks.txt ├── LL_mask.png ├── RL_landmarks.txt ├── RL_mask.png └── utils_example1.jpg ├── HybridGNet ├── inferenceWithHybridGNet.py ├── models │ ├── HybridGNet2IGSC.py │ └── modelUtils.py ├── trainHybridGNetOnDataset.py └── utils │ ├── dataset_for_train.py │ └── utils.py ├── LICENSE ├── README.md ├── TechnicalValidation ├── IndividualRCA │ ├── BiasAnalysis.ipynb │ ├── Individual_RCA_BigScale.ipynb │ ├── dataset_affine.py │ ├── estimate_individual_rca_datasets.py │ ├── estimate_rca_affine_deformable_test_set.py │ ├── histogram_whole_dataset.png │ ├── histogram_whole_dataset_white.png │ ├── histograms_Dice_RCA_Max.pdf │ ├── histograms_Dice_RCA_Max.png │ ├── histograms_Dice_RCA_Mean.pdf │ ├── histograms_Dice_RCA_Mean.png │ ├── how_we_saved_annotations.py │ ├── latent_space_train.npy │ ├── model │ │ ├── affine.py │ │ ├── deformableNet.py │ │ └── unet.py │ ├── test_images_heart.txt │ ├── test_images_lungs.txt │ ├── train_images_heart.txt │ └── train_images_lungs.txt └── PhysicianAnnotations │ ├── 1_ExcelToPandasCSV.ipynb │ ├── 1_ExcelToPandasCSV_Findings.ipynb │ ├── 2_EvaluateAnnotations.ipynb │ ├── 3_Evaluate_landmarks.ipynb │ ├── DataSubsets │ ├── CANDID-PTX.csv │ ├── CANDID-PTX_julia_annotations.csv │ ├── CANDID-PTX_martina_annotations.csv │ ├── CheXpert.csv │ ├── CheXpert_julia_annotations.csv │ ├── CheXpert_martina_annotations.csv │ ├── ChestX-Ray8.csv │ ├── ChestX-Ray8_julia_annotations.csv │ ├── ChestX-Ray8_martina_annotations.csv │ ├── MIMIC-CXR-JPG.csv │ ├── MIMIC-CXR-JPG_julia_annotations.csv │ ├── MIMIC-CXR-JPG_martina_annotations.csv │ ├── Padchest.csv │ ├── Padchest_julia_annotations.csv │ ├── Padchest_martina_annotations.csv │ ├── VinDr-CXR.csv │ ├── VinDr-CXR_julia_annotations.csv │ └── VinDr-CXR_martina_annotations.csv │ ├── ToAnnotate │ ├── CANDID_name_mapping.csv │ ├── CheXpert_name_mapping.csv │ ├── ChestX-Ray8_name_mapping.csv │ ├── MIMIC_name_mapping.csv │ ├── Padchest_name_mapping.csv │ ├── VinBigData_name_mapping.csv │ └── hists │ │ ├── Candid_histogram.png │ │ ├── histogram_chest8.png │ │ ├── histogram_chexp.png │ │ ├── histogram_mimic.png │ │ ├── histogram_padchest.png │ │ └── histogram_vindr.png │ ├── df_rca_affine_deformable_lh.csv │ ├── histograms.png │ ├── julia_annotations.csv │ ├── julia_annotations.xls │ ├── julia_annotations_merge.csv │ ├── julia_annotations_set-findings.csv │ ├── julia_annotations_set-findings.xls │ ├── julia_eval.csv │ ├── martina_annotations.csv │ ├── martina_annotations.xls │ ├── martina_annotations_merge.csv │ ├── martina_annotations_set-findings.csv │ ├── martina_annotations_set-findings.xls │ ├── martina_eval.csv │ ├── martina_vs_julia_eval.csv │ ├── metrics.py │ ├── mse_per_landmark_1.pdf │ ├── mse_per_landmark_2.pdf │ ├── rca_chest_landmark.pdf │ └── samples_code.py ├── Weights └── .gitkeep └── figures ├── histogram_CheXmask.pdf └── histogram_CheXmask.png /Annotations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaggion/CheXmask-Database/8ae0c338caced10869f8be1860e320146c40d096/Annotations/.gitkeep -------------------------------------------------------------------------------- /DataPostprocessing/ReversePreprocessing/back_chexpert.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | import cv2 4 | 5 | def get_RLE_from_mask(mask): 6 | mask = (mask / 255).astype(int) 7 | pixels = mask.flatten() 8 | pixels = np.concatenate([[0], pixels, [0]]) 9 | runs = np.where(pixels[1:] != pixels[:-1])[0] + 1 10 | runs[1::2] -= runs[::2] 11 | return ' '.join(str(x) for x in runs) 12 | 13 | 14 | def get_mask_from_RLE(rle, height, width): 15 | runs = np.array([int(x) for x in rle.split()]) 16 | starts = runs[::2] 17 | lengths = runs[1::2] 18 | 19 | mask = np.zeros((height * width), dtype=np.uint8) 20 | 21 | for start, length in zip(starts, lengths): 22 | start -= 1 23 | end = start + length 24 | mask[start:end] = 255 25 | 26 | mask = mask.reshape((height, width)) 27 | 28 | return mask 29 | 30 | def getDenseMask(graph, h, w): 31 | img = np.zeros([h, w]) 32 | graph = graph.reshape(-1, 1, 2).astype('int') 33 | img = cv2.drawContours(img, [graph], -1, 255, -1) 34 | return img 35 | 36 | 37 | path = ".../Annotations/Preprocessed/CheXpert.csv" 38 | path2 = ".../paddings.csv" # path to paddings csv 39 | 40 | df = pd.read_csv(path) 41 | pads = pd.read_csv(path2) 42 | pads.filename = pads.filename.str.replace(".png", ".jpg") 43 | pads.set_index('filename', inplace=True) 44 | 45 | new_df = pd.DataFrame(columns=df.columns) 46 | new_df.to_csv(".../Annotations/OriginalResolution/CheXpert.csv", index=False) 47 | 48 | for index, row in df.iterrows(): 49 | print(index) 50 | image_id = row["Path"] 51 | 52 | pad_row = pads.loc[image_id] 53 | 54 | height, width = pad_row["height"], pad_row["width"] 55 | 56 | landmarks = np.array(eval(row["Landmarks"])).reshape(-1, 2) / 1024 57 | max_shape = max(height, width) 58 | landmarks = landmarks * max_shape 59 | 60 | pad_left = pad_row["pad_left"] 61 | pad_top = pad_row["pad_top"] 62 | 63 | landmarks[:, 0] = landmarks[:, 0] - pad_left 64 | landmarks[:, 1] = landmarks[:, 1] - pad_top 65 | landmarks = np.round(landmarks).astype(int) 66 | 67 | RL = landmarks[:44] 68 | LL = landmarks[44:94] 69 | H = landmarks[94:] 70 | 71 | RL_ = getDenseMask(RL, height, width) 72 | LL_ = getDenseMask(LL, height, width) 73 | H_ = getDenseMask(H, height, width) 74 | 75 | RL_RLE = get_RLE_from_mask(RL_) 76 | LL_RLE = get_RLE_from_mask(LL_) 77 | H_RLE = get_RLE_from_mask(H_) 78 | 79 | # columns = image_id Dice RCA (Mean) Dice RCA (Max) Landmarks Left Lung Right Lung Heart Height Width 80 | 81 | new_row = { 82 | "Path": row["Path"], 83 | "Dice RCA (Mean)": row["Dice RCA (Mean)"], 84 | "Dice RCA (Max)": row["Dice RCA (Max)"], 85 | "Landmarks": landmarks, 86 | "Left Lung": LL_RLE, 87 | "Right Lung": RL_RLE, 88 | "Heart": H_RLE, 89 | "Height": height, 90 | "Width": width 91 | } 92 | 93 | new_df = new_df.append(new_row, ignore_index=True) 94 | 95 | if index % 5000 == 0 and index != 0: 96 | new_df.to_csv(".../Annotations/OriginalResolution/CheXpert.csv", mode='a', header=False, index=False) 97 | 98 | del new_df 99 | 100 | new_df = pd.DataFrame(columns=df.columns) 101 | 102 | new_df.to_csv(".../Annotations/OriginalResolution/CheXpert.csv", mode='a', header=False, index=False) -------------------------------------------------------------------------------- /DataPostprocessing/ReversePreprocessing/back_mimic.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | import cv2 4 | 5 | def get_RLE_from_mask(mask): 6 | mask = (mask / 255).astype(int) 7 | pixels = mask.flatten() 8 | pixels = np.concatenate([[0], pixels, [0]]) 9 | runs = np.where(pixels[1:] != pixels[:-1])[0] + 1 10 | runs[1::2] -= runs[::2] 11 | return ' '.join(str(x) for x in runs) 12 | 13 | 14 | def get_mask_from_RLE(rle, height, width): 15 | runs = np.array([int(x) for x in rle.split()]) 16 | starts = runs[::2] 17 | lengths = runs[1::2] 18 | 19 | mask = np.zeros((height * width), dtype=np.uint8) 20 | 21 | for start, length in zip(starts, lengths): 22 | start -= 1 23 | end = start + length 24 | mask[start:end] = 255 25 | 26 | mask = mask.reshape((height, width)) 27 | 28 | return mask 29 | 30 | def getDenseMask(graph, h, w): 31 | img = np.zeros([h, w]) 32 | graph = graph.reshape(-1, 1, 2).astype('int') 33 | img = cv2.drawContours(img, [graph], -1, 255, -1) 34 | return img 35 | 36 | 37 | path = "../../Annotations/Preprocessed/MIMIC-CXR-JPG.csv" 38 | path2 = ... # path to paddings.csv 39 | 40 | df = pd.read_csv(path) 41 | pads = pd.read_csv(path2) 42 | 43 | pads.filename = pads.filename.str.split('/').str[-1].str.split('.').str[0] 44 | pads.set_index('filename', inplace=True) 45 | 46 | new_df = pd.DataFrame(columns=df.columns) 47 | new_df.to_csv("../../Annotations/OriginalResolution/MIMIC-CXR-JPG.csv", index=False) 48 | 49 | count = 0 50 | 51 | for index in range(0, len(df)): 52 | print(index) 53 | 54 | row = df.iloc[index] 55 | 56 | image_id = row["dicom_id"] 57 | 58 | pad_row = pads.loc[image_id] 59 | 60 | height, width = pad_row["width"], pad_row["height"] 61 | 62 | # pad_left_rem pad_top_rem pad_right_rem pad_bottom_rem 63 | pad_left_rem = pad_row["pad_left_rem"] 64 | pad_top_rem = pad_row["pad_top_rem"] 65 | pad_right_rem = pad_row["pad_right_rem"] 66 | pad_bottom_rem = pad_row["pad_bottom_rem"] 67 | 68 | height2 = height - pad_top_rem - pad_bottom_rem 69 | width2 = width - pad_left_rem - pad_right_rem 70 | 71 | landmarks = np.array(eval(row["Landmarks"])).reshape(-1, 2) / 1024 72 | max_shape = max(height2, width2) 73 | landmarks = landmarks * max_shape 74 | 75 | pad_left = pad_row["pad_left"] 76 | pad_top = pad_row["pad_top"] 77 | 78 | landmarks[:, 0] = landmarks[:, 0] - pad_left + pad_left_rem 79 | landmarks[:, 1] = landmarks[:, 1] - pad_top + pad_top_rem 80 | landmarks = np.round(landmarks).astype(int) 81 | 82 | RL = landmarks[:44] 83 | LL = landmarks[44:94] 84 | H = landmarks[94:] 85 | 86 | RL_ = getDenseMask(RL, height, width) 87 | LL_ = getDenseMask(LL, height, width) 88 | H_ = getDenseMask(H, height, width) 89 | 90 | RL_RLE = get_RLE_from_mask(RL_) 91 | LL_RLE = get_RLE_from_mask(LL_) 92 | H_RLE = get_RLE_from_mask(H_) 93 | 94 | # columns = image_id Dice RCA (Mean) Dice RCA (Max) Landmarks Left Lung Right Lung Heart Height Width 95 | 96 | new_row = { 97 | "dicom_id": row["dicom_id"], 98 | "Dice RCA (Mean)": row["Dice RCA (Mean)"], 99 | "Dice RCA (Max)": row["Dice RCA (Max)"], 100 | "Landmarks": landmarks, 101 | "Left Lung": LL_RLE, 102 | "Right Lung": RL_RLE, 103 | "Heart": H_RLE, 104 | "Height": height, 105 | "Width": width 106 | } 107 | 108 | new_df = new_df.append(new_row, ignore_index=True) 109 | 110 | if index % 20000 == 0 and index != 0: 111 | new_df.to_csv("../../Annotations/OriginalResolution/MIMIC-CXR-JPG.csv", mode='a', header=False, index=False) 112 | 113 | del new_df 114 | 115 | new_df = pd.DataFrame(columns=df.columns) 116 | 117 | new_df.to_csv("../../Annotations/OriginalResolution/MIMIC-CXR-JPG.csv", mode='a', header=False, index=False) -------------------------------------------------------------------------------- /DataPostprocessing/ReversePreprocessing/back_padchest.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | import cv2 4 | 5 | def get_RLE_from_mask(mask): 6 | mask = (mask / 255).astype(int) 7 | pixels = mask.flatten() 8 | pixels = np.concatenate([[0], pixels, [0]]) 9 | runs = np.where(pixels[1:] != pixels[:-1])[0] + 1 10 | runs[1::2] -= runs[::2] 11 | return ' '.join(str(x) for x in runs) 12 | 13 | 14 | def get_mask_from_RLE(rle, height, width): 15 | runs = np.array([int(x) for x in rle.split()]) 16 | starts = runs[::2] 17 | lengths = runs[1::2] 18 | 19 | mask = np.zeros((height * width), dtype=np.uint8) 20 | 21 | for start, length in zip(starts, lengths): 22 | start -= 1 23 | end = start + length 24 | mask[start:end] = 255 25 | 26 | mask = mask.reshape((height, width)) 27 | 28 | return mask 29 | 30 | def getDenseMask(graph, h, w): 31 | img = np.zeros([h, w]) 32 | graph = graph.reshape(-1, 1, 2).astype('int') 33 | img = cv2.drawContours(img, [graph], -1, 255, -1) 34 | return img 35 | 36 | 37 | path = "../Annotations/Preprocessed/Padchest.csv" 38 | path2 = ... # path to padding csv 39 | 40 | df = pd.read_csv(path) 41 | pads = pd.read_csv(path2) 42 | pads.set_index('filename', inplace=True) 43 | 44 | new_df = pd.DataFrame(columns=df.columns) 45 | 46 | for index, row in df.iterrows(): 47 | print(index) 48 | image_id = row["ImageID"] 49 | 50 | pad_row = pads.loc[image_id] 51 | 52 | height, width = pad_row["width"], pad_row["height"] 53 | 54 | # pad_left_rem pad_top_rem pad_right_rem pad_bottom_rem 55 | pad_left_rem = pad_row["pad_left_rem"] 56 | pad_top_rem = pad_row["pad_top_rem"] 57 | pad_right_rem = pad_row["pad_right_rem"] 58 | pad_bottom_rem = pad_row["pad_bottom_rem"] 59 | 60 | height2 = height - pad_top_rem - pad_bottom_rem 61 | width2 = width - pad_left_rem - pad_right_rem 62 | 63 | landmarks = np.array(eval(row["Landmarks"])).reshape(-1, 2) / 1024 64 | max_shape = max(height2, width2) 65 | landmarks = landmarks * max_shape 66 | 67 | pad_left = pad_row["pad_left"] 68 | pad_top = pad_row["pad_top"] 69 | 70 | landmarks[:, 0] = landmarks[:, 0] - pad_left + pad_left_rem 71 | landmarks[:, 1] = landmarks[:, 1] - pad_top + pad_top_rem 72 | landmarks = np.round(landmarks).astype(int) 73 | 74 | RL = landmarks[:44] 75 | LL = landmarks[44:94] 76 | H = landmarks[94:] 77 | 78 | RL_ = getDenseMask(RL, height, width) 79 | LL_ = getDenseMask(LL, height, width) 80 | H_ = getDenseMask(H, height, width) 81 | 82 | RL_RLE = get_RLE_from_mask(RL_) 83 | LL_RLE = get_RLE_from_mask(LL_) 84 | H_RLE = get_RLE_from_mask(H_) 85 | 86 | # columns = image_id Dice RCA (Mean) Dice RCA (Max) Landmarks Left Lung Right Lung Heart Height Width 87 | 88 | new_row = { 89 | "ImageID": row["ImageID"], 90 | "Dice RCA (Mean)": row["Dice RCA (Mean)"], 91 | "Dice RCA (Max)": row["Dice RCA (Max)"], 92 | "Landmarks": landmarks, 93 | "Left Lung": LL_RLE, 94 | "Right Lung": RL_RLE, 95 | "Heart": H_RLE, 96 | "Height": height, 97 | "Width": width 98 | } 99 | 100 | new_df = new_df.append(new_row, ignore_index=True) 101 | 102 | new_df.to_csv("../Annotations/OriginalResolution/Padchest.csv", index=False) -------------------------------------------------------------------------------- /DataPostprocessing/ReversePreprocessing/back_vin.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | import cv2 4 | 5 | def get_RLE_from_mask(mask): 6 | mask = (mask / 255).astype(int) 7 | pixels = mask.flatten() 8 | pixels = np.concatenate([[0], pixels, [0]]) 9 | runs = np.where(pixels[1:] != pixels[:-1])[0] + 1 10 | runs[1::2] -= runs[::2] 11 | return ' '.join(str(x) for x in runs) 12 | 13 | 14 | def get_mask_from_RLE(rle, height, width): 15 | runs = np.array([int(x) for x in rle.split()]) 16 | starts = runs[::2] 17 | lengths = runs[1::2] 18 | 19 | mask = np.zeros((height * width), dtype=np.uint8) 20 | 21 | for start, length in zip(starts, lengths): 22 | start -= 1 23 | end = start + length 24 | mask[start:end] = 255 25 | 26 | mask = mask.reshape((height, width)) 27 | 28 | return mask 29 | 30 | def getDenseMask(graph, h, w): 31 | img = np.zeros([h, w]) 32 | graph = graph.reshape(-1, 1, 2).astype('int') 33 | img = cv2.drawContours(img, [graph], -1, 255, -1) 34 | return img 35 | 36 | 37 | path = "../Annotations/Preprocessed/VinDr-CXR.csv" 38 | path2 = ... # path to paddings csv 39 | 40 | df = pd.read_csv(path) 41 | pads = pd.read_csv(path2) 42 | 43 | pads.filename = pads.filename.str.replace(".dicom", "") 44 | pads.filename = pads.filename.str.split("/").str[-1] 45 | 46 | new_df = pd.DataFrame(columns=df.columns) 47 | 48 | for index, row in df.iterrows(): 49 | print(index) 50 | image_id = row["image_id"] 51 | 52 | pad_row = pads[pads.filename == image_id].iloc[0] 53 | 54 | height, width = pad_row["height"], pad_row["width"] 55 | 56 | landmarks = np.array(eval(row["Landmarks"])).reshape(-1, 2) / 1024 57 | max_shape = max(height, width) 58 | landmarks = landmarks * max_shape 59 | 60 | pad_left = pad_row["pad_left"] 61 | pad_top = pad_row["pad_top"] 62 | 63 | landmarks[:, 0] = landmarks[:, 0] - pad_left 64 | landmarks[:, 1] = landmarks[:, 1] - pad_top 65 | landmarks = np.round(landmarks).astype(int) 66 | 67 | RL = landmarks[:44] 68 | LL = landmarks[44:94] 69 | H = landmarks[94:] 70 | 71 | RL_ = getDenseMask(RL, height, width) 72 | LL_ = getDenseMask(LL, height, width) 73 | H_ = getDenseMask(H, height, width) 74 | 75 | RL_RLE = get_RLE_from_mask(RL_) 76 | LL_RLE = get_RLE_from_mask(LL_) 77 | H_RLE = get_RLE_from_mask(H_) 78 | 79 | # columns = image_id Dice RCA (Mean) Dice RCA (Max) Landmarks Left Lung Right Lung Heart Height Width 80 | 81 | new_row = { 82 | "image_id": row["image_id"], 83 | "Dice RCA (Mean)": row["Dice RCA (Mean)"], 84 | "Dice RCA (Max)": row["Dice RCA (Max)"], 85 | "Landmarks": landmarks, 86 | "Left Lung": LL_RLE, 87 | "Right Lung": RL_RLE, 88 | "Heart": H_RLE, 89 | "Height": height, 90 | "Width": width 91 | } 92 | 93 | new_df = new_df.append(new_row, ignore_index=True) 94 | 95 | new_df.to_csv("../Annotations/OriginalResolution/VinDr-CXR.csv", index=False) -------------------------------------------------------------------------------- /DataPostprocessing/utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import cv2 3 | 4 | def get_RLE_from_mask(mask): 5 | mask = (mask / 255).astype(int) 6 | pixels = mask.flatten() 7 | pixels = np.concatenate([[0], pixels, [0]]) 8 | runs = np.where(pixels[1:] != pixels[:-1])[0] + 1 9 | runs[1::2] -= runs[::2] 10 | return ' '.join(str(x) for x in runs) 11 | 12 | 13 | def get_mask_from_RLE(rle, height, width): 14 | runs = np.array([int(x) for x in rle.split()]) 15 | starts = runs[::2] 16 | lengths = runs[1::2] 17 | 18 | mask = np.zeros((height * width), dtype=np.uint8) 19 | 20 | for start, length in zip(starts, lengths): 21 | start -= 1 22 | end = start + length 23 | mask[start:end] = 255 24 | 25 | mask = mask.reshape((height, width)) 26 | 27 | return mask 28 | 29 | def getDenseMaskFromLandmarks(landmarks, imagesize = 1024): 30 | img = np.zeros([imagesize,imagesize]) 31 | landmarks = landmarks.reshape(-1, 1, 2).astype('int') 32 | img = cv2.drawContours(img, [landmarks], -1, 255, -1) 33 | return img -------------------------------------------------------------------------------- /DataPreparation/chexpert.py: -------------------------------------------------------------------------------- 1 | import pathlib 2 | import re 3 | import numpy as np 4 | 5 | def load_paths(path): 6 | path = pathlib.Path(path) 7 | paths = list(path.glob('*')) 8 | return [str(fp) for fp in paths] 9 | 10 | def pad_to_square_centered(img): 11 | h, w = img.shape[:2] 12 | longest_edge = max(h, w) 13 | result = np.zeros((longest_edge, longest_edge), dtype=img.dtype) 14 | pad_h = (longest_edge - h) // 2 15 | pad_w = (longest_edge - w) // 2 16 | result[pad_h:pad_h + h, pad_w:pad_w + w] = img 17 | 18 | pad_left = pad_w 19 | pad_top = pad_h 20 | pad_right = longest_edge - w - pad_w 21 | pad_bottom = longest_edge - h - pad_h 22 | 23 | return result, pad_left, pad_top, pad_right, pad_bottom 24 | 25 | path = "CheXpert-v1 0/Original" 26 | outpath = "CheXpert-v1 0/Preprocessed" 27 | 28 | 29 | # saves metadata in a csv file 30 | import cv2 31 | import os 32 | 33 | with open("CheXpert-v1 0/Preprocessed/paddings.csv", '+w') as f: 34 | f.write("filename,width,height,pad_left,pad_top,pad_right,pad_bottom \n") 35 | 36 | j = 0 37 | for folder in load_paths(path): 38 | if '.csv' in folder: 39 | continue 40 | 41 | out_folder = outpath + '/' + folder.split('/')[-1] 42 | # if out_folder does not exist, create it 43 | try: 44 | os.mkdir(out_folder) 45 | except: 46 | pass 47 | 48 | n = len(load_paths(folder)) 49 | j = 0 50 | for patient in load_paths(folder): 51 | if j%100 == 0: 52 | print(j, 'of', n) 53 | j += 1 54 | 55 | out_patient_folder = out_folder + '/' + patient.split('/')[-1] 56 | 57 | try: 58 | os.mkdir(out_patient_folder) 59 | except: 60 | pass 61 | 62 | for study in load_paths(patient): 63 | out_study_folder = out_patient_folder + '/' + study.split('/')[-1] 64 | 65 | try: 66 | os.mkdir(out_study_folder) 67 | except: 68 | pass 69 | 70 | imgpath = study + '/view1_frontal.jpg' 71 | 72 | # save image 73 | file_out = out_study_folder + '/view1_frontal.png' 74 | 75 | if os.path.isfile(imgpath): 76 | 77 | data = cv2.imread(imgpath, cv2.IMREAD_GRAYSCALE) 78 | h, w = data.shape[:2] 79 | 80 | reshaped, pad_left, pad_top, pad_right, pad_bottom = pad_to_square_centered(data) 81 | 82 | # scale to 1024x1024 83 | scaled = cv2.resize(reshaped, (1024, 1024)) 84 | 85 | cv2.imwrite(file_out, scaled) 86 | 87 | outline = "%s,%d,%d,%d,%d,%d,%d \n"%(file_out.replace(outpath, ''), h, w, pad_left, pad_top, pad_right, pad_bottom) 88 | f.write(outline) -------------------------------------------------------------------------------- /DataPreparation/mimic.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | import cv2 4 | import pandas as pd 5 | import gc 6 | 7 | def remove_padding(img): 8 | 9 | gray = 255*(img > 1) # To invert the text to white 10 | coords = cv2.findNonZero(gray) # Find all non-zero points (text) 11 | 12 | x, y, w, h = cv2.boundingRect(coords) # Find minimum spanning bounding box 13 | cropimg = img[y:y+h, x:x+w] # Crop the image - note we do this on the original image 14 | 15 | pad_left, pad_top, pad_right, pad_bottom = x, y, img.shape[1] - x - w, img.shape[0] - y - h 16 | 17 | return cropimg, pad_left, pad_top, pad_right, pad_bottom 18 | 19 | def pad_to_square_centered(img): 20 | h, w = img.shape[:2] 21 | longest_edge = max(h, w) 22 | result = np.zeros((longest_edge, longest_edge), dtype=img.dtype) 23 | pad_h = (longest_edge - h) // 2 24 | pad_w = (longest_edge - w) // 2 25 | result[pad_h:pad_h + h, pad_w:pad_w + w] = img 26 | 27 | pad_left = pad_w 28 | pad_top = pad_h 29 | pad_right = longest_edge - w - pad_w 30 | pad_bottom = longest_edge - h - pad_h 31 | 32 | return result, pad_left, pad_top, pad_right, pad_bottom 33 | 34 | df = pd.read_csv("mimic-cxr-jpg/2.0.0/mimic-cxr-2.0.0-metadata_no_LAT.csv.gz") 35 | 36 | base_path = "mimic-cxr-jpg/2.0.0/files/" 37 | output_path = "mimic-cxr-jpg/2.0.0/files_preprocessed/" 38 | 39 | try: 40 | os.mkdir(output_path) 41 | except: 42 | pass 43 | 44 | n = len(df) 45 | i = 0 46 | 47 | with open("mimic_padd_added.csv", '+w') as f: 48 | f.write("filename,height,width,pad_left_rem,pad_top_rem,pad_right_rem,pad_bottom_rem,pad_left,pad_top,pad_right,pad_bottom \n") 49 | 50 | with open("mimic_errors.csv", "+w") as errorf: 51 | for index, row in df.iterrows(): 52 | 53 | if i % 1000 == 0: 54 | print("Processing image %d of %d"%(i, n)) 55 | i+=1 56 | 57 | subject_id = 'p' + str(row['subject_id']) 58 | study_id = 's' + str(row['study_id']) 59 | dicom_id = row['dicom_id'] + '.jpg' 60 | 61 | sub = subject_id[:3] 62 | 63 | try: 64 | os.mkdir(os.path.join(output_path, sub)) 65 | except: 66 | pass 67 | 68 | subject = os.path.join(output_path, sub, subject_id) 69 | 70 | try: 71 | os.mkdir(subject) 72 | except: 73 | pass 74 | 75 | study = os.path.join(subject, study_id) 76 | 77 | try: 78 | os.mkdir(study) 79 | except: 80 | pass 81 | 82 | img_path = os.path.join(base_path, sub, subject_id, study_id, dicom_id) 83 | out_path = os.path.join(output_path, sub, subject_id, study_id, dicom_id) 84 | 85 | try: 86 | img = cv2.imread(img_path, 0) 87 | 88 | h, w = img.shape[:2] 89 | 90 | # remove padding 91 | img2, pad_left, pad_top, pad_right, pad_bottom = remove_padding(img) 92 | reshaped, pad_left2, pad_top2, pad_right2, pad_bottom2 = pad_to_square_centered(img2) 93 | # scale to 1024x1024 94 | scaled = cv2.resize(reshaped, (1024, 1024)) 95 | cv2.imwrite(out_path, scaled) 96 | 97 | del img 98 | del img2 99 | del scaled 100 | 101 | gc.collect() 102 | 103 | image = os.path.join(sub, subject_id, study_id, dicom_id) 104 | 105 | outline = "%s,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d \n"%(image, h, w, pad_left, pad_top, pad_right, pad_bottom, pad_left2, pad_top2, pad_right2, pad_bottom2) 106 | f.write(outline) 107 | 108 | except: 109 | errorf.write(image + " \n") 110 | print("Error with image %s"%image) -------------------------------------------------------------------------------- /DataPreparation/padchest.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | import cv2 4 | import pandas as pd 5 | import gc 6 | 7 | def remove_padding(img): 8 | 9 | gray = 255*(img > 1) # To invert the text to white 10 | coords = cv2.findNonZero(gray) # Find all non-zero points (text) 11 | 12 | x, y, w, h = cv2.boundingRect(coords) # Find minimum spanning bounding box 13 | cropimg = img[y:y+h, x:x+w] # Crop the image - note we do this on the original image 14 | 15 | pad_left, pad_top, pad_right, pad_bottom = x, y, img.shape[1] - x - w, img.shape[0] - y - h 16 | 17 | return cropimg, pad_left, pad_top, pad_right, pad_bottom 18 | 19 | def pad_to_square_centered(img): 20 | h, w = img.shape[:2] 21 | longest_edge = max(h, w) 22 | result = np.zeros((longest_edge, longest_edge), dtype=img.dtype) 23 | pad_h = (longest_edge - h) // 2 24 | pad_w = (longest_edge - w) // 2 25 | result[pad_h:pad_h + h, pad_w:pad_w + w] = img 26 | 27 | pad_left = pad_w 28 | pad_top = pad_h 29 | pad_right = longest_edge - w - pad_w 30 | pad_bottom = longest_edge - h - pad_h 31 | 32 | return result, pad_left, pad_top, pad_right, pad_bottom 33 | 34 | df = pd.read_csv("padchest/PADCHEST_chest_x_ray_images_labels_160K_01.02.19_filtered.csv.gz") 35 | new_csv = pd.DataFrame(columns=['filename', 'width', 'height', 'pad_left', 'pad_top', 'pad_right', 'pad_bottom']) 36 | 37 | images = df.ImageID.values 38 | 39 | del df 40 | gc.collect() 41 | 42 | j = 0 43 | n = len(images) 44 | 45 | print("Total images: %s"%n) 46 | 47 | errors = [] 48 | 49 | 50 | with open("padchest_padd_added.csv", '+w') as f: 51 | f.write("filename,height,width,pad_left_rem,pad_top_rem,pad_right_rem,pad_bottom_rem,pad_left,pad_top,pad_right,pad_bottom \n") 52 | 53 | with open("padchest_errors.csv", "+w") as errorf: 54 | for image in images: 55 | if j % 1000 == 0: 56 | print("Image %s of %s"%(j, n)) 57 | j = j+1 58 | 59 | img_path = os.path.join("padchest/Images", image) 60 | out_path = os.path.join("padchest_preprocessed", image) 61 | 62 | if os.path.isfile(out_path): 63 | continue 64 | 65 | try: 66 | img = cv2.imread(img_path, 0) 67 | h, w = img.shape[:2] 68 | 69 | # remove padding 70 | img2, pad_left, pad_top, pad_right, pad_bottom = remove_padding(img) 71 | reshaped, pad_left2, pad_top2, pad_right2, pad_bottom2 = pad_to_square_centered(img2) 72 | # scale to 1024x1024 73 | scaled = cv2.resize(reshaped, (1024, 1024)) 74 | cv2.imwrite(out_path, scaled) 75 | 76 | del img 77 | del img2 78 | del scaled 79 | 80 | gc.collect() 81 | 82 | outline = "%s,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d \n"%(image, h, w, pad_left, pad_top, pad_right, pad_bottom, pad_left2, pad_top2, pad_right2, pad_bottom2) 83 | f.write(outline) 84 | except: 85 | errorf.write(image + " \n") 86 | print("Error with image %s"%image) 87 | -------------------------------------------------------------------------------- /DataPreparation/vindr-cxr.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pydicom 3 | from pydicom.pixel_data_handlers.util import apply_voi_lut 4 | 5 | def read_xray(path, voi_lut = True, fix_monochrome = True): 6 | dicom = pydicom.read_file(path) 7 | 8 | # VOI LUT (if available by DICOM device) is used to transform raw DICOM data to "human-friendly" view 9 | if voi_lut: 10 | data = apply_voi_lut(dicom.pixel_array, dicom) 11 | else: 12 | data = dicom.pixel_array 13 | 14 | # depending on this value, X-ray may look inverted - fix that: 15 | if fix_monochrome and dicom.PhotometricInterpretation == "MONOCHROME1": 16 | data = np.amax(data) - data 17 | 18 | data = data - np.min(data) 19 | data = data / np.max(data) 20 | data = (data * 255).astype(np.uint8) 21 | 22 | return data 23 | 24 | import pathlib 25 | import re 26 | 27 | def get_dicom_fps(dicom_dir): 28 | dicom_dir = pathlib.Path(dicom_dir) 29 | dicom_fps = list(dicom_dir.glob('*/*.dicom')) 30 | # read all dicom files 31 | dicom_fps = [str(fp) for fp in dicom_fps] 32 | return dicom_fps 33 | 34 | path = "VinBigData/original/" 35 | outpath = "VinBigData/pngs/" 36 | 37 | dicom_fps = get_dicom_fps(path) 38 | print('dicom_fps', len(dicom_fps)) 39 | 40 | # saves metadata in a csv file 41 | import pandas as pd 42 | import cv2 43 | 44 | def pad_to_square_centered(img): 45 | h, w = img.shape[:2] 46 | longest_edge = max(h, w) 47 | result = np.zeros((longest_edge, longest_edge), dtype=img.dtype) 48 | pad_h = (longest_edge - h) // 2 49 | pad_w = (longest_edge - w) // 2 50 | result[pad_h:pad_h + h, pad_w:pad_w + w] = img 51 | 52 | pad_left = pad_w 53 | pad_top = pad_h 54 | pad_right = longest_edge - w - pad_w 55 | pad_bottom = longest_edge - h - pad_h 56 | 57 | return result, pad_left, pad_top, pad_right, pad_bottom 58 | 59 | new_csv = pd.DataFrame(columns=['filename', 'width', 'height', 'pad_left', 'pad_top', 'pad_right', 'pad_bottom']) 60 | 61 | j = 0 62 | for file in dicom_fps: 63 | # print progress 64 | j += 1 65 | if j % 100 == 0: 66 | print(j, '/', len(dicom_fps)) 67 | 68 | data = read_xray(file) 69 | filename = file.replace(path, '') 70 | h, w = data.shape[:2] 71 | 72 | reshaped, pad_left, pad_top, pad_right, pad_bottom = pad_to_square_centered(data) 73 | 74 | # scale to 1024x1024 75 | scaled = cv2.resize(reshaped, (1024, 1024)) 76 | 77 | # save image 78 | file_out = outpath + filename.replace('.dicom', '.png') 79 | cv2.imwrite(file_out, scaled) 80 | new_csv = new_csv.append({'filename': filename, 'width': w, 'height': h, 'pad_left': pad_left, 'pad_top': pad_top, 'pad_right': pad_right, 'pad_bottom': pad_bottom}, ignore_index=True) 81 | 82 | save_csv = outpath + 'paddings.csv' 83 | new_csv.to_csv(save_csv, index=False) -------------------------------------------------------------------------------- /Example/H_landmarks.txt: -------------------------------------------------------------------------------- 1 | 294 694 2 | 275 648 3 | 276 601 4 | 285 555 5 | 304 505 6 | 324 461 7 | 347 436 8 | 374 422 9 | 398 417 10 | 420 417 11 | 443 418 12 | 463 420 13 | 483 439 14 | 508 480 15 | 537 532 16 | 562 580 17 | 582 630 18 | 597 684 19 | 591 728 20 | 563 751 21 | 527 755 22 | 488 758 23 | 445 754 24 | 408 747 25 | 368 739 26 | 328 724 27 | -------------------------------------------------------------------------------- /Example/H_mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaggion/CheXmask-Database/8ae0c338caced10869f8be1860e320146c40d096/Example/H_mask.png -------------------------------------------------------------------------------- /Example/LL_landmarks.txt: -------------------------------------------------------------------------------- 1 | 516 94 2 | 553 104 3 | 589 126 4 | 626 155 5 | 656 192 6 | 676 228 7 | 689 264 8 | 698 302 9 | 707 340 10 | 711 377 11 | 716 413 12 | 719 452 13 | 722 490 14 | 725 525 15 | 728 564 16 | 731 602 17 | 735 639 18 | 737 677 19 | 740 714 20 | 740 751 21 | 737 789 22 | 730 821 23 | 717 800 24 | 696 774 25 | 672 758 26 | 645 746 27 | 614 742 28 | 594 733 29 | 590 707 30 | 587 676 31 | 581 646 32 | 572 615 33 | 560 585 34 | 549 556 35 | 532 527 36 | 514 498 37 | 500 469 38 | 491 439 39 | 482 412 40 | 471 384 41 | 464 359 42 | 463 339 43 | 461 315 44 | 447 294 45 | 428 274 46 | 424 239 47 | 424 198 48 | 428 157 49 | 447 121 50 | 477 99 51 | -------------------------------------------------------------------------------- /Example/LL_mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaggion/CheXmask-Database/8ae0c338caced10869f8be1860e320146c40d096/Example/LL_mask.png -------------------------------------------------------------------------------- /Example/RL_landmarks.txt: -------------------------------------------------------------------------------- 1 | 274 102 2 | 238 107 3 | 200 126 4 | 163 153 5 | 130 187 6 | 106 224 7 | 90 263 8 | 80 300 9 | 73 337 10 | 66 374 11 | 63 411 12 | 62 450 13 | 62 487 14 | 62 524 15 | 59 560 16 | 57 599 17 | 54 635 18 | 49 671 19 | 48 709 20 | 49 745 21 | 53 784 22 | 61 819 23 | 80 795 24 | 108 766 25 | 138 744 26 | 170 730 27 | 202 720 28 | 233 714 29 | 265 710 30 | 291 702 31 | 285 656 32 | 276 611 33 | 280 569 34 | 296 526 35 | 311 484 36 | 319 442 37 | 327 400 38 | 333 357 39 | 343 315 40 | 348 273 41 | 351 230 42 | 351 188 43 | 342 147 44 | 313 112 45 | -------------------------------------------------------------------------------- /Example/RL_mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaggion/CheXmask-Database/8ae0c338caced10869f8be1860e320146c40d096/Example/RL_mask.png -------------------------------------------------------------------------------- /Example/utils_example1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaggion/CheXmask-Database/8ae0c338caced10869f8be1860e320146c40d096/Example/utils_example1.jpg -------------------------------------------------------------------------------- /HybridGNet/inferenceWithHybridGNet.py: -------------------------------------------------------------------------------- 1 | from models.HybridGNet2IGSC import Hybrid 2 | 3 | import os 4 | import numpy as np 5 | from torchvision import transforms 6 | import torch 7 | 8 | from utils.utils import scipy_to_torch_sparse, genMatrixesLungsHeart 9 | import scipy.sparse as sp 10 | 11 | import cv2 12 | import pathlib 13 | import re 14 | 15 | def natural_key(string_): 16 | """See http://www.codinghorror.com/blog/archives/001018.html""" 17 | return [int(s) if s.isdigit() else s for s in re.split(r'(\d+)', string_)] 18 | 19 | 20 | 21 | def getDenseMask(RL, LL, H): 22 | img = np.zeros([1024,1024], dtype = 'uint8') 23 | 24 | RL = RL.reshape(-1, 1, 2).astype('int') 25 | LL = LL.reshape(-1, 1, 2).astype('int') 26 | H = H.reshape(-1, 1, 2).astype('int') 27 | 28 | img = cv2.drawContours(img, [RL], -1, 1, -1) 29 | img = cv2.drawContours(img, [LL], -1, 1, -1) 30 | img = cv2.drawContours(img, [H], -1, 2, -1) 31 | 32 | return img 33 | 34 | if __name__ == "__main__": 35 | device = "cuda:0" 36 | 37 | A, AD, D, U = genMatrixesLungsHeart() 38 | N1 = A.shape[0] 39 | N2 = AD.shape[0] 40 | 41 | A = sp.csc_matrix(A).tocoo() 42 | AD = sp.csc_matrix(AD).tocoo() 43 | D = sp.csc_matrix(D).tocoo() 44 | U = sp.csc_matrix(U).tocoo() 45 | 46 | D_ = [D.copy()] 47 | U_ = [U.copy()] 48 | 49 | config = {} 50 | 51 | config['n_nodes'] = [N1, N1, N1, N2, N2, N2] 52 | A_ = [A.copy(), A.copy(), A.copy(), AD.copy(), AD.copy(), AD.copy()] 53 | A_t, D_t, U_t = ([scipy_to_torch_sparse(x).to('cuda:0') for x in X] for X in (A_, D_, U_)) 54 | 55 | config['latents'] = 64 56 | config['inputsize'] = 1024 57 | 58 | f = 32 59 | config['filters'] = [2, f, f, f, f//2, f//2, f//2] 60 | config['skip_features'] = f 61 | 62 | hybrid = Hybrid(config.copy(), D_t, U_t, A_t).to(device) 63 | hybrid.load_state_dict(torch.load("../Weights/SegmentationModel/bestMSE.pt")) 64 | hybrid.eval() 65 | print('Model loaded') 66 | 67 | folder = '../Datasets/Padchest/Images' 68 | data_root = pathlib.Path(folder) 69 | all_files = list(data_root.glob('*.png')) 70 | all_files = [str(path) for path in all_files] 71 | all_files.sort(key = natural_key) 72 | 73 | contador = 0 74 | with torch.no_grad(): 75 | for image in all_files: 76 | print('\r',contador+1,'of', len(all_files),end='') 77 | 78 | image_name = all_files[contador].split('/')[-1] 79 | image_path = os.path.join('../Datasets/Padchest/Output', image_name).replace('.png', '.txt') 80 | 81 | img = cv2.imread(image, 0) / 255.0 82 | 83 | data = torch.from_numpy(img).unsqueeze(0).unsqueeze(0).to(device).float() 84 | 85 | output = hybrid(data) 86 | if len(output) > 1: 87 | output = output[0] 88 | output = output.cpu().numpy().reshape(-1, 2) * 1024 89 | output = output.round().astype('int') 90 | 91 | np.savetxt(image_path, output, fmt='%i', delimiter=' ') 92 | 93 | contador+=1 94 | 95 | RL = output[:44] 96 | LL = output[44:94] 97 | H = output[94:] 98 | outseg = getDenseMask(RL, LL, H) 99 | 100 | cv2.imwrite(image_path.replace('.txt', '_mask.png').replace('Output', 'Masks'), outseg) -------------------------------------------------------------------------------- /HybridGNet/models/HybridGNet2IGSC.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | from models.modelUtils import ChebConv, Pool, residualBlock 5 | import torchvision.ops.roi_align as roi_align 6 | 7 | import numpy as np 8 | 9 | class EncoderConv(nn.Module): 10 | def __init__(self, latents = 64, hw = 32): 11 | super(EncoderConv, self).__init__() 12 | 13 | self.latents = latents 14 | self.c = 4 15 | 16 | self.size = self.c * np.array([2,4,8,16,32], dtype = np.intc) 17 | 18 | self.maxpool = nn.MaxPool2d(2) 19 | 20 | self.dconv_down1 = residualBlock(1, self.size[0]) 21 | self.dconv_down2 = residualBlock(self.size[0], self.size[1]) 22 | self.dconv_down3 = residualBlock(self.size[1], self.size[2]) 23 | self.dconv_down4 = residualBlock(self.size[2], self.size[3]) 24 | self.dconv_down5 = residualBlock(self.size[3], self.size[4]) 25 | self.dconv_down6 = residualBlock(self.size[4], self.size[4]) 26 | 27 | self.fc_mu = nn.Linear(in_features=self.size[4]*hw*hw, out_features=self.latents) 28 | self.fc_logvar = nn.Linear(in_features=self.size[4]*hw*hw, out_features=self.latents) 29 | 30 | def forward(self, x): 31 | x = self.dconv_down1(x) 32 | x = self.maxpool(x) 33 | 34 | x = self.dconv_down2(x) 35 | x = self.maxpool(x) 36 | 37 | conv3 = self.dconv_down3(x) 38 | x = self.maxpool(conv3) 39 | 40 | conv4 = self.dconv_down4(x) 41 | x = self.maxpool(conv4) 42 | 43 | conv5 = self.dconv_down5(x) 44 | x = self.maxpool(conv5) 45 | 46 | conv6 = self.dconv_down6(x) 47 | 48 | x = conv6.view(conv6.size(0), -1) # flatten batch of multi-channel feature maps to a batch of feature vectors 49 | 50 | x_mu = self.fc_mu(x) 51 | x_logvar = self.fc_logvar(x) 52 | 53 | return x_mu, x_logvar, conv6, conv5 54 | 55 | 56 | class SkipBlock(nn.Module): 57 | def __init__(self, in_filters, window): 58 | super(SkipBlock, self).__init__() 59 | 60 | self.window = window 61 | self.graphConv_pre = ChebConv(in_filters, 2, 1, bias = False) 62 | 63 | def lookup(self, pos, layer, salida = (1,1)): 64 | B = pos.shape[0] 65 | N = pos.shape[1] 66 | F = layer.shape[1] 67 | h = layer.shape[-1] 68 | 69 | ## Scale from [0,1] to [0, h] 70 | pos = pos * h 71 | 72 | _x1 = (self.window[0] // 2) * 1.0 73 | _x2 = (self.window[0] // 2 + 1) * 1.0 74 | _y1 = (self.window[1] // 2) * 1.0 75 | _y2 = (self.window[1] // 2 + 1) * 1.0 76 | 77 | boxes = [] 78 | for batch in range(0, B): 79 | x1 = pos[batch,:,0].reshape(-1, 1) - _x1 80 | x2 = pos[batch,:,0].reshape(-1, 1) + _x2 81 | y1 = pos[batch,:,1].reshape(-1, 1) - _y1 82 | y2 = pos[batch,:,1].reshape(-1, 1) + _y2 83 | 84 | aux = torch.cat([x1, y1, x2, y2], axis = 1) 85 | boxes.append(aux) 86 | 87 | skip = roi_align(layer, boxes, output_size = salida, aligned=True) 88 | vista = skip.view([B, N, -1]) 89 | 90 | return vista 91 | 92 | def forward(self, x, adj, conv_layer): 93 | pos = self.graphConv_pre(x, adj) 94 | skip = self.lookup(pos, conv_layer) 95 | 96 | return torch.cat((x, skip, pos), axis = 2), pos 97 | 98 | 99 | class Hybrid(nn.Module): 100 | def __init__(self, config, downsample_matrices, upsample_matrices, adjacency_matrices): 101 | super(Hybrid, self).__init__() 102 | 103 | self.config = config 104 | hw = config['inputsize'] // 32 105 | self.z = config['latents'] 106 | self.encoder = EncoderConv(latents = self.z, hw = hw) 107 | 108 | self.downsample_matrices = downsample_matrices 109 | self.upsample_matrices = upsample_matrices 110 | self.adjacency_matrices = adjacency_matrices 111 | self.kld_weight = 1e-5 112 | 113 | n_nodes = config['n_nodes'] 114 | self.filters = config['filters'] 115 | self.K = 6 116 | self.window = (3,3) 117 | 118 | # Genero la capa fully connected del decoder 119 | outshape = self.filters[-1] * n_nodes[-1] 120 | self.dec_lin = torch.nn.Linear(self.z, outshape) 121 | 122 | self.normalization2u = torch.nn.InstanceNorm1d(self.filters[1]) 123 | self.normalization3u = torch.nn.InstanceNorm1d(self.filters[2]) 124 | self.normalization4u = torch.nn.InstanceNorm1d(self.filters[3]) 125 | self.normalization5u = torch.nn.InstanceNorm1d(self.filters[4]) 126 | self.normalization6u = torch.nn.InstanceNorm1d(self.filters[5]) 127 | 128 | outsize1 = self.encoder.size[4] 129 | outsize2 = self.encoder.size[4] 130 | 131 | # Guardo las capas de convoluciones en grafo 132 | self.graphConv_up6 = ChebConv(self.filters[6], self.filters[5], self.K) 133 | self.graphConv_up5 = ChebConv(self.filters[5], self.filters[4], self.K) 134 | 135 | self.SC_1 = SkipBlock(self.filters[4], self.window) 136 | 137 | self.graphConv_up4 = ChebConv(self.filters[4] + outsize1 + 2, self.filters[3], self.K) 138 | self.graphConv_up3 = ChebConv(self.filters[3], self.filters[2], self.K) 139 | 140 | self.SC_2 = SkipBlock(self.filters[2], self.window) 141 | 142 | self.graphConv_up2 = ChebConv(self.filters[2] + outsize2 + 2, self.filters[1], self.K) 143 | self.graphConv_up1 = ChebConv(self.filters[1], self.filters[0], 1, bias = False) 144 | 145 | self.pool = Pool() 146 | 147 | self.reset_parameters() 148 | 149 | def reset_parameters(self): 150 | torch.nn.init.normal_(self.dec_lin.weight, 0, 0.1) 151 | 152 | 153 | def sampling(self, mu, log_var): 154 | std = torch.exp(0.5*log_var) 155 | eps = torch.randn_like(std) 156 | return eps.mul(std).add_(mu) 157 | 158 | 159 | def forward(self, x): 160 | self.mu, self.log_var, conv6, conv5 = self.encoder(x) 161 | 162 | if self.training: 163 | z = self.sampling(self.mu, self.log_var) 164 | else: 165 | z = self.mu 166 | 167 | x = self.dec_lin(z) 168 | x = F.relu(x) 169 | 170 | x = x.reshape(x.shape[0], -1, self.filters[-1]) 171 | 172 | x = self.graphConv_up6(x, self.adjacency_matrices[5]._indices()) 173 | x = self.normalization6u(x) 174 | x = F.relu(x) 175 | 176 | x = self.graphConv_up5(x, self.adjacency_matrices[4]._indices()) 177 | x = self.normalization5u(x) 178 | x = F.relu(x) 179 | 180 | x, pos1 = self.SC_1(x, self.adjacency_matrices[3]._indices(), conv6) 181 | 182 | x = self.graphConv_up4(x, self.adjacency_matrices[3]._indices()) 183 | x = self.normalization4u(x) 184 | x = F.relu(x) 185 | 186 | x = self.pool(x, self.upsample_matrices[0]) 187 | 188 | x = self.graphConv_up3(x, self.adjacency_matrices[2]._indices()) 189 | x = self.normalization3u(x) 190 | x = F.relu(x) 191 | 192 | x, pos2 = self.SC_2(x, self.adjacency_matrices[1]._indices(), conv5) 193 | 194 | x = self.graphConv_up2(x, self.adjacency_matrices[1]._indices()) 195 | x = self.normalization2u(x) 196 | x = F.relu(x) 197 | 198 | x = self.graphConv_up1(x, self.adjacency_matrices[0]._indices()) # Sin relu y sin bias 199 | 200 | return x, pos1, pos2 -------------------------------------------------------------------------------- /HybridGNet/models/modelUtils.py: -------------------------------------------------------------------------------- 1 | from torch_geometric.nn.conv import MessagePassing 2 | from torch_geometric.nn.conv.cheb_conv import ChebConv 3 | from torch_geometric.nn.inits import zeros, normal 4 | 5 | # We change the default initialization from zeros to a normal distribution 6 | class ChebConv(ChebConv): 7 | def reset_parameters(self): 8 | for lin in self.lins: 9 | normal(lin, mean = 0, std = 0.1) 10 | #lin.reset_parameters() 11 | normal(self.bias, mean = 0, std = 0.1) 12 | #zeros(self.bias) 13 | 14 | # Pooling from COMA: https://github.com/pixelite1201/pytorch_coma/blob/master/layers.py 15 | class Pool(MessagePassing): 16 | def __init__(self): 17 | # source_to_target is the default value for flow, but is specified here for explicitness 18 | super(Pool, self).__init__(flow='source_to_target') 19 | 20 | def forward(self, x, pool_mat, dtype=None): 21 | pool_mat = pool_mat.transpose(0, 1) 22 | out = self.propagate(edge_index=pool_mat._indices(), x=x, norm=pool_mat._values(), size=pool_mat.size()) 23 | return out 24 | 25 | def message(self, x_j, norm): 26 | return norm.view(1, -1, 1) * x_j 27 | 28 | 29 | import torch.nn as nn 30 | import torch.nn.functional as F 31 | 32 | class residualBlock(nn.Module): 33 | def __init__(self, in_channels, out_channels, stride=1): 34 | """ 35 | Args: 36 | in_channels (int): Number of input channels. 37 | out_channels (int): Number of output channels. 38 | stride (int): Controls the stride. 39 | """ 40 | super(residualBlock, self).__init__() 41 | 42 | self.skip = nn.Sequential() 43 | 44 | if stride != 1 or in_channels != out_channels: 45 | self.skip = nn.Sequential( 46 | nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=1, stride=stride, bias=False), 47 | nn.BatchNorm2d(out_channels, track_running_stats=False)) 48 | else: 49 | self.skip = None 50 | 51 | self.block = nn.Sequential(nn.BatchNorm2d(in_channels, track_running_stats=False), 52 | nn.ReLU(inplace=True), 53 | nn.Conv2d(in_channels, out_channels, 3, padding=1), 54 | nn.BatchNorm2d(out_channels, track_running_stats=False), 55 | nn.ReLU(inplace=True), 56 | nn.Conv2d(out_channels, out_channels, 3, padding=1) 57 | ) 58 | 59 | def forward(self, x): 60 | identity = x 61 | out = self.block(x) 62 | 63 | if self.skip is not None: 64 | identity = self.skip(x) 65 | 66 | out += identity 67 | out = F.relu(out) 68 | 69 | return out -------------------------------------------------------------------------------- /HybridGNet/trainHybridGNetOnDataset.py: -------------------------------------------------------------------------------- 1 | import os 2 | import torch 3 | import torch.nn.functional as F 4 | import argparse 5 | import random 6 | 7 | from torch.optim.lr_scheduler import StepLR 8 | from torch.utils.tensorboard import SummaryWriter 9 | from torchvision import transforms 10 | 11 | import scipy.sparse as sp 12 | import numpy as np 13 | import pandas as pd 14 | 15 | from utils.utils import scipy_to_torch_sparse, genMatrixesLungs, genMatrixesLungsHeart, CrossVal 16 | from utils.dataset_for_train import LandmarksDataset, ToTensor 17 | 18 | from models.modelUtils import Pool 19 | from models.HybridGNet2IGSC import Hybrid 20 | import time 21 | 22 | def trainer(train_dataset, val_dataset, model, config): 23 | torch.manual_seed(420) 24 | 25 | device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 26 | print(device) 27 | 28 | model = model.to(device) 29 | 30 | train_loader = torch.utils.data.DataLoader(train_dataset, batch_size = config['batch_size'], 31 | shuffle = True, num_workers = 8, drop_last = True) 32 | 33 | optimizer = torch.optim.Adam(params = model.parameters(), lr = config['lr'], weight_decay = config['weight_decay']) 34 | 35 | train_loss_avg = [] 36 | train_seg_loss_avg = [] 37 | train_kld_loss_avg = [] 38 | val_loss_avg = [] 39 | 40 | tensorboard = "Training/RCA_Dataset" 41 | 42 | folder = os.path.join(tensorboard, config['name']) 43 | 44 | try: 45 | os.mkdir(folder) 46 | except: 47 | pass 48 | 49 | writer = SummaryWriter(log_dir = folder) 50 | 51 | best = 1e12 52 | 53 | print('Training ...') 54 | 55 | scheduler = StepLR(optimizer, step_size=config['stepsize'], gamma=config['gamma']) 56 | pool = Pool() 57 | 58 | iterator = train_loader.__iter__() 59 | len_train = len(train_loader) 60 | count = 0 61 | 62 | for epoch in range(config['epochs']): 63 | model.train() 64 | 65 | train_loss_avg.append(0) 66 | train_seg_loss_avg.append(0) 67 | train_kld_loss_avg.append(0) 68 | num_batches = 0 69 | 70 | t_1 = time.time() 71 | 72 | for j in range(0, 500): 73 | 74 | if count % len_train == 0: 75 | iterator = train_loader.__iter__() 76 | count = 0 77 | 78 | sample_1 = iterator.__next__() 79 | count += 1 80 | 81 | image_1, target_1 = sample_1['image'].to(device), sample_1['landmarks'].to(device) 82 | 83 | out_1 = model(image_1) 84 | 85 | optimizer.zero_grad() 86 | 87 | target_down_1 = pool(target_1, model.downsample_matrices[0]) 88 | ts = target_1.shape[1] 89 | ts_d= target_down_1.shape[1] 90 | 91 | out, pre1, pre2 = out_1 92 | 93 | pre1loss = F.mse_loss(pre1[:,:ts_d,:], target_down_1[:,:,:2]) 94 | pre2loss = F.mse_loss(pre2[:,:ts,:], target_1[:,:,:2]) 95 | outloss = F.mse_loss(out[:,:ts,:], target_1[:,:,:2]) 96 | 97 | loss = outloss + pre1loss + pre2loss 98 | 99 | # KLD loss 100 | kld_loss = -0.5 * torch.mean(torch.mean(1 + model.log_var - model.mu ** 2 - model.log_var.exp(), dim=1), dim=0) 101 | 102 | # Total loss 103 | loss = loss + 1e-5 * kld_loss 104 | 105 | train_seg_loss_avg[-1] += outloss.item() 106 | train_kld_loss_avg[-1] += kld_loss.item() 107 | train_loss_avg[-1] += loss.item() 108 | 109 | loss.backward() 110 | 111 | # one step of the optmizer (using the gradients from backpropagation) 112 | optimizer.step() 113 | 114 | num_batches += 1 115 | 116 | if j % 100 == 0: 117 | t_2 = time.time() 118 | print('Epoch [%d / %d] Batch [%d / %d] train segmentation error: %f' % (epoch+1, config['epochs'], j, 500, outloss.item()*1024*1024)) 119 | print('Time since epoch beggining: %f' % (t_2 - t_1)) 120 | 121 | train_loss_avg[-1] /= num_batches 122 | train_seg_loss_avg[-1] /= num_batches 123 | train_kld_loss_avg[-1] /= num_batches 124 | 125 | print('Epoch [%d / %d] train average segmentation error: %f' % (epoch+1, config['epochs'], train_seg_loss_avg[-1]*1024*1024)) 126 | print('Epoch [%d / %d] train average kld error: %f' % (epoch+1, config['epochs'], train_kld_loss_avg[-1]*1024*1024)) 127 | 128 | t_2 = time.time() 129 | print('Total epoch time: %f' % (t_2 - t_1)) 130 | 131 | num_batches = 0 132 | 133 | writer.add_scalar('Train/Loss', train_loss_avg[-1], epoch) 134 | writer.add_scalar('Train/Seg MSE', train_seg_loss_avg[-1] * 1024 * 1024, epoch) 135 | writer.add_scalar('Train/KLD', train_kld_loss_avg[-1], epoch) 136 | 137 | scheduler.step() 138 | 139 | torch.save(model.state_dict(), os.path.join(folder, "final.pt")) 140 | 141 | 142 | if __name__ == "__main__": 143 | parser = argparse.ArgumentParser() 144 | 145 | parser.add_argument("--model", default = "HybridGNet", type=str) 146 | parser.add_argument("--epochs", default = 200, type = int) 147 | parser.add_argument("--lr", default = 1e-4, type = float) 148 | parser.add_argument("--stepsize", default = 1, type = int) 149 | parser.add_argument("--gamma", default = 0.99, type = float) 150 | 151 | ## 5-fold Cross validation fold 152 | parser.add_argument("--fold", default = 1, type = int) 153 | 154 | # Number of filters at low resolution for HybridGNet 155 | parser.add_argument("--f", default = 32, type=int) 156 | 157 | parser.add_argument("--dataset", default = "CANDID", type=str) 158 | 159 | config = parser.parse_args() 160 | config = vars(config) 161 | 162 | print('Organs: Lungs and Heart') 163 | A, AD, D, U = genMatrixesLungsHeart() 164 | 165 | if config['dataset'] == "CANDID-PTX": 166 | df = pd.read_csv("../Annotations/CANDID-PTX.csv") 167 | elif config['dataset'] == "ChestX-Ray8": 168 | df = pd.read_csv('../Annotations/ChestX-Ray8.csv') 169 | elif config['dataset'] == "CheXpert": 170 | df = pd.read_csv('..-Annotations/CheXpert.csv') 171 | elif config['dataset'] == "MIMIC-CXR-JPG": 172 | df = pd.read_csv('../Annotations/MIMIC-CXR-JPG.csv') 173 | elif config['dataset'] == "Padchest": 174 | df = pd.read_csv('../Annotations/Padchest.csv') 175 | elif config['dataset'] == "VinDr-CXR": 176 | df = pd.read_csv('../Annotations/VinDr-CXR.csv') 177 | else: 178 | raise ValueError("Dataset not supported, please choose between CANDID-PTX, ChestX-Ray8, CheXpert, MIMIC-CXR-JPG, Padchest or VinDr-CXR") 179 | 180 | config['name'] = config['dataset'] 181 | 182 | images = df.iloc[:,0].values 183 | 184 | # SET UP THE LANDMARK DATASET TO INCORPORATE THE PATH TO YOUR IMAGES 185 | train_dataset = LandmarksDataset(images=images, 186 | transform = ToTensor() 187 | ) 188 | 189 | val_dataset = None 190 | 191 | config['latents'] = 64 192 | config['batch_size'] = 4 193 | config['val_batch_size'] = 1 194 | config['weight_decay'] = 1e-5 195 | config['inputsize'] = 1024 196 | 197 | config['device'] = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 198 | 199 | print('Model: HybrigGNet') 200 | 201 | f = int(config['f']) 202 | print(f, 'filters') 203 | config['filters'] = [2, f, f, f, f//2, f//2, f//2] 204 | 205 | N1 = A.shape[0] 206 | N2 = AD.shape[0] 207 | 208 | A = sp.csc_matrix(A).tocoo() 209 | AD = sp.csc_matrix(AD).tocoo() 210 | D = sp.csc_matrix(D).tocoo() 211 | U = sp.csc_matrix(U).tocoo() 212 | 213 | D_ = [D.copy()] 214 | U_ = [U.copy()] 215 | 216 | config['n_nodes'] = [N1, N1, N1, N2, N2, N2] 217 | A_ = [A.copy(), A.copy(), A.copy(), AD.copy(), AD.copy(), AD.copy()] 218 | A_t, D_t, U_t = ([scipy_to_torch_sparse(x).to('cuda:0') for x in X] for X in (A_, D_, U_)) 219 | 220 | model = Hybrid(config, D_t, U_t, A_t) 221 | 222 | trainer(train_dataset, val_dataset, model, config) -------------------------------------------------------------------------------- /HybridGNet/utils/dataset_for_train.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import torch 4 | from skimage import io, transform 5 | 6 | import numpy as np 7 | from torch.utils.data import Dataset, DataLoader 8 | from torchvision import transforms 9 | import cv2 10 | 11 | class LandmarksDataset(Dataset): 12 | def __init__(self, images, dataset_name, transform=None): 13 | 14 | self.images = images 15 | self.transform = transform 16 | self.dataset_name = dataset_name 17 | 18 | def __len__(self): 19 | return len(self.images) 20 | 21 | def __getitem__(self, idx): 22 | if torch.is_tensor(idx): 23 | idx = idx.tolist() 24 | 25 | img_id = self.images[idx] 26 | 27 | ### SET UP THE PATHS TO THE IMAGES AND THE LANDMARKS 28 | ### The recommendation is to export the landmarks to a txt file with the same name as the image 29 | ### This will use less RAM than having the full landmarks set in memory 30 | 31 | if self.dataset_name == "Padchest": 32 | base_path = "../Datasets/PadChest/Images/" 33 | base_landmark_path = "../Datasets/PadChest/Output/" 34 | 35 | raise Exception("Please complete the paths for the dataset images and landmarks") 36 | 37 | img_path = os.path.join(base_path, img_id) 38 | landmark_path = os.path.join(base_landmark_path, img_id.replace('.png', '.txt')) 39 | 40 | image = cv2.imread(img_path, 0).astype('float') / 255.0 41 | image = np.expand_dims(image, axis=2) 42 | 43 | landmarks = np.loadtxt(landmark_path, delimiter = ' ') 44 | landmarks = np.clip(landmarks, 0, 1024) 45 | 46 | sample = {'image': image, 'landmarks': landmarks} 47 | 48 | if self.transform: 49 | sample = self.transform(sample) 50 | 51 | return sample 52 | 53 | 54 | def adjust_gamma(image, gamma=1.0): 55 | # build a lookup table mapping the pixel values [0, 255] to 56 | # their adjusted gamma values 57 | invGamma = 1.0 / gamma 58 | table = np.array([((i / 255.0) ** invGamma) * 255 59 | for i in np.arange(0, 256)]).astype("uint8") 60 | 61 | # apply gamma correction using the lookup table 62 | return np.float32(cv2.LUT(image.astype('uint8'), table)) 63 | 64 | class AugColor(object): 65 | def __init__(self, gammaFactor): 66 | self.gammaf = gammaFactor 67 | 68 | def __call__(self, sample): 69 | image, landmarks = sample['image'], sample['landmarks'] 70 | 71 | # Gamma 72 | gamma = np.random.uniform(1 - self.gammaf, 1 + self.gammaf / 2) 73 | 74 | image = adjust_gamma(image * 255, gamma) / 255 75 | 76 | # Adds a little noise 77 | image = image + np.random.normal(0, 1/128, image.shape) 78 | 79 | return {'image': image, 'landmarks': landmarks} 80 | 81 | 82 | class ToTensor(object): 83 | """Convert ndarrays in sample to Tensors.""" 84 | 85 | def __call__(self, sample): 86 | image, landmarks = sample['image'], sample['landmarks'] 87 | 88 | # swap color axis because 89 | # numpy image: H x W x C 90 | # torch image: C X H X W 91 | 92 | h, w = image.shape[:2] 93 | 94 | if len(image.shape) == 2: 95 | image = np.expand_dims(image, 0) 96 | else: 97 | image = np.transpose(image, (2, 0, 1)) 98 | 99 | landmarks[:,0] /= w 100 | landmarks[:,1] /= h 101 | 102 | # concatenate a vectors of 1 to the landmarks 103 | landmarks = np.concatenate((landmarks, np.ones((landmarks.shape[0], 1))), axis=1) 104 | 105 | return {'image': torch.from_numpy(image).float(), 106 | 'landmarks': torch.from_numpy(landmarks).float()} 107 | 108 | 109 | class Rotate(object): 110 | def __init__(self, angle): 111 | self.angle = angle 112 | 113 | def __call__(self, sample): 114 | image, landmarks = sample['image'], sample['landmarks'] 115 | 116 | # Get a random angle on a normal distribution, with the given standard deviation 117 | angle = np.random.normal(0, self.angle / 3) 118 | 119 | # Compute the padding size based on the image diagonal length 120 | h, w = image.shape[:2] 121 | diagonal = int(np.ceil(np.sqrt(h ** 2 + w ** 2))) 122 | pad_x = (diagonal - w) // 2 123 | pad_y = (diagonal - h) // 2 124 | 125 | # Pad the image 126 | padded_image = cv2.copyMakeBorder(image, pad_y, pad_y, pad_x, pad_x, cv2.BORDER_CONSTANT, value=0) 127 | padded_h, padded_w = padded_image.shape[:2] 128 | 129 | # Rotate the padded image 130 | center = (padded_w // 2, padded_h // 2) 131 | rotation_matrix = cv2.getRotationMatrix2D(center, angle, 1) 132 | rotated_image = cv2.warpAffine(padded_image, rotation_matrix, (padded_w, padded_h)) 133 | 134 | # Compute the rotated landmarks 135 | landmarks += np.array([pad_x, pad_y]) # Account for padding 136 | ones = np.ones(shape=(len(landmarks), 1)) 137 | landmarks_hom = np.hstack([landmarks, ones]) 138 | rotated_landmarks = np.dot(rotation_matrix, landmarks_hom.T).T 139 | 140 | # Calculate the bounding box that includes all the landmarks 141 | x_min, y_min = np.min(rotated_landmarks, axis=0).astype(int) 142 | x_max, y_max = np.max(rotated_landmarks, axis=0).astype(int) 143 | 144 | extra_width = 1024 - (x_max - x_min) 145 | extra_height = 1024 - (y_max - y_min) 146 | 147 | if extra_width > 0: 148 | x_min -= extra_width // 2 149 | x_max += extra_width // 2 + extra_width % 2 150 | 151 | if extra_height > 0: 152 | y_min -= extra_height // 2 153 | y_max += extra_height // 2 + extra_height % 2 154 | 155 | # Check if the bounding box is square, if it is not, make it square and centered 156 | 157 | if (x_max - x_min) > (y_max - y_min): 158 | extra = (x_max - x_min) - (y_max - y_min) 159 | y_min -= extra // 2 160 | y_max += extra // 2 + extra % 2 161 | else: 162 | extra = (y_max - y_min) - (x_max - x_min) 163 | x_min -= extra // 2 164 | x_max += extra // 2 + extra % 2 165 | 166 | # Crop the rotated image to the bounding box size while keeping all landmarks 167 | image = rotated_image[y_min:y_max, x_min:x_max] 168 | landmarks = rotated_landmarks - np.array([x_min, y_min]) 169 | 170 | h, w = image.shape[:2] 171 | if h != w: 172 | print("Image error in rotation", "shape", image.shape) 173 | raise ValueError('Image is not square') 174 | 175 | if h > 1024: 176 | image = cv2.resize(image, (1024, 1024)) 177 | landmarks *= 1024 / h 178 | 179 | return {'image': image, 'landmarks': landmarks} 180 | 181 | 182 | class RandomScale(object): 183 | def __call__(self, sample): 184 | image, landmarks = sample['image'], sample['landmarks'] 185 | 186 | # Pongo limites para evitar que los landmarks salgan del contorno 187 | min_x = np.min(landmarks[:,0]) 188 | max_x = np.max(landmarks[:,0]) 189 | ancho = max_x - min_x 190 | 191 | min_y = np.min(landmarks[:,1]) 192 | max_y = np.max(landmarks[:,1]) 193 | alto = max_y - min_y 194 | 195 | max_var_x = 1024 / ancho 196 | max_var_y = 1024 / alto 197 | 198 | min_var_x = 0.80 199 | min_var_y = 0.80 200 | 201 | varx = np.random.uniform(min_var_x, max_var_x) 202 | vary = np.random.uniform(min_var_x, max_var_y) 203 | 204 | landmarks[:,0] = landmarks[:,0] * varx 205 | landmarks[:,1] = landmarks[:,1] * vary 206 | 207 | h, w = image.shape[:2] 208 | new_h = np.round(h * vary).astype('int') 209 | new_w = np.round(w * varx).astype('int') 210 | 211 | img = transform.resize(image, (new_h, new_w)) 212 | 213 | # Cropeo o padeo aleatoriamente 214 | min_x = np.round(np.min(landmarks[:,0])).astype('int') 215 | max_x = np.round(np.max(landmarks[:,0])).astype('int') 216 | 217 | min_y = np.round(np.min(landmarks[:,1])).astype('int') 218 | max_y = np.round(np.max(landmarks[:,1])).astype('int') 219 | 220 | if new_h > 1024: 221 | rango = 1024 - (max_y - min_y) 222 | maxl0y = new_h - 1025 223 | 224 | if rango > 0 and min_y > 0: 225 | l0y = min_y - np.random.randint(0, min(rango, min_y)) 226 | l0y = min(maxl0y, l0y) 227 | else: 228 | l0y = min_y 229 | 230 | l1y = l0y + 1024 231 | 232 | img = img[l0y:l1y,:] 233 | landmarks[:,1] -= l0y 234 | 235 | elif new_h < 1024: 236 | pad = h - new_h 237 | p0 = np.random.randint(np.floor(pad/4), np.ceil(3*pad/4)) 238 | p1 = pad - p0 239 | 240 | img = np.pad(img, ((p0, p1), (0, 0)), mode='constant', constant_values=0) 241 | landmarks[:,1] += p0 242 | 243 | if new_w > 1024: 244 | rango = 1024 - (max_x - min_x) 245 | maxl0x = new_w - 1025 246 | 247 | if rango > 0 and min_x > 0: 248 | l0x = min_x - np.random.randint(0, min(rango, min_x)) 249 | l0x = min(maxl0x, l0x) 250 | else: 251 | l0x = min_x 252 | 253 | l1x = l0x + 1024 254 | 255 | img = img[:, l0x:l1x] 256 | landmarks[:,0] -= l0x 257 | 258 | elif new_w < 1024: 259 | pad = w - new_w 260 | p0 = np.random.randint(np.floor(pad/4), np.ceil(3*pad/4)) 261 | p1 = pad - p0 262 | 263 | img = np.pad(img, ((0, 0), (p0, p1)), mode='constant', constant_values=0) 264 | landmarks[:,0] += p0 265 | 266 | if img.shape[0] != 1024 or img.shape[1] != 1024: 267 | print('Original', [new_h,new_w]) 268 | print('Salida', img.shape) 269 | raise Exception('Error') 270 | 271 | 272 | return {'image': img, 'landmarks': landmarks} -------------------------------------------------------------------------------- /HybridGNet/utils/utils.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import cv2 3 | import os 4 | import matplotlib.pyplot as plt 5 | 6 | def draw_organ(ax, array, color = 'b'): 7 | N = array.shape[0] 8 | for i in range(0, N): 9 | x, y = array[i,:] 10 | circ = plt.Circle((x, y), radius=3, color=color, fill = True) 11 | ax.add_patch(circ) 12 | return 13 | 14 | def draw_lines(ax, array, color = 'b'): 15 | N = array.shape[0] 16 | for i in range(0, N): 17 | x1, y1 = array[i-1,:] 18 | x2, y2 = array[i,:] 19 | ax.plot([x1, x2], [y1, y2], color=color, linestyle='-', linewidth=1) 20 | return 21 | 22 | def drawOrgans(RL, LL, H = None, img = None, ax = None): 23 | if ax is None: 24 | fig, ax = plt.subplots() 25 | 26 | if img is not None: 27 | plt.imshow(img, cmap='gray') 28 | else: 29 | img = np.zeros([1024, 1024]) 30 | plt.imshow(img) 31 | 32 | plt.axis('off') 33 | 34 | draw_lines(ax, RL, 'r') 35 | draw_lines(ax, LL, 'g') 36 | 37 | draw_organ(ax, RL, 'r') 38 | draw_organ(ax, LL, 'g') 39 | 40 | if H is not None: 41 | draw_lines(ax, H, 'y') 42 | draw_organ(ax, H, 'y') 43 | 44 | return 45 | 46 | import scipy.sparse as sp 47 | import torch 48 | 49 | def scipy_to_torch_sparse(scp_matrix): 50 | values = scp_matrix.data 51 | indices = np.vstack((scp_matrix.row, scp_matrix.col)) 52 | i = torch.LongTensor(indices) 53 | v = torch.FloatTensor(values) 54 | shape = scp_matrix.shape 55 | 56 | sparse_tensor = torch.sparse.FloatTensor(i, v, torch.Size(shape)) 57 | return sparse_tensor 58 | 59 | ## Adjacency Matrix 60 | def mOrgan(N): 61 | sub = np.zeros([N, N]) 62 | for i in range(0, N): 63 | sub[i, i-1] = 1 64 | sub[i, (i+1)%N] = 1 65 | return sub 66 | 67 | ## Downsampling Matrix 68 | def mOrganD(N): 69 | N2 = int(np.ceil(N/2)) 70 | sub = np.zeros([N2, N]) 71 | 72 | for i in range(0, N2): 73 | if (2*i+1) == N: 74 | sub[i, 2*i] = 1 75 | else: 76 | sub[i, 2*i] = 1/2 77 | sub[i, 2*i+1] = 1/2 78 | 79 | return sub 80 | 81 | ## Upsampling Matrix 82 | def mOrganU(N): 83 | N2 = int(np.ceil(N/2)) 84 | sub = np.zeros([N, N2]) 85 | 86 | for i in range(0, N): 87 | if i % 2 == 0: 88 | sub[i, i//2] = 1 89 | else: 90 | sub[i, i//2] = 1/2 91 | sub[i, (i//2 + 1) % N2] = 1/2 92 | 93 | return sub 94 | 95 | ## Generating Matrixes for every organ 96 | def genMatrixesLungs(): 97 | RLUNG = 44 98 | LLUNG = 50 99 | 100 | Asub1 = mOrgan(RLUNG) 101 | Asub2 = mOrgan(LLUNG) 102 | 103 | ADsub1 = mOrgan(int(np.ceil(RLUNG / 2))) 104 | ADsub2 = mOrgan(int(np.ceil(LLUNG / 2))) 105 | 106 | Dsub1 = mOrganD(RLUNG) 107 | Dsub2 = mOrganD(LLUNG) 108 | 109 | Usub1 = mOrganU(RLUNG) 110 | Usub2 = mOrganU(LLUNG) 111 | 112 | p1 = RLUNG 113 | p2 = p1 + LLUNG 114 | 115 | p1_ = int(np.ceil(RLUNG / 2)) 116 | p2_ = p1_ + int(np.ceil(LLUNG / 2)) 117 | 118 | A = np.zeros([p2, p2]) 119 | 120 | A[:p1, :p1] = Asub1 121 | A[p1:p2, p1:p2] = Asub2 122 | 123 | AD = np.zeros([p2_, p2_]) 124 | 125 | AD[:p1_, :p1_] = ADsub1 126 | AD[p1_:p2_, p1_:p2_] = ADsub2 127 | 128 | D = np.zeros([p2_, p2]) 129 | 130 | D[:p1_, :p1] = Dsub1 131 | D[p1_:p2_, p1:p2] = Dsub2 132 | 133 | U = np.zeros([p2, p2_]) 134 | 135 | U[:p1, :p1_] = Usub1 136 | U[p1:p2, p1_:p2_] = Usub2 137 | 138 | return A, AD, D, U 139 | 140 | 141 | def genMatrixesLungsHeart(): 142 | RLUNG = 44 143 | LLUNG = 50 144 | HEART = 26 145 | 146 | Asub1 = mOrgan(RLUNG) 147 | Asub2 = mOrgan(LLUNG) 148 | Asub3 = mOrgan(HEART) 149 | 150 | ADsub1 = mOrgan(int(np.ceil(RLUNG / 2))) 151 | ADsub2 = mOrgan(int(np.ceil(LLUNG / 2))) 152 | ADsub3 = mOrgan(int(np.ceil(HEART / 2))) 153 | 154 | Dsub1 = mOrganD(RLUNG) 155 | Dsub2 = mOrganD(LLUNG) 156 | Dsub3 = mOrganD(HEART) 157 | 158 | Usub1 = mOrganU(RLUNG) 159 | Usub2 = mOrganU(LLUNG) 160 | Usub3 = mOrganU(HEART) 161 | 162 | p1 = RLUNG 163 | p2 = p1 + LLUNG 164 | p3 = p2 + HEART 165 | 166 | p1_ = int(np.ceil(RLUNG / 2)) 167 | p2_ = p1_ + int(np.ceil(LLUNG / 2)) 168 | p3_ = p2_ + int(np.ceil(HEART / 2)) 169 | 170 | A = np.zeros([p3, p3]) 171 | 172 | A[:p1, :p1] = Asub1 173 | A[p1:p2, p1:p2] = Asub2 174 | A[p2:p3, p2:p3] = Asub3 175 | 176 | AD = np.zeros([p3_, p3_]) 177 | 178 | AD[:p1_, :p1_] = ADsub1 179 | AD[p1_:p2_, p1_:p2_] = ADsub2 180 | AD[p2_:p3_, p2_:p3_] = ADsub3 181 | 182 | D = np.zeros([p3_, p3]) 183 | 184 | D[:p1_, :p1] = Dsub1 185 | D[p1_:p2_, p1:p2] = Dsub2 186 | D[p2_:p3_, p2:p3] = Dsub3 187 | 188 | U = np.zeros([p3, p3_]) 189 | 190 | U[:p1, :p1_] = Usub1 191 | U[p1:p2, p1_:p2_] = Usub2 192 | U[p2:p3, p2_:p3_] = Usub3 193 | 194 | return A, AD, D, U 195 | 196 | 197 | def CrossVal(all_files, iFold, k = 5): 198 | #Performs 5-Fold-CrossValidation 199 | 200 | total = len(all_files) 201 | val = int(total/k) 202 | 203 | indices = list(range(total)) 204 | 205 | train_indices = indices[0:(iFold-1)*val] + indices[iFold*val:] 206 | val_indices = indices[(iFold-1)*val:iFold*val] 207 | 208 | train_paths = [all_files[i] for i in train_indices] 209 | val_paths = [all_files[i] for i in val_indices] 210 | 211 | return train_paths, val_paths -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CheXmask Database Code Repository 2 | 3 | This repository accompanies the CheXmask Database, a large-scale dataset of anatomical segmentation masks for chest x-ray images. It includes segmentation masks for both original resolutions and preprocessed versions, detailed segmentation metrics, and associated pseudo-landmarks for the contours of the lungs and heart. 4 | 5 | ![CheXmask](figures/histogram_CheXmask.png) 6 | 7 | ## Dataset 8 | 9 | CheXmask is an extensive and uniformly annotated set of chest radiographs, compiled from six public databases: CANDID-PTX, ChestX-ray8, Chexpert, MIMIC-CXR-JPG, Padchest, and VinDr-CXR. Our dataset comprises 676,803 anatomical segmentation masks, derived from images processed using the HybridGNet model to ensure consistent, high-quality segmentation. 10 | 11 | Each dataset associated with this research is available as a CSV file. Each CSV file contains image IDs and corresponding pre-processed segmentation masks in Run-Length Encoding (RLE) format. Please note that the original chest X-ray images are not included in this dataset due to proprietary and privacy considerations. To access the dataset, please visit the following PhysioNet repo: [https://physionet.org/content/chexmask-cxr-segmentation-data](https://physionet.org/content/chexmask-cxr-segmentation-data). 12 | 13 | ## Code 14 | 15 | The code in this repository is organized into different directories: 16 | 17 | - `Annotations/`: Contains a placeholder file for the annotations csv files. 18 | - `DataPreparation/`: Includes scripts for preparing the dataset, to be used with the HybridGNet if desired. 19 | - `DataPostprocessing/`: Includes scripts to remove the pre-processing steps applied to the original chest X-ray images. Also includes scripts to convert the segmentation masks from RLE format to binary masks. 20 | - `HybridGNet/`: Includes the code for training and using the HybridGNet model. 21 | - `TechnicalValidation/`: Contains scripts for evaluating the segmentation quality using the Reverse Classification Accuracy (RCA) framework and the Physician studies. 22 | - `Weights/`: Contains a placeholder file for the weights of the HybridGNet model. Should be downloaded from [here](https://drive.google.com/file/d/1yNCaLjKooiDQ2dDEfdK2K88s9_E8hpJS/view?usp=sharing) 23 | - `Example.ipynb`: Shows how to read the masks with pandas, numpy and display them with matplotlib. 24 | 25 | ## Requirements 26 | 27 | To run the code in this repository, the following dependencies are required: 28 | 29 | - Python 3 30 | - NumPy 31 | - Pandas 32 | - Matplotlib 33 | - OpenCV 34 | 35 | To use the HybridGNet model, the following additional dependencies are required: 36 | 37 | - PyTorch 38 | - PyTorchGeometric 39 | 40 | Please refer to the source code of the HybridGNet for additional information: [https://github.com/ngaggion/HybridGNet](https://github.com/ngaggion/HybridGNet). 41 | 42 | ## Citations 43 | 44 | If you use this dataset or code in your research, please cite the following PhysioNet repository: 45 | 46 | ``` 47 | @misc{gaggion2023chexmaskPhysioNet, 48 | author = {Gaggion, N. and Mosquera, C. and Aineseder, M. and Mansilla, L. and Milone, D. and Ferrante, E.}, 49 | title = {{CheXmask Database: a large-scale dataset of anatomical segmentation masks for chest x-ray images (version 0.1)}}, 50 | year = {2023}, 51 | howpublished = {PhysioNet}, 52 | note = {\url{https://doi.org/10.13026/dx54-8351}} 53 | } 54 | ``` 55 | 56 | If you use the HybridGNet model in your research, please cite the following paper: 57 | 58 | ``` 59 | @article{Gaggion_2022, 60 | doi = {10.1109/tmi.2022.3224660}, 61 | url = {https://doi.org/10.1109%2Ftmi.2022.3224660}, 62 | year = 2022, 63 | publisher = {Institute of Electrical and Electronics Engineers ({IEEE})}, 64 | author = {Nicolas Gaggion and Lucas Mansilla and Candelaria Mosquera and Diego H. Milone and Enzo Ferrante}, 65 | title = {Improving anatomical plausibility in medical image segmentation via hybrid graph neural networks: applications to chest x-ray analysis}, 66 | journal = {{IEEE} Transactions on Medical Imaging} 67 | } 68 | ``` -------------------------------------------------------------------------------- /TechnicalValidation/IndividualRCA/dataset_affine.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import torch 4 | from skimage import io, transform 5 | 6 | import numpy as np 7 | from torch.utils.data import Dataset, DataLoader 8 | from torchvision import transforms 9 | import cv2 10 | 11 | class LandmarksDataset(Dataset): 12 | def __init__(self, images, img_path, label_path, transform=None, heart = False): 13 | 14 | self.images = images 15 | self.img_path = img_path 16 | self.label_path = label_path 17 | 18 | self.RL_path = os.path.join(self.label_path, 'RL') 19 | self.LL_path = os.path.join(self.label_path, 'LL') 20 | 21 | if heart: 22 | self.H_path = os.path.join(self.label_path, 'H') 23 | 24 | self.heart = heart 25 | 26 | self.transform = transform 27 | 28 | def __len__(self): 29 | return len(self.images) 30 | 31 | def __getitem__(self, idx): 32 | if torch.is_tensor(idx): 33 | idx = idx.tolist() 34 | 35 | img_name = self.images[idx] 36 | 37 | img_path = os.path.join(self.img_path, img_name) 38 | 39 | RL_path = os.path.join(self.RL_path, img_name.replace('.png', '.npy')) 40 | LL_path = os.path.join(self.LL_path, img_name.replace('.png', '.npy')) 41 | 42 | image = io.imread(img_path).astype('float') / 255.0 43 | image = np.expand_dims(image, axis=2) 44 | 45 | RL = np.load(RL_path).astype('float') 46 | LL = np.load(LL_path).astype('float') 47 | 48 | if self.heart: 49 | H_path = os.path.join(self.H_path, img_name.replace('.png', '.npy')) 50 | H = np.load(H_path).astype('float') 51 | landmarks = np.concatenate([RL, LL, H], axis = 0) 52 | else: 53 | landmarks = np.concatenate([RL, LL], axis = 0) 54 | 55 | sample = {'image': image, 'landmarks': landmarks} 56 | 57 | if self.transform: 58 | sample = self.transform(sample) 59 | 60 | return sample 61 | 62 | 63 | 64 | class RandomScale(object): 65 | def __call__(self, sample): 66 | image, landmarks = sample['image'], sample['landmarks'] 67 | 68 | # Pongo limites para evitar que los landmarks salgan del contorno 69 | min_x = np.min(landmarks[:,0]) 70 | max_x = np.max(landmarks[:,0]) 71 | ancho = max_x - min_x 72 | 73 | min_y = np.min(landmarks[:,1]) 74 | max_y = np.max(landmarks[:,1]) 75 | alto = max_y - min_y 76 | 77 | max_var_x = 1024 / ancho 78 | max_var_y = 1024 / alto 79 | 80 | min_var_x = 0.70 81 | min_var_y = 0.70 82 | 83 | varx = np.random.uniform(min_var_x, max_var_x) 84 | vary = np.random.uniform(min_var_y, max_var_y) 85 | 86 | landmarks[:,0] = landmarks[:,0] * varx 87 | landmarks[:,1] = landmarks[:,1] * vary 88 | 89 | h, w = image.shape[:2] 90 | new_h = np.round(h * vary).astype('int') 91 | new_w = np.round(w * varx).astype('int') 92 | 93 | img = transform.resize(image, (new_h, new_w)) 94 | 95 | # Cropeo o padeo aleatoriamente 96 | min_x = np.round(np.min(landmarks[:,0])).astype('int') 97 | max_x = np.round(np.max(landmarks[:,0])).astype('int') 98 | 99 | min_y = np.round(np.min(landmarks[:,1])).astype('int') 100 | max_y = np.round(np.max(landmarks[:,1])).astype('int') 101 | 102 | if new_h > 1024: 103 | rango = 1024 - (max_y - min_y) 104 | maxl0y = new_h - 1025 105 | 106 | if rango > 0 and min_y > 0: 107 | l0y = min_y - np.random.randint(0, min(rango, min_y)) 108 | l0y = min(maxl0y, l0y) 109 | else: 110 | l0y = min_y 111 | 112 | l1y = l0y + 1024 113 | 114 | img = img[l0y:l1y,:] 115 | landmarks[:,1] -= l0y 116 | 117 | elif new_h < 1024: 118 | pad = h - new_h 119 | p0 = np.random.randint(np.floor(pad/4), np.ceil(3*pad/4)) 120 | p1 = pad - p0 121 | 122 | img = np.pad(img, ((p0, p1), (0, 0), (0, 0)), mode='constant', constant_values=0) 123 | landmarks[:,1] += p0 124 | 125 | if new_w > 1024: 126 | rango = 1024 - (max_x - min_x) 127 | maxl0x = new_w - 1025 128 | 129 | if rango > 0 and min_x > 0: 130 | l0x = min_x - np.random.randint(0, min(rango, min_x)) 131 | l0x = min(maxl0x, l0x) 132 | else: 133 | l0x = min_x 134 | 135 | l1x = l0x + 1024 136 | 137 | img = img[:, l0x:l1x] 138 | landmarks[:,0] -= l0x 139 | 140 | elif new_w < 1024: 141 | pad = w - new_w 142 | p0 = np.random.randint(np.floor(pad/4), np.ceil(3*pad/4)) 143 | p1 = pad - p0 144 | 145 | img = np.pad(img, ((0, 0), (p0, p1), (0, 0)), mode='constant', constant_values=0) 146 | landmarks[:,0] += p0 147 | 148 | if img.shape[0] != 1024 or img.shape[1] != 1024: 149 | print('Original', [new_h,new_w]) 150 | print('Salida', img.shape) 151 | raise Exception('Error') 152 | 153 | return {'image': img, 'landmarks': landmarks} 154 | 155 | def adjust_gamma(image, gamma=1.0): 156 | # build a lookup table mapping the pixel values [0, 255] to 157 | # their adjusted gamma values 158 | invGamma = 1.0 / gamma 159 | table = np.array([((i / 255.0) ** invGamma) * 255 160 | for i in np.arange(0, 256)]).astype("uint8") 161 | 162 | # apply gamma correction using the lookup table 163 | return np.float32(cv2.LUT(image.astype('uint8'), table)) 164 | 165 | class AugColor(object): 166 | def __init__(self, gammaFactor): 167 | self.gammaf = gammaFactor 168 | 169 | def __call__(self, sample): 170 | image, landmarks = sample['image'], sample['landmarks'] 171 | 172 | # Gamma 173 | gamma = np.random.uniform(1 - self.gammaf, 1 + self.gammaf / 2) 174 | 175 | image[:,:,0] = adjust_gamma(image[:,:,0] * 255, gamma) / 255 176 | 177 | # Adds a little noise 178 | image = image + np.random.normal(0, 1/128, image.shape) 179 | 180 | return {'image': image, 'landmarks': landmarks} 181 | 182 | class Rotate(object): 183 | def __init__(self, angle): 184 | self.angle = angle 185 | 186 | def __call__(self, sample): 187 | image, landmarks = sample['image'], sample['landmarks'] 188 | 189 | angle = np.random.uniform(- self.angle, self.angle) 190 | 191 | # pad to keep all the image 192 | image = np.pad(image, ((250, 250), (250, 250), (0, 0)), mode='constant', constant_values=0) 193 | image = transform.rotate(image, angle) 194 | image = image[250:1024+250, 250:1024+250] 195 | 196 | centro = image.shape[0] / 2, image.shape[1] / 2 197 | 198 | landmarks -= centro 199 | 200 | theta = np.deg2rad(angle) 201 | c, s = np.cos(theta), np.sin(theta) 202 | R = np.array(((c, -s), (s, c))) 203 | 204 | landmarks = np.dot(landmarks, R) 205 | 206 | landmarks += centro 207 | 208 | return {'image': image, 'landmarks': landmarks} 209 | 210 | class ToTensor(object): 211 | """Convert ndarrays in sample to Tensors.""" 212 | 213 | def __call__(self, sample): 214 | image, landmarks = sample['image'], sample['landmarks'] 215 | 216 | # swap color axis because 217 | # numpy image: H x W x C 218 | # torch image: C X H X W 219 | 220 | size = image.shape[0] 221 | image = image.transpose((2, 0, 1)) 222 | landmarks = landmarks / size 223 | landmarks = np.clip(landmarks, 0, 1) 224 | 225 | # concatenate a vectors of 1 to the landmarks 226 | landmarks = np.concatenate((landmarks, np.ones((landmarks.shape[0], 1))), axis=1) 227 | 228 | return {'image': torch.from_numpy(image).float(), 229 | 'landmarks': torch.from_numpy(landmarks).float()} 230 | 231 | 232 | class RandomScaleRotate(object): 233 | def __init__(self, angle, scale_min = 0.7): 234 | self.angle = angle 235 | self.scale_min = scale_min 236 | 237 | def __call__(self, sample): 238 | image, landmarks = sample['image'], sample['landmarks'] 239 | 240 | # Roto los landmarks 241 | 242 | angle = np.random.uniform(- self.angle, self.angle) 243 | 244 | # pad to keep all the image 245 | image = np.pad(image, ((250, 250), (250, 250), (0, 0)), mode='constant', constant_values=0) 246 | image = transform.rotate(image, angle) 247 | image = image[250:1024+250, 250:1024+250] 248 | 249 | centro = image.shape[0] / 2, image.shape[1] / 2 250 | 251 | landmarks -= centro 252 | 253 | theta = np.deg2rad(angle) 254 | c, s = np.cos(theta), np.sin(theta) 255 | R = np.array(((c, -s), (s, c))) 256 | 257 | landmarks = np.dot(landmarks, R) 258 | landmarks += centro 259 | 260 | landmarks = np.clip(landmarks, 0, 1024) 261 | 262 | # Pongo limites para evitar que los landmarks salgan del contorno 263 | min_x = np.min(landmarks[:,0]) 264 | max_x = np.max(landmarks[:,0]) 265 | ancho = max_x - min_x 266 | 267 | min_y = np.min(landmarks[:,1]) 268 | max_y = np.max(landmarks[:,1]) 269 | alto = max_y - min_y 270 | 271 | max_var_x = 1024 / ancho 272 | max_var_y = 1024 / alto 273 | 274 | min_var_x = self.scale_min 275 | min_var_y = self.scale_min 276 | 277 | varx = np.random.uniform(min_var_x, max_var_x) 278 | vary = np.random.uniform(min_var_y, max_var_y) 279 | 280 | landmarks[:,0] = landmarks[:,0] * varx 281 | landmarks[:,1] = landmarks[:,1] * vary 282 | 283 | h, w = image.shape[:2] 284 | new_h = np.round(h * vary).astype('int') 285 | new_w = np.round(w * varx).astype('int') 286 | 287 | img = transform.resize(image, (new_h, new_w)) 288 | 289 | # Cropeo o padeo aleatoriamente 290 | min_x = np.round(np.min(landmarks[:,0])).astype('int') 291 | max_x = np.round(np.max(landmarks[:,0])).astype('int') 292 | 293 | min_y = np.round(np.min(landmarks[:,1])).astype('int') 294 | max_y = np.round(np.max(landmarks[:,1])).astype('int') 295 | 296 | if new_h > 1024: 297 | rango = 1024 - (max_y - min_y) 298 | maxl0y = new_h - 1025 299 | 300 | if rango > 0 and min_y > 0: 301 | l0y = min_y - np.random.randint(0, min(rango, min_y)) 302 | l0y = min(maxl0y, l0y) 303 | else: 304 | l0y = min_y 305 | 306 | l1y = l0y + 1024 307 | 308 | img = img[l0y:l1y,:] 309 | landmarks[:,1] -= l0y 310 | 311 | elif new_h < 1024: 312 | pad = h - new_h 313 | p0 = np.random.randint(np.floor(pad/4), np.ceil(3*pad/4)) 314 | p1 = pad - p0 315 | 316 | img = np.pad(img, ((p0, p1), (0, 0), (0, 0)), mode='constant', constant_values=0) 317 | landmarks[:,1] += p0 318 | 319 | if new_w > 1024: 320 | rango = 1024 - (max_x - min_x) 321 | maxl0x = new_w - 1025 322 | 323 | if rango > 0 and min_x > 0: 324 | l0x = min_x - np.random.randint(0, min(rango, min_x)) 325 | l0x = min(maxl0x, l0x) 326 | else: 327 | l0x = min_x 328 | 329 | l1x = l0x + 1024 330 | 331 | img = img[:, l0x:l1x] 332 | landmarks[:,0] -= l0x 333 | 334 | elif new_w < 1024: 335 | pad = w - new_w 336 | p0 = np.random.randint(np.floor(pad/4), np.ceil(3*pad/4)) 337 | p1 = pad - p0 338 | 339 | img = np.pad(img, ((0, 0), (p0, p1), (0, 0)), mode='constant', constant_values=0) 340 | landmarks[:,0] += p0 341 | 342 | if img.shape[0] != 1024 or img.shape[1] != 1024: 343 | print('Original', [new_h,new_w]) 344 | print('Salida', img.shape) 345 | raise Exception('Error') 346 | 347 | return {'image': img, 'landmarks': landmarks} -------------------------------------------------------------------------------- /TechnicalValidation/IndividualRCA/estimate_individual_rca_datasets.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import torch 4 | import numpy as np 5 | import cv2 6 | import matplotlib.pyplot as plt 7 | import pandas as pd 8 | from medpy.metric import dc 9 | from skimage import transform 10 | 11 | from model.affine import SiameseReg 12 | from model.unet import UNet 13 | from model.deformableNet import DeformableNet, GridSampler 14 | 15 | 16 | def load_landmarks(path): 17 | RL_path = "../../../Chest-xray-landmark-dataset/landmarks/RL/" + path.replace("png", "npy") 18 | LL_path = "../../../Chest-xray-landmark-dataset/landmarks/LL/" + path.replace("png", "npy") 19 | H_path = "../../../Chest-xray-landmark-dataset/landmarks/H/" + path.replace("png", "npy") 20 | RL = np.load(RL_path) 21 | LL = np.load(LL_path) 22 | H = np.load(H_path) 23 | 24 | return RL, LL, H 25 | 26 | 27 | def landmark_to_mask(RL, LL, H): 28 | RL = RL.reshape(-1, 1, 2).astype('int') 29 | LL = LL.reshape(-1, 1, 2).astype('int') 30 | H = H.reshape(-1, 1, 2).astype('int') 31 | mask = np.zeros((1024, 1024)) 32 | mask = cv2.drawContours(mask, [RL], -1, 1, -1) 33 | mask = cv2.drawContours(mask, [LL], -1, 1, -1) 34 | mask = cv2.drawContours(mask, [H], -1, 2, -1) 35 | 36 | return mask 37 | 38 | 39 | def apply_registration_affine(img, params): 40 | params = params.cpu().numpy() 41 | affine_matrix = np.zeros((2, 3)) 42 | affine_matrix[0, :] = params[0, 0:3] # Changed from 0:3 to 0:2 43 | affine_matrix[1, :] = params[0, 3:6] # Changed from 3:6 to 2:4 44 | affine_matrix[:2, 2] = affine_matrix[:2, 2] * 1024 45 | img = cv2.warpAffine(img.astype('float'), affine_matrix, (img.shape[1], img.shape[0])) 46 | 47 | return img 48 | 49 | 50 | def apply_registration_deformable(img, params, modelResampler): 51 | img = torch.from_numpy(img).unsqueeze(0).unsqueeze(0).to(config['device']).float() 52 | img = modelResampler(img, params) 53 | 54 | return img.cpu().numpy().squeeze() 55 | 56 | 57 | def load_models(config): 58 | modelAffine = SiameseReg(config).float().to(config['device']) 59 | modelAffine.load_state_dict(torch.load("../../Weights/IndividualRCA/Affine/bestMSE.pt"), strict=False) 60 | modelAffine.eval() 61 | 62 | modelDeformable = DeformableNet((1024, 1024), batchnorm=True).to(config['device']) 63 | modelDeformable.load_state_dict(torch.load("../../Weights/IndividualRCA/Deformable/modelDeformable.pt")) 64 | modelDeformable.eval() 65 | 66 | modelResampler = GridSampler((1024, 1024), mode='nearest').to(config['device']) 67 | modelResampler.eval() 68 | 69 | modelFinder = SiameseReg(config).float().to(config['device']) 70 | modelFinder.load_state_dict(torch.load("../../Weights/IndividualRCA/Selector/bestMSE.pt"), strict=False) 71 | modelFinder.eval() 72 | 73 | return modelAffine, modelDeformable, modelResampler, modelFinder 74 | 75 | 76 | def process_images(images_test, images_train, latent_matrix, modelFinder, modelAffine, modelDeformable, 77 | modelResampler, config, ext = 'jpg', replace_1 = "Images", replace_2 = "Output"): 78 | # Initialize a DataFrame to store the results 79 | df_reg = pd.DataFrame(columns=['Image', 80 | 'Dice_RCA_Max', 'Dice_RCA_Mean', 81 | 'L_Dice_RCA_Max', 'L_Dice_RCA_Mean', 82 | 'H_Dice_RCA_Max', 'H_Dice_RCA_Mean']) 83 | 84 | with torch.no_grad(): 85 | count = 0 86 | for image in images_test: 87 | img = cv2.imread(image, 0) / 255.0 88 | source = torch.from_numpy(img).unsqueeze(0).unsqueeze(0).to(config['device']).float() 89 | 90 | # Calculate the latent vector for the current image using the model finder 91 | mu, _ = modelFinder.encoder(source) 92 | distances = latent_matrix @ mu.T 93 | _, sorted_distances_indices = torch.sort(distances, dim=0, descending=True) 94 | 95 | # Select indices of the top 5 nearest images in the latent space 96 | idxs = sorted_distances_indices[0:5].squeeze().cpu().numpy() 97 | target_image_names = [images_train[i] for i in idxs] 98 | 99 | # Calculate ground truth parameters and masks for the nearest images 100 | gt_params, gt_masks = calculate_ground_truth( 101 | target_image_names, config, modelAffine, modelDeformable, source) 102 | 103 | landmark_path = image.replace(replace_1, replace_2)[0:-3] + 'txt' 104 | 105 | pred = np.loadtxt(landmark_path, dtype='int') 106 | pred = landmark_to_mask(pred[:44, :], pred[44:94, :], pred[94:120,:]) 107 | 108 | rca_dice_lung_list = [] 109 | rca_dice_heart_list = [] 110 | rca_dice_avg_list = [] 111 | 112 | for j in range(0, len(gt_params)): 113 | params = gt_params[j] 114 | mask = gt_masks[j] 115 | 116 | # Apply registration on the predicted mask using the calculated parameters 117 | pred_reg = apply_registration_affine(pred, params[0]) 118 | pred_reg = apply_registration_deformable(pred_reg, params[1], modelResampler) 119 | 120 | lung_mask = pred_reg == 1 121 | heart_mask = pred_reg == 2 122 | 123 | rca_dice_lung = dc(lung_mask, mask == 1) 124 | rca_dice_heart = dc(heart_mask, mask == 2) 125 | 126 | rca_dice_lung_list.append(rca_dice_lung) 127 | rca_dice_heart_list.append(rca_dice_heart) 128 | rca_dice_avg_list.append((rca_dice_lung + rca_dice_heart) / 2) 129 | 130 | rca_max = max(rca_dice_avg_list) 131 | rca_avg = np.mean(rca_dice_avg_list) 132 | rca_max_lung = max(rca_dice_lung_list) 133 | rca_avg_lung = np.mean(rca_dice_lung_list) 134 | rca_max_heart = max(rca_dice_heart_list) 135 | rca_avg_heart = np.mean(rca_dice_heart_list) 136 | 137 | df_reg.loc[len(df_reg)] = [image, rca_max, rca_avg, 138 | rca_max_lung, rca_avg_lung, 139 | rca_max_heart, rca_avg_heart] 140 | 141 | print('Image:', count, 'of', len(images_test)) 142 | #print('Image') 143 | #print('Dice RCA Max: ', rca_max, 'Dice RCA Avg: ', rca_avg) 144 | #print('Dice RCA Max Lung: ', rca_max_lung, 'Dice RCA Avg Lung: ', rca_avg_lung) 145 | #print('Dice RCA Max Heart: ', rca_max_heart, 'Dice RCA Avg Heart: ', rca_avg_heart) 146 | #print("") 147 | count += 1 148 | 149 | return df_reg 150 | 151 | 152 | def calculate_ground_truth(image_names, config, modelAffine, modelDeformable, source): 153 | gt_params = [] 154 | gt_masks = [] 155 | 156 | for img_near in image_names: 157 | img_target = cv2.imread("../Chest-xray-landmark-dataset/Images/" + img_near, 0) / 255.0 158 | target = torch.from_numpy(img_target).unsqueeze(0).unsqueeze(0).to(config['device']).float() 159 | 160 | RL_, LL_, H_ = load_landmarks(img_near) 161 | mask_gt = landmark_to_mask(RL_, LL_, H_) 162 | 163 | params1 = modelAffine(target, source).detach() 164 | src_affine = apply_registration_affine(source.cpu().numpy().squeeze(), params1) 165 | src_affine = torch.from_numpy(src_affine).unsqueeze(0).unsqueeze(0).to(config['device']).float() 166 | params2 = modelDeformable(src_affine, target)[1].detach() 167 | 168 | gt_params = gt_params + [(params1, params2)] 169 | gt_masks = gt_masks + [mask_gt] 170 | 171 | return gt_params, gt_masks 172 | 173 | 174 | if __name__ == '__main__': 175 | images_train = open("train_images_lungs.txt", 'r').read().splitlines() 176 | images_train_heart = open("train_images_heart.txt", 'r').read().splitlines() 177 | 178 | # get indices of images with heart in the Weights set 179 | idxs = [] 180 | for i in range(len(images_train)): 181 | if images_train[i] in images_train_heart: 182 | idxs.append(i) 183 | 184 | idxs = np.array(idxs) 185 | 186 | config = { 187 | 'latents': 64, 188 | 'inputsize': 1024, 189 | 'device': torch.device("cuda:0" if torch.cuda.is_available() else "cpu"), 190 | 'sampling': False 191 | } 192 | 193 | latent_space = np.load("latent_space_train.npy") 194 | latent_matrix = torch.from_numpy(latent_space).to(config['device']) 195 | latent_matrix = latent_matrix[idxs, :] 196 | 197 | path = "../../Datasets/Padchest" 198 | 199 | # Here we have all the image ids in a CSV file 200 | images_test = pd.read_csv(os.path.join(path, "Padchest.csv"))['Image'].values 201 | images_test = [os.path.join(os.path.join(path, "Images"), i) for i in images_test] 202 | 203 | # Load the models 204 | modelAffine, modelDeformable, modelResampler, modelFinder = load_models(config) 205 | 206 | # Process the images and generate the DataFrame with the results 207 | df_rca = process_images(images_test, images_train_heart, latent_matrix, modelFinder, 208 | modelAffine, modelDeformable, modelResampler, config, '.png', 'Images', 'Output') 209 | 210 | # Save the DataFrame to a CSV file 211 | df_rca.to_csv(os.path.join(path, "Padchest_RCA.csv")) 212 | -------------------------------------------------------------------------------- /TechnicalValidation/IndividualRCA/estimate_rca_affine_deformable_test_set.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import torch 4 | import numpy as np 5 | import cv2 6 | import matplotlib.pyplot as plt 7 | import pandas as pd 8 | from medpy.metric import dc 9 | from skimage import transform 10 | 11 | from model.affine import SiameseReg 12 | from model.unet import UNet 13 | from model.deformableNet import DeformableNet, GridSampler 14 | 15 | 16 | def load_landmarks(path): 17 | RL_path = "../../../Chest-xray-landmark-dataset/landmarks/RL/" + path.replace("png", "npy") 18 | LL_path = "../../../Chest-xray-landmark-dataset/landmarks/LL/" + path.replace("png", "npy") 19 | H_path = "../../../Chest-xray-landmark-dataset/landmarks/H/" + path.replace("png", "npy") 20 | RL = np.load(RL_path) 21 | LL = np.load(LL_path) 22 | H = np.load(H_path) 23 | 24 | return RL, LL, H 25 | 26 | 27 | def landmark_to_mask(RL, LL, H): 28 | RL = RL.reshape(-1, 1, 2).astype('int') 29 | LL = LL.reshape(-1, 1, 2).astype('int') 30 | H = H.reshape(-1, 1, 2).astype('int') 31 | mask = np.zeros((1024, 1024)) 32 | mask = cv2.drawContours(mask, [RL], -1, 1, -1) 33 | mask = cv2.drawContours(mask, [LL], -1, 1, -1) 34 | mask = cv2.drawContours(mask, [H], -1, 2, -1) 35 | 36 | return mask 37 | 38 | 39 | def apply_registration_affine(img, params): 40 | params = params.cpu().numpy() 41 | affine_matrix = np.zeros((2, 3)) 42 | affine_matrix[0, :] = params[0, 0:3] # Changed from 0:3 to 0:2 43 | affine_matrix[1, :] = params[0, 3:6] # Changed from 3:6 to 2:4 44 | affine_matrix[:2, 2] = affine_matrix[:2, 2] * 1024 45 | img = cv2.warpAffine(img.astype('float'), affine_matrix, (img.shape[1], img.shape[0])) 46 | 47 | return img 48 | 49 | 50 | def apply_registration_deformable(img, params, modelResampler): 51 | img = torch.from_numpy(img).unsqueeze(0).unsqueeze(0).to(config['device']).float() 52 | img = modelResampler(img, params) 53 | 54 | return img.cpu().numpy().squeeze() 55 | 56 | 57 | def load_models(config): 58 | modelAffine = SiameseReg(config).float().to(config['device']) 59 | modelAffine.load_state_dict(torch.load("../../Weights/IndividualRCA/Affine/bestMSE.pt"), strict=False) 60 | modelAffine.eval() 61 | 62 | modelDeformable = DeformableNet((1024, 1024), batchnorm=True).to(config['device']) 63 | modelDeformable.load_state_dict(torch.load("../../Weights/IndividualRCA/Deformable/modelDeformable.pt")) 64 | modelDeformable.eval() 65 | 66 | modelResampler = GridSampler((1024, 1024), mode='nearest').to(config['device']) 67 | modelResampler.eval() 68 | 69 | modelFinder = SiameseReg(config).float().to(config['device']) 70 | modelFinder.load_state_dict(torch.load("../../Weights/IndividualRCA/Selector/bestMSE.pt"), strict=False) 71 | modelFinder.eval() 72 | 73 | modelPreds = [] 74 | for i in range(10): 75 | modelPred = UNet(n_classes=3).to(config['device']) 76 | modelPred.load_state_dict(torch.load(f"../../Weights/IndividualRCA/unet_lh_for_rca/epoch{i}.pt"), strict=False) 77 | modelPred.eval() 78 | modelPreds.append(modelPred) 79 | 80 | return modelAffine, modelDeformable, modelResampler, modelFinder, modelPreds 81 | 82 | 83 | def process_images(images_test, images_train, latent_matrix, modelFinder, modelAffine, modelDeformable, 84 | modelResampler, modelPreds, config): 85 | # Initialize a DataFrame to store the results 86 | df_reg = pd.DataFrame(columns=['Dice_Real', 'Dice_RCA_Max', 'Dice_RCA_Mean', 87 | 'L_Dice_Real', 'L_Dice_RCA_Max', 'L_Dice_RCA_Mean', 88 | 'H_Dice_Real', 'H_Dice_RCA_Max', 'H_Dice_RCA_Mean']) 89 | 90 | with torch.no_grad(): 91 | for image in images_test: 92 | image_path = "../Chest-xray-landmark-dataset/Images/" + image 93 | img = cv2.imread(image_path, 0) / 255.0 94 | source = torch.from_numpy(img).unsqueeze(0).unsqueeze(0).to(config['device']).float() 95 | RL, LL, H = load_landmarks(image) 96 | mask_GT = landmark_to_mask(RL, LL, H) 97 | 98 | # Calculate the latent vector for the current image using the model finder 99 | mu, _ = modelFinder.encoder(source) 100 | distances = latent_matrix @ mu.T 101 | _, sorted_distances_indices = torch.sort(distances, dim=0, descending=True) 102 | 103 | # Select indices of the top 5 nearest images in the latent space 104 | idxs = sorted_distances_indices[0:5].squeeze().cpu().numpy() 105 | target_image_names = [images_train[i] for i in idxs] 106 | 107 | # Calculate ground truth parameters and masks for the nearest images 108 | gt_params, gt_masks = calculate_ground_truth( 109 | target_image_names, config, modelAffine, modelDeformable, source) 110 | 111 | for t in range(0, len(modelPreds)): 112 | pred = modelPreds[t](source)[0].argmax(dim=0).cpu().numpy() 113 | 114 | real_dice_lung = dc(pred == 1, mask_GT == 1) 115 | real_dice_heart = dc(pred == 2, mask_GT == 2) 116 | real_dice_avg = (real_dice_lung + real_dice_heart) / 2 117 | 118 | rca_dice_lung_list = [] 119 | rca_dice_heart_list = [] 120 | rca_dice_avg_list = [] 121 | 122 | for j in range(0, len(gt_params)): 123 | params = gt_params[j] 124 | mask = gt_masks[j] 125 | 126 | # Apply registration on the predicted mask using the calculated parameters 127 | pred_reg = apply_registration_affine(pred, params[0]) 128 | pred_reg = apply_registration_deformable(pred_reg, params[1], modelResampler) 129 | 130 | lung_mask = pred_reg == 1 131 | heart_mask = pred_reg == 2 132 | 133 | rca_dice_lung = dc(lung_mask, mask == 1) 134 | rca_dice_heart = dc(heart_mask, mask == 2) 135 | 136 | rca_dice_lung_list.append(rca_dice_lung) 137 | rca_dice_heart_list.append(rca_dice_heart) 138 | rca_dice_avg_list.append((rca_dice_lung + rca_dice_heart) / 2) 139 | 140 | rca_max = max(rca_dice_avg_list) 141 | rca_avg = np.mean(rca_dice_avg_list) 142 | rca_max_lung = max(rca_dice_lung_list) 143 | rca_avg_lung = np.mean(rca_dice_lung_list) 144 | rca_max_heart = max(rca_dice_heart_list) 145 | rca_avg_heart = np.mean(rca_dice_heart_list) 146 | 147 | df_reg.loc[len(df_reg)] = [real_dice_avg, rca_max, rca_avg, 148 | real_dice_lung, rca_max_lung, rca_avg_lung, 149 | real_dice_heart, rca_max_heart, rca_avg_heart] 150 | 151 | # print('Image') 152 | # print('Dice Real: ', real_dice_avg, 'Dice RCA Max: ', rca_max, 'Dice RCA Avg: ', rca_avg) 153 | # print('Dice Real Lung: ', real_dice_lung, 'Dice RCA Max Lung: ', rca_max_lung, 'Dice RCA Avg Lung: ', rca_avg_lung) 154 | # print('Dice Real Heart: ', real_dice_heart, 'Dice RCA Max Heart: ', rca_max_heart, 'Dice RCA Avg Heart: ', rca_avg_heart) 155 | # print("") 156 | 157 | return df_reg 158 | 159 | 160 | def calculate_ground_truth(image_names, config, modelAffine, modelDeformable, source): 161 | gt_params = [] 162 | gt_masks = [] 163 | 164 | for img_near in image_names: 165 | img_target = cv2.imread("../Chest-xray-landmark-dataset/Images/" + img_near, 0) / 255.0 166 | target = torch.from_numpy(img_target).unsqueeze(0).unsqueeze(0).to(config['device']).float() 167 | 168 | RL_, LL_, H_ = load_landmarks(img_near) 169 | mask_gt = landmark_to_mask(RL_, LL_, H_) 170 | 171 | params1 = modelAffine(target, source).detach() 172 | src_affine = apply_registration_affine(source.cpu().numpy().squeeze(), params1) 173 | src_affine = torch.from_numpy(src_affine).unsqueeze(0).unsqueeze(0).to(config['device']).float() 174 | params2 = modelDeformable(src_affine, target)[1].detach() 175 | 176 | gt_params = gt_params + [(params1, params2)] 177 | gt_masks = gt_masks + [mask_gt] 178 | 179 | return gt_params, gt_masks 180 | 181 | 182 | if __name__ == '__main__': 183 | images_train = open("train_images_lungs.txt", 'r').read().splitlines() 184 | images_train_heart = open("train_images_heart.txt", 'r').read().splitlines() 185 | 186 | # get indices of images with heart in the Weights set 187 | idxs = [] 188 | for i in range(len(images_train)): 189 | if images_train[i] in images_train_heart: 190 | idxs.append(i) 191 | 192 | idxs = np.array(idxs) 193 | 194 | config = { 195 | 'latents': 64, 196 | 'inputsize': 1024, 197 | 'device': torch.device("cuda:0" if torch.cuda.is_available() else "cpu"), 198 | 'sampling': False 199 | } 200 | 201 | latent_space = np.load("latent_space_train.npy") 202 | latent_matrix = torch.from_numpy(latent_space).to(config['device']) 203 | latent_matrix = latent_matrix[idxs, :] 204 | 205 | images_test = open("test_images_heart.txt", 'r').read().splitlines() 206 | 207 | # Load the models 208 | modelAffine, modelDeformable, modelResampler, modelFinder, modelPreds = load_models(config) 209 | 210 | # Process the images and generate the DataFrame with the results 211 | df_rca = process_images(images_test, images_train_heart, latent_matrix, modelFinder, 212 | modelAffine, modelDeformable, modelResampler, modelPreds, config) 213 | 214 | # Save the DataFrame to a CSV file 215 | df_rca.to_csv("df_rca_affine_deformable_lh.csv") 216 | -------------------------------------------------------------------------------- /TechnicalValidation/IndividualRCA/histogram_whole_dataset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaggion/CheXmask-Database/8ae0c338caced10869f8be1860e320146c40d096/TechnicalValidation/IndividualRCA/histogram_whole_dataset.png -------------------------------------------------------------------------------- /TechnicalValidation/IndividualRCA/histogram_whole_dataset_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaggion/CheXmask-Database/8ae0c338caced10869f8be1860e320146c40d096/TechnicalValidation/IndividualRCA/histogram_whole_dataset_white.png -------------------------------------------------------------------------------- /TechnicalValidation/IndividualRCA/histograms_Dice_RCA_Max.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaggion/CheXmask-Database/8ae0c338caced10869f8be1860e320146c40d096/TechnicalValidation/IndividualRCA/histograms_Dice_RCA_Max.pdf -------------------------------------------------------------------------------- /TechnicalValidation/IndividualRCA/histograms_Dice_RCA_Max.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaggion/CheXmask-Database/8ae0c338caced10869f8be1860e320146c40d096/TechnicalValidation/IndividualRCA/histograms_Dice_RCA_Max.png -------------------------------------------------------------------------------- /TechnicalValidation/IndividualRCA/histograms_Dice_RCA_Mean.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaggion/CheXmask-Database/8ae0c338caced10869f8be1860e320146c40d096/TechnicalValidation/IndividualRCA/histograms_Dice_RCA_Mean.pdf -------------------------------------------------------------------------------- /TechnicalValidation/IndividualRCA/histograms_Dice_RCA_Mean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaggion/CheXmask-Database/8ae0c338caced10869f8be1860e320146c40d096/TechnicalValidation/IndividualRCA/histograms_Dice_RCA_Mean.png -------------------------------------------------------------------------------- /TechnicalValidation/IndividualRCA/how_we_saved_annotations.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import cv2 3 | import numpy as np 4 | import matplotlib.pyplot as plt 5 | 6 | def get_RLE_from_mask(mask): 7 | mask = (mask / 255).astype(int) 8 | pixels = mask.flatten() 9 | pixels = np.concatenate([[0], pixels, [0]]) 10 | runs = np.where(pixels[1:] != pixels[:-1])[0] + 1 11 | runs[1::2] -= runs[::2] 12 | return ' '.join(str(x) for x in runs) 13 | 14 | 15 | def get_mask_from_RLE(rle, height, width): 16 | runs = np.array([int(x) for x in rle.split()]) 17 | starts = runs[::2] 18 | lengths = runs[1::2] 19 | 20 | mask = np.zeros((height * width), dtype=np.uint8) 21 | 22 | for start, length in zip(starts, lengths): 23 | start -= 1 24 | end = start + length 25 | mask[start:end] = 255 26 | 27 | mask = mask.reshape((height, width)) 28 | 29 | return mask 30 | 31 | def getDenseMask(graph, imagesize = 1024): 32 | img = np.zeros([imagesize,imagesize]) 33 | graph = graph.reshape(-1, 1, 2).astype('int') 34 | img = cv2.drawContours(img, [graph], -1, 255, -1) 35 | return img 36 | 37 | path = "Datasets/CANDID/CANDID_RCA.csv" 38 | 39 | df = pd.read_csv(path) 40 | 41 | # Create an empty DataFrame 42 | output = pd.DataFrame(columns=['ImageID', 'Dice RCA (Max)', 'Dice RCA (Mean)', 'Landmarks', 'Left Lung', 'Right Lung', 'Heart']) 43 | 44 | # Iterate over the rows of the DataFrame 45 | for index, row in df.iterrows(): 46 | 47 | print(index) 48 | 49 | image_path = row['Image'] 50 | dice_rca_max = row['Dice_RCA_Max'] 51 | dice_rca_mean = row['Dice_RCA_Mean'] 52 | 53 | if "Padchest" in image_path: 54 | landmark = image_path.replace('Images', 'Output').replace('.png', '.txt') 55 | id = image_path.split('/')[-1] 56 | elif "MIMIC" in image_path: 57 | landmark = image_path.replace('Images', 'Output').replace('.jpg', '.txt') 58 | elif "CANDID" in image_path: 59 | landmark = image_path.replace('Images', 'Output').replace('.jpg', '.txt') 60 | id = image_path.split('/')[-1] 61 | elif "VinBigData" in image_path: 62 | landmark = image_path.replace('pngs', 'Output').replace('.png', '.txt') 63 | elif "CheXpert" in image_path: 64 | landmark = image_path.replace('Preprocessed', 'Output').replace('.png', '.txt') 65 | elif "Chest8" in image_path: 66 | landmark = image_path.replace("ChestX-ray8", "Output").replace('images/','')[0:-3] + 'txt' 67 | 68 | data = np.loadtxt(landmark, delimiter=' ').astype('int') 69 | flattened_data = data.flatten() 70 | coordinates_str = ','.join(map(str, flattened_data)) 71 | 72 | RL = getDenseMask(data[:44]) 73 | LL = getDenseMask(data[44:94]) 74 | H = getDenseMask(data[94:]) 75 | 76 | RL_RLE = get_RLE_from_mask(RL) 77 | LL_RLE = get_RLE_from_mask(LL) 78 | H_RLE = get_RLE_from_mask(H) 79 | 80 | new_row = { 81 | 'ImageID': id, 82 | 'Dice RCA (Max)': dice_rca_mean, 83 | 'Dice RCA (Mean)': dice_rca_max, 84 | 'Landmarks': coordinates_str, 85 | 'Right Lung': RL_RLE, 86 | 'Left Lung': LL_RLE, 87 | 'Heart': H_RLE 88 | } 89 | 90 | output = output.append(new_row, ignore_index=True) 91 | 92 | output.to_csv("Annotations/CANDID-PTX.csv") 93 | -------------------------------------------------------------------------------- /TechnicalValidation/IndividualRCA/latent_space_train.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaggion/CheXmask-Database/8ae0c338caced10869f8be1860e320146c40d096/TechnicalValidation/IndividualRCA/latent_space_train.npy -------------------------------------------------------------------------------- /TechnicalValidation/IndividualRCA/model/affine.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | 5 | import numpy as np 6 | 7 | class residualBlock(nn.Module): 8 | def __init__(self, in_channels, out_channels, stride=1): 9 | """ 10 | Args: 11 | in_channels (int): Number of input channels. 12 | out_channels (int): Number of output channels. 13 | stride (int): Controls the stride. 14 | """ 15 | super(residualBlock, self).__init__() 16 | 17 | self.skip = nn.Sequential() 18 | 19 | if stride != 1 or in_channels != out_channels: 20 | self.skip = nn.Sequential( 21 | nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=1, stride=stride, bias=False), 22 | nn.BatchNorm2d(out_channels, track_running_stats=False)) 23 | else: 24 | self.skip = None 25 | 26 | self.block = nn.Sequential(nn.BatchNorm2d(in_channels, track_running_stats=False), 27 | nn.ReLU(inplace=True), 28 | nn.Conv2d(in_channels, out_channels, 3, padding=1), 29 | nn.BatchNorm2d(out_channels, track_running_stats=False), 30 | nn.ReLU(inplace=True), 31 | nn.Conv2d(out_channels, out_channels, 3, padding=1) 32 | ) 33 | 34 | def forward(self, x): 35 | identity = x 36 | out = self.block(x) 37 | 38 | if self.skip is not None: 39 | identity = self.skip(x) 40 | 41 | out += identity 42 | out = F.relu(out) 43 | 44 | return out 45 | 46 | class EncoderConv(nn.Module): 47 | def __init__(self, latents = 64, hw = 32): 48 | super(EncoderConv, self).__init__() 49 | 50 | self.latents = latents 51 | self.c = 4 52 | 53 | self.size = self.c * np.array([2,4,8,16,32], dtype = np.intc) 54 | 55 | self.maxpool = nn.MaxPool2d(2) 56 | 57 | self.dconv_down1 = residualBlock(1, self.size[0]) 58 | self.dconv_down2 = residualBlock(self.size[0], self.size[1]) 59 | self.dconv_down3 = residualBlock(self.size[1], self.size[2]) 60 | self.dconv_down4 = residualBlock(self.size[2], self.size[3]) 61 | self.dconv_down5 = residualBlock(self.size[3], self.size[4]) 62 | self.dconv_down6 = residualBlock(self.size[4], self.size[4]) 63 | 64 | self.fc_mu = nn.Linear(in_features=self.size[4]*hw*hw, out_features=self.latents) 65 | self.fc_logvar = nn.Linear(in_features=self.size[4]*hw*hw, out_features=self.latents) 66 | 67 | def forward(self, x): 68 | x = self.dconv_down1(x) 69 | x = self.maxpool(x) 70 | 71 | x = self.dconv_down2(x) 72 | x = self.maxpool(x) 73 | 74 | conv3 = self.dconv_down3(x) 75 | x = self.maxpool(conv3) 76 | 77 | conv4 = self.dconv_down4(x) 78 | x = self.maxpool(conv4) 79 | 80 | conv5 = self.dconv_down5(x) 81 | x = self.maxpool(conv5) 82 | 83 | conv6 = self.dconv_down6(x) 84 | 85 | x = conv6.view(conv6.size(0), -1) # flatten batch of multi-channel feature maps to a batch of feature vectors 86 | 87 | x_mu = self.fc_mu(x) 88 | x_logvar = self.fc_logvar(x) 89 | 90 | return x_mu, x_logvar 91 | 92 | 93 | class SiameseReg(nn.Module): 94 | def __init__(self, config): 95 | super(SiameseReg, self).__init__() 96 | 97 | self.config = config 98 | hw = config['inputsize'] // 32 99 | self.z = config['latents'] 100 | 101 | self.encoder = EncoderConv(latents = self.z, hw = hw) 102 | self.fc1 = nn.Linear(in_features=self.z*2, out_features=self.z*2) 103 | self.fc_out = nn.Linear(in_features=self.z*2, out_features=6) 104 | 105 | 106 | def sampling(self, mu, log_var): 107 | std = torch.exp(0.5*log_var) 108 | eps = torch.randn_like(std) 109 | return eps.mul(std).add_(mu) 110 | 111 | 112 | def forward(self, x1, x2): 113 | self.mu_1, self.log_var_1 = self.encoder(x1) 114 | self.mu_2, self.log_var_2 = self.encoder(x2) 115 | 116 | if self.training and self.config['sampling']: 117 | z1 = self.sampling(self.mu_1, self.log_var_1) 118 | z2 = self.sampling(self.mu_2, self.log_var_2) 119 | else: 120 | z1 = self.mu_1 121 | z2 = self.mu_2 122 | 123 | z = torch.cat((z1, z2), dim = 1) 124 | 125 | z = F.relu(self.fc1(z)) 126 | out = self.fc_out(z) 127 | 128 | return out -------------------------------------------------------------------------------- /TechnicalValidation/IndividualRCA/model/deformableNet.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | 5 | from torch.distributions import Normal 6 | 7 | 8 | class DeformableNet(nn.Module): 9 | def __init__(self, input_size, batchnorm=True): 10 | super().__init__() 11 | self.unet = UNet(in_channels=2, batchnorm=batchnorm) 12 | self.grid_sampler = GridSampler(input_size) 13 | 14 | def forward(self, src, tgt): 15 | x = torch.cat([src, tgt], dim=1) 16 | flow = self.unet(x) 17 | reg_img = self.grid_sampler(src, flow) 18 | 19 | return reg_img, flow 20 | 21 | 22 | class GridSampler(nn.Module): 23 | """ https://github.com/voxelmorph/voxelmorph """ 24 | 25 | def __init__(self, input_size, mode='bilinear'): 26 | super().__init__() 27 | 28 | self.input_size = input_size 29 | self.mode = mode 30 | 31 | vectors = [torch.arange(0, s) for s in self.input_size] 32 | grids = torch.meshgrid(vectors, indexing='ij') 33 | grid = torch.stack(grids) 34 | grid = torch.unsqueeze(grid, 0) 35 | grid = grid.type(torch.FloatTensor) 36 | 37 | self.register_buffer('grid', grid) 38 | 39 | def forward(self, src, flow): 40 | new_locs = self.grid + flow 41 | 42 | for i in range(len(self.input_size)): 43 | new_locs[:, i, ...] = 2 * (new_locs[:, i, ...] / (self.input_size[i] - 1) - 0.5) 44 | 45 | new_locs = new_locs.permute(0, 2, 3, 1) 46 | new_locs = new_locs[..., [1, 0]] 47 | 48 | return F.grid_sample(src, new_locs, mode=self.mode, align_corners=True) 49 | 50 | 51 | class UNet(nn.Module): 52 | 53 | def __init__(self, in_channels, out_channels=None, batchnorm=True): 54 | super().__init__() 55 | 56 | if out_channels is None: 57 | out_channels = in_channels 58 | 59 | self.encoder = nn.Sequential( 60 | nn.Conv2d(in_channels, 16, 3, 1, 1), 61 | nn.BatchNorm2d(16) if batchnorm else nn.Identity(), 62 | nn.ELU(), 63 | nn.Conv2d(16, 16, 3, 1, 1), 64 | nn.BatchNorm2d(16) if batchnorm else nn.Identity(), 65 | nn.ELU(), 66 | nn.MaxPool2d(2), 67 | nn.Conv2d(16, 32, 3, 1, 1), 68 | nn.BatchNorm2d(32) if batchnorm else nn.Identity(), 69 | nn.ELU(), 70 | nn.Conv2d(32, 32, 3, 1, 1), 71 | nn.BatchNorm2d(32) if batchnorm else nn.Identity(), 72 | nn.ELU(), 73 | nn.MaxPool2d(2), 74 | nn.Conv2d(32, 64, 3, 1, 1), 75 | nn.BatchNorm2d(64) if batchnorm else nn.Identity(), 76 | nn.ELU(), 77 | nn.Conv2d(64, 64, 3, 1, 1), 78 | nn.BatchNorm2d(64) if batchnorm else nn.Identity(), 79 | nn.ELU(), 80 | nn.MaxPool2d(2), 81 | nn.Conv2d(64, 128, 3, 1, 1), 82 | nn.BatchNorm2d(128) if batchnorm else nn.Identity(), 83 | nn.ELU(), 84 | nn.Conv2d(128, 128, 3, 1, 1), 85 | nn.BatchNorm2d(128) if batchnorm else nn.Identity(), 86 | nn.ELU(), 87 | nn.Dropout(p=0.5) 88 | ) 89 | 90 | self.decoder = nn.Sequential( 91 | nn.Upsample(scale_factor=2, mode='nearest'), 92 | nn.Conv2d(128, 64, 3, 1, 1), 93 | nn.BatchNorm2d(64) if batchnorm else nn.Identity(), 94 | nn.ELU(), 95 | nn.Conv2d(64, 64, 3, 1, 1), 96 | nn.BatchNorm2d(64) if batchnorm else nn.Identity(), 97 | nn.ELU(), 98 | nn.Conv2d(64, 64, 3, 1, 1), 99 | nn.BatchNorm2d(64) if batchnorm else nn.Identity(), 100 | nn.ELU(), 101 | nn.Dropout(p=0.5), 102 | nn.Upsample(scale_factor=2, mode='nearest'), 103 | nn.Conv2d(64, 32, 3, 1, 1), 104 | nn.BatchNorm2d(32) if batchnorm else nn.Identity(), 105 | nn.ELU(), 106 | nn.Conv2d(32, 32, 3, 1, 1), 107 | nn.BatchNorm2d(32) if batchnorm else nn.Identity(), 108 | nn.ELU(), 109 | nn.Conv2d(32, 32, 3, 1, 1), 110 | nn.BatchNorm2d(32) if batchnorm else nn.Identity(), 111 | nn.ELU(), 112 | nn.Dropout(p=0.5), 113 | nn.Upsample(scale_factor=2, mode='nearest'), 114 | nn.Conv2d(32, 16, 3, 1, 1), 115 | nn.BatchNorm2d(16) if batchnorm else nn.Identity(), 116 | nn.ELU(), 117 | nn.Conv2d(16, 16, 3, 1, 1), 118 | nn.BatchNorm2d(16) if batchnorm else nn.Identity(), 119 | nn.ELU(), 120 | nn.Conv2d(16, 16, 3, 1, 1), 121 | nn.BatchNorm2d(16) if batchnorm else nn.Identity(), 122 | ) 123 | 124 | self.flow = nn.Conv2d(16, out_channels, 3, 1, 1) 125 | self.flow.weight = nn.Parameter(Normal(0, 1e-5).sample(self.flow.weight.shape)) 126 | self.flow.bias = nn.Parameter(torch.zeros(self.flow.bias.shape)) 127 | 128 | def forward(self, x): 129 | x = self.encoder(x) 130 | x = self.decoder(x) 131 | x = self.flow(x) 132 | 133 | return x 134 | -------------------------------------------------------------------------------- /TechnicalValidation/IndividualRCA/model/unet.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | import torch.nn.functional as F 4 | import numpy as np 5 | 6 | class residualBlock(nn.Module): 7 | def __init__(self, in_channels, out_channels, stride=1): 8 | """ 9 | Args: 10 | in_channels (int): Number of input channels. 11 | out_channels (int): Number of output channels. 12 | stride (int): Controls the stride. 13 | """ 14 | super(residualBlock, self).__init__() 15 | 16 | self.skip = nn.Sequential() 17 | 18 | if stride != 1 or in_channels != out_channels: 19 | self.skip = nn.Sequential( 20 | nn.Conv2d(in_channels=in_channels, out_channels=out_channels, kernel_size=1, stride=stride, bias=False), 21 | nn.BatchNorm2d(out_channels, track_running_stats=False)) 22 | else: 23 | self.skip = None 24 | 25 | self.block = nn.Sequential(nn.BatchNorm2d(in_channels, track_running_stats=False), 26 | nn.ReLU(inplace=True), 27 | nn.Conv2d(in_channels, out_channels, 3, padding=1), 28 | nn.BatchNorm2d(out_channels, track_running_stats=False), 29 | nn.ReLU(inplace=True), 30 | nn.Conv2d(out_channels, out_channels, 3, padding=1) 31 | ) 32 | 33 | def forward(self, x): 34 | identity = x 35 | out = self.block(x) 36 | 37 | if self.skip is not None: 38 | identity = self.skip(x) 39 | 40 | out += identity 41 | out = F.relu(out) 42 | 43 | return out 44 | 45 | class UNet(nn.Module): 46 | def __init__(self, c = 4, n_classes = 4): 47 | super(UNet, self).__init__() 48 | 49 | self.c = c 50 | 51 | size = self.c * np.array([2,4,8,16,32], dtype = np.intc) 52 | 53 | self.maxpool = nn.MaxPool2d(2) 54 | 55 | self.dconv_down1 = residualBlock(1, size[0]) 56 | self.dconv_down2 = residualBlock(size[0], size[1]) 57 | self.dconv_down3 = residualBlock(size[1], size[2]) 58 | self.dconv_down4 = residualBlock(size[2], size[3]) 59 | self.dconv_down5 = residualBlock(size[3], size[4]) 60 | 61 | self.bottleneck = residualBlock(size[4], size[4]) 62 | 63 | self.upsample = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=True) 64 | 65 | self.dconv_up5 = residualBlock(2 * size[4] , size[4]) 66 | self.dconv_up4 = residualBlock(size[4] + size[3] , size[3]) 67 | self.dconv_up3 = residualBlock(size[3] + size[2], size[2]) 68 | self.dconv_up2 = residualBlock(size[2] + size[1], size[1]) 69 | self.dconv_up1 = residualBlock(size[1] + size[0], size[0]) 70 | self.conv_last = nn.Conv2d(size[0], n_classes, 1) 71 | 72 | 73 | def forward(self, x): 74 | conv1 = self.dconv_down1(x) 75 | x = self.maxpool(conv1) 76 | 77 | conv2 = self.dconv_down2(x) 78 | x = self.maxpool(conv2) 79 | 80 | conv3 = self.dconv_down3(x) 81 | x = self.maxpool(conv3) 82 | 83 | conv4 = self.dconv_down4(x) 84 | x = self.maxpool(conv4) 85 | 86 | conv5 = self.dconv_down5(x) 87 | x = self.maxpool(conv5) 88 | 89 | self.bottle = self.bottleneck(x) 90 | 91 | x = self.upsample(self.bottle) 92 | x = torch.cat((x, conv5), dim=1) 93 | x = self.dconv_up5(x) 94 | 95 | x = self.upsample(x) 96 | x = torch.cat((x, conv4), dim=1) 97 | x = self.dconv_up4(x) 98 | 99 | x = self.upsample(x) 100 | x = torch.cat((x, conv3), dim=1) 101 | x = self.dconv_up3(x) 102 | 103 | x = self.upsample(x) 104 | x = torch.cat((x, conv2), dim=1) 105 | x = self.dconv_up2(x) 106 | 107 | x = self.upsample(x) 108 | x = torch.cat((x, conv1), dim=1) 109 | x = self.dconv_up1(x) 110 | 111 | out = self.conv_last(x) 112 | 113 | return out 114 | 115 | 116 | def one_hot(labels: torch.Tensor, 117 | num_classes: int, 118 | device: torch.device = None, 119 | dtype: torch.dtype = None, 120 | eps: float = 1e-6) -> torch.Tensor: 121 | r"""Converts an integer label 2D tensor to a one-hot 3D tensor. 122 | 123 | Args: 124 | labels (torch.Tensor) : tensor with labels of shape :math:`(N, H, W)`, 125 | where N is batch siz. Each value is an integer 126 | representing correct classification. 127 | num_classes (int): number of classes in labels. 128 | device (Optional[torch.device]): the desired device of returned tensor. 129 | Default: if None, uses the current device for the default tensor type 130 | (see torch.set_default_tensor_type()). device will be the CPU for CPU 131 | tensor types and the current CUDA device for CUDA tensor types. 132 | dtype (Optional[torch.dtype]): the desired data type of returned 133 | tensor. Default: if None, infers data type from values. 134 | 135 | Returns: 136 | torch.Tensor: the labels in one hot tensor. 137 | 138 | Examples:: 139 | >>> labels = torch.LongTensor([[[0, 1], [2, 0]]]) 140 | >>> tgm.losses.one_hot(labels, num_classes=3) 141 | tensor([[[[1., 0.], 142 | [0., 1.]], 143 | [[0., 1.], 144 | [0., 0.]], 145 | [[0., 0.], 146 | [1., 0.]]]] 147 | """ 148 | if not torch.is_tensor(labels): 149 | raise TypeError("Input labels type is not a torch.Tensor. Got {}" 150 | .format(type(labels))) 151 | if not len(labels.shape) == 3: 152 | raise ValueError("Invalid depth shape, we expect BxHxW. Got: {}" 153 | .format(labels.shape)) 154 | if not labels.dtype == torch.int64: 155 | raise ValueError( 156 | "labels must be of the same dtype torch.int64. Got: {}" .format( 157 | labels.dtype)) 158 | if num_classes < 1: 159 | raise ValueError("The number of classes must be bigger than one." 160 | " Got: {}".format(num_classes)) 161 | batch_size, height, width = labels.shape 162 | one_hot = torch.zeros(batch_size, num_classes, height, width, 163 | device=device, dtype=dtype) 164 | return one_hot.scatter_(1, labels.unsqueeze(1), 1.0) + eps 165 | 166 | 167 | class DiceLoss(nn.Module): 168 | r"""Criterion that computes Sørensen-Dice Coefficient loss. 169 | 170 | According to [1], we compute the Sørensen-Dice Coefficient as follows: 171 | 172 | .. math:: 173 | 174 | \text{Dice}(x, class) = \frac{2 |X| \cap |Y|}{|X| + |Y|} 175 | 176 | where: 177 | - :math:`X` expects to be the scores of each class. 178 | - :math:`Y` expects to be the one-hot tensor with the class labels. 179 | 180 | the loss, is finally computed as: 181 | 182 | .. math:: 183 | 184 | \text{loss}(x, class) = 1 - \text{Dice}(x, class) 185 | 186 | [1] https://en.wikipedia.org/wiki/S%C3%B8rensen%E2%80%93Dice_coefficient 187 | 188 | Shape: 189 | - Input: :math:`(N, C, H, W)` where C = number of classes. 190 | - Target: :math:`(N, H, W)` where each value is 191 | :math:`0 ≤ targets[i] ≤ C−1`. 192 | 193 | Examples: 194 | >>> N = 5 # num_classes 195 | >>> loss = tgm.losses.DiceLoss() 196 | >>> input = torch.randn(1, N, 3, 5, requires_grad=True) 197 | >>> target = torch.empty(1, 3, 5, dtype=torch.long).random_(N) 198 | >>> output = loss(input, target) 199 | >>> output.backward() 200 | """ 201 | 202 | def __init__(self) -> None: 203 | super(DiceLoss, self).__init__() 204 | self.eps: float = 1e-6 205 | 206 | def forward( 207 | self, 208 | input: torch.Tensor, 209 | target: torch.Tensor) -> torch.Tensor: 210 | if not torch.is_tensor(input): 211 | raise TypeError("Input type is not a torch.Tensor. Got {}" 212 | .format(type(input))) 213 | if not len(input.shape) == 4: 214 | raise ValueError("Invalid input shape, we expect BxNxHxW. Got: {}" 215 | .format(input.shape)) 216 | if not input.shape[-2:] == target.shape[-2:]: 217 | raise ValueError("input and target shapes must be the same. Got: {}" 218 | .format(input.shape, input.shape)) 219 | if not input.device == target.device: 220 | raise ValueError( 221 | "input and target must be in the same device. Got: {}" .format( 222 | input.device, target.device)) 223 | # compute softmax over the classes axis 224 | input_soft = F.softmax(input, dim=1) 225 | 226 | # create the labels one hot tensor 227 | target_one_hot = one_hot(target, num_classes=input.shape[1], 228 | device=input.device, dtype=input.dtype) 229 | 230 | # compute the actual dice score 231 | dims = (1, 2, 3) 232 | intersection = torch.sum(input_soft * target_one_hot, dims) 233 | cardinality = torch.sum(input_soft + target_one_hot, dims) 234 | 235 | dice_score = 2. * intersection / (cardinality + self.eps) 236 | return torch.mean(1. - dice_score) 237 | 238 | 239 | 240 | class OneClassDiceLoss(nn.Module): 241 | def __init__(self) -> None: 242 | super(OneClassDiceLoss, self).__init__() 243 | self.eps: float = 1e-6 244 | 245 | def forward( 246 | self, 247 | input: torch.Tensor, 248 | target: torch.Tensor) -> torch.Tensor: 249 | if not torch.is_tensor(input): 250 | raise TypeError("Input type is not a torch.Tensor. Got {}" 251 | .format(type(input))) 252 | if not input.shape[-2:] == target.shape[-2:]: 253 | raise ValueError("input and target shapes must be the same. Got: {}" 254 | .format(input.shape, input.shape)) 255 | if not input.device == target.device: 256 | raise ValueError( 257 | "input and target must be in the same device. Got: {}" .format( 258 | input.device, target.device)) 259 | 260 | # compute the actual dice score 261 | dims = (1, 2) 262 | intersection = torch.sum(input * target, dims) 263 | cardinality = torch.sum(input + target, dims) 264 | 265 | dice_score = 2. * intersection / (cardinality + self.eps) 266 | return torch.mean(1. - dice_score) -------------------------------------------------------------------------------- /TechnicalValidation/IndividualRCA/test_images_heart.txt: -------------------------------------------------------------------------------- 1 | 131648682133615188228601432675633412320_yw4cvh.png 2 | 112178367511062740933520645402062866244_5sqjdi.png 3 | 186710864282628233004220911541749949393_sb4ype.png 4 | 10996416492353037588312781035930080694_8rstz0.png 5 | 162648608648453851477134030640090786312_97dr7j.png 6 | 86236188012565981583483399251510590408_2_vw97dk.png 7 | 134230638227943448483430779879455672678_nkryka.png 8 | 199072368172967010909636348828886390156_co5vik.png 9 | 28679939396004151795585406073042323038_psrr3m.png 10 | 182153357033863986819477081579951131676_yetgon.png 11 | 278147989796380509519073707235611243961_lgo9hx.png 12 | 287674558154154196238750035876376456720_de0kdu.png 13 | 97600927297254707513662656394991173027_0k06p2.png 14 | 137967645291829623589025104429037566245_ty0ua4.png 15 | 221516731243657777508708160092801128398_d8pxyz.png 16 | 18104280246346700713752899198427570737_ezdqyq.png 17 | 228505125625411655559042398552685461655_cvearx.png 18 | 245895219126534788779109786698958326807_1a9ait.png 19 | 108392178527872886183110835574345380047_dc2agp.png 20 | 121369866312829737404259612004787672904_od40ao.png 21 | 224904318374059042978778904037988750967_dbs3bj.png 22 | 241628199575537995884366376583690323975_tgclgn.png 23 | 335389077904052440696851091734860414013_2ukkfm.png 24 | 163458460465612645961271966254638198735_8tlhrq.png 25 | 287658295007948631772703202498743716060_3yiz93.png 26 | 131603548504748381186066883008735293279_x4x4ls.png 27 | 62774794894109549387630855543283311955_9jbiyo.png 28 | JPCLN094.png 29 | JPCLN118.png 30 | JPCNN007.png 31 | JPCLN147.png 32 | JPCLN139.png 33 | JPCLN011.png 34 | JPCNN005.png 35 | JPCNN084.png 36 | JPCNN010.png 37 | JPCNN087.png 38 | JPCNN048.png 39 | JPCNN036.png 40 | JPCNN055.png 41 | JPCLN055.png 42 | JPCLN137.png 43 | JPCNN044.png 44 | JPCLN045.png 45 | JPCLN071.png 46 | JPCLN110.png 47 | JPCLN081.png 48 | JPCNN021.png 49 | JPCLN152.png 50 | JPCLN100.png 51 | JPCLN012.png 52 | JPCNN035.png 53 | JPCNN017.png 54 | JPCNN074.png 55 | JPCLN089.png 56 | JPCLN037.png 57 | JPCLN072.png 58 | JPCLN008.png 59 | JPCLN153.png 60 | JPCLN097.png 61 | JPCLN044.png 62 | JPCNN062.png 63 | JPCLN041.png 64 | JPCLN140.png 65 | JPCLN109.png 66 | JPCLN096.png 67 | JPCLN136.png 68 | JPCLN138.png 69 | JPCNN061.png 70 | JPCLN117.png 71 | JPCLN026.png 72 | JPCNN057.png 73 | JPCLN114.png 74 | JPCLN068.png 75 | JPCNN008.png 76 | JPCNN072.png 77 | -------------------------------------------------------------------------------- /TechnicalValidation/IndividualRCA/test_images_lungs.txt: -------------------------------------------------------------------------------- 1 | 111018930978236771390670303639566239985_1bnarb.png 2 | 159364525876157332485573893353941089455_aepmh7.png 3 | 10996416492353037588312781035930080694_8rstz0.png 4 | 145698282304700116300882843111967537977_f6qhtv.png 5 | 127776313362848303557368271134995152837_apvrf1.png 6 | 103416378058309979932405295235813040436_1g2pmw.png 7 | 278147989796380509519073707235611243961_lgo9hx.png 8 | 245895219126534788779109786698958326807_1a9ait.png 9 | 47513502650936350354964244779459913004-3_th1gik.png 10 | 182153357033863986819477081579951131676_yetgon.png 11 | 146381384591357414868726401545939282660_96eirs.png 12 | 111016754890854009969250427027267219881_wkp0ya.png 13 | 282084762366059076387208905274707563447_d4ptrb.png 14 | 64947154984935334365130497226051564059_piq56n.png 15 | 173480850478104643017119358503404014196_qey2vk.png 16 | 117922406082772720514160287292487390144_3qnpe6.png 17 | 112456448066531979951054695594530500685_wrc6wl.png 18 | 114041240061582113028394479929792704149_u7xmxu.png 19 | 10155709300728342918543955138521808206_f7cj92.png 20 | 327043651451092435505761971551575640871_3qwimg.png 21 | 1256842362861431725328351539259305635_u1qifz.png 22 | 289108620230468119174968226927683707287_9wupg2.png 23 | 199072368172967010909636348828886390156_co5vik.png 24 | 257475948814337233640540177569885227207_jaj8iw.png 25 | 126455653911678893263056441159987922516_uz7fwy.png 26 | 287658295007948631772703202498743716060_3yiz93.png 27 | 131603548504748381186066883008735293279_x4x4ls.png 28 | CHNCXR_0411_1.png 29 | CHNCXR_0103_0.png 30 | CHNCXR_0225_0.png 31 | CHNCXR_0263_0.png 32 | CHNCXR_0627_1.png 33 | CHNCXR_0077_0.png 34 | CHNCXR_0165_0.png 35 | CHNCXR_0390_1.png 36 | CHNCXR_0416_1.png 37 | CHNCXR_0247_0.png 38 | CHNCXR_0599_1.png 39 | CHNCXR_0172_0.png 40 | CHNCXR_0659_1.png 41 | CHNCXR_0420_1.png 42 | CHNCXR_0100_0.png 43 | CHNCXR_0426_1.png 44 | CHNCXR_0625_1.png 45 | CHNCXR_0440_1.png 46 | CHNCXR_0370_1.png 47 | CHNCXR_0261_0.png 48 | CHNCXR_0604_1.png 49 | CHNCXR_0628_1.png 50 | CHNCXR_0137_0.png 51 | CHNCXR_0173_0.png 52 | CHNCXR_0595_1.png 53 | CHNCXR_0098_0.png 54 | CHNCXR_0366_1.png 55 | CHNCXR_0005_0.png 56 | CHNCXR_0567_1.png 57 | CHNCXR_0073_0.png 58 | CHNCXR_0044_0.png 59 | CHNCXR_0582_1.png 60 | CHNCXR_0392_1.png 61 | CHNCXR_0655_1.png 62 | CHNCXR_0639_1.png 63 | CHNCXR_0307_0.png 64 | CHNCXR_0597_1.png 65 | CHNCXR_0529_1.png 66 | CHNCXR_0380_1.png 67 | CHNCXR_0387_1.png 68 | CHNCXR_0282_0.png 69 | CHNCXR_0452_1.png 70 | CHNCXR_0394_1.png 71 | CHNCXR_0117_0.png 72 | CHNCXR_0521_1.png 73 | CHNCXR_0543_1.png 74 | CHNCXR_0536_1.png 75 | CHNCXR_0585_1.png 76 | CHNCXR_0480_1.png 77 | CHNCXR_0640_1.png 78 | CHNCXR_0068_0.png 79 | CHNCXR_0465_1.png 80 | CHNCXR_0544_1.png 81 | CHNCXR_0048_0.png 82 | CHNCXR_0149_0.png 83 | CHNCXR_0266_0.png 84 | CHNCXR_0130_0.png 85 | CHNCXR_0572_1.png 86 | CHNCXR_0280_0.png 87 | CHNCXR_0651_1.png 88 | CHNCXR_0534_1.png 89 | CHNCXR_0155_0.png 90 | CHNCXR_0650_1.png 91 | CHNCXR_0229_0.png 92 | CHNCXR_0617_1.png 93 | CHNCXR_0113_0.png 94 | CHNCXR_0088_0.png 95 | CHNCXR_0159_0.png 96 | CHNCXR_0504_1.png 97 | CHNCXR_0180_0.png 98 | CHNCXR_0573_1.png 99 | CHNCXR_0378_1.png 100 | CHNCXR_0141_0.png 101 | CHNCXR_0328_1.png 102 | CHNCXR_0404_1.png 103 | CHNCXR_0648_1.png 104 | CHNCXR_0045_0.png 105 | CHNCXR_0176_0.png 106 | JPCNN081.png 107 | JPCNN059.png 108 | JPCLN131.png 109 | JPCNN087.png 110 | JPCNN037.png 111 | JPCNN056.png 112 | JPCNN027.png 113 | JPCLN119.png 114 | JPCLN139.png 115 | JPCLN022.png 116 | JPCLN030.png 117 | JPCLN133.png 118 | JPCLN050.png 119 | JPCLN051.png 120 | JPCLN103.png 121 | JPCLN031.png 122 | JPCLN114.png 123 | JPCLN004.png 124 | JPCNN061.png 125 | JPCLN146.png 126 | JPCLN010.png 127 | JPCNN041.png 128 | JPCLN017.png 129 | JPCLN151.png 130 | JPCLN058.png 131 | JPCNN079.png 132 | JPCLN060.png 133 | JPCNN008.png 134 | JPCLN138.png 135 | JPCLN065.png 136 | JPCNN060.png 137 | JPCNN009.png 138 | JPCLN048.png 139 | JPCLN095.png 140 | JPCNN093.png 141 | JPCNN074.png 142 | JPCNN086.png 143 | JPCLN152.png 144 | JPCNN012.png 145 | JPCNN063.png 146 | JPCNN043.png 147 | JPCLN090.png 148 | JPCLN071.png 149 | JPCLN147.png 150 | JPCLN067.png 151 | JPCNN070.png 152 | JPCLN024.png 153 | JPCLN105.png 154 | JPCLN032.png 155 | MCUCXR_0195_1.png 156 | MCUCXR_0294_1.png 157 | MCUCXR_0057_0.png 158 | MCUCXR_0068_0.png 159 | MCUCXR_0053_0.png 160 | MCUCXR_0002_0.png 161 | MCUCXR_0020_0.png 162 | MCUCXR_0052_0.png 163 | MCUCXR_0126_1.png 164 | MCUCXR_0062_0.png 165 | MCUCXR_0069_0.png 166 | MCUCXR_0022_0.png 167 | MCUCXR_0313_1.png 168 | MCUCXR_0369_1.png 169 | MCUCXR_0218_1.png 170 | MCUCXR_0170_1.png 171 | MCUCXR_0038_0.png 172 | MCUCXR_0004_0.png 173 | MCUCXR_0275_1.png 174 | MCUCXR_0289_1.png 175 | MCUCXR_0352_1.png 176 | MCUCXR_0089_0.png 177 | MCUCXR_0056_0.png 178 | MCUCXR_0150_1.png 179 | MCUCXR_0102_0.png 180 | MCUCXR_0101_0.png 181 | MCUCXR_0362_1.png 182 | -------------------------------------------------------------------------------- /TechnicalValidation/IndividualRCA/train_images_heart.txt: -------------------------------------------------------------------------------- 1 | 23519598294167460669966745319398153772_4fvhwr.png 2 | 269577503052282080020245006456074886585_yqub0o.png 3 | 159216970546428043543933950339353602394_t0rgza.png 4 | 185103609082807172741758831509642702963_w5om81.png 5 | 17517381147706809156163698942582418325_tutfbr.png 6 | 131241147536242919540082434604973143614_3wsmbz.png 7 | 30661727075761817007267292459310975718_86nsuj.png 8 | 115733055953927986967208164376379449432_li9tme.png 9 | 103416378058309979932405295235813040436_1g2pmw.png 10 | 100469495785351489872749036114751610212_rfyvv7.png 11 | 84856267960116899671078402865134921840_j5zwtq.png 12 | 145769364607358984473800346357899635518_w3mh7f.png 13 | 293237814606131965920904052737398761929_wo83it.png 14 | 234342142542575223651664904935164209557_57uem0.png 15 | 114041240061582113028394479929792704149_u7xmxu.png 16 | 147036015370488755117195440122198442935_gj6nyv.png 17 | 17871983995291973291210205507121602296_hd8ger.png 18 | 111524717177122678951114473577277787051_kt9www.png 19 | 145698282304700116300882843111967537977_f6qhtv.png 20 | 87180314458623865269977662447356279507_tcw56c.png 21 | 137388819814035428537164273550346941304_uaixht.png 22 | 145427013841140062478220774079107134701-2_yq5war.png 23 | 204422322492048939194499080859737112307_pjmtqu.png 24 | 289108620230468119174968226927683707287_9wupg2.png 25 | 155691603412384342537901614325751648892_2x8184.png 26 | 16061525395627866800451108188666498463_f9mrv9.png 27 | 174600210814713315828954104869955154340_y7nqyx.png 28 | 160327085026839567388469656243162554002_4rz0zs.png 29 | 145987248740080156691960355717233717054_fbjstn.png 30 | 313697209576187055457395864503092360851_8joobi.png 31 | 309362609894399458585188858667076734340_312chn.png 32 | 177242461410205305911459438621785113319_wxnhs2.png 33 | 175993948212683701562003497511214331387_sr16co.png 34 | 121982858364557665434391755127892214185_1e27fz.png 35 | 257475948814337233640540177569885227207_jaj8iw.png 36 | 126455653911678893263056441159987922516_uz7fwy.png 37 | 107826458213940261956471276912177567239_9467w9.png 38 | 266463130540256183061556297994476560538_jkbs4h.png 39 | 304673909503207424801802419702123736315_vyckk1.png 40 | 24716339483627393401392199056832608433_wtt5qq.png 41 | 108757234707826731622534463166732014399_zneaoc.png 42 | 47557396307760215809300057577936592627_m7mq32.png 43 | 239328921181741075231340728462151367739_z8w503.png 44 | 176139149165255627395732438927565115933_2_cvhkvs.png 45 | 229647061532869405127486256298724729451_o2d0lv.png 46 | 110057948387370363009458390264986734403_myst7q.png 47 | 150925806630506707155140197611651038951_506rh8.png 48 | 255685135940885410514594136492164638096_m86wzw.png 49 | 173480850478104643017119358503404014196_qey2vk.png 50 | 220991425902150564296792633654689602163_hrvmt4.png 51 | 325164322016612295040208415099913503835_djqek1.png 52 | 62251368659414281768550499840080640229_8jw2hb.png 53 | 49538570955854652971715060419437210854_qfxazj.png 54 | 174670802714657222439280962872741263127_7fupbg.png 55 | 69949397621405752847745986326314413051_wai6lj.png 56 | 111018930978236771390670303639566239985_1bnarb.png 57 | 282084762366059076387208905274707563447_d4ptrb.png 58 | 153727633750702336351810492317390365896_iycqix.png 59 | 111016754890854009969250427027267219881_wkp0ya.png 60 | 62440969118040059965044820734401488887_6hzwt0.png 61 | 127193766790524852458047964184829087241_klomkd.png 62 | 137978876620819180655083942883406964928_so8cty.png 63 | 104055072637907464008767269203861924017_huh41k.png 64 | 10287653421930576798556842610982533460_vpbhw6.png 65 | 63510470621460583865307590457045621750_wfd1y4.png 66 | 167426234824700370197428233814927580454_mgkykn.png 67 | 292918128360817403765919130571877152208_3ibhhb.png 68 | 33728467384543072824771678110722875201_f9qa0p.png 69 | 10155709300728342918543955138521808206_f7cj92.png 70 | 242212504721475153281566846983009162353_1v0l8r.png 71 | 146381384591357414868726401545939282660_96eirs.png 72 | 13353724432735380699905228693882625716_1tbyf9.png 73 | 47513502650936350354964244779459913004-3_th1gik.png 74 | 26740916849083948015827135783885268528_e8uwfn.png 75 | 33669092406557001556710892720325153219_eq21sh.png 76 | 129242675756041973355687068645321283308_ufajm6.png 77 | 207601568257371629456571671613073108105_rd3vig.png 78 | 147856697855406216911500275209241729282_u5p8bx.png 79 | 10383960670432673238945376919735423432_hd3moq.png 80 | 119688533980562249677219589534851011987_60uvre.png 81 | 112456448066531979951054695594530500685_wrc6wl.png 82 | 133117830164386750221716087231377502440_w4gp1z.png 83 | 91815015110608941073755910413288028905_kwjolc.png 84 | 306454472377761167397878598740821355166_kvraex.png 85 | 251817752826726742233508350809968475767_o1z8dd.png 86 | 15606713623657301981614912219511809416_2_94v295.png 87 | 242159935290547449788910895844948743899_pxrrfs.png 88 | 232572031807344965043258496995347066691_g4zxoq.png 89 | 26740916849083948015827135783885268528-2_6hkbx5.png 90 | 120641250378848817226599591125249193787_1vwu72.png 91 | 327043651451092435505761971551575640871_3qwimg.png 92 | 307400577352691476839033094654629005292_2_eyg2tu.png 93 | 89871789430838637694883774745784638013_y2t9e8.png 94 | 117503487105610561494614672580071778723_egzn5w.png 95 | 127776313362848303557368271134995152837_apvrf1.png 96 | 106111337169988693038323274011777746837_0yin7u.png 97 | 159364525876157332485573893353941089455_aepmh7.png 98 | 208665949772531573966572593951616806531_n8qw25.png 99 | 1256842362861431725328351539259305635_u1qifz.png 100 | 157943178667978315593423977854134065828_w1j177.png 101 | 278781644981372306896738110447513784487_2z4xsa.png 102 | 115252833781991791197512509423446047187_pnh4fu.png 103 | 117922406082772720514160287292487390144_3qnpe6.png 104 | 296076958641695785561110249259065599111_tfzq7g.png 105 | 235853514537661378209836459660303802572_-bl64a.png 106 | 62774794894109549387630855543283311955_2_tdirok.png 107 | 152733907856972127971985724804321889809_u7ghw8.png 108 | 316310243794898186923644098315438248232_5jyw88.png 109 | 284353054075043225622260270287627142906_kduagg.png 110 | 64947154984935334365130497226051564059_piq56n.png 111 | JPCLN075.png 112 | JPCLN009.png 113 | JPCNN064.png 114 | JPCLN051.png 115 | JPCLN131.png 116 | JPCLN112.png 117 | JPCLN082.png 118 | JPCNN011.png 119 | JPCLN108.png 120 | JPCLN142.png 121 | JPCNN091.png 122 | JPCNN027.png 123 | JPCLN015.png 124 | JPCNN034.png 125 | JPCLN084.png 126 | JPCNN043.png 127 | JPCLN050.png 128 | JPCNN052.png 129 | JPCLN022.png 130 | JPCLN145.png 131 | JPCLN047.png 132 | JPCLN035.png 133 | JPCNN050.png 134 | JPCLN098.png 135 | JPCNN003.png 136 | JPCNN053.png 137 | JPCLN132.png 138 | JPCNN090.png 139 | JPCNN082.png 140 | JPCNN001.png 141 | JPCLN060.png 142 | JPCLN087.png 143 | JPCNN080.png 144 | JPCNN069.png 145 | JPCNN073.png 146 | JPCLN007.png 147 | JPCLN061.png 148 | JPCNN054.png 149 | JPCNN078.png 150 | JPCLN064.png 151 | JPCLN006.png 152 | JPCLN019.png 153 | JPCLN135.png 154 | JPCNN041.png 155 | JPCLN043.png 156 | JPCLN034.png 157 | JPCLN069.png 158 | JPCLN062.png 159 | JPCLN151.png 160 | JPCLN099.png 161 | JPCLN004.png 162 | JPCNN071.png 163 | JPCLN028.png 164 | JPCNN020.png 165 | JPCLN003.png 166 | JPCLN057.png 167 | JPCLN070.png 168 | JPCLN025.png 169 | JPCNN047.png 170 | JPCNN004.png 171 | JPCNN068.png 172 | JPCNN065.png 173 | JPCLN146.png 174 | JPCLN149.png 175 | JPCNN049.png 176 | JPCLN126.png 177 | JPCLN053.png 178 | JPCNN092.png 179 | JPCLN021.png 180 | JPCLN024.png 181 | JPCLN090.png 182 | JPCLN111.png 183 | JPCLN141.png 184 | JPCLN148.png 185 | JPCLN013.png 186 | JPCNN063.png 187 | JPCLN038.png 188 | JPCNN040.png 189 | JPCLN039.png 190 | JPCNN023.png 191 | JPCLN030.png 192 | JPCNN083.png 193 | JPCLN018.png 194 | JPCLN067.png 195 | JPCLN143.png 196 | JPCNN016.png 197 | JPCNN060.png 198 | JPCLN033.png 199 | JPCLN040.png 200 | JPCLN095.png 201 | JPCNN013.png 202 | JPCLN079.png 203 | JPCLN102.png 204 | JPCLN056.png 205 | JPCLN058.png 206 | JPCNN037.png 207 | JPCLN154.png 208 | JPCLN101.png 209 | JPCLN125.png 210 | JPCLN127.png 211 | JPCLN054.png 212 | JPCNN081.png 213 | JPCNN030.png 214 | JPCLN103.png 215 | JPCNN009.png 216 | JPCNN031.png 217 | JPCNN046.png 218 | JPCLN085.png 219 | JPCNN029.png 220 | JPCNN025.png 221 | JPCLN023.png 222 | JPCLN002.png 223 | JPCNN070.png 224 | JPCLN078.png 225 | JPCNN085.png 226 | JPCLN092.png 227 | JPCNN086.png 228 | JPCLN083.png 229 | JPCLN086.png 230 | JPCNN026.png 231 | JPCNN093.png 232 | JPCLN032.png 233 | JPCNN058.png 234 | JPCNN079.png 235 | JPCLN074.png 236 | JPCNN002.png 237 | JPCLN119.png 238 | JPCNN042.png 239 | JPCLN046.png 240 | JPCNN089.png 241 | JPCNN028.png 242 | JPCLN063.png 243 | JPCLN122.png 244 | JPCLN052.png 245 | JPCLN065.png 246 | JPCLN027.png 247 | JPCLN014.png 248 | JPCLN116.png 249 | JPCNN012.png 250 | JPCNN038.png 251 | JPCLN076.png 252 | JPCLN073.png 253 | JPCLN123.png 254 | JPCNN018.png 255 | JPCNN051.png 256 | JPCLN017.png 257 | JPCLN129.png 258 | JPCLN088.png 259 | JPCLN120.png 260 | JPCNN076.png 261 | JPCLN124.png 262 | JPCLN107.png 263 | JPCNN059.png 264 | JPCNN022.png 265 | JPCLN077.png 266 | JPCNN088.png 267 | JPCNN015.png 268 | JPCNN045.png 269 | JPCNN066.png 270 | JPCNN056.png 271 | JPCLN010.png 272 | JPCLN113.png 273 | JPCLN150.png 274 | JPCNN014.png 275 | JPCLN091.png 276 | JPCNN067.png 277 | JPCNN039.png 278 | JPCLN115.png 279 | JPCNN077.png 280 | JPCLN031.png 281 | JPCNN033.png 282 | JPCLN104.png 283 | JPCLN134.png 284 | JPCLN128.png 285 | JPCLN049.png 286 | JPCLN121.png 287 | JPCNN006.png 288 | JPCLN059.png 289 | JPCLN130.png 290 | JPCLN144.png 291 | JPCLN066.png 292 | JPCLN093.png 293 | JPCNN075.png 294 | JPCLN133.png 295 | JPCLN020.png 296 | JPCNN032.png 297 | JPCLN042.png 298 | JPCNN024.png 299 | JPCNN019.png 300 | JPCLN001.png 301 | JPCLN106.png 302 | JPCLN080.png 303 | JPCLN048.png 304 | JPCLN029.png 305 | JPCLN005.png 306 | JPCLN105.png 307 | JPCLN036.png 308 | -------------------------------------------------------------------------------- /TechnicalValidation/IndividualRCA/train_images_lungs.txt: -------------------------------------------------------------------------------- 1 | 110057948387370363009458390264986734403_myst7q.png 2 | 204422322492048939194499080859737112307_pjmtqu.png 3 | 293237814606131965920904052737398761929_wo83it.png 4 | 108757234707826731622534463166732014399_zneaoc.png 5 | 325164322016612295040208415099913503835_djqek1.png 6 | 176139149165255627395732438927565115933_2_cvhkvs.png 7 | 129242675756041973355687068645321283308_ufajm6.png 8 | 266463130540256183061556297994476560538_jkbs4h.png 9 | 87180314458623865269977662447356279507_tcw56c.png 10 | 234342142542575223651664904935164209557_57uem0.png 11 | 112178367511062740933520645402062866244_5sqjdi.png 12 | 133117830164386750221716087231377502440_w4gp1z.png 13 | 313697209576187055457395864503092360851_8joobi.png 14 | 62774794894109549387630855543283311955_9jbiyo.png 15 | 17871983995291973291210205507121602296_hd8ger.png 16 | 221516731243657777508708160092801128398_d8pxyz.png 17 | 208665949772531573966572593951616806531_n8qw25.png 18 | 10287653421930576798556842610982533460_vpbhw6.png 19 | 186710864282628233004220911541749949393_sb4ype.png 20 | 251817752826726742233508350809968475767_o1z8dd.png 21 | 137967645291829623589025104429037566245_ty0ua4.png 22 | 306454472377761167397878598740821355166_kvraex.png 23 | 134230638227943448483430779879455672678_nkryka.png 24 | 228505125625411655559042398552685461655_cvearx.png 25 | 89871789430838637694883774745784638013_y2t9e8.png 26 | 121369866312829737404259612004787672904_od40ao.png 27 | 160327085026839567388469656243162554002_4rz0zs.png 28 | 13353724432735380699905228693882625716_1tbyf9.png 29 | 162648608648453851477134030640090786312_97dr7j.png 30 | 28679939396004151795585406073042323038_psrr3m.png 31 | 115252833781991791197512509423446047187_pnh4fu.png 32 | 255685135940885410514594136492164638096_m86wzw.png 33 | 269577503052282080020245006456074886585_yqub0o.png 34 | 131241147536242919540082434604973143614_3wsmbz.png 35 | 145427013841140062478220774079107134701-2_yq5war.png 36 | 137388819814035428537164273550346941304_uaixht.png 37 | 232572031807344965043258496995347066691_g4zxoq.png 38 | 33728467384543072824771678110722875201_f9qa0p.png 39 | 145987248740080156691960355717233717054_fbjstn.png 40 | 147036015370488755117195440122198442935_gj6nyv.png 41 | 174600210814713315828954104869955154340_y7nqyx.png 42 | 137978876620819180655083942883406964928_so8cty.png 43 | 119688533980562249677219589534851011987_60uvre.png 44 | 242212504721475153281566846983009162353_1v0l8r.png 45 | 296076958641695785561110249259065599111_tfzq7g.png 46 | 120641250378848817226599591125249193787_1vwu72.png 47 | 63510470621460583865307590457045621750_wfd1y4.png 48 | 26740916849083948015827135783885268528_e8uwfn.png 49 | 157943178667978315593423977854134065828_w1j177.png 50 | 152733907856972127971985724804321889809_u7ghw8.png 51 | 86236188012565981583483399251510590408_2_vw97dk.png 52 | 108392178527872886183110835574345380047_dc2agp.png 53 | 33669092406557001556710892720325153219_eq21sh.png 54 | 239328921181741075231340728462151367739_z8w503.png 55 | 167426234824700370197428233814927580454_mgkykn.png 56 | 100469495785351489872749036114751610212_rfyvv7.png 57 | 335389077904052440696851091734860414013_2ukkfm.png 58 | 17517381147706809156163698942582418325_tutfbr.png 59 | 97600927297254707513662656394991173027_0k06p2.png 60 | 69949397621405752847745986326314413051_wai6lj.png 61 | 10383960670432673238945376919735423432_hd3moq.png 62 | 242159935290547449788910895844948743899_pxrrfs.png 63 | 30661727075761817007267292459310975718_86nsuj.png 64 | 278781644981372306896738110447513784487_2z4xsa.png 65 | 23519598294167460669966745319398153772_4fvhwr.png 66 | 174670802714657222439280962872741263127_7fupbg.png 67 | 111524717177122678951114473577277787051_kt9www.png 68 | 175993948212683701562003497511214331387_sr16co.png 69 | 185103609082807172741758831509642702963_w5om81.png 70 | 292918128360817403765919130571877152208_3ibhhb.png 71 | 316310243794898186923644098315438248232_5jyw88.png 72 | 153727633750702336351810492317390365896_iycqix.png 73 | 150925806630506707155140197611651038951_506rh8.png 74 | 47557396307760215809300057577936592627_m7mq32.png 75 | 284353054075043225622260270287627142906_kduagg.png 76 | 224904318374059042978778904037988750967_dbs3bj.png 77 | 159216970546428043543933950339353602394_t0rgza.png 78 | 304673909503207424801802419702123736315_vyckk1.png 79 | 18104280246346700713752899198427570737_ezdqyq.png 80 | 131648682133615188228601432675633412320_yw4cvh.png 81 | 49538570955854652971715060419437210854_qfxazj.png 82 | 127193766790524852458047964184829087241_klomkd.png 83 | 220991425902150564296792633654689602163_hrvmt4.png 84 | 107826458213940261956471276912177567239_9467w9.png 85 | 26740916849083948015827135783885268528-2_6hkbx5.png 86 | 155691603412384342537901614325751648892_2x8184.png 87 | 307400577352691476839033094654629005292_2_eyg2tu.png 88 | 241628199575537995884366376583690323975_tgclgn.png 89 | 104055072637907464008767269203861924017_huh41k.png 90 | 106111337169988693038323274011777746837_0yin7u.png 91 | 121982858364557665434391755127892214185_1e27fz.png 92 | 229647061532869405127486256298724729451_o2d0lv.png 93 | 287674558154154196238750035876376456720_de0kdu.png 94 | 235853514537661378209836459660303802572_-bl64a.png 95 | 207601568257371629456571671613073108105_rd3vig.png 96 | 91815015110608941073755910413288028905_kwjolc.png 97 | 62774794894109549387630855543283311955_2_tdirok.png 98 | 62251368659414281768550499840080640229_8jw2hb.png 99 | 145769364607358984473800346357899635518_w3mh7f.png 100 | 24716339483627393401392199056832608433_wtt5qq.png 101 | 115733055953927986967208164376379449432_li9tme.png 102 | 84856267960116899671078402865134921840_j5zwtq.png 103 | 15606713623657301981614912219511809416_2_94v295.png 104 | 309362609894399458585188858667076734340_312chn.png 105 | 177242461410205305911459438621785113319_wxnhs2.png 106 | 62440969118040059965044820734401488887_6hzwt0.png 107 | 117503487105610561494614672580071778723_egzn5w.png 108 | 147856697855406216911500275209241729282_u5p8bx.png 109 | 16061525395627866800451108188666498463_f9mrv9.png 110 | 163458460465612645961271966254638198735_8tlhrq.png 111 | CHNCXR_0391_1.png 112 | CHNCXR_0645_1.png 113 | CHNCXR_0569_1.png 114 | CHNCXR_0096_0.png 115 | CHNCXR_0635_1.png 116 | CHNCXR_0070_0.png 117 | CHNCXR_0153_0.png 118 | CHNCXR_0305_0.png 119 | CHNCXR_0129_0.png 120 | CHNCXR_0626_1.png 121 | CHNCXR_0291_0.png 122 | CHNCXR_0539_1.png 123 | CHNCXR_0606_1.png 124 | CHNCXR_0054_0.png 125 | CHNCXR_0469_1.png 126 | CHNCXR_0146_0.png 127 | CHNCXR_0620_1.png 128 | CHNCXR_0583_1.png 129 | CHNCXR_0405_1.png 130 | CHNCXR_0255_0.png 131 | CHNCXR_0066_0.png 132 | CHNCXR_0605_1.png 133 | CHNCXR_0588_1.png 134 | CHNCXR_0124_0.png 135 | CHNCXR_0099_0.png 136 | CHNCXR_0325_0.png 137 | CHNCXR_0501_1.png 138 | CHNCXR_0274_0.png 139 | CHNCXR_0545_1.png 140 | CHNCXR_0055_0.png 141 | CHNCXR_0271_0.png 142 | CHNCXR_0478_1.png 143 | CHNCXR_0454_1.png 144 | CHNCXR_0607_1.png 145 | CHNCXR_0107_0.png 146 | CHNCXR_0455_1.png 147 | CHNCXR_0424_1.png 148 | CHNCXR_0427_1.png 149 | CHNCXR_0312_0.png 150 | CHNCXR_0570_1.png 151 | CHNCXR_0428_1.png 152 | CHNCXR_0654_1.png 153 | CHNCXR_0288_0.png 154 | CHNCXR_0035_0.png 155 | CHNCXR_0162_0.png 156 | CHNCXR_0593_1.png 157 | CHNCXR_0335_1.png 158 | CHNCXR_0661_1.png 159 | CHNCXR_0145_0.png 160 | CHNCXR_0522_1.png 161 | CHNCXR_0566_1.png 162 | CHNCXR_0538_1.png 163 | CHNCXR_0106_0.png 164 | CHNCXR_0537_1.png 165 | CHNCXR_0579_1.png 166 | CHNCXR_0456_1.png 167 | CHNCXR_0136_0.png 168 | CHNCXR_0283_0.png 169 | CHNCXR_0406_1.png 170 | CHNCXR_0589_1.png 171 | CHNCXR_0381_1.png 172 | CHNCXR_0042_0.png 173 | CHNCXR_0550_1.png 174 | CHNCXR_0031_0.png 175 | CHNCXR_0092_0.png 176 | CHNCXR_0558_1.png 177 | CHNCXR_0546_1.png 178 | CHNCXR_0314_0.png 179 | CHNCXR_0613_1.png 180 | CHNCXR_0475_1.png 181 | CHNCXR_0629_1.png 182 | CHNCXR_0018_0.png 183 | CHNCXR_0154_0.png 184 | CHNCXR_0297_0.png 185 | CHNCXR_0252_0.png 186 | CHNCXR_0277_0.png 187 | CHNCXR_0610_1.png 188 | CHNCXR_0477_1.png 189 | CHNCXR_0555_1.png 190 | CHNCXR_0581_1.png 191 | CHNCXR_0057_0.png 192 | CHNCXR_0602_1.png 193 | CHNCXR_0474_1.png 194 | CHNCXR_0383_1.png 195 | CHNCXR_0559_1.png 196 | CHNCXR_0577_1.png 197 | CHNCXR_0590_1.png 198 | CHNCXR_0177_0.png 199 | CHNCXR_0160_0.png 200 | CHNCXR_0615_1.png 201 | CHNCXR_0080_0.png 202 | CHNCXR_0460_1.png 203 | CHNCXR_0084_0.png 204 | CHNCXR_0227_0.png 205 | CHNCXR_0430_1.png 206 | CHNCXR_0421_1.png 207 | CHNCXR_0078_0.png 208 | CHNCXR_0388_1.png 209 | CHNCXR_0138_0.png 210 | CHNCXR_0633_1.png 211 | CHNCXR_0296_0.png 212 | CHNCXR_0260_0.png 213 | CHNCXR_0278_0.png 214 | CHNCXR_0086_0.png 215 | CHNCXR_0636_1.png 216 | CHNCXR_0132_0.png 217 | CHNCXR_0515_1.png 218 | CHNCXR_0384_1.png 219 | CHNCXR_0150_0.png 220 | CHNCXR_0575_1.png 221 | CHNCXR_0631_1.png 222 | CHNCXR_0301_0.png 223 | CHNCXR_0432_1.png 224 | CHNCXR_0262_0.png 225 | CHNCXR_0467_1.png 226 | CHNCXR_0618_1.png 227 | CHNCXR_0592_1.png 228 | CHNCXR_0443_1.png 229 | CHNCXR_0142_0.png 230 | CHNCXR_0049_0.png 231 | CHNCXR_0526_1.png 232 | CHNCXR_0300_0.png 233 | CHNCXR_0123_0.png 234 | CHNCXR_0445_1.png 235 | CHNCXR_0143_0.png 236 | CHNCXR_0257_0.png 237 | CHNCXR_0304_0.png 238 | CHNCXR_0164_0.png 239 | CHNCXR_0457_1.png 240 | CHNCXR_0552_1.png 241 | CHNCXR_0053_0.png 242 | CHNCXR_0110_0.png 243 | CHNCXR_0333_1.png 244 | CHNCXR_0556_1.png 245 | CHNCXR_0619_1.png 246 | CHNCXR_0093_0.png 247 | CHNCXR_0596_1.png 248 | CHNCXR_0322_0.png 249 | CHNCXR_0008_0.png 250 | CHNCXR_0308_0.png 251 | CHNCXR_0632_1.png 252 | CHNCXR_0166_0.png 253 | CHNCXR_0444_1.png 254 | CHNCXR_0479_1.png 255 | CHNCXR_0641_1.png 256 | CHNCXR_0158_0.png 257 | CHNCXR_0608_1.png 258 | CHNCXR_0647_1.png 259 | CHNCXR_0102_0.png 260 | CHNCXR_0157_0.png 261 | CHNCXR_0329_1.png 262 | CHNCXR_0133_0.png 263 | CHNCXR_0402_1.png 264 | CHNCXR_0064_0.png 265 | CHNCXR_0324_0.png 266 | CHNCXR_0611_1.png 267 | CHNCXR_0027_0.png 268 | CHNCXR_0642_1.png 269 | CHNCXR_0331_1.png 270 | CHNCXR_0245_0.png 271 | CHNCXR_0062_0.png 272 | CHNCXR_0074_0.png 273 | CHNCXR_0553_1.png 274 | CHNCXR_0470_1.png 275 | CHNCXR_0362_1.png 276 | CHNCXR_0365_1.png 277 | CHNCXR_0450_1.png 278 | CHNCXR_0095_0.png 279 | CHNCXR_0385_1.png 280 | CHNCXR_0527_1.png 281 | CHNCXR_0458_1.png 282 | CHNCXR_0151_0.png 283 | CHNCXR_0397_1.png 284 | CHNCXR_0126_0.png 285 | CHNCXR_0439_1.png 286 | CHNCXR_0535_1.png 287 | CHNCXR_0311_0.png 288 | CHNCXR_0010_0.png 289 | CHNCXR_0326_0.png 290 | CHNCXR_0472_1.png 291 | CHNCXR_0168_0.png 292 | CHNCXR_0412_1.png 293 | CHNCXR_0089_0.png 294 | CHNCXR_0408_1.png 295 | CHNCXR_0279_0.png 296 | CHNCXR_0473_1.png 297 | CHNCXR_0105_0.png 298 | CHNCXR_0461_1.png 299 | CHNCXR_0051_0.png 300 | CHNCXR_0523_1.png 301 | CHNCXR_0131_0.png 302 | CHNCXR_0643_1.png 303 | CHNCXR_0306_0.png 304 | CHNCXR_0612_1.png 305 | CHNCXR_0511_1.png 306 | CHNCXR_0226_0.png 307 | CHNCXR_0621_1.png 308 | CHNCXR_0085_0.png 309 | CHNCXR_0270_0.png 310 | CHNCXR_0116_0.png 311 | CHNCXR_0317_0.png 312 | CHNCXR_0019_0.png 313 | CHNCXR_0236_0.png 314 | CHNCXR_0112_0.png 315 | CHNCXR_0135_0.png 316 | CHNCXR_0246_0.png 317 | CHNCXR_0021_0.png 318 | CHNCXR_0299_0.png 319 | CHNCXR_0476_1.png 320 | CHNCXR_0144_0.png 321 | CHNCXR_0372_1.png 322 | CHNCXR_0170_0.png 323 | CHNCXR_0079_0.png 324 | CHNCXR_0576_1.png 325 | CHNCXR_0438_1.png 326 | CHNCXR_0600_1.png 327 | CHNCXR_0097_0.png 328 | CHNCXR_0161_0.png 329 | CHNCXR_0451_1.png 330 | CHNCXR_0318_0.png 331 | CHNCXR_0591_1.png 332 | CHNCXR_0119_0.png 333 | CHNCXR_0174_0.png 334 | CHNCXR_0389_1.png 335 | CHNCXR_0233_0.png 336 | CHNCXR_0401_1.png 337 | CHNCXR_0152_0.png 338 | CHNCXR_0542_1.png 339 | CHNCXR_0167_0.png 340 | CHNCXR_0232_0.png 341 | CHNCXR_0373_1.png 342 | CHNCXR_0004_0.png 343 | CHNCXR_0265_0.png 344 | CHNCXR_0442_1.png 345 | CHNCXR_0657_1.png 346 | CHNCXR_0361_1.png 347 | CHNCXR_0264_0.png 348 | CHNCXR_0393_1.png 349 | CHNCXR_0463_1.png 350 | CHNCXR_0090_0.png 351 | CHNCXR_0111_0.png 352 | CHNCXR_0571_1.png 353 | CHNCXR_0272_0.png 354 | CHNCXR_0532_1.png 355 | CHNCXR_0437_1.png 356 | CHNCXR_0414_1.png 357 | CHNCXR_0653_1.png 358 | CHNCXR_0646_1.png 359 | CHNCXR_0658_1.png 360 | CHNCXR_0108_0.png 361 | CHNCXR_0425_1.png 362 | CHNCXR_0334_1.png 363 | CHNCXR_0323_0.png 364 | CHNCXR_0410_1.png 365 | CHNCXR_0531_1.png 366 | CHNCXR_0660_1.png 367 | CHNCXR_0125_0.png 368 | CHNCXR_0603_1.png 369 | CHNCXR_0127_0.png 370 | CHNCXR_0510_1.png 371 | CHNCXR_0644_1.png 372 | CHNCXR_0598_1.png 373 | CHNCXR_0453_1.png 374 | CHNCXR_0413_1.png 375 | CHNCXR_0403_1.png 376 | CHNCXR_0624_1.png 377 | CHNCXR_0379_1.png 378 | CHNCXR_0580_1.png 379 | CHNCXR_0091_0.png 380 | CHNCXR_0023_0.png 381 | CHNCXR_0087_0.png 382 | CHNCXR_0175_0.png 383 | CHNCXR_0609_1.png 384 | CHNCXR_0419_1.png 385 | CHNCXR_0371_1.png 386 | CHNCXR_0616_1.png 387 | CHNCXR_0468_1.png 388 | CHNCXR_0594_1.png 389 | CHNCXR_0267_0.png 390 | CHNCXR_0396_1.png 391 | CHNCXR_0462_1.png 392 | CHNCXR_0302_0.png 393 | CHNCXR_0275_0.png 394 | CHNCXR_0289_0.png 395 | CHNCXR_0128_0.png 396 | CHNCXR_0052_0.png 397 | CHNCXR_0363_1.png 398 | CHNCXR_0415_1.png 399 | CHNCXR_0601_1.png 400 | CHNCXR_0377_1.png 401 | CHNCXR_0568_1.png 402 | CHNCXR_0061_0.png 403 | CHNCXR_0058_0.png 404 | CHNCXR_0376_1.png 405 | CHNCXR_0547_1.png 406 | CHNCXR_0140_0.png 407 | CHNCXR_0287_0.png 408 | CHNCXR_0524_1.png 409 | CHNCXR_0662_1.png 410 | CHNCXR_0169_0.png 411 | CHNCXR_0147_0.png 412 | CHNCXR_0026_0.png 413 | CHNCXR_0067_0.png 414 | CHNCXR_0239_0.png 415 | CHNCXR_0400_1.png 416 | CHNCXR_0386_1.png 417 | CHNCXR_0407_1.png 418 | CHNCXR_0118_0.png 419 | CHNCXR_0230_0.png 420 | CHNCXR_0448_1.png 421 | CHNCXR_0231_0.png 422 | CHNCXR_0638_1.png 423 | JPCLN063.png 424 | JPCLN062.png 425 | JPCNN030.png 426 | JPCNN088.png 427 | JPCLN111.png 428 | JPCLN110.png 429 | JPCLN124.png 430 | JPCNN054.png 431 | JPCNN028.png 432 | JPCNN053.png 433 | JPCNN071.png 434 | JPCLN120.png 435 | JPCLN115.png 436 | JPCNN036.png 437 | JPCLN044.png 438 | JPCNN038.png 439 | JPCLN106.png 440 | JPCNN069.png 441 | JPCNN019.png 442 | JPCLN047.png 443 | JPCLN012.png 444 | JPCNN017.png 445 | JPCLN015.png 446 | JPCNN075.png 447 | JPCNN090.png 448 | JPCLN109.png 449 | JPCNN058.png 450 | JPCNN016.png 451 | JPCLN132.png 452 | JPCLN053.png 453 | JPCLN019.png 454 | JPCLN021.png 455 | JPCLN068.png 456 | JPCLN009.png 457 | JPCLN113.png 458 | JPCNN021.png 459 | JPCNN035.png 460 | JPCNN065.png 461 | JPCNN062.png 462 | JPCNN033.png 463 | JPCLN069.png 464 | JPCLN036.png 465 | JPCNN024.png 466 | JPCLN043.png 467 | JPCNN067.png 468 | JPCLN154.png 469 | JPCNN022.png 470 | JPCLN056.png 471 | JPCLN070.png 472 | JPCLN096.png 473 | JPCLN076.png 474 | JPCNN040.png 475 | JPCLN108.png 476 | JPCLN143.png 477 | JPCNN029.png 478 | JPCLN084.png 479 | JPCNN023.png 480 | JPCLN011.png 481 | JPCLN057.png 482 | JPCNN010.png 483 | JPCLN052.png 484 | JPCLN079.png 485 | JPCLN087.png 486 | JPCLN123.png 487 | JPCLN094.png 488 | JPCNN047.png 489 | JPCLN137.png 490 | JPCNN055.png 491 | JPCLN046.png 492 | JPCLN028.png 493 | JPCLN002.png 494 | JPCLN144.png 495 | JPCLN081.png 496 | JPCLN148.png 497 | JPCNN083.png 498 | JPCLN083.png 499 | JPCLN086.png 500 | JPCLN018.png 501 | JPCNN025.png 502 | JPCNN091.png 503 | JPCNN026.png 504 | JPCLN129.png 505 | JPCLN026.png 506 | JPCLN041.png 507 | JPCNN076.png 508 | JPCLN149.png 509 | JPCLN064.png 510 | JPCNN032.png 511 | JPCLN038.png 512 | JPCLN075.png 513 | JPCLN099.png 514 | JPCNN013.png 515 | JPCLN020.png 516 | JPCLN104.png 517 | JPCLN116.png 518 | JPCLN005.png 519 | JPCLN130.png 520 | JPCNN048.png 521 | JPCLN055.png 522 | JPCLN003.png 523 | JPCLN150.png 524 | JPCLN033.png 525 | JPCLN037.png 526 | JPCNN077.png 527 | JPCLN078.png 528 | JPCNN052.png 529 | JPCLN127.png 530 | JPCLN093.png 531 | JPCNN080.png 532 | JPCLN040.png 533 | JPCLN091.png 534 | JPCLN001.png 535 | JPCNN002.png 536 | JPCNN044.png 537 | JPCLN059.png 538 | JPCLN072.png 539 | JPCLN121.png 540 | JPCLN098.png 541 | JPCLN013.png 542 | JPCLN135.png 543 | JPCLN122.png 544 | JPCNN011.png 545 | JPCNN051.png 546 | JPCNN073.png 547 | JPCNN007.png 548 | JPCLN008.png 549 | JPCLN125.png 550 | JPCLN134.png 551 | JPCLN100.png 552 | JPCLN092.png 553 | JPCNN034.png 554 | JPCLN045.png 555 | JPCLN007.png 556 | JPCLN054.png 557 | JPCLN126.png 558 | JPCLN097.png 559 | JPCNN042.png 560 | JPCLN145.png 561 | JPCNN057.png 562 | JPCLN023.png 563 | JPCNN084.png 564 | JPCNN085.png 565 | JPCNN046.png 566 | JPCLN080.png 567 | JPCLN140.png 568 | JPCNN020.png 569 | JPCNN018.png 570 | JPCNN005.png 571 | JPCLN102.png 572 | JPCNN031.png 573 | JPCNN068.png 574 | JPCLN025.png 575 | JPCLN089.png 576 | JPCLN049.png 577 | JPCLN061.png 578 | JPCLN136.png 579 | JPCLN112.png 580 | JPCLN039.png 581 | JPCLN035.png 582 | JPCLN141.png 583 | JPCNN015.png 584 | JPCLN029.png 585 | JPCNN045.png 586 | JPCNN078.png 587 | JPCNN072.png 588 | JPCLN066.png 589 | JPCLN006.png 590 | JPCNN049.png 591 | JPCNN092.png 592 | JPCNN003.png 593 | JPCLN014.png 594 | JPCNN001.png 595 | JPCNN050.png 596 | JPCNN039.png 597 | JPCLN101.png 598 | JPCLN153.png 599 | JPCLN118.png 600 | JPCLN077.png 601 | JPCNN006.png 602 | JPCLN107.png 603 | JPCNN089.png 604 | JPCLN085.png 605 | JPCNN014.png 606 | JPCNN082.png 607 | JPCLN128.png 608 | JPCLN042.png 609 | JPCLN074.png 610 | JPCLN142.png 611 | JPCNN066.png 612 | JPCNN004.png 613 | JPCNN064.png 614 | JPCLN117.png 615 | JPCLN027.png 616 | JPCLN082.png 617 | JPCLN073.png 618 | JPCLN088.png 619 | JPCLN034.png 620 | MCUCXR_0049_0.png 621 | MCUCXR_0040_0.png 622 | MCUCXR_0055_0.png 623 | MCUCXR_0100_0.png 624 | MCUCXR_0251_1.png 625 | MCUCXR_0166_1.png 626 | MCUCXR_0141_1.png 627 | MCUCXR_0041_0.png 628 | MCUCXR_0006_0.png 629 | MCUCXR_0196_1.png 630 | MCUCXR_0350_1.png 631 | MCUCXR_0064_0.png 632 | MCUCXR_0301_1.png 633 | MCUCXR_0108_1.png 634 | MCUCXR_0001_0.png 635 | MCUCXR_0096_0.png 636 | MCUCXR_0084_0.png 637 | MCUCXR_0182_1.png 638 | MCUCXR_0082_0.png 639 | MCUCXR_0008_0.png 640 | MCUCXR_0003_0.png 641 | MCUCXR_0203_1.png 642 | MCUCXR_0074_0.png 643 | MCUCXR_0077_0.png 644 | MCUCXR_0086_0.png 645 | MCUCXR_0253_1.png 646 | MCUCXR_0213_1.png 647 | MCUCXR_0043_0.png 648 | MCUCXR_0311_1.png 649 | MCUCXR_0338_1.png 650 | MCUCXR_0282_1.png 651 | MCUCXR_0309_1.png 652 | MCUCXR_0063_0.png 653 | MCUCXR_0387_1.png 654 | MCUCXR_0348_1.png 655 | MCUCXR_0044_0.png 656 | MCUCXR_0047_0.png 657 | MCUCXR_0255_1.png 658 | MCUCXR_0399_1.png 659 | MCUCXR_0079_0.png 660 | MCUCXR_0097_0.png 661 | MCUCXR_0017_0.png 662 | MCUCXR_0027_0.png 663 | MCUCXR_0243_1.png 664 | MCUCXR_0095_0.png 665 | MCUCXR_0375_1.png 666 | MCUCXR_0070_0.png 667 | MCUCXR_0194_1.png 668 | MCUCXR_0258_1.png 669 | MCUCXR_0140_1.png 670 | MCUCXR_0030_0.png 671 | MCUCXR_0028_0.png 672 | MCUCXR_0372_1.png 673 | MCUCXR_0254_1.png 674 | MCUCXR_0054_0.png 675 | MCUCXR_0266_1.png 676 | MCUCXR_0011_0.png 677 | MCUCXR_0059_0.png 678 | MCUCXR_0046_0.png 679 | MCUCXR_0048_0.png 680 | MCUCXR_0015_0.png 681 | MCUCXR_0035_0.png 682 | MCUCXR_0072_0.png 683 | MCUCXR_0099_0.png 684 | MCUCXR_0383_1.png 685 | MCUCXR_0316_1.png 686 | MCUCXR_0051_0.png 687 | MCUCXR_0331_1.png 688 | MCUCXR_0013_0.png 689 | MCUCXR_0042_0.png 690 | MCUCXR_0113_1.png 691 | MCUCXR_0087_0.png 692 | MCUCXR_0083_0.png 693 | MCUCXR_0021_0.png 694 | MCUCXR_0104_1.png 695 | MCUCXR_0390_1.png 696 | MCUCXR_0103_0.png 697 | MCUCXR_0085_0.png 698 | MCUCXR_0090_0.png 699 | MCUCXR_0117_1.png 700 | MCUCXR_0264_1.png 701 | MCUCXR_0094_0.png 702 | MCUCXR_0005_0.png 703 | MCUCXR_0024_0.png 704 | MCUCXR_0019_0.png 705 | MCUCXR_0144_1.png 706 | MCUCXR_0162_1.png 707 | MCUCXR_0223_1.png 708 | MCUCXR_0091_0.png 709 | MCUCXR_0354_1.png 710 | MCUCXR_0026_0.png 711 | MCUCXR_0060_0.png 712 | MCUCXR_0188_1.png 713 | MCUCXR_0045_0.png 714 | MCUCXR_0173_1.png 715 | MCUCXR_0071_0.png 716 | MCUCXR_0393_1.png 717 | MCUCXR_0081_0.png 718 | MCUCXR_0061_0.png 719 | MCUCXR_0031_0.png 720 | MCUCXR_0075_0.png 721 | MCUCXR_0080_0.png 722 | MCUCXR_0023_0.png 723 | MCUCXR_0016_0.png 724 | MCUCXR_0058_0.png 725 | MCUCXR_0334_1.png 726 | MCUCXR_0092_0.png 727 | MCUCXR_0029_0.png 728 | MCUCXR_0228_1.png 729 | MCUCXR_0367_1.png 730 | MCUCXR_0142_1.png 731 | -------------------------------------------------------------------------------- /TechnicalValidation/PhysicianAnnotations/1_ExcelToPandasCSV.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": null, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import pandas as pd \n", 10 | "import numpy as np \n", 11 | "import cv2 \n", 12 | "\n", 13 | "def get_RLE_from_mask(mask):\n", 14 | " mask = (mask / 255).astype(int)\n", 15 | " pixels = mask.flatten()\n", 16 | " pixels = np.concatenate([[0], pixels, [0]])\n", 17 | " runs = np.where(pixels[1:] != pixels[:-1])[0] + 1\n", 18 | " runs[1::2] -= runs[::2]\n", 19 | " return ' '.join(str(x) for x in runs)\n", 20 | "\n", 21 | "\n", 22 | "def get_mask_from_RLE(rle, height, width):\n", 23 | " runs = np.array([int(x) for x in rle.split()])\n", 24 | " starts = runs[::2]\n", 25 | " lengths = runs[1::2]\n", 26 | "\n", 27 | " mask = np.zeros((height * width), dtype=np.uint8)\n", 28 | "\n", 29 | " for start, length in zip(starts, lengths):\n", 30 | " start -= 1 \n", 31 | " end = start + length\n", 32 | " mask[start:end] = 255\n", 33 | "\n", 34 | " mask = mask.reshape((height, width))\n", 35 | " \n", 36 | " return mask\n", 37 | "\n", 38 | "def getDenseMask(graph, imagesize = 1024):\n", 39 | " img = np.zeros([imagesize,imagesize])\n", 40 | " graph = graph.reshape(-1, 1, 2).astype('int')\n", 41 | " img = cv2.drawContours(img, [graph], -1, 255, -1)\n", 42 | " return img\n", 43 | "\n", 44 | "input_path = \"julia_annotations.xls\"\n", 45 | "dataframe = pd.read_excel(input_path, header=0, index_col=0)\n", 46 | "\n", 47 | "output = pd.DataFrame(columns=['ImageID', 'Dataset', 'Landmarks', 'Right Lung', 'Left Lung', 'Heart'])\n", 48 | "\n", 49 | "# iter rows\n", 50 | "for index, row in dataframe.iterrows():\n", 51 | " # columns are Path, Right lung, Left lung, Heart, Comments\n", 52 | " \n", 53 | " path = str(index)[1:]\n", 54 | " dataset = path.split(\"/\")[0]\n", 55 | " img_name = path.split(\"/\")[1]\n", 56 | " \n", 57 | " name_mapping_file = \"ToAnnotate/\" + dataset + \"_name_mapping.csv\"\n", 58 | " \n", 59 | " _name_mapping = pd.read_csv(name_mapping_file)\n", 60 | " original_name = _name_mapping[_name_mapping[\"New\"] == img_name][\"Original\"].values[0]\n", 61 | " \n", 62 | " if dataset == \"MIMIC\":\n", 63 | " original_name = original_name.split(\"/\")[-1].replace(\".jpg\", \"\")\n", 64 | " dataset = \"MIMIC-CXR-JPG\"\n", 65 | " elif dataset == \"CheXpert\":\n", 66 | " original_name = original_name.replace(\"Datasets/CheXpert/Preprocessed/\", \"\").replace(\".png\", \".jpg\")\n", 67 | " elif dataset == \"CANDID\":\n", 68 | " original_name = original_name.split(\"/\")[-1]\n", 69 | " dataset = \"CANDID-PTX\"\n", 70 | " elif dataset == \"Padchest\":\n", 71 | " original_name = original_name.split(\"/\")[-1]\n", 72 | " elif dataset == \"ChestX-Ray8\":\n", 73 | " original_name = original_name.split(\"/\")[-1]\n", 74 | " elif dataset == \"VinBigData\":\n", 75 | " original_name = original_name.split(\"/\")[-1].replace(\".png\", \"\") \n", 76 | " dataset = \"VinDr-CXR\" \n", 77 | " \n", 78 | " RL = row[\"right_lung\"]\n", 79 | " LL = row[\"left_lung\"]\n", 80 | " H = row[\"heart\"]\n", 81 | " \n", 82 | " # RL, LL, and H are strings like \"[[x1, y1], [x2, y2], ...]\"\n", 83 | " # we need to convert them to numpy arrays\n", 84 | " \n", 85 | " RL = np.array(eval(RL)) / 100 * 1024\n", 86 | " RL = np.round(RL, 0).astype(int)\n", 87 | " LL = np.array(eval(LL)) / 100 * 1024\n", 88 | " LL = np.round(LL, 0).astype(int)\n", 89 | " H = np.array(eval(H)) / 100 * 1024\n", 90 | " H = np.round(H, 0).astype(int)\n", 91 | " \n", 92 | " RL_ = getDenseMask(RL)\n", 93 | " LL_ = getDenseMask(LL)\n", 94 | " H_ = getDenseMask(H)\n", 95 | " \n", 96 | " RL_RLE = get_RLE_from_mask(RL_)\n", 97 | " LL_RLE = get_RLE_from_mask(LL_)\n", 98 | " H_RLE = get_RLE_from_mask(H_)\n", 99 | " \n", 100 | " # Sometimes there are more than 44 points for RL, 50 for LL, and 26 for H \n", 101 | " # due to some LabelStudio issues\n", 102 | " # But it's only one or two in some minor cases, so we can just cut it off or pad it with the last point\n", 103 | " \n", 104 | " if len(RL) > 44:\n", 105 | " RL = RL[:44]\n", 106 | " elif len(RL) < 44:\n", 107 | " RL = np.concatenate([RL, np.ones((44 - len(RL), 2)) * RL[-1]])\n", 108 | " \n", 109 | " if len(LL) > 50:\n", 110 | " LL = LL[:50]\n", 111 | " elif len(LL) < 50:\n", 112 | " LL = np.concatenate([LL, np.ones((50 - len(LL), 2)) * LL[-1]])\n", 113 | " \n", 114 | " if len(H) > 26:\n", 115 | " H = H[:26]\n", 116 | " elif len(H) < 26:\n", 117 | " H = np.concatenate([H, np.ones((26 - len(H), 2)) * H[-1]])\n", 118 | " \n", 119 | " data = np.concatenate([RL, LL, H])\n", 120 | " \n", 121 | " flattened_data = data.flatten()\n", 122 | " coordinates_str = ','.join(map(str, flattened_data))\n", 123 | " \n", 124 | " print(\"Original name: \", original_name)\n", 125 | " \n", 126 | " id = original_name\n", 127 | " \n", 128 | " new_row = {\n", 129 | " 'ImageID': id,\n", 130 | " 'Dataset': dataset, \n", 131 | " 'Landmarks': coordinates_str, \n", 132 | " 'Right Lung': RL_RLE, \n", 133 | " 'Left Lung': LL_RLE, \n", 134 | " 'Heart': H_RLE\n", 135 | " }\n", 136 | "\n", 137 | " output = pd.concat([output, pd.DataFrame([new_row])], ignore_index=True)\n", 138 | " \n", 139 | "output.to_csv(input_path.replace(\".xls\", \".csv\"), index=False)" 140 | ] 141 | } 142 | ], 143 | "metadata": { 144 | "kernelspec": { 145 | "display_name": "Python 3.9.12 ('base')", 146 | "language": "python", 147 | "name": "python3" 148 | }, 149 | "language_info": { 150 | "codemirror_mode": { 151 | "name": "ipython", 152 | "version": 3 153 | }, 154 | "file_extension": ".py", 155 | "mimetype": "text/x-python", 156 | "name": "python", 157 | "nbconvert_exporter": "python", 158 | "pygments_lexer": "ipython3", 159 | "version": "3.10.13" 160 | }, 161 | "orig_nbformat": 4, 162 | "vscode": { 163 | "interpreter": { 164 | "hash": "e2f31d7e842d4c02a4fdd8a49a54c27977ffbe51e51f3f2334136391596cbe6a" 165 | } 166 | } 167 | }, 168 | "nbformat": 4, 169 | "nbformat_minor": 2 170 | } 171 | -------------------------------------------------------------------------------- /TechnicalValidation/PhysicianAnnotations/1_ExcelToPandasCSV_Findings.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 10, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "Original name: 0e39840b-24e7c4c5-b05e2bf9-f6aab11a-dc323629\n", 13 | "Original name: c030283d-b5cd5262-a59288c9-c1a4ad57-b4fbca10\n", 14 | "Original name: c3d68978-16adbf15-8549fc2d-640e2b44-823afeb9\n", 15 | "Original name: 0d208c41-8d0bb2b0-aeda94a9-e8bf1872-fbe28a24\n", 16 | "Original name: a1c37055-a0367e02-917befd4-fe67e568-3cb650a5\n", 17 | "Original name: f894f8f9-b349a21f-6b7c8164-af30f758-4ac73289\n", 18 | "Original name: f8b6e835-89d234dd-a8de9f18-5a723f58-279d4cc8\n", 19 | "Original name: 66049acc-ee1e6b29-1298f53b-32d453b3-41377f10\n", 20 | "Original name: 2a1f9d96-ef385d23-58689072-0a84ce9c-0ae5a38e\n", 21 | "Original name: b8d59b2a-be55b95c-770d7295-b8f3b006-af21dee7\n", 22 | "Original name: 9f0d230b-b83bb5af-e2b73a21-9e5b6357-82d0e148\n", 23 | "Original name: b9f0582f-f6a3d0c2-ab526960-547920ed-d4ff898d\n", 24 | "Original name: 76f38ea0-13104a68-b7fccfd8-c2876f1e-05eb3817\n", 25 | "Original name: 47715176-d3c203b0-e49bac9f-187685ad-481553c1\n", 26 | "Original name: 160aeb25-0f25a53d-0feb594b-1ae2e150-e7acd609\n", 27 | "Original name: 2577b081-c65a3a97-870ec2a8-96658cc1-4aa4b2a6\n", 28 | "Original name: b204bd07-e764545f-852df255-f1ae766f-4918c8a8\n", 29 | "Original name: 2dfb70da-37202c12-26456b96-1239badf-17139268\n", 30 | "Original name: 8ab9f0bd-ccbc52b5-304dc361-c7ad344c-aeb037c7\n", 31 | "Original name: 8056042956632572313048998993078424193_l7czww.png\n", 32 | "Original name: 216840111366964013451228379692012229120345763_01-033-112.png\n", 33 | "Original name: 216840111366964013962490064942014138093442261_01-174-028.png\n", 34 | "Original name: 216840111366964013534861372972013007110618923_02-007-094.png\n", 35 | "Original name: 216840111366964012989926673512011133085406280_00-151-032.png\n", 36 | "Original name: 216840111366964013686042548532013249140135578_02-025-019.png\n", 37 | "Original name: 216840111366964012819207061112010307120012550_04-018-176.png\n", 38 | "Original name: 216840111366964013534861372972012327084810347_01-154-120.png\n", 39 | "Original name: 216840111366964013274515230432012040100738842_01-096-041.png\n", 40 | "Original name: 216840111366964013334747595492012096091319718_01-107-103.png\n", 41 | "Original name: 216840111366964013293097335992012051112417282_01-110-182.png\n", 42 | "Original name: 216840111366964012989926673512011084170640896_00-164-125.png\n", 43 | "Original name: 216840111366964012948363412702011018125712695_00-123-193.png\n", 44 | "Original name: 216840111366964013307756408102012074112932371_01-085-056.png\n", 45 | "Original name: 216840111366964013534861372972012342160750800_01-128-182.png\n", 46 | "Original name: 216840111366964012558082906712009300090627925_00-080-159.png\n", 47 | "Original name: 283824971761111769540542572340007517370_64bar7.png\n", 48 | "Original name: 216840111366964013534861372972012342131246138_01-134-002.png\n", 49 | "Original name: 216840111366964013307756408102012065124809827_01-083-153.png\n", 50 | "Original name: 216840111366964012558082906712009301143127208_00-108-034.png\n", 51 | "Original name: 00013310_001.png\n", 52 | "Original name: 00025110_014.png\n", 53 | "Original name: 00026963_032.png\n", 54 | "Original name: 00012049_002.png\n", 55 | "Original name: 00026190_005.png\n", 56 | "Original name: 00002387_006.png\n", 57 | "Original name: 00002492_003.png\n", 58 | "Original name: 00000744_003.png\n", 59 | "Original name: 00009001_007.png\n", 60 | "Original name: 00006875_014.png\n", 61 | "Original name: 00004746_003.png\n", 62 | "Original name: 00018292_000.png\n", 63 | "Original name: 00016037_000.png\n", 64 | "Original name: 00003887_004.png\n", 65 | "Original name: 00001437_024.png\n", 66 | "Original name: 00020947_007.png\n", 67 | "Original name: 00020326_090.png\n", 68 | "Original name: 00002224_005.png\n", 69 | "Original name: 00026387_001.png\n", 70 | "Original name: 00017405_018.png\n", 71 | "Original name: 0.9.91.172410.12.7.1.3.45771546820.6250833132009.1.jpg\n", 72 | "Original name: 0.1.96.667417.06.5.2.7.952313125993928.5657660411153.2.jpg\n", 73 | "Original name: 4.1.46.958385.01.8.9.5.551711036731409.7447621122395.1.jpg\n", 74 | "Original name: 6.0.09.296055.30.9.9.2.95038201504.1748851780394.5.jpg\n", 75 | "Original name: 1.6.72.806164.41.9.8.8.641538800956364.4744888205897.9.jpg\n", 76 | "Original name: 3.3.89.763497.80.8.3.0.33817192889.0361603108456.1.jpg\n", 77 | "Original name: 7.1.61.823859.49.9.4.7.55901173645.7022901849009.2.jpg\n", 78 | "Original name: 8.0.487.000059.5.641.3.9174870999.9348746037.699230.jpg\n", 79 | "Original name: 9.0.55.823542.28.7.5.3.56409738982301.8513504447399.3.jpg\n", 80 | "Original name: 7.0.13.404798.58.9.5.0.29608614881.4505368319397.8.jpg\n", 81 | "Original name: 2.9.43.144559.29.0.9.6.11172332886.3853205707852.5.jpg\n", 82 | "Original name: 4.7.07.986222.28.4.1.4.593160860190141.3212292868988.9.jpg\n", 83 | "Original name: 2.6.76.592265.53.1.3.5.10665511369.9322632009476.9.jpg\n", 84 | "Original name: 3.9.84.476338.13.1.8.6.346697248842392.4971012069217.1.jpg\n", 85 | "Original name: 3.2.29.382459.07.4.5.9.62587361664.8040771826150.1.jpg\n", 86 | "Original name: 0.1.86.208816.70.4.4.5.538335855345887.0371041281264.8.jpg\n", 87 | "Original name: 2.4.41.317345.62.3.1.7.62004154938.6509690573274.6.jpg\n", 88 | "Original name: 5.3.29.337075.58.4.7.9.702788426857014.8031765728882.5.jpg\n", 89 | "Original name: 2.1.119.350319.2.235.0.7473694013.3155053665.870769.jpg\n", 90 | "Original name: 2.3.27.617339.58.4.3.0.179727158208339.7269299613487.7.jpg\n", 91 | "Original name: 2bac12b65fe143e0d0a4b56b320782e8\n", 92 | "Original name: 68f7ee667e33c638abc21bcd543ad9d5\n", 93 | "Original name: 3156b5feb62ed8cfdafef21f1f82a6c1\n", 94 | "Original name: 3b887c0550e9722c9b86b7c22ad09e11\n", 95 | "Original name: ba1795ee5daae1ed415756c3f4f21b48\n", 96 | "Original name: e9581123b6819b2cd1bcf6ed35481520\n", 97 | "Original name: 106a3da41d2e3d9f508c09b28e8abdaf\n", 98 | "Original name: afa4de6570c31504ba4b77978377ccc9\n", 99 | "Original name: 91a12fbbe1ad5eb62cdf97edeb122280\n", 100 | "Original name: 24d6b814577360fb4d11e0e6aecfefb0\n", 101 | "Original name: b01037a08ba72220deddf845bfd02466\n", 102 | "Original name: 0b6006be69ea1764f9bf80e5091b1e8e\n", 103 | "Original name: c8205cbdfff77ee90770156c3075a38f\n", 104 | "Original name: c80fe973b34b764cf37e43efb755c0b8\n", 105 | "Original name: 60679b51d403a3a1fc020a0a86fedd8d\n", 106 | "Original name: b934b20d2bc6a9ad44b46aef2776268b\n", 107 | "Original name: 0d5597f8b17330d498fc5e13893e3081\n", 108 | "Original name: 713f94c56bcf0a3622522744fb0d24d7\n", 109 | "Original name: 5d6c0df203f0e3f04467e27507029026\n", 110 | "Original name: 8794eea4b84bc93cdf786327e3e606f6\n", 111 | "Original name: train/patient04298/study2/view1_frontal.jpg\n", 112 | "Original name: train/patient14049/study5/view1_frontal.jpg\n", 113 | "Original name: train/patient18401/study1/view1_frontal.jpg\n", 114 | "Original name: train/patient60831/study1/view1_frontal.jpg\n", 115 | "Original name: train/patient60334/study1/view1_frontal.jpg\n", 116 | "Original name: train/patient12461/study1/view1_frontal.jpg\n", 117 | "Original name: train/patient27585/study1/view1_frontal.jpg\n", 118 | "Original name: train/patient37155/study9/view1_frontal.jpg\n", 119 | "Original name: train/patient18591/study2/view1_frontal.jpg\n", 120 | "Original name: train/patient04613/study3/view1_frontal.jpg\n", 121 | "Original name: train/patient16398/study45/view1_frontal.jpg\n", 122 | "Original name: train/patient21490/study2/view1_frontal.jpg\n", 123 | "Original name: train/patient20266/study5/view1_frontal.jpg\n", 124 | "Original name: train/patient06627/study1/view1_frontal.jpg\n", 125 | "Original name: train/patient24165/study2/view1_frontal.jpg\n", 126 | "Original name: train/patient29703/study13/view1_frontal.jpg\n", 127 | "Original name: train/patient15895/study11/view1_frontal.jpg\n", 128 | "Original name: train/patient33146/study11/view1_frontal.jpg\n", 129 | "Original name: train/patient23400/study1/view1_frontal.jpg\n", 130 | "Original name: train/patient04157/study3/view1_frontal.jpg\n", 131 | "Original name: train/patient18415/study1/view1_frontal.jpg\n", 132 | "Original name: train/patient10961/study1/view1_frontal.jpg\n", 133 | "Original name: train/patient12961/study19/view1_frontal.jpg\n", 134 | "Original name: train/patient07974/study11/view1_frontal.jpg\n", 135 | "Original name: train/patient24297/study3/view1_frontal.jpg\n", 136 | "Original name: train/patient12939/study3/view1_frontal.jpg\n" 137 | ] 138 | } 139 | ], 140 | "source": [ 141 | "import pandas as pd \n", 142 | "import numpy as np \n", 143 | "import cv2 \n", 144 | "\n", 145 | "def get_RLE_from_mask(mask):\n", 146 | " mask = (mask / 255).astype(int)\n", 147 | " pixels = mask.flatten()\n", 148 | " pixels = np.concatenate([[0], pixels, [0]])\n", 149 | " runs = np.where(pixels[1:] != pixels[:-1])[0] + 1\n", 150 | " runs[1::2] -= runs[::2]\n", 151 | " return ' '.join(str(x) for x in runs)\n", 152 | "\n", 153 | "def get_mask_from_RLE(rle, height, width):\n", 154 | " runs = np.array([int(x) for x in rle.split()])\n", 155 | " starts = runs[::2]\n", 156 | " lengths = runs[1::2]\n", 157 | "\n", 158 | " mask = np.zeros((height * width), dtype=np.uint8)\n", 159 | "\n", 160 | " for start, length in zip(starts, lengths):\n", 161 | " start -= 1 \n", 162 | " end = start + length\n", 163 | " mask[start:end] = 255\n", 164 | "\n", 165 | " mask = mask.reshape((height, width))\n", 166 | " \n", 167 | " return mask\n", 168 | "\n", 169 | "def getDenseMask(graph, imagesize = 1024):\n", 170 | " img = np.zeros([imagesize,imagesize])\n", 171 | " graph = graph.reshape(-1, 1, 2).astype('int')\n", 172 | " img = cv2.drawContours(img, [graph], -1, 255, -1)\n", 173 | " return img\n", 174 | "\n", 175 | "input_path = \"martina_annotations_set-findings.xls\"\n", 176 | "dataframe = pd.read_excel(input_path, header=0, index_col=0)\n", 177 | "\n", 178 | "output = pd.DataFrame(columns=['ImageID', 'Dataset', 'Landmarks', 'Right Lung', 'Left Lung', 'Heart'])\n", 179 | "\n", 180 | "# iter rows\n", 181 | "for index, row in dataframe.iterrows():\n", 182 | " # columns are Path, Right lung, Left lung, Heart, Comments\n", 183 | " \n", 184 | " path = str(index)[1:]\n", 185 | " \n", 186 | " try:\n", 187 | " dataset = path.split(\"/\")[0]\n", 188 | " disease = path.split(\"/\")[1]\n", 189 | " img_name = path.split(\"/\")[2]\n", 190 | " except:\n", 191 | " dataset = \"ChestX-Ray8\"\n", 192 | " img_name = path\n", 193 | " disease = \"\"\n", 194 | " \n", 195 | " if dataset == \"MIMIC-CXR-JPG\":\n", 196 | " original_name = img_name.split(\"_\")[-1].replace(\".jpg\", \"\")\n", 197 | " elif dataset == \"Padchest\":\n", 198 | " original_name = img_name\n", 199 | " elif dataset == \"ChestX-Ray8\":\n", 200 | " original_name = img_name\n", 201 | " elif dataset == \"CANDID-PTX\":\n", 202 | " original_name = img_name\n", 203 | " elif dataset == \"VinDr-CXR\":\n", 204 | " original_name = img_name[:-4]\n", 205 | " elif dataset == \"CheXpert\":\n", 206 | " original_name = img_name.replace(\"_\", \"/\").replace(\"/fr\", \"_fr\").replace(\".png\",\".jpg\")\n", 207 | " \n", 208 | " RL = row[\"right_lung\"]\n", 209 | " LL = row[\"left_lung\"]\n", 210 | " H = row[\"heart\"]\n", 211 | " \n", 212 | " # RL, LL, and H are strings like \"[[x1, y1], [x2, y2], ...]\"\n", 213 | " # we need to convert them to numpy arrays\n", 214 | " \n", 215 | " RL = np.array(eval(RL)) / 100 * 1024\n", 216 | " RL = np.round(RL, 0).astype(int)\n", 217 | " LL = np.array(eval(LL)) / 100 * 1024\n", 218 | " LL = np.round(LL, 0).astype(int)\n", 219 | " H = np.array(eval(H)) / 100 * 1024\n", 220 | " H = np.round(H, 0).astype(int)\n", 221 | " \n", 222 | " RL_ = getDenseMask(RL)\n", 223 | " LL_ = getDenseMask(LL)\n", 224 | " H_ = getDenseMask(H)\n", 225 | " \n", 226 | " RL_RLE = get_RLE_from_mask(RL_)\n", 227 | " LL_RLE = get_RLE_from_mask(LL_)\n", 228 | " H_RLE = get_RLE_from_mask(H_)\n", 229 | " \n", 230 | " # Sometimes there are more than 44 points for RL, 50 for LL, and 26 for H \n", 231 | " # due to some LabelStudio issues\n", 232 | " # But it's only one or two in some minor cases, so we can just cut it off or pad it with the last point\n", 233 | " \n", 234 | " if len(RL) > 44:\n", 235 | " RL = RL[:44]\n", 236 | " elif len(RL) < 44:\n", 237 | " RL = np.concatenate([RL, np.ones((44 - len(RL), 2)) * RL[-1]])\n", 238 | " \n", 239 | " if len(LL) > 50:\n", 240 | " LL = LL[:50]\n", 241 | " elif len(LL) < 50:\n", 242 | " LL = np.concatenate([LL, np.ones((50 - len(LL), 2)) * LL[-1]])\n", 243 | " \n", 244 | " if len(H) > 26:\n", 245 | " H = H[:26]\n", 246 | " elif len(H) < 26:\n", 247 | " H = np.concatenate([H, np.ones((26 - len(H), 2)) * H[-1]])\n", 248 | " \n", 249 | " data = np.concatenate([RL, LL, H])\n", 250 | " \n", 251 | " flattened_data = data.flatten()\n", 252 | " coordinates_str = ','.join(map(str, flattened_data))\n", 253 | " \n", 254 | " print(\"Original name: \", original_name)\n", 255 | " \n", 256 | " id = original_name\n", 257 | " \n", 258 | " new_row = {\n", 259 | " 'ImageID': id,\n", 260 | " 'Dataset': dataset, \n", 261 | " 'Landmarks': coordinates_str, \n", 262 | " 'Right Lung': RL_RLE, \n", 263 | " 'Left Lung': LL_RLE, \n", 264 | " 'Heart': H_RLE\n", 265 | " }\n", 266 | "\n", 267 | " output = pd.concat([output, pd.DataFrame([new_row])], ignore_index=True)\n", 268 | " \n", 269 | "output.to_csv(input_path.replace(\".xls\", \".csv\"), index=False)" 270 | ] 271 | }, 272 | { 273 | "cell_type": "code", 274 | "execution_count": 6, 275 | "metadata": {}, 276 | "outputs": [], 277 | "source": [ 278 | "df1 = pd.read_csv(input_path.replace(\".xls\", \".csv\"))\n", 279 | "df2 = pd.read_csv(\"julia_annotations.csv\")\n", 280 | "\n", 281 | "df3 = pd.concat([df1, df2], ignore_index=True)\n", 282 | "df3.to_csv(\"julia_annotations_merge.csv\", index=False)" 283 | ] 284 | }, 285 | { 286 | "cell_type": "code", 287 | "execution_count": 7, 288 | "metadata": {}, 289 | "outputs": [ 290 | { 291 | "name": "stdout", 292 | "output_type": "stream", 293 | "text": [ 294 | "Saved: MIMIC-CXR-JPG\n", 295 | "Annotations: 49\n", 296 | "Dataset: 49\n", 297 | "Saved: Padchest\n", 298 | "Annotations: 50\n", 299 | "Dataset: 44\n", 300 | "Saved: ChestX-Ray8\n", 301 | "Annotations: 50\n", 302 | "Dataset: 50\n", 303 | "Saved: CANDID-PTX\n", 304 | "Annotations: 50\n", 305 | "Dataset: 50\n", 306 | "Saved: VinDr-CXR\n", 307 | "Annotations: 50\n", 308 | "Dataset: 50\n", 309 | "Saved: CheXpert\n", 310 | "Annotations: 56\n", 311 | "Dataset: 56\n" 312 | ] 313 | } 314 | ], 315 | "source": [ 316 | "import pandas as pd \n", 317 | "\n", 318 | "physician_annotations = \"julia_annotations_merge.csv\"\n", 319 | "\n", 320 | "df = pd.read_csv(physician_annotations)\n", 321 | "\n", 322 | "datasets = df[\"Dataset\"].unique()\n", 323 | "dataset_path = \"../../../Annotations/Preprocessed/\"\n", 324 | "\n", 325 | "for dataset in datasets:\n", 326 | " \n", 327 | " data = pd.read_csv(dataset_path + dataset + \".csv\") \n", 328 | " column_name = data.columns[0]\n", 329 | " \n", 330 | " subset_annotations = df[df[\"Dataset\"] == dataset]\n", 331 | " subset_dataset = data[data[column_name].isin(subset_annotations[\"ImageID\"])]\n", 332 | " \n", 333 | " # save both to Subsets folder\n", 334 | " \n", 335 | " subset_annotations.to_csv(\"DataSubsets/\" + dataset + \"_annotations.csv\", index = False)\n", 336 | " subset_dataset.to_csv(\"DataSubsets/\" + dataset + \".csv\", index = False)\n", 337 | " \n", 338 | " print(\"Saved: \", dataset)\n", 339 | " print(\"Annotations: \", len(subset_annotations))\n", 340 | " print(\"Dataset: \", len(subset_dataset))\n", 341 | " \n", 342 | " del subset_annotations\n", 343 | " del subset_dataset\n", 344 | " del data\n", 345 | " " 346 | ] 347 | }, 348 | { 349 | "cell_type": "code", 350 | "execution_count": 12, 351 | "metadata": {}, 352 | "outputs": [], 353 | "source": [ 354 | "df1 = pd.read_csv(input_path.replace(\".xls\", \".csv\"))\n", 355 | "df2 = pd.read_csv(\"martina_annotations.csv\")\n", 356 | "\n", 357 | "df3 = pd.concat([df1, df2], ignore_index=True)\n", 358 | "df3.to_csv(\"martina_annotations_merge.csv\", index=False)" 359 | ] 360 | }, 361 | { 362 | "cell_type": "code", 363 | "execution_count": 13, 364 | "metadata": {}, 365 | "outputs": [ 366 | { 367 | "name": "stdout", 368 | "output_type": "stream", 369 | "text": [ 370 | "Saved: MIMIC-CXR-JPG\n", 371 | "Annotations: 49\n", 372 | "Saved: Padchest\n", 373 | "Annotations: 50\n", 374 | "Saved: ChestX-Ray8\n", 375 | "Annotations: 50\n", 376 | "Saved: CANDID-PTX\n", 377 | "Annotations: 50\n", 378 | "Saved: VinDr-CXR\n", 379 | "Annotations: 50\n", 380 | "Saved: CheXpert\n", 381 | "Annotations: 56\n" 382 | ] 383 | } 384 | ], 385 | "source": [ 386 | "import pandas as pd \n", 387 | "\n", 388 | "physician_annotations = \"martina_annotations_merge.csv\"\n", 389 | "\n", 390 | "df = pd.read_csv(physician_annotations)\n", 391 | "\n", 392 | "datasets = df[\"Dataset\"].unique()\n", 393 | "dataset_path = \"../../../Annotations/Preprocessed/\"\n", 394 | "\n", 395 | "for dataset in datasets:\n", 396 | " subset_annotations = df[df[\"Dataset\"] == dataset]\n", 397 | " \n", 398 | " # save both to Subsets folder\n", 399 | " \n", 400 | " subset_annotations.to_csv(\"DataSubsets/\" + dataset + \"_martina_annotations.csv\", index = False)\n", 401 | " \n", 402 | " print(\"Saved: \", dataset)\n", 403 | " print(\"Annotations: \", len(subset_annotations))\n", 404 | " \n", 405 | " del subset_annotations" 406 | ] 407 | } 408 | ], 409 | "metadata": { 410 | "kernelspec": { 411 | "display_name": "Python 3.9.12 ('base')", 412 | "language": "python", 413 | "name": "python3" 414 | }, 415 | "language_info": { 416 | "codemirror_mode": { 417 | "name": "ipython", 418 | "version": 3 419 | }, 420 | "file_extension": ".py", 421 | "mimetype": "text/x-python", 422 | "name": "python", 423 | "nbconvert_exporter": "python", 424 | "pygments_lexer": "ipython3", 425 | "version": "3.10.13" 426 | }, 427 | "orig_nbformat": 4, 428 | "vscode": { 429 | "interpreter": { 430 | "hash": "e2f31d7e842d4c02a4fdd8a49a54c27977ffbe51e51f3f2334136391596cbe6a" 431 | } 432 | } 433 | }, 434 | "nbformat": 4, 435 | "nbformat_minor": 2 436 | } 437 | -------------------------------------------------------------------------------- /TechnicalValidation/PhysicianAnnotations/ToAnnotate/CANDID_name_mapping.csv: -------------------------------------------------------------------------------- 1 | Original,New 2 | Datasets/CANDID/Images/6.9.40.671009.61.2.2.8.16082272357.3599132170074.3.jpg,image_1.png 3 | Datasets/CANDID/Images/0.7.38.139040.44.1.5.3.119218029993450.5593519175506.1.jpg,image_2.png 4 | Datasets/CANDID/Images/8.9.30.080491.33.8.3.5.97432523989.0900003747219.7.jpg,image_3.png 5 | Datasets/CANDID/Images/3.5.41.829196.33.8.3.2.18710533313129.8517570361169.8.jpg,image_4.png 6 | Datasets/CANDID/Images/3.0.31.533306.83.2.1.7.39983797946.9558103866324.3.jpg,image_5.png 7 | Datasets/CANDID/Images/6.5.77.075723.63.3.8.2.32955742259.5986344775925.9.jpg,image_6.png 8 | Datasets/CANDID/Images/5.4.36.133708.75.3.4.2.88568334095.2770852738310.7.jpg,image_7.png 9 | Datasets/CANDID/Images/8.8.77.611010.58.0.3.1.949681922771483.4500385126225.4.jpg,image_8.png 10 | Datasets/CANDID/Images/0.2.04.425394.89.5.0.9.16154914683.1391372199746.9.jpg,image_9.png 11 | Datasets/CANDID/Images/2.6.82.591742.15.8.3.5.20190163287.5013053987463.3.jpg,image_10.png 12 | Datasets/CANDID/Images/0.3.81.149238.46.8.7.6.76477424244.3942899860186.8.jpg,image_11.png 13 | Datasets/CANDID/Images/8.2.26.644475.99.8.6.7.99099004083.9509659304558.5.jpg,image_12.png 14 | Datasets/CANDID/Images/2.2.35.636878.70.0.9.5.11154677465.3024248727552.9.jpg,image_13.png 15 | Datasets/CANDID/Images/9.3.754.191852.0.752.3.3593516387.9542932261.35572.jpg,image_14.png 16 | Datasets/CANDID/Images/8.8.83.726179.56.8.1.4.671555099611879.1932486759135.2.jpg,image_15.png 17 | Datasets/CANDID/Images/2.2.97.487762.59.4.8.9.551471620642412.9912138971155.2.jpg,image_16.png 18 | Datasets/CANDID/Images/8.0.06.903109.09.9.1.0.815403969612406.4671501562951.4.jpg,image_17.png 19 | Datasets/CANDID/Images/7.4.821.977967.8.223.3.9004901527.1444561809.504364.jpg,image_18.png 20 | Datasets/CANDID/Images/1.5.46.421669.18.0.9.2.622347884189486.2482474840259.6.jpg,image_19.png 21 | Datasets/CANDID/Images/8.6.62.906428.33.7.4.6.679656697037703.1527316416642.8.jpg,image_20.png 22 | Datasets/CANDID/Images/0.8.71.054903.69.9.1.5.502503655356095.5360254312941.2.jpg,image_21.png 23 | Datasets/CANDID/Images/6.8.94.521778.44.3.1.2.74250093262.5620028614577.3.jpg,image_22.png 24 | Datasets/CANDID/Images/9.7.65.619038.47.3.0.8.21018740029.8856230165833.7.jpg,image_23.png 25 | Datasets/CANDID/Images/7.4.42.491097.13.1.9.0.85422025932.2626267342781.4.jpg,image_24.png 26 | Datasets/CANDID/Images/1.0.838.250322.8.463.2.8126452993.0633498328.030504.jpg,image_25.png 27 | Datasets/CANDID/Images/9.5.49.529509.23.8.0.1.18541992834.7151844121016.9.jpg,image_26.png 28 | Datasets/CANDID/Images/0.4.01.462491.00.5.4.8.176763641110085.0767674162999.3.jpg,image_27.png 29 | Datasets/CANDID/Images/6.1.81.478158.58.0.8.0.147691041488033.8472241923689.3.jpg,image_28.png 30 | Datasets/CANDID/Images/6.1.74.279033.16.4.9.0.471976038227725.5995579214161.7.jpg,image_29.png 31 | Datasets/CANDID/Images/9.5.46.444200.33.0.6.4.94001999106.5551390019007.2.jpg,image_30.png 32 | -------------------------------------------------------------------------------- /TechnicalValidation/PhysicianAnnotations/ToAnnotate/CheXpert_name_mapping.csv: -------------------------------------------------------------------------------- 1 | Original,New 2 | Datasets/CheXpert/Preprocessed/train/patient09499/study6/view1_frontal.png,image_1.png 3 | Datasets/CheXpert/Preprocessed/train/patient28740/study10/view1_frontal.png,image_2.png 4 | Datasets/CheXpert/Preprocessed/train/patient19635/study5/view1_frontal.png,image_3.png 5 | Datasets/CheXpert/Preprocessed/train/patient51418/study1/view1_frontal.png,image_4.png 6 | Datasets/CheXpert/Preprocessed/train/patient09503/study6/view1_frontal.png,image_5.png 7 | Datasets/CheXpert/Preprocessed/train/patient62969/study1/view1_frontal.png,image_6.png 8 | Datasets/CheXpert/Preprocessed/train/patient06860/study2/view1_frontal.png,image_7.png 9 | Datasets/CheXpert/Preprocessed/train/patient38149/study3/view1_frontal.png,image_8.png 10 | Datasets/CheXpert/Preprocessed/train/patient01504/study1/view1_frontal.png,image_9.png 11 | Datasets/CheXpert/Preprocessed/train/patient28827/study1/view1_frontal.png,image_10.png 12 | Datasets/CheXpert/Preprocessed/train/patient55735/study1/view1_frontal.png,image_11.png 13 | Datasets/CheXpert/Preprocessed/train/patient04650/study1/view1_frontal.png,image_12.png 14 | Datasets/CheXpert/Preprocessed/train/patient33326/study15/view1_frontal.png,image_13.png 15 | Datasets/CheXpert/Preprocessed/train/patient41783/study4/view1_frontal.png,image_14.png 16 | Datasets/CheXpert/Preprocessed/train/patient32697/study1/view1_frontal.png,image_15.png 17 | Datasets/CheXpert/Preprocessed/train/patient06081/study18/view1_frontal.png,image_16.png 18 | Datasets/CheXpert/Preprocessed/train/patient18186/study20/view1_frontal.png,image_17.png 19 | Datasets/CheXpert/Preprocessed/train/patient56446/study1/view1_frontal.png,image_18.png 20 | Datasets/CheXpert/Preprocessed/train/patient08622/study2/view1_frontal.png,image_19.png 21 | Datasets/CheXpert/Preprocessed/train/patient16259/study1/view1_frontal.png,image_20.png 22 | Datasets/CheXpert/Preprocessed/train/patient55039/study1/view1_frontal.png,image_21.png 23 | Datasets/CheXpert/Preprocessed/train/patient12096/study9/view1_frontal.png,image_22.png 24 | Datasets/CheXpert/Preprocessed/train/patient37435/study9/view1_frontal.png,image_23.png 25 | Datasets/CheXpert/Preprocessed/train/patient07209/study5/view1_frontal.png,image_24.png 26 | Datasets/CheXpert/Preprocessed/train/patient02811/study1/view1_frontal.png,image_25.png 27 | Datasets/CheXpert/Preprocessed/train/patient12266/study1/view1_frontal.png,image_26.png 28 | Datasets/CheXpert/Preprocessed/train/patient23195/study2/view1_frontal.png,image_27.png 29 | Datasets/CheXpert/Preprocessed/train/patient16335/study2/view1_frontal.png,image_28.png 30 | Datasets/CheXpert/Preprocessed/train/patient39244/study2/view1_frontal.png,image_29.png 31 | Datasets/CheXpert/Preprocessed/train/patient53157/study3/view1_frontal.png,image_30.png 32 | -------------------------------------------------------------------------------- /TechnicalValidation/PhysicianAnnotations/ToAnnotate/ChestX-Ray8_name_mapping.csv: -------------------------------------------------------------------------------- 1 | Original,New 2 | Datasets/Chest8/ChestX-ray8/images_004/images/00008297_010.png,image_1.png 3 | Datasets/Chest8/ChestX-ray8/images_010/images/00021917_000.png,image_2.png 4 | Datasets/Chest8/ChestX-ray8/images_001/images/00000181_023.png,image_3.png 5 | Datasets/Chest8/ChestX-ray8/images_003/images/00004594_003.png,image_4.png 6 | Datasets/Chest8/ChestX-ray8/images_003/images/00004533_023.png,image_5.png 7 | Datasets/Chest8/ChestX-ray8/images_006/images/00013302_000.png,image_6.png 8 | Datasets/Chest8/ChestX-ray8/images_008/images/00016109_008.png,image_7.png 9 | Datasets/Chest8/ChestX-ray8/images_006/images/00011831_001.png,image_8.png 10 | Datasets/Chest8/ChestX-ray8/images_012/images/00029817_004.png,image_9.png 11 | Datasets/Chest8/ChestX-ray8/images_003/images/00005347_001.png,image_10.png 12 | Datasets/Chest8/ChestX-ray8/images_005/images/00010676_001.png,image_11.png 13 | Datasets/Chest8/ChestX-ray8/images_007/images/00015065_000.png,image_12.png 14 | Datasets/Chest8/ChestX-ray8/images_005/images/00010222_026.png,image_13.png 15 | Datasets/Chest8/ChestX-ray8/images_011/images/00027927_032.png,image_14.png 16 | Datasets/Chest8/ChestX-ray8/images_010/images/00024263_000.png,image_15.png 17 | Datasets/Chest8/ChestX-ray8/images_010/images/00021543_002.png,image_16.png 18 | Datasets/Chest8/ChestX-ray8/images_002/images/00001961_002.png,image_17.png 19 | Datasets/Chest8/ChestX-ray8/images_003/images/00004992_001.png,image_18.png 20 | Datasets/Chest8/ChestX-ray8/images_005/images/00009995_009.png,image_19.png 21 | Datasets/Chest8/ChestX-ray8/images_003/images/00005365_021.png,image_20.png 22 | Datasets/Chest8/ChestX-ray8/images_009/images/00020230_003.png,image_21.png 23 | Datasets/Chest8/ChestX-ray8/images_004/images/00006808_000.png,image_22.png 24 | Datasets/Chest8/ChestX-ray8/images_004/images/00007034_044.png,image_23.png 25 | Datasets/Chest8/ChestX-ray8/images_004/images/00008420_000.png,image_24.png 26 | Datasets/Chest8/ChestX-ray8/images_002/images/00003830_001.png,image_25.png 27 | Datasets/Chest8/ChestX-ray8/images_002/images/00003030_003.png,image_26.png 28 | Datasets/Chest8/ChestX-ray8/images_003/images/00005067_009.png,image_27.png 29 | Datasets/Chest8/ChestX-ray8/images_011/images/00028147_001.png,image_28.png 30 | Datasets/Chest8/ChestX-ray8/images_008/images/00017865_000.png,image_29.png 31 | Datasets/Chest8/ChestX-ray8/images_011/images/00028162_002.png,image_30.png 32 | -------------------------------------------------------------------------------- /TechnicalValidation/PhysicianAnnotations/ToAnnotate/MIMIC_name_mapping.csv: -------------------------------------------------------------------------------- 1 | Original,New 2 | Datasets/MIMIC/Images/p16/p16674342/s54965752/aa9035b3-15e8462a-486fee06-bc434cf0-363a9562.jpg,image_1.png 3 | Datasets/MIMIC/Images/p15/p15894036/s54147998/b537764f-42bcf279-8c733f35-b74b51ce-94e27585.jpg,image_2.png 4 | Datasets/MIMIC/Images/p18/p18068560/s50868130/35c1e1b9-4d1db41f-e761e520-bff59ec9-2f99ec15.jpg,image_3.png 5 | Datasets/MIMIC/Images/p17/p17288529/s58518008/cb70857e-74df1260-8a3fca32-aaf71146-91587d44.jpg,image_4.png 6 | Datasets/MIMIC/Images/p15/p15211758/s50893415/8a5919c0-3dbcac46-c9097710-53dad68c-cb945f3e.jpg,image_5.png 7 | Datasets/MIMIC/Images/p19/p19817306/s53845466/3d0184d5-14d43e88-e3031c3e-3e3adeb0-c765ce79.jpg,image_6.png 8 | Datasets/MIMIC/Images/p10/p10320090/s59169604/72274138-7ce66b73-e2efdbbb-d256f0ce-90ca234e.jpg,image_7.png 9 | Datasets/MIMIC/Images/p12/p12501926/s57338179/f3125018-38813edb-090e831d-ea2914d6-cbd882c1.jpg,image_8.png 10 | Datasets/MIMIC/Images/p13/p13414140/s58551765/23d2fb71-be3ab3cd-28513cd6-637898cf-89f2a600.jpg,image_9.png 11 | Datasets/MIMIC/Images/p13/p13614432/s57693095/09b2c7f4-b016ff6a-10b64c29-e1e48d24-305b128c.jpg,image_10.png 12 | Datasets/MIMIC/Images/p10/p10072167/s55283974/250a78d4-af5baabd-28ba3b84-13941316-dc3f1d7d.jpg,image_11.png 13 | Datasets/MIMIC/Images/p11/p11936058/s58456268/d1306bc1-2d0e7521-b54f9a49-0c9809c7-0d9b8b34.jpg,image_12.png 14 | Datasets/MIMIC/Images/p14/p14260564/s53447127/2d3bb499-2f1b2940-abf7d60b-e9211859-d38a9e31.jpg,image_13.png 15 | Datasets/MIMIC/Images/p17/p17155701/s57851620/ece3a509-dc770b0d-38155a8e-547025b0-8e4920f1.jpg,image_14.png 16 | Datasets/MIMIC/Images/p11/p11285537/s52380526/28f51842-7da9734c-56a16898-dc565138-200a9b99.jpg,image_15.png 17 | Datasets/MIMIC/Images/p18/p18279765/s57727261/b644376c-5142a23b-54c4a0fd-95f714e0-e8191e6f.jpg,image_16.png 18 | Datasets/MIMIC/Images/p13/p13246084/s53244919/e3fde7e9-64eee3bb-ef111816-f2676df5-7a1d6344.jpg,image_17.png 19 | Datasets/MIMIC/Images/p18/p18835690/s55607549/fbf3310d-fb72728b-9ff85384-1e950d0f-e27ad88c.jpg,image_18.png 20 | Datasets/MIMIC/Images/p14/p14531057/s59790477/2ef6bd02-612279f4-616a3c03-2da35e29-e4689df1.jpg,image_19.png 21 | Datasets/MIMIC/Images/p17/p17637826/s50660966/f03cb3f4-f26bad02-f78478a1-1d281d85-34525a17.jpg,image_20.png 22 | Datasets/MIMIC/Images/p14/p14691065/s55178936/05e6dbbb-dcca4fdb-cdb210e0-ddc25d85-cb3da0d3.jpg,image_21.png 23 | Datasets/MIMIC/Images/p11/p11964706/s54230905/4faa4abe-37c38a51-a5c2454b-a3e224ad-20afdc0a.jpg,image_22.png 24 | Datasets/MIMIC/Images/p18/p18450763/s50131296/8897fe51-c449d221-66f5871a-6238c6cd-3ebb32d1.jpg,image_23.png 25 | Datasets/MIMIC/Images/p15/p15539564/s55869955/22c1b5e7-99fa52a0-6450e7b4-cfc0f445-ab641b6f.jpg,image_24.png 26 | Datasets/MIMIC/Images/p17/p17123392/s58957817/787b618e-8da7255b-c92a5321-00ae07e0-d08dfd74.jpg,image_25.png 27 | Datasets/MIMIC/Images/p15/p15027069/s59201939/40abda26-c5d365c8-08dbd533-74946cab-b5c758be.jpg,image_26.png 28 | Datasets/MIMIC/Images/p18/p18559633/s52797881/795a7c3e-d8202892-bc839744-24452477-7ba85ed0.jpg,image_27.png 29 | Datasets/MIMIC/Images/p11/p11242955/s52770282/6a8d22dd-96168387-55483e2f-a3fe8898-2ae24eef.jpg,image_28.png 30 | Datasets/MIMIC/Images/p12/p12128727/s55517270/4a8c0092-8d761d7c-417f6abf-f6cdca22-5c042751.jpg,image_29.png 31 | Datasets/MIMIC/Images/p14/p14894374/s56025896/3b75eb41-3487b810-dea899b9-de6a9ca4-b73dd6e0.jpg,image_30.png 32 | -------------------------------------------------------------------------------- /TechnicalValidation/PhysicianAnnotations/ToAnnotate/Padchest_name_mapping.csv: -------------------------------------------------------------------------------- 1 | Original,New 2 | Datasets/Padchest/Images/216840111366964013402131755672012181100418624_01-141-118.png,image_1.png 3 | Datasets/Padchest/Images/216840111366964013686042548532013221082422693_02-000-178.png,image_2.png 4 | Datasets/Padchest/Images/216840111366964013530061063072012321085627057_01-146-083.png,image_3.png 5 | Datasets/Padchest/Images/216840111366964012989926673512011147185707170_00-147-191.png,image_4.png 6 | Datasets/Padchest/Images/145334097759362661396838319438789295635_l8htn4.png,image_5.png 7 | Datasets/Padchest/Images/216840111366964012487858717522009281090101082_00-073-168.png,image_6.png 8 | Datasets/Padchest/Images/216840111366964013307756408102012093102659013_01-110-104.png,image_7.png 9 | Datasets/Padchest/Images/216840111366964012734950068292010152103831303_00-123-147.png,image_8.png 10 | Datasets/Padchest/Images/216840111366964013534861372972012338141749172_01-132-197.png,image_9.png 11 | Datasets/Padchest/Images/216840111366964014008416513202014156101219015_01-192-013.png,image_10.png 12 | Datasets/Padchest/Images/223887483957886725759625810180819317596_3rc0oo.png,image_11.png 13 | Datasets/Padchest/Images/216840111366964013686042548532013268105422846_02-020-077.png,image_12.png 14 | Datasets/Padchest/Images/216840111366964013649107288022013097201210236_02-071-103.png,image_13.png 15 | Datasets/Padchest/Images/216840111366964012558082906712009348103010426_00-091-029.png,image_14.png 16 | Datasets/Padchest/Images/216840111366964013217898866992011326134811437_01-029-193.png,image_15.png 17 | Datasets/Padchest/Images/9010531952147033496535640582177286331_l7g7sr.png,image_16.png 18 | Datasets/Padchest/Images/216840111366964013590140476722013071144231366_02-055-193.png,image_17.png 19 | Datasets/Padchest/Images/216840111366964013217898866992011332134237003_01-031-075.png,image_18.png 20 | Datasets/Padchest/Images/216840111366964012989926673512011069132526434_00-167-094.png,image_19.png 21 | Datasets/Padchest/Images/216840111366964013530061063072012325154902058_01-155-154.png,image_20.png 22 | Datasets/Padchest/Images/216840111366964013274515230432012039114328088_01-102-065.png,image_21.png 23 | Datasets/Padchest/Images/216840111366964012989926673512011158162955453_01-015-100.png,image_22.png 24 | Datasets/Padchest/Images/216840111366964012373310883942009112125735231_00-069-182.png,image_23.png 25 | Datasets/Padchest/Images/216840111366964012373310883942009147104238290_00-098-036.png,image_24.png 26 | Datasets/Padchest/Images/216840111366964012768025509942010183134243327_03-139-084.png,image_25.png 27 | Datasets/Padchest/Images/12131094702766103504306897605252047439_jjao1d.png,image_26.png 28 | Datasets/Padchest/Images/216840111366964013530061063072012321131046494_01-151-082.png,image_27.png 29 | Datasets/Padchest/Images/196111960564836351259754229027413296391_e3w72r.png,image_28.png 30 | Datasets/Padchest/Images/216840111366964013402131755672012177131901533_01-139-022.png,image_29.png 31 | Datasets/Padchest/Images/216840111366964012819207061112010295084205126_03-190-091.png,image_30.png 32 | -------------------------------------------------------------------------------- /TechnicalValidation/PhysicianAnnotations/ToAnnotate/VinBigData_name_mapping.csv: -------------------------------------------------------------------------------- 1 | Original,New 2 | Datasets/VinBigData/pngs/train/95b9b32e68bfb95100cc300664f9aae5.png,image_1.png 3 | Datasets/VinBigData/pngs/train/ff335f1c7745c6184a5732cc5a01092d.png,image_2.png 4 | Datasets/VinBigData/pngs/train/f4962890f0c6db4611fec9d3d0e97ea2.png,image_3.png 5 | Datasets/VinBigData/pngs/train/e5c03ed2c3823410a7a9571117c776ad.png,image_4.png 6 | Datasets/VinBigData/pngs/train/124ec853c372fcf7f4428b26b32be62f.png,image_5.png 7 | Datasets/VinBigData/pngs/test/b946a39ccc9817429acdf4034de8ad5a.png,image_6.png 8 | Datasets/VinBigData/pngs/train/39b3e751f3256e7b2da318acd07303d3.png,image_7.png 9 | Datasets/VinBigData/pngs/test/6f04094bb30e028d5ae7dba96788e9dd.png,image_8.png 10 | Datasets/VinBigData/pngs/train/a0348415e6b96ae019627d01bf8e6e8c.png,image_9.png 11 | Datasets/VinBigData/pngs/train/954e88119b53f34c3b013905b94fc4fe.png,image_10.png 12 | Datasets/VinBigData/pngs/train/6f772618681e1eb89694966fe89f6a31.png,image_11.png 13 | Datasets/VinBigData/pngs/train/b4d9fcbd83e33396cf8178e6b755acd8.png,image_12.png 14 | Datasets/VinBigData/pngs/train/ed5a8bb58eb699827e3d05b535f28933.png,image_13.png 15 | Datasets/VinBigData/pngs/train/96a75857c0c98d3f8713d10e0f9531ca.png,image_14.png 16 | Datasets/VinBigData/pngs/train/577807f1ee94692b516b0b184b89ab35.png,image_15.png 17 | Datasets/VinBigData/pngs/train/1b3379be7c72c3ae5404df423ed74388.png,image_16.png 18 | Datasets/VinBigData/pngs/train/774c1ca3798d3054ac1a413570e665c9.png,image_17.png 19 | Datasets/VinBigData/pngs/train/fd3328ac67ebfde350f68a954c738c77.png,image_18.png 20 | Datasets/VinBigData/pngs/train/c13fe1df2a06cc56d5b15e569c616fbc.png,image_19.png 21 | Datasets/VinBigData/pngs/train/1c75d6cd1db3342517d4bf4c251400a1.png,image_20.png 22 | Datasets/VinBigData/pngs/train/1d21c69075630b741fe0629e696fa167.png,image_21.png 23 | Datasets/VinBigData/pngs/train/3aa551a987c904204d7b59d3dd11d680.png,image_22.png 24 | Datasets/VinBigData/pngs/train/27129548cc62420449c33ed5fb41eec6.png,image_23.png 25 | Datasets/VinBigData/pngs/train/24c9723acd53177da3c6fd40b0d80d6d.png,image_24.png 26 | Datasets/VinBigData/pngs/train/5780e843511eb33150ad2d151aeed276.png,image_25.png 27 | Datasets/VinBigData/pngs/train/629f7adb357049205885894711a12343.png,image_26.png 28 | Datasets/VinBigData/pngs/test/ec6c4e81b448275577d3e4a74acbe443.png,image_27.png 29 | Datasets/VinBigData/pngs/train/1f51eb9814d10526ba32069aa0f99b4f.png,image_28.png 30 | Datasets/VinBigData/pngs/train/c36dafa1ed22b81682dbbd30a6dd0b32.png,image_29.png 31 | Datasets/VinBigData/pngs/train/bd1aeb3b9a96232d36037a08070a2149.png,image_30.png 32 | -------------------------------------------------------------------------------- /TechnicalValidation/PhysicianAnnotations/ToAnnotate/hists/Candid_histogram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaggion/CheXmask-Database/8ae0c338caced10869f8be1860e320146c40d096/TechnicalValidation/PhysicianAnnotations/ToAnnotate/hists/Candid_histogram.png -------------------------------------------------------------------------------- /TechnicalValidation/PhysicianAnnotations/ToAnnotate/hists/histogram_chest8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaggion/CheXmask-Database/8ae0c338caced10869f8be1860e320146c40d096/TechnicalValidation/PhysicianAnnotations/ToAnnotate/hists/histogram_chest8.png -------------------------------------------------------------------------------- /TechnicalValidation/PhysicianAnnotations/ToAnnotate/hists/histogram_chexp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaggion/CheXmask-Database/8ae0c338caced10869f8be1860e320146c40d096/TechnicalValidation/PhysicianAnnotations/ToAnnotate/hists/histogram_chexp.png -------------------------------------------------------------------------------- /TechnicalValidation/PhysicianAnnotations/ToAnnotate/hists/histogram_mimic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaggion/CheXmask-Database/8ae0c338caced10869f8be1860e320146c40d096/TechnicalValidation/PhysicianAnnotations/ToAnnotate/hists/histogram_mimic.png -------------------------------------------------------------------------------- /TechnicalValidation/PhysicianAnnotations/ToAnnotate/hists/histogram_padchest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaggion/CheXmask-Database/8ae0c338caced10869f8be1860e320146c40d096/TechnicalValidation/PhysicianAnnotations/ToAnnotate/hists/histogram_padchest.png -------------------------------------------------------------------------------- /TechnicalValidation/PhysicianAnnotations/ToAnnotate/hists/histogram_vindr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaggion/CheXmask-Database/8ae0c338caced10869f8be1860e320146c40d096/TechnicalValidation/PhysicianAnnotations/ToAnnotate/hists/histogram_vindr.png -------------------------------------------------------------------------------- /TechnicalValidation/PhysicianAnnotations/histograms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaggion/CheXmask-Database/8ae0c338caced10869f8be1860e320146c40d096/TechnicalValidation/PhysicianAnnotations/histograms.png -------------------------------------------------------------------------------- /TechnicalValidation/PhysicianAnnotations/julia_annotations.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaggion/CheXmask-Database/8ae0c338caced10869f8be1860e320146c40d096/TechnicalValidation/PhysicianAnnotations/julia_annotations.xls -------------------------------------------------------------------------------- /TechnicalValidation/PhysicianAnnotations/julia_annotations_set-findings.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaggion/CheXmask-Database/8ae0c338caced10869f8be1860e320146c40d096/TechnicalValidation/PhysicianAnnotations/julia_annotations_set-findings.xls -------------------------------------------------------------------------------- /TechnicalValidation/PhysicianAnnotations/martina_annotations.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaggion/CheXmask-Database/8ae0c338caced10869f8be1860e320146c40d096/TechnicalValidation/PhysicianAnnotations/martina_annotations.xls -------------------------------------------------------------------------------- /TechnicalValidation/PhysicianAnnotations/martina_annotations_set-findings.xls: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaggion/CheXmask-Database/8ae0c338caced10869f8be1860e320146c40d096/TechnicalValidation/PhysicianAnnotations/martina_annotations_set-findings.xls -------------------------------------------------------------------------------- /TechnicalValidation/PhysicianAnnotations/mse_per_landmark_1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaggion/CheXmask-Database/8ae0c338caced10869f8be1860e320146c40d096/TechnicalValidation/PhysicianAnnotations/mse_per_landmark_1.pdf -------------------------------------------------------------------------------- /TechnicalValidation/PhysicianAnnotations/mse_per_landmark_2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaggion/CheXmask-Database/8ae0c338caced10869f8be1860e320146c40d096/TechnicalValidation/PhysicianAnnotations/mse_per_landmark_2.pdf -------------------------------------------------------------------------------- /TechnicalValidation/PhysicianAnnotations/rca_chest_landmark.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaggion/CheXmask-Database/8ae0c338caced10869f8be1860e320146c40d096/TechnicalValidation/PhysicianAnnotations/rca_chest_landmark.pdf -------------------------------------------------------------------------------- /TechnicalValidation/PhysicianAnnotations/samples_code.py: -------------------------------------------------------------------------------- 1 | import os 2 | import random 3 | import pandas as pd 4 | import numpy as np 5 | import shutil 6 | import matplotlib.pyplot as plt 7 | 8 | # Function to create histogram and sample files 9 | def process_dataframe(df, name): 10 | # Create histogram with 10 bins 11 | hist, bin_edges = np.histogram(df['Dice_RCA_Max'], bins=10) 12 | 13 | # Sample 1 file per bin 14 | samples_per_bin = [] 15 | for i in range(len(bin_edges) - 1): 16 | filtered_df = df[(df['Dice_RCA_Max'] >= bin_edges[i]) & (df['Dice_RCA_Max'] < bin_edges[i+1])] 17 | sample = filtered_df.sample() 18 | samples_per_bin.append(sample) 19 | 20 | # Sample 20 random files 21 | random_samples = df.sample(20) 22 | 23 | # Create a new folder 24 | os.makedirs(name, exist_ok=True) 25 | 26 | # Save the 30 files and create a dictionary with the original and new names 27 | original_to_new = {} 28 | 29 | combined_samples = pd.concat(samples_per_bin + [random_samples]) 30 | for i, (_, sample) in enumerate(combined_samples.iterrows()): 31 | image_path = sample['Image'] 32 | new_name = f'image_{i + 1}.png' 33 | 34 | new_path = os.path.join(name, new_name) 35 | 36 | # Load the segmentation map (replace with the path to your segmentation map) 37 | if "Padchest" in image_path: 38 | landmark = image_path.replace('Images', 'Output').replace('.png', '.txt') 39 | elif "MIMIC" in image_path or "CANDID" in image_path: 40 | landmark = image_path.replace('Images', 'Output').replace('.jpg', '.txt') 41 | elif "VinBigData" in image_path: 42 | landmark = image_path.replace('pngs', 'Output').replace('.png', '.txt') 43 | elif "CheXpert" in image_path: 44 | landmark = image_path.replace('Preprocessed', 'Output').replace('.png', '.txt') 45 | elif "Chest8" in image_path: 46 | landmark = image_path.replace("ChestX-ray8", "Output").replace('images/','')[0:-3] + 'txt' 47 | 48 | # Copy the file to the new location (assumes it's an image file) 49 | shutil.copy(image_path, new_path) 50 | shutil.copy(landmark, new_path.replace('.png', '.txt')) 51 | 52 | original_to_new[image_path] = new_name 53 | 54 | # Save the histogram of sampled values 55 | plt.figure() 56 | plt.hist(combined_samples['Dice_RCA_Max'], bins=bin_edges) 57 | plt.xlabel('Dice_RCA_Max') 58 | plt.ylabel('Frequency') 59 | plt.title(f'Histogram of sampled values for {name}') 60 | plt.savefig(os.path.join(name, 'histogram.png')) 61 | 62 | return original_to_new 63 | 64 | # Read CSV files 65 | df1 = pd.read_csv('Datasets/CANDID/CANDID_RCA.csv') 66 | name1 = 'CANDID' 67 | df1 = df1[df1['Dice_RCA_Max'] > 0.7] 68 | 69 | df2 = pd.read_csv('Datasets/Chest8/CHEST8_RCA.csv') 70 | name2 = 'ChestX-Ray8' 71 | df2 = df2[df2['Dice_RCA_Max'] > 0.7] 72 | 73 | df3 = pd.read_csv('Datasets/CheXpert/CHEXPERT_RCA.csv') 74 | name3 = 'CheXpert' 75 | df3 = df3[df3['Dice_RCA_Max'] > 0.7] 76 | 77 | df4 = pd.read_csv('Datasets/MIMIC/MIMIC_RCA.csv') 78 | name4 = "MIMIC" 79 | df4 = df4[df4['Dice_RCA_Max'] > 0.7] 80 | 81 | df5 = pd.read_csv('Datasets/Padchest/Padchest_RCA.csv') 82 | name5 = 'Padchest' 83 | df5 = df5[df5['Dice_RCA_Max'] > 0.7] 84 | 85 | df6 = pd.read_csv('Datasets/VinBigData/VinBigData_RCA.csv') 86 | name6 = 'VinBigData' 87 | df6 = df6[df6['Dice_RCA_Max'] > 0.7] 88 | 89 | # Process dataframes and get dictionaries with original and new names 90 | orig_to_new1 = process_dataframe(df1, name1) 91 | orig_to_new2 = process_dataframe(df2, name2) 92 | orig_to_new3 = process_dataframe(df3, name3) 93 | orig_to_new4 = process_dataframe(df4, name4) 94 | orig_to_new5 = process_dataframe(df5, name5) 95 | orig_to_new6 = process_dataframe(df6, name6) 96 | 97 | # Save the dictionaries as CSV files 98 | pd.DataFrame(orig_to_new1.items(), columns=['Original', 'New']).to_csv(f'{name1}_name_mapping.csv', index=False) 99 | pd.DataFrame(orig_to_new2.items(), columns=['Original', 'New']).to_csv(f'{name2}_name_mapping.csv', index=False) 100 | pd.DataFrame(orig_to_new3.items(), columns=['Original', 'New']).to_csv(f'{name3}_name_mapping.csv', index=False) 101 | pd.DataFrame(orig_to_new4.items(), columns=['Original', 'New']).to_csv(f'{name4}_name_mapping.csv', index=False) 102 | pd.DataFrame(orig_to_new5.items(), columns=['Original', 'New']).to_csv(f'{name5}_name_mapping.csv', index=False) 103 | pd.DataFrame(orig_to_new6.items(), columns=['Original', 'New']).to_csv(f'{name6}_name_mapping.csv', index=False) -------------------------------------------------------------------------------- /Weights/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaggion/CheXmask-Database/8ae0c338caced10869f8be1860e320146c40d096/Weights/.gitkeep -------------------------------------------------------------------------------- /figures/histogram_CheXmask.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaggion/CheXmask-Database/8ae0c338caced10869f8be1860e320146c40d096/figures/histogram_CheXmask.pdf -------------------------------------------------------------------------------- /figures/histogram_CheXmask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngaggion/CheXmask-Database/8ae0c338caced10869f8be1860e320146c40d096/figures/histogram_CheXmask.png --------------------------------------------------------------------------------