├── 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 |
--------------------------------------------------------------------------------