├── docs ├── performance.png ├── pipeline.png └── samples.png ├── readme.md └── run_gmm_chp_segmentation.py /docs/performance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EhsanTadayon/choroid-plexus-segmentation/729d5e515ae649d68874f6b8b8f1d98e9d6518e0/docs/performance.png -------------------------------------------------------------------------------- /docs/pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EhsanTadayon/choroid-plexus-segmentation/729d5e515ae649d68874f6b8b8f1d98e9d6518e0/docs/pipeline.png -------------------------------------------------------------------------------- /docs/samples.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EhsanTadayon/choroid-plexus-segmentation/729d5e515ae649d68874f6b8b8f1d98e9d6518e0/docs/samples.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Choroid plexus segmentation using Gaussian Mixture Models (GMM) 2 | 3 | Studies of choroid plexus have recently gained attention. Given its role in CSF production, choroid plexus plays a crucial role in CSF-based clearance systems. Moreover, choroid plexus epithelium is lined with numerous transporters that transport various CSF proteins including Aβ to the blood. T1-weighted MRIs provide a non-invasive imaging technique to study the morphological characteristics of choroid plexus and also enable more advanced functional and perfusion imaging studies. Previous studies have used Freesurfer for automatic choroid plexus segmentation. Here, we present a lightweight algorithm that aims to improve choroid plexus segmentation using Gaussian Mixture Models (GMM). We tested the accuracy of the algorithm against manual segmentations as well as Freesurfer. 4 | 5 |

Citation

6 | Our paper describing this lightweight algorithm with potential implications for multi-modal neuroimaging studies of choroid plexus in dementia is published. If you use ChP-GMM segmentation, please cite our paper: 7 | 8 | Tadayon, E., Moret, B., Sprugnoli, G., Monti, L., Pascual-Leone, A., Santarnecchi, E. and Alzheimer’s Disease Neuroimaging Initiative, 2020. Improving Choroid Plexus Segmentation in the Healthy and Diseased Brain: Relevance for Tau-PET Imaging in Dementia. Journal of Alzheimer's Disease 9 | . 10 | 11 |

Pipeline

12 | 13 | 14 | 15 |

Comparing GMM and Freesurfer against Manual Segmentation (MS) in 20 subjects of Human Connectome Project (HCP) dataset

16 | 17 | MSNC: Manual Segmentation using T1-weighted MRIs with No Contrast
18 | MSNC1/2: MS performed by researcher 1 or 2
19 | Dice Coefficient (DC): A metric that calculates spatial similarity between two segmentations 20 | 21 |

Choroid plexus segmentation for three representative subjects of HCP dataset using Freesurfer and GMM

22 | 23 | 24 |

Required packages

25 | 26 | * FSL 27 | * Freesurfer 28 | * Python: nibabel, sklearn, numpy 29 | 30 |

How to run the code

31 | The current version of the script requires Freesurfer processed files (recon-all) for ventricular segmentation. In future, we aim to add the possibility to use other ventricular segmentation algorithms to speed up the process. After running recon-all, you can get the choroid plexus segmentation as follows: 32 | 33 | In the terminal: 34 | ```bash 35 | python run_gmm_chp_segmentation.py 36 | ``` 37 | The resulting choroid plexus segmentation can be found under `//mri/choroid_susan_segmentation.nii.gz` 38 | 39 |

Contact

