ChrisXzZ commited on
Commit
400158c
·
verified ·
1 Parent(s): eb16422

Delete p-processing.py

Browse files
Files changed (1) hide show
  1. p-processing.py +0 -240
p-processing.py DELETED
@@ -1,240 +0,0 @@
1
- import os
2
- import argparse
3
- from concurrent.futures import ProcessPoolExecutor, as_completed
4
-
5
- import nibabel as nib
6
- import numpy as np
7
- from scipy import ndimage
8
- from tqdm import tqdm
9
-
10
-
11
- def parse_pairs(pair_str):
12
- pairs = []
13
- items = [x.strip() for x in pair_str.split(",") if x.strip()]
14
- for item in items:
15
- if "-" not in item:
16
- raise ValueError(f"Invalid pair format: {item}. Expected format like '1-2,3-4'")
17
- a, b = item.split("-")
18
- pairs.append((int(a.strip()), int(b.strip())))
19
- return pairs
20
-
21
-
22
- def parse_labels(label_str):
23
- return [int(x.strip()) for x in label_str.split(",") if x.strip()]
24
-
25
-
26
- def find_nii_gz_files(input_dir):
27
- files = []
28
- for root, _, filenames in os.walk(input_dir):
29
- for fname in filenames:
30
- if fname.endswith(".nii.gz"):
31
- files.append(os.path.join(root, fname))
32
- return sorted(files)
33
-
34
-
35
- def get_structure(connectivity=26):
36
- if connectivity == 6:
37
- return ndimage.generate_binary_structure(3, 1)
38
- elif connectivity == 26:
39
- return ndimage.generate_binary_structure(3, 2)
40
- else:
41
- raise ValueError("connectivity must be 6 or 26")
42
-
43
-
44
- def remove_components_without_overlap_after_dilating_a(
45
- seg,
46
- label_a,
47
- label_b,
48
- dilation_iters,
49
- structure
50
- ):
51
- mask_a = (seg == label_a)
52
- mask_b = (seg == label_b)
53
-
54
- if not np.any(mask_a):
55
- return seg
56
-
57
- cc_map, num_cc = ndimage.label(mask_a, structure=structure)
58
-
59
- for cc_id in range(1, num_cc + 1):
60
- component = (cc_map == cc_id)
61
-
62
- dilated_component = ndimage.binary_dilation(
63
- component,
64
- structure=structure,
65
- iterations=dilation_iters
66
- )
67
-
68
- has_overlap = np.any(dilated_component & mask_b)
69
-
70
- if not has_overlap:
71
- seg[component] = 0
72
-
73
- return seg
74
-
75
-
76
- def remove_small_components(seg, label_value, min_size, structure):
77
- mask = (seg == label_value)
78
- if not np.any(mask):
79
- return seg
80
-
81
- cc_map, num_cc = ndimage.label(mask, structure=structure)
82
-
83
- for cc_id in range(1, num_cc + 1):
84
- component = (cc_map == cc_id)
85
- voxel_count = int(component.sum())
86
- if voxel_count < min_size:
87
- seg[component] = 0
88
-
89
- return seg
90
-
91
-
92
- def process_one_file(
93
- file_path,
94
- input_dir,
95
- output_dir,
96
- pairs,
97
- target_labels,
98
- min_size,
99
- dilation_iters,
100
- connectivity
101
- ):
102
- try:
103
- img = nib.load(file_path)
104
- seg = np.asanyarray(img.dataobj).astype(np.int32)
105
-
106
- structure = get_structure(connectivity)
107
-
108
- for label_a, label_b in pairs:
109
- seg = remove_components_without_overlap_after_dilating_a(
110
- seg=seg,
111
- label_a=label_a,
112
- label_b=label_b,
113
- dilation_iters=dilation_iters,
114
- structure=structure
115
- )
116
-
117
-
118
- for label in target_labels:
119
- seg = remove_small_components(
120
- seg=seg,
121
- label_value=label,
122
- min_size=min_size,
123
- structure=structure
124
- )
125
-
126
- rel_path = os.path.relpath(file_path, input_dir)
127
- out_path = os.path.join(output_dir, rel_path)
128
- os.makedirs(os.path.dirname(out_path), exist_ok=True)
129
-
130
- out_img = nib.Nifti1Image(
131
- seg.astype(img.get_data_dtype()),
132
- affine=img.affine,
133
- header=img.header
134
- )
135
- nib.save(out_img, out_path)
136
-
137
- return file_path, "success", ""
138
-
139
- except Exception as e:
140
- return file_path, "failed", str(e)
141
-
142
-
143
- def main():
144
- parser = argparse.ArgumentParser(
145
- description="Remove connected components of the first label in each pair if dilating that component does not overlap the second label, then remove small components."
146
- )
147
-
148
- parser.add_argument(
149
- "--input_dir",
150
- type=str,
151
- required=True
152
- )
153
- parser.add_argument(
154
- "--output_dir",
155
- type=str,
156
- required=True
157
- )
158
- parser.add_argument(
159
- "--pairs",
160
- type=str,
161
- default="1-2,3-4,5-6,7-8"
162
- )
163
- parser.add_argument(
164
- "--target_labels",
165
- type=str,
166
- default=None
167
- )
168
- parser.add_argument(
169
- "--min_size",
170
- type=int,
171
- default=30
172
- )
173
- parser.add_argument(
174
- "--dilation_iters",
175
- type=int,
176
- default=1
177
- )
178
- parser.add_argument(
179
- "--connectivity",
180
- type=int,
181
- default=26,
182
- choices=[6, 26]
183
- )
184
- parser.add_argument(
185
- "--num_workers",
186
- type=int,
187
- default=4
188
- )
189
-
190
- args = parser.parse_args()
191
-
192
- pairs = parse_pairs(args.pairs)
193
-
194
- if args.target_labels is None:
195
- target_labels = [a for a, _ in pairs]
196
- else:
197
- target_labels = parse_labels(args.target_labels)
198
-
199
- os.makedirs(args.output_dir, exist_ok=True)
200
-
201
- file_list = find_nii_gz_files(args.input_dir)
202
- if len(file_list) == 0:
203
- print(f"No .nii.gz files found in: {args.input_dir}")
204
- return
205
-
206
- results = []
207
- with ProcessPoolExecutor(max_workers=args.num_workers) as executor:
208
- futures = [
209
- executor.submit(
210
- process_one_file,
211
- file_path,
212
- args.input_dir,
213
- args.output_dir,
214
- pairs,
215
- target_labels,
216
- args.min_size,
217
- args.dilation_iters,
218
- args.connectivity
219
- )
220
- for file_path in file_list
221
- ]
222
-
223
- for future in tqdm(as_completed(futures), total=len(futures), desc="Processing"):
224
- results.append(future.result())
225
-
226
- success_count = sum(1 for _, status, _ in results if status == "success")
227
- failed_cases = [(fp, err) for fp, status, err in results if status == "failed"]
228
-
229
- print(f"\nDone. Success: {success_count}, Failed: {len(failed_cases)}")
230
-
231
- if failed_cases:
232
- failed_txt = os.path.join(args.output_dir, "failed_cases.txt")
233
- with open(failed_txt, "w", encoding="utf-8") as f:
234
- for fp, err in failed_cases:
235
- f.write(f"{fp}\t{err}\n")
236
- print(f"Failed cases saved to: {failed_txt}")
237
-
238
-
239
- if __name__ == "__main__":
240
- main()