champ7 commited on
Commit
c0f067b
·
verified ·
1 Parent(s): bc8bbcf

Create microsam.py

Browse files
Files changed (1) hide show
  1. microsam.py +285 -0
microsam.py ADDED
@@ -0,0 +1,285 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ !pip install numpy tifffile scikit-image matplotlib huggingface_hub scikit-learn micro_sam
2
+ !conda install -c conda-forge micro_sam
3
+
4
+ import os
5
+ import glob
6
+ import time
7
+ import numpy as np
8
+ import tifffile as tiff
9
+ import matplotlib.pyplot as plt
10
+ from skimage.filters import gaussian, median
11
+ from skimage.morphology import square
12
+ from skimage import exposure
13
+ from huggingface_hub import snapshot_download
14
+ from sklearn.metrics import f1_score
15
+
16
+ # --- micro_sam imports ---
17
+ import micro_sam.training as sam_training
18
+ from micro_sam.util import export_custom_sam_model
19
+ from micro_sam.automatic_segmentation import (
20
+ get_predictor_and_segmenter,
21
+ automatic_instance_segmentation
22
+ )
23
+
24
+
25
+ # ==========================================
26
+ # 1. PREPROCESSING FUNCTIONS
27
+ # ==========================================
28
+
29
+ def preprocess_1(input_data):
30
+ """ W3: Cells (Brightfield) -> Blur, Median, Contrast """
31
+ if isinstance(input_data, str):
32
+ img_raw = tiff.imread(input_data).astype(np.float32)
33
+ else:
34
+ img_raw = input_data.astype(np.float32)
35
+
36
+ img_min, img_max = np.min(img_raw), np.max(img_raw)
37
+ if img_max > img_min:
38
+ img_8bit = ((img_raw - img_min) / (img_max - img_min) * 255.0).astype(np.uint8)
39
+ else:
40
+ img_8bit = np.zeros(img_raw.shape, dtype=np.uint8)
41
+
42
+ img_inverted = 255 - img_8bit
43
+ img_blur = gaussian(img_inverted, sigma=1, preserve_range=True)
44
+ img_filtered = median(img_blur, footprint=square(5)).astype(np.uint8)
45
+
46
+ p_low, p_high = np.percentile(img_filtered, (0.75, 99.25))
47
+ if p_low >= p_high:
48
+ final_img = img_filtered
49
+ else:
50
+ final_img = exposure.rescale_intensity(
51
+ img_filtered, in_range=(p_low, p_high), out_range=(0, 255)
52
+ ).astype(np.float32)
53
+
54
+ # Output float in range [0, 255] for micro_sam dataloader compatibility
55
+ return final_img.astype(np.float32)
56
+
57
+ def preprocess_2(input_data):
58
+ """ Percentile Clipping (1st, 99.9th), Normalization, Standardization -> Scaled to [0, 255] """
59
+ if isinstance(input_data, str):
60
+ img = tiff.imread(input_data).astype(np.float32)
61
+ else:
62
+ img = input_data.astype(np.float32)
63
+
64
+ # Percentile clipping
65
+ p_low, p_high = np.percentile(img, (1.0, 99.9))
66
+ img_clipped = np.clip(img, p_low, p_high)
67
+
68
+ # Scale to [0, 1]
69
+ denom = (p_high - p_low) if (p_high - p_low) > 0 else 1.0
70
+ img_scaled = (img_clipped - p_low) / denom
71
+
72
+ # Zero-mean, unit-variance standardization
73
+ mean, std = np.mean(img_scaled), np.std(img_scaled)
74
+ img_standardized = (img_scaled - mean) / (std + 1e-8)
75
+
76
+ # Scale exactly to [0, 255] to bypass micro_sam ValueError
77
+ s_min, s_max = np.min(img_standardized), np.max(img_standardized)
78
+ if s_max > s_min:
79
+ img_final = ((img_standardized - s_min) / (s_max - s_min) * 255.0)
80
+ else:
81
+ img_final = np.zeros_like(img_standardized)
82
+
83
+ return img_final.astype(np.float32)
84
+
85
+ def preprocess_1_then_2(input_data):
86
+ """ Sequential application of Preprocessing 1 followed by Preprocessing 2 """
87
+ out_1 = preprocess_1(input_data)
88
+ out_2 = preprocess_2(out_1)
89
+ return out_2
90
+
91
+
92
+ # ==========================================
93
+ # 2. METRICS & PLOTTING
94
+ # ==========================================
95
+
96
+ def calculate_metrics(y_true, y_pred):
97
+ """ Calculate IoU and F1 score for binary masks. """
98
+ y_true_bin = (y_true > 0).astype(np.uint8)
99
+ y_pred_bin = (y_pred > 0).astype(np.uint8)
100
+
101
+ intersection = np.logical_and(y_true_bin, y_pred_bin).sum()
102
+ union = np.logical_or(y_true_bin, y_pred_bin).sum()
103
+
104
+ iou = intersection / union if union > 0 else 0.0
105
+ f1 = f1_score(y_true_bin.flatten(), y_pred_bin.flatten())
106
+
107
+ return iou, f1
108
+
109
+ def plot_results(title, actual_img, preprocessed_img, gt_mask, pred_mask, output_filename):
110
+ """ Generates 1x3 subplot and displays it instantly for review. """
111
+ fig, axes = plt.subplots(1, 3, figsize=(18, 6))
112
+ fig.suptitle(title, fontsize=16)
113
+
114
+ # Image 1: Actual Image with GT Mask
115
+ axes[0].imshow(actual_img, cmap='gray')
116
+ axes[0].imshow(gt_mask > 0, cmap='Reds', alpha=0.3)
117
+ axes[0].set_title("Actual Image + GT Mask")
118
+ axes[0].axis('off')
119
+
120
+ # Image 2: Preprocessed Image (Input to model)
121
+ axes[1].imshow(preprocessed_img, cmap='gray')
122
+ axes[1].set_title("Preprocessed Input")
123
+ axes[1].axis('off')
124
+
125
+ # Image 3: Actual Image with Predicted Mask
126
+ axes[2].imshow(actual_img, cmap='gray')
127
+ axes[2].imshow(pred_mask > 0, cmap='Blues', alpha=0.3)
128
+ axes[2].set_title("Actual Image + Predicted Mask")
129
+ axes[2].axis('off')
130
+
131
+ plt.tight_layout()
132
+ plt.savefig(output_filename)
133
+
134
+ # Show the plot immediately so you can monitor direction
135
+ plt.show(block=False)
136
+ plt.pause(2) # Pauses briefly to render the UI before continuing
137
+ plt.close()
138
+
139
+
140
+ # ==========================================
141
+ # 3. PIPELINE & DATA HANDLING
142
+ # ==========================================
143
+
144
+ def download_data():
145
+ print("Downloading dataset 'champ7/CEllDataW3'...")
146
+ local_dir = snapshot_download(repo_id="champ7/CEllDataW3", repo_type="dataset")
147
+ return os.path.join(local_dir, "combined_data")
148
+
149
+ def get_file_pairs(data_dir):
150
+ tif_files = sorted(glob.glob(os.path.join(data_dir, "*.TIF")))
151
+ pairs = []
152
+ for tif in tif_files:
153
+ mask_path = tif.replace(".TIF", "_seg.npy")
154
+ if os.path.exists(mask_path):
155
+ pairs.append((tif, mask_path))
156
+ return pairs
157
+
158
+ def prepare_training_data(pairs, prep_func, prep_name):
159
+ """ Prepares data for the subset pairs. """
160
+ prep_dir = f"./preprocessed_train_data_{prep_name}"
161
+ os.makedirs(prep_dir, exist_ok=True)
162
+
163
+ for img_path, mask_path in pairs:
164
+ base_name = os.path.basename(img_path)
165
+ img_prep = prep_func(img_path)
166
+ tiff.imwrite(os.path.join(prep_dir, base_name), img_prep)
167
+
168
+ mask = np.load(mask_path)
169
+ tiff.imwrite(os.path.join(prep_dir, base_name.replace(".TIF", "_seg.tif")), mask.astype(np.int32))
170
+
171
+ return prep_dir
172
+
173
+ def train_model(train_dir, model_name):
174
+ print(f"\n--- Training {model_name} (Single Execution) ---")
175
+
176
+ train_loader = sam_training.default_sam_loader(
177
+ raw_paths=train_dir,
178
+ raw_key="*.TIF",
179
+ label_paths=train_dir,
180
+ label_key="*_seg.tif",
181
+ patch_shape=(1, 512, 512),
182
+ batch_size=2,
183
+ with_segmentation_decoder=True,
184
+ is_train=True,
185
+ num_workers=2,
186
+ shuffle=True
187
+ )
188
+
189
+ sam_training.train_sam(
190
+ name=model_name,
191
+ model_type="vit_b",
192
+ train_loader=train_loader,
193
+ val_loader=train_loader,
194
+ n_epochs=10,
195
+ n_objects_per_batch=5,
196
+ with_segmentation_decoder=True,
197
+ device="cuda"
198
+ )
199
+
200
+ checkpoint_path = os.path.join("checkpoints", model_name, "best.pt")
201
+ export_path = f"./{model_name}_exported.pth"
202
+ export_custom_sam_model(checkpoint_path=checkpoint_path, model_type="vit_b", save_path=export_path)
203
+ return export_path
204
+
205
+
206
+ def run_inference_and_evaluate(pairs, prep_func, prep_name, model_type, model_checkpoint, is_pretrained=True):
207
+ model_status = "Pretrained" if is_pretrained else "Trained"
208
+ print(f"\nEvaluating {prep_name} with {model_status} Model...")
209
+
210
+ predictor, segmenter = get_predictor_and_segmenter(
211
+ model_type=model_type,
212
+ checkpoint=model_checkpoint if not is_pretrained else None,
213
+ )
214
+
215
+ total_iou, total_f1 = 0, 0
216
+
217
+ for idx, (img_path, mask_path) in enumerate(pairs):
218
+ raw_img = tiff.imread(img_path)
219
+ gt_mask = np.load(mask_path)
220
+
221
+ input_img = prep_func(raw_img)
222
+ pred_mask = automatic_instance_segmentation(predictor, segmenter, input_img)
223
+
224
+ iou, f1 = calculate_metrics(gt_mask, pred_mask)
225
+ total_iou += iou
226
+ total_f1 += f1
227
+
228
+ # Plot 2 images per combination as requested
229
+ if idx < 2:
230
+ plot_name = f"{prep_name}_{model_status}_sample_{idx+1}.png"
231
+ title = f"{prep_name} | {model_status} | IoU: {iou:.3f} | F1: {f1:.3f}"
232
+ plot_results(title, raw_img, input_img, gt_mask, pred_mask, plot_name)
233
+
234
+ avg_iou = total_iou / len(pairs)
235
+ avg_f1 = total_f1 / len(pairs)
236
+ print(f"Finished {model_status} | {prep_name} -> F1: {avg_f1:.4f}, IoU: {avg_iou:.4f}")
237
+ return avg_iou, avg_f1
238
+
239
+
240
+ # ==========================================
241
+ # 4. MAIN EXECUTION
242
+ # ==========================================
243
+
244
+ if __name__ == "__main__":
245
+ data_dir = download_data()
246
+
247
+ # CONSTRAINT MET: Only take 10 images from the dataset
248
+ file_pairs = get_file_pairs(data_dir)[:10]
249
+ print(f"Using a subset of {len(file_pairs)} image-mask pairs for faster processing.")
250
+
251
+ combinations = [
252
+ ("Preprocessing 1", preprocess_1, "prep1"),
253
+ ("Preprocessing 2", preprocess_2, "prep2"),
254
+ ("Preprocessing 1 + 2", preprocess_1_then_2, "prep12")
255
+ ]
256
+
257
+ results = []
258
+
259
+ # 1. Evaluate Pretrained Model (3 Iterations)
260
+ for prep_title, prep_func, prep_tag in combinations:
261
+ iou, f1 = run_inference_and_evaluate(
262
+ file_pairs, prep_func, prep_title,
263
+ model_type="vit_b", model_checkpoint=None, is_pretrained=True
264
+ )
265
+ results.append({"Pipeline": f"{prep_title} -> Pretrained Model", "F1": f1, "IoU@0.0": iou})
266
+
267
+ # 2. Train Model (CONSTRAINT MET: Train Once)
268
+ # We prepare training data using 'Preprocessing 1 + 2'
269
+ train_data_path = prepare_training_data(file_pairs, preprocess_1_then_2, "prep12")
270
+ trained_model_path = train_model(train_data_path, "sam_finetuned_single")
271
+
272
+ # 3. Evaluate Trained Model (3 Iterations)
273
+ for prep_title, prep_func, prep_tag in combinations:
274
+ iou, f1 = run_inference_and_evaluate(
275
+ file_pairs, prep_func, prep_title,
276
+ model_type="vit_b", model_checkpoint=trained_model_path, is_pretrained=False
277
+ )
278
+ results.append({"Pipeline": f"{prep_title} -> Trained Model", "F1": f1, "IoU@0.0": iou})
279
+
280
+ # 4. Output Final Table
281
+ print("\n### Final Evaluation Metrics (10 Image Subset)")
282
+ print("| Pipeline Combination | F1 Score | IoU@0.0 |")
283
+ print("| :--- | :--- | :--- |")
284
+ for res in results:
285
+ print(f"| {res['Pipeline']} | {res['F1']:.4f} | {res['IoU@0.0']:.4f} |")