40 | For further questions, please email me at ehsan.tadayon84@gmail.com. 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /run_gmm_chp_segmentation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | ChP-Seg: A lightweight python script for accurate segmentation of choroid plexus 4 | Author: Ehsan Tadayon, MD 5 | Date: 2019 6 | """ 7 | 8 | import sys 9 | import subprocess 10 | import numpy as np 11 | import nibabel as nib 12 | from sklearn.mixture import GaussianMixture, BayesianGaussianMixture 13 | 14 | 15 | def run_cmd(cmd): 16 | """Run a shell command and print its output and errors.""" 17 | print(cmd) 18 | result = subprocess.run(cmd, shell=True, capture_output=True, text=True) 19 | print(result.stdout) 20 | show_error(result.stderr) 21 | return result.stdout, result.stderr 22 | 23 | 24 | def show_error(err): 25 | """Print error messages if any.""" 26 | if err: 27 | print(err) 28 | 29 | 30 | def susan(input_img): 31 | """ 32 | Run the 'susan' command on the given image. 33 | The command assumes the image file has a '.nii.gz' extension. 34 | """ 35 | base_img = input_img.split('.nii')[0] 36 | cmd = f'susan {base_img}.nii.gz 1 1 3 1 0 {base_img}_susan.nii.gz' 37 | run_cmd(cmd) 38 | 39 | 40 | def save_segmentation(clf, out_name, mask_t1_vals, X, mask_indices, mask_obj, subjects_dir, subj): 41 | """ 42 | Save segmentation result from a clustering model. 43 | 44 | Parameters: 45 | clf : clustering model (e.g. GaussianMixture or BayesianGaussianMixture) 46 | out_name : output filename (e.g. 'lh_choroid_gmmb_mask.nii.gz') 47 | mask_t1_vals : intensity values from the T1 image within the mask 48 | X : feature array (e.g. intensity values reshaped to (-1, 1)) 49 | mask_indices : tuple of indices (i, j, k) where the mask is 1 50 | mask_obj : nibabel image object (used to access the affine) 51 | subjects_dir : base directory of the subjects 52 | subj : subject identifier 53 | """ 54 | # Initialize an empty image volume 55 | new_img = np.zeros((256, 256, 256)) 56 | 57 | # Predict labels and decide which cluster corresponds to the choroid plexus 58 | predictions = clf.predict(X) 59 | if np.mean(mask_t1_vals[predictions == 1]) > np.mean(mask_t1_vals[predictions == 0]): 60 | choroid_ind = np.where(predictions == 1)[0] 61 | else: 62 | choroid_ind = np.where(predictions == 0)[0] 63 | 64 | choroid_coords = (mask_indices[0][choroid_ind], 65 | mask_indices[1][choroid_ind], 66 | mask_indices[2][choroid_ind]) 67 | new_img[choroid_coords] = 1 68 | img_obj = nib.Nifti1Image(new_img, mask_obj.affine) 69 | out_path = f'{subjects_dir}/{subj}/mri/{out_name}' 70 | nib.save(img_obj, out_path) 71 | 72 | 73 | def write_stats(input_img, fname): 74 | """ 75 | Write volume statistics (from fslstats) for the given image to a file. 76 | 77 | Parameters: 78 | input_img : path to the image file 79 | fname : path to the output text file 80 | """ 81 | cmd = f'fslstats {input_img} -V' 82 | out, _ = run_cmd(cmd) 83 | stat = out.split('\n')[0].split(' ')[0] 84 | with open(fname, 'w') as f: 85 | f.write(stat) 86 | 87 | 88 | def main(): 89 | if len(sys.argv) < 3: 90 | print("Usage: python script.py ") 91 | sys.exit(1) 92 | 93 | subjects_dir = sys.argv[1] 94 | subj = sys.argv[2] 95 | 96 | # Load the T1 volume using nibabel's get_fdata (preferred in Python 3) 97 | t1_path = f'{subjects_dir}/{subj}/mri/T1.mgz' 98 | T1 = nib.load(t1_path).get_fdata() 99 | 100 | # Create masks for choroid and ventricle segmentation 101 | print('Creating masks: choroid+ventricle_mask.nii.gz and aseg_choroid_mask.nii.gz') 102 | cmd = ( 103 | f'mri_binarize --i {subjects_dir}/{subj}/mri/aseg.mgz ' 104 | f'--match 31 63 --o {subjects_dir}/{subj}/mri/aseg_choroid_mask.nii.gz' 105 | ) 106 | run_cmd(cmd) 107 | 108 | cmd = ( 109 | f'mri_binarize --i {subjects_dir}/{subj}/mri/aseg.mgz ' 110 | f'--match 4 5 31 --o {subjects_dir}/{subj}/mri/lh_choroid+ventricle_mask.nii.gz' 111 | ) 112 | run_cmd(cmd) 113 | 114 | cmd = ( 115 | f'mri_binarize --i {subjects_dir}/{subj}/mri/aseg.mgz ' 116 | f'--match 43 44 63 --o {subjects_dir}/{subj}/mri/rh_choroid+ventricle_mask.nii.gz' 117 | ) 118 | run_cmd(cmd) 119 | 120 | # --- Left Hemisphere Processing --- 121 | print('Processing left hemisphere...') 122 | lh_mask_path = f'{subjects_dir}/{subj}/mri/lh_choroid+ventricle_mask.nii.gz' 123 | lh_mask_obj = nib.load(lh_mask_path) 124 | lh_mask = lh_mask_obj.get_fdata() 125 | lh_mask_indices = np.where(lh_mask == 1) 126 | mask_t1_vals = T1[lh_mask_indices] 127 | X = mask_t1_vals.reshape(-1, 1) 128 | 129 | bgmm_lh = BayesianGaussianMixture(n_components=2, covariance_type='full').fit(X) 130 | save_segmentation(bgmm_lh, 'lh_choroid_gmmb_mask.nii.gz', mask_t1_vals, X, 131 | lh_mask_indices, lh_mask_obj, subjects_dir, subj) 132 | 133 | # Apply Susan filtering on left hemisphere 134 | lh_gmmb_mask_path = f'{subjects_dir}/{subj}/mri/lh_choroid_gmmb_mask.nii.gz' 135 | susan(lh_gmmb_mask_path) 136 | 137 | # Read the Susan-filtered mask and further segment 138 | lh_choroid_gmmb_mask = nib.load(lh_gmmb_mask_path).get_fdata() 139 | lh_choroid_gmmb_mask_indices = np.where(lh_choroid_gmmb_mask == 1) 140 | lh_choroid_gmmb_susan = nib.load( 141 | f'{subjects_dir}/{subj}/mri/lh_choroid_gmmb_mask_susan.nii.gz' 142 | ).get_fdata() 143 | susan_vals = lh_choroid_gmmb_susan[lh_choroid_gmmb_mask_indices] 144 | 145 | bgmm_susan_lh = BayesianGaussianMixture(n_components=3).fit(susan_vals.reshape(-1, 1)) 146 | susan_predictions = bgmm_susan_lh.predict(susan_vals.reshape(-1, 1)) 147 | means = bgmm_susan_lh.means_.flatten() 148 | choroid_cluster = int(np.argmax(means)) 149 | 150 | lh_choroid_seg = np.zeros(lh_choroid_gmmb_mask.shape) 151 | indices = np.where(susan_predictions == choroid_cluster) 152 | lh_choroid_seg[(lh_choroid_gmmb_mask_indices[0][indices], 153 | lh_choroid_gmmb_mask_indices[1][indices], 154 | lh_choroid_gmmb_mask_indices[2][indices])] = 1 155 | 156 | lh_choroid_seg_obj = nib.Nifti1Image(lh_choroid_seg, 157 | nib.load(lh_gmmb_mask_path).affine) 158 | nib.save(lh_choroid_seg_obj, 159 | f'{subjects_dir}/{subj}/mri/lh_choroid_susan_segmentation.nii.gz') 160 | 161 | # --- Right Hemisphere Processing --- 162 | print('Processing right hemisphere...') 163 | rh_mask_path = f'{subjects_dir}/{subj}/mri/rh_choroid+ventricle_mask.nii.gz' 164 | rh_mask_obj = nib.load(rh_mask_path) 165 | rh_mask = rh_mask_obj.get_fdata() 166 | rh_mask_indices = np.where(rh_mask == 1) 167 | mask_t1_vals_rh = T1[rh_mask_indices] 168 | X_rh = mask_t1_vals_rh.reshape(-1, 1) 169 | 170 | # Fit both GMM and BayesianGMM (only Bayesian is used for segmentation) 171 | _ = GaussianMixture(n_components=2, covariance_type='full').fit(X_rh) 172 | bgmm_rh = BayesianGaussianMixture(n_components=2, covariance_type='full').fit(X_rh) 173 | save_segmentation(bgmm_rh, 'rh_choroid_gmmb_mask.nii.gz', mask_t1_vals_rh, X_rh, 174 | rh_mask_indices, rh_mask_obj, subjects_dir, subj) 175 | 176 | # Apply Susan filtering on right hemisphere 177 | rh_gmmb_mask_path = f'{subjects_dir}/{subj}/mri/rh_choroid_gmmb_mask.nii.gz' 178 | susan(rh_gmmb_mask_path) 179 | 180 | rh_choroid_gmmb_mask = nib.load(rh_gmmb_mask_path).get_fdata() 181 | rh_choroid_gmmb_susan = nib.load( 182 | f'{subjects_dir}/{subj}/mri/rh_choroid_gmmb_mask_susan.nii.gz' 183 | ).get_fdata() 184 | rh_choroid_gmmb_mask_indices = np.where(rh_choroid_gmmb_mask == 1) 185 | susan_vals_rh = rh_choroid_gmmb_susan[rh_choroid_gmmb_mask_indices] 186 | 187 | bgmm_susan_rh = BayesianGaussianMixture(n_components=3).fit(susan_vals_rh.reshape(-1, 1)) 188 | susan_predictions_rh = bgmm_susan_rh.predict(susan_vals_rh.reshape(-1, 1)) 189 | means_rh = bgmm_susan_rh.means_.flatten() 190 | choroid_cluster_rh = int(np.argmax(means_rh)) 191 | 192 | rh_choroid_seg = np.zeros(rh_choroid_gmmb_mask.shape) 193 | indices_rh = np.where(susan_predictions_rh == choroid_cluster_rh) 194 | rh_choroid_seg[(rh_choroid_gmmb_mask_indices[0][indices_rh], 195 | rh_choroid_gmmb_mask_indices[1][indices_rh], 196 | rh_choroid_gmmb_mask_indices[2][indices_rh])] = 1 197 | 198 | rh_choroid_seg_obj = nib.Nifti1Image(rh_choroid_seg, 199 | nib.load(rh_gmmb_mask_path).affine) 200 | nib.save(rh_choroid_seg_obj, 201 | f'{subjects_dir}/{subj}/mri/rh_choroid_susan_segmentation.nii.gz') 202 | 203 | # --- Combine Final Masks --- 204 | cmd = ( 205 | f'fslmaths {subjects_dir}/{subj}/mri/lh_choroid_susan_segmentation.nii.gz ' 206 | f'-add {subjects_dir}/{subj}/mri/rh_choroid_susan_segmentation.nii.gz ' 207 | f'{subjects_dir}/{subj}/mri/choroid_susan_segmentation.nii.gz' 208 | ) 209 | run_cmd(cmd) 210 | 211 | cmd = ( 212 | f'fslmaths {subjects_dir}/{subj}/mri/lh_choroid_gmmb_mask.nii.gz ' 213 | f'-add {subjects_dir}/{subj}/mri/rh_choroid_gmmb_mask.nii.gz ' 214 | f'{subjects_dir}/{subj}/mri/choroid_gmmb_mask.nii.gz' 215 | ) 216 | run_cmd(cmd) 217 | 218 | # --- Save Stats --- 219 | img_names = [ 220 | 'lh_choroid_gmmb_mask', 221 | 'lh_choroid_susan_segmentation', 222 | 'rh_choroid_gmmb_mask', 223 | 'rh_choroid_susan_segmentation', 224 | 'choroid_susan_segmentation' 225 | ] 226 | 227 | for img in img_names: 228 | input_img = f'{subjects_dir}/{subj}/mri/{img}.nii.gz' 229 | fname = f'{subjects_dir}/{subj}/mri/{img}_stat.txt' 230 | write_stats(input_img, fname) 231 | 232 | 233 | if __name__ == '__main__': 234 | main() 235 | --------------------------------------------------------------------------------