champ7 commited on
Commit
883aa4d
·
verified ·
1 Parent(s): 78e4b05

Update nellie.py

Browse files
Files changed (1) hide show
  1. nellie.py +67 -54
nellie.py CHANGED
@@ -1,6 +1,7 @@
1
  import os
2
  import glob
3
  import logging
 
4
  import numpy as np
5
  import matplotlib.pyplot as plt
6
  import tifffile as tiff
@@ -15,7 +16,7 @@ from nellie.segmentation.labelling import Label
15
  from skimage.filters import rank, sobel, gaussian
16
  from scipy.ndimage import median_filter
17
  from skimage import exposure
18
- from skimage.morphology import disk
19
  from skimage.segmentation import find_boundaries
20
  from skimage.util import img_as_ubyte
21
 
@@ -31,12 +32,10 @@ def preprocess_method_1(img_raw: np.ndarray) -> np.ndarray:
31
  img_min, img_max = np.min(img_float), np.max(img_float)
32
  img_8bit = (((img_float - img_min) / (img_max - img_min) * 255).astype(np.uint8)
33
  if img_max > img_min else np.zeros(img_raw.shape, dtype=np.uint8))
34
-
35
  img_inverted = 255 - img_8bit
36
  img_blur = gaussian(img_inverted, sigma=1, preserve_range=True)
37
  img_filtered = median_filter(img_blur, size=5).astype(np.uint8)
38
  p_low, p_high = np.percentile(img_filtered, (0.75, 99.25))
39
-
40
  if p_low >= p_high: return img_filtered
41
  return exposure.rescale_intensity(img_filtered, in_range=(p_low, p_high), out_range=(0, 255)).astype(np.uint8)
42
 
@@ -54,7 +53,6 @@ def preprocess_method_2(img_input: np.ndarray) -> np.ndarray:
54
  def preprocess_combined(img_raw: np.ndarray) -> np.ndarray:
55
  return preprocess_method_2(preprocess_method_1(img_raw))
56
 
57
-
58
  # =====================================================================
59
  # Vectorized Metric Evaluation (Fixed IoU and Bounds)
60
  # =====================================================================
@@ -63,25 +61,23 @@ def evaluate_predictions(pred_mask: np.ndarray, gt_mask: np.ndarray, iou_thresho
63
  pred_mask, gt_mask = pred_mask.astype(np.int32), gt_mask.astype(np.int32)
64
  pred_labels = np.unique(pred_mask)[np.unique(pred_mask) != 0]
65
  gt_labels = np.unique(gt_mask)[np.unique(gt_mask) != 0]
66
-
67
  num_pred, num_gt = len(pred_labels), len(gt_labels)
68
-
69
  pred_bg, gt_bg = pred_mask > 0, gt_mask > 0
70
  union_pixel = np.logical_or(pred_bg, gt_bg).sum()
71
  global_pixel_iou = np.logical_and(pred_bg, gt_bg).sum() / union_pixel if union_pixel > 0 else 0.0
72
-
73
  if num_gt == 0 and num_pred == 0: return global_pixel_iou, 1.0, 1.0, 1.0, num_pred, num_gt
74
  if num_gt == 0 or num_pred == 0: return global_pixel_iou, 0.0, 0.0, 0.0, num_pred, num_gt
75
-
76
  overlapping = gt_bg & pred_bg
77
  if not np.any(overlapping): return global_pixel_iou, 0.0, 0.0, 0.0, num_pred, num_gt
78
-
79
  gt_id_map = {id_: i for i, id_ in enumerate(gt_labels)}
80
  pred_id_map = {id_: j for j, id_ in enumerate(pred_labels)}
81
-
82
  gt_indices = np.array([gt_id_map[x] for x in gt_mask[overlapping]])
83
  pred_indices = np.array([pred_id_map[x] for x in pred_mask[overlapping]])
84
-
85
  intersection = csr_matrix((np.ones(len(gt_indices), dtype=np.int32), (gt_indices, pred_indices)), shape=(num_gt, num_pred)).toarray()
86
 
87
  gt_vec = np.array([np.bincount(gt_mask.ravel())[id_] for id_ in gt_labels])[:, None]
@@ -89,18 +85,16 @@ def evaluate_predictions(pred_mask: np.ndarray, gt_mask: np.ndarray, iou_thresho
89
 
90
  union = gt_vec + pred_vec - intersection
91
  iou_matrix = np.divide(intersection, union, out=np.zeros_like(intersection, dtype=float), where=union != 0)
92
-
93
- # TP is the number of predictions that successfully match a GT with IoU > threshold
94
  matches = iou_matrix > iou_threshold
95
- tp = np.sum(np.any(matches, axis=0)) # Number of matched predictions
96
 
97
  precision = tp / num_pred if num_pred > 0 else 0.0
98
  recall = tp / num_gt if num_gt > 0 else 0.0
99
  instance_f1 = (2 * precision * recall) / (precision + recall) if (precision + recall) > 0 else 0.0
100
-
101
  return global_pixel_iou, precision, recall, instance_f1, num_pred, num_gt
102
 
103
-
104
  # =====================================================================
105
  # Dataset Prep & Core Pipeline
106
  # =====================================================================
@@ -112,7 +106,7 @@ def prepare_datasets(raw_tif_files, base_dir="generated_datasets"):
112
  "Prep 1->2": os.path.join(base_dir, "dataset_prep1_2")
113
  }
114
  for d in dirs.values(): os.makedirs(d, exist_ok=True)
115
-
116
  for filepath in tqdm(raw_tif_files, desc="Building Datasets"):
117
  fname = os.path.basename(filepath)
118
  if os.path.exists(os.path.join(dirs["Prep 1->2"], fname)):
@@ -123,17 +117,18 @@ def prepare_datasets(raw_tif_files, base_dir="generated_datasets"):
123
  tiff.imwrite(os.path.join(dirs["Prep 1"], fname), preprocess_method_1(img_raw))
124
  tiff.imwrite(os.path.join(dirs["Prep 2"], fname), preprocess_method_2(img_raw))
125
  tiff.imwrite(os.path.join(dirs["Prep 1->2"], fname), preprocess_combined(img_raw))
 
126
  return dirs
127
 
128
  def run_nellie_experiment(dataset_dir, combined_data_path, run_native_filter):
129
  files = sorted(glob.glob(os.path.join(dataset_dir, "*.TIF")))
130
  results, saved_masks = [], {}
131
-
132
  for filepath in tqdm(files, desc=f"Evaluating", leave=False):
133
  filename = os.path.basename(filepath)
134
  gt_mask_path = os.path.join(combined_data_path, filename.replace(".TIF", "_seg.npy"))
135
  if not os.path.exists(gt_mask_path): continue
136
-
137
  f_info = FileInfo(filepath=filepath)
138
  f_info.find_metadata()
139
  f_info.load_metadata()
@@ -142,88 +137,105 @@ def run_nellie_experiment(dataset_dir, combined_data_path, run_native_filter):
142
  f_info.axes = "ZYX" if img_array.ndim == 3 else "YX"
143
  f_info.good_axes, f_info.good_dims = True, True
144
  im_info = ImInfo(file_info=f_info)
145
-
146
  if run_native_filter:
147
  frangi_filter = Filter(im_info=im_info, device="auto")
148
  frangi_filter.run()
149
  else:
150
  prep_path = im_info.create_output_path(pipeline_path="im_preprocessed", ext=".ome.tif")
151
  im_info.allocate_memory(output_path=prep_path, data=img_array, dtype=str(img_array.dtype))
152
-
153
  label_processor = Label(im_info=im_info, otsu_thresh_intensity=False, min_radius_um=10.0, device="auto")
154
  label_processor.run()
155
-
156
  pred_mask = np.squeeze(tiff.imread(im_info.pipeline_paths["im_instance_label"]))
157
 
158
  gt_data = np.load(gt_mask_path, allow_pickle=True)
159
  if gt_data.ndim == 0 and isinstance(gt_data.item(), dict):
160
  gt_data = gt_data.item().get("masks", gt_data.item())
161
  gt_to_compare = np.squeeze(gt_data)
162
-
163
- # Strict IoU = 0.5 Evaluation
164
  metrics = evaluate_predictions(pred_mask, gt_to_compare, iou_threshold=0.5)
165
  results.append(metrics)
166
  saved_masks[filename] = (pred_mask, metrics[4])
167
-
168
  return results, saved_masks
169
 
170
-
171
  # =====================================================================
172
- # Plot Single Experiment (Triggers inside loop)
173
  # =====================================================================
174
- def plot_single_experiment_visual(sample_filename, combined_data_path, data_src_dir, config_name, pred_mask, pred_count):
175
  logger.info(f"Generating visual plot for: {config_name}")
176
 
 
177
  gt_data = np.load(os.path.join(combined_data_path, sample_filename.replace(".TIF", "_seg.npy")), allow_pickle=True)
178
  if gt_data.ndim == 0 and isinstance(gt_data.item(), dict):
179
  gt_data = gt_data.item().get("masks", gt_data.item())
180
  gt_mask = np.squeeze(gt_data)
181
-
182
  gt_count = len(np.unique(gt_mask)[np.unique(gt_mask) != 0])
183
- gt_boundaries = find_boundaries(gt_mask, mode="inner")
184
 
185
  feed_img = tiff.imread(os.path.join(data_src_dir, sample_filename))
186
- pred_boundaries = find_boundaries(pred_mask, mode="inner")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  cmap_choice = "gray" if "Prep 1" in data_src_dir or "Raw" in data_src_dir else "magma"
188
-
189
  fig, axes = plt.subplots(1, 3, figsize=(18, 6))
190
-
191
- # 1. Ground Truth Overlay
192
- axes[0].imshow(feed_img, cmap=cmap_choice)
193
- axes[0].imshow(np.dstack([gt_boundaries, np.zeros_like(gt_boundaries), np.zeros_like(gt_boundaries), gt_boundaries.astype(float)]))
194
- axes[0].set_title(f"GT Overlay (Count: {gt_count})", fontsize=12, fontweight="bold")
 
195
  axes[0].axis("off")
196
-
197
- # 2. Input Image Alone
198
  axes[1].imshow(feed_img, cmap=cmap_choice)
199
  axes[1].set_title(f"Data Fed into Nellie", fontsize=12, fontweight="bold")
200
  axes[1].axis("off")
201
-
202
- # 3. Predicted Mask Overlay
203
- axes[2].imshow(feed_img, cmap=cmap_choice)
204
- overlay = np.zeros((*pred_mask.shape, 4))
205
- overlay[pred_boundaries] = [1.0, 0.0, 0.0, 1.0]
206
- axes[2].imshow(overlay)
207
- axes[2].set_title(f"Predicted Mask (Count: {pred_count})", fontsize=12, fontweight="bold")
208
  axes[2].axis("off")
209
-
210
  plt.suptitle(f"EXPERIMENT: {config_name}\nFile: {sample_filename}", fontsize=16, fontweight="bold")
211
  plt.tight_layout()
212
  plt.show()
213
 
214
-
215
  # =====================================================================
216
  # Main Execution Flow
217
  # =====================================================================
218
  def main():
 
 
219
  logger.info("Downloading dataset...")
220
  dataset_dir = snapshot_download(repo_id="champ7/CEllDataW3", repo_type="dataset", allow_patterns="combined_data/*")
221
  combined_data_path = os.path.join(dataset_dir, "combined_data")
 
 
222
  raw_tif_files = sorted(glob.glob(os.path.join(combined_data_path, "*.TIF")))
 
 
 
 
 
223
  sample_filename = os.path.basename(raw_tif_files[0]) # File to plot for each step
224
-
225
  dirs = prepare_datasets(raw_tif_files)
226
-
227
  configs = [
228
  {"name": "Prep 1 Only (Bypassing Nellie Filter)", "data_src": "Prep 1", "run_filter": False},
229
  {"name": "Prep 2 Only (Bypassing Nellie Filter)", "data_src": "Prep 2", "run_filter": False},
@@ -233,10 +245,10 @@ def main():
233
  {"name": "Prep 2 + Nellie Native Filter", "data_src": "Prep 2", "run_filter": True},
234
  {"name": "Prep 1->2 + Nellie Native Filter", "data_src": "Prep 1->2", "run_filter": True},
235
  ]
236
-
237
  aggregate_table = []
238
  headers = ["Strategy Configuration", "Total GT Cells", "Total Pred Cells", "Mean Pixel IoU", "Mean Precision", "Mean Recall", "Mean F1"]
239
-
240
  for config in configs:
241
  logger.info(f"Running strategy: {config['name']}")
242
 
@@ -248,7 +260,7 @@ def main():
248
 
249
  ious, precs, recs, f1s = [m[0] for m in metrics], [m[1] for m in metrics], [m[2] for m in metrics], [m[3] for m in metrics]
250
  total_pred, total_gt = sum([m[4] for m in metrics]), sum([m[5] for m in metrics])
251
-
252
  current_result_row = [
253
  config["name"],
254
  total_gt, total_pred,
@@ -262,13 +274,14 @@ def main():
262
  print(f"="*80)
263
  print(tabulate([current_result_row], headers=headers, tablefmt="fancy_grid"))
264
  print("\n")
265
-
266
  # 2. Trigger the plot immediately
267
  pred_mask_sample, pred_count_sample = saved_masks[sample_filename]
268
  plot_single_experiment_visual(
269
  sample_filename=sample_filename,
270
  combined_data_path=combined_data_path,
271
  data_src_dir=dirs[config["data_src"]],
 
272
  config_name=config["name"],
273
  pred_mask=pred_mask_sample,
274
  pred_count=pred_count_sample
 
1
  import os
2
  import glob
3
  import logging
4
+ import random
5
  import numpy as np
6
  import matplotlib.pyplot as plt
7
  import tifffile as tiff
 
16
  from skimage.filters import rank, sobel, gaussian
17
  from scipy.ndimage import median_filter
18
  from skimage import exposure
19
+ from skimage.morphology import disk, binary_dilation
20
  from skimage.segmentation import find_boundaries
21
  from skimage.util import img_as_ubyte
22
 
 
32
  img_min, img_max = np.min(img_float), np.max(img_float)
33
  img_8bit = (((img_float - img_min) / (img_max - img_min) * 255).astype(np.uint8)
34
  if img_max > img_min else np.zeros(img_raw.shape, dtype=np.uint8))
 
35
  img_inverted = 255 - img_8bit
36
  img_blur = gaussian(img_inverted, sigma=1, preserve_range=True)
37
  img_filtered = median_filter(img_blur, size=5).astype(np.uint8)
38
  p_low, p_high = np.percentile(img_filtered, (0.75, 99.25))
 
39
  if p_low >= p_high: return img_filtered
40
  return exposure.rescale_intensity(img_filtered, in_range=(p_low, p_high), out_range=(0, 255)).astype(np.uint8)
41
 
 
53
  def preprocess_combined(img_raw: np.ndarray) -> np.ndarray:
54
  return preprocess_method_2(preprocess_method_1(img_raw))
55
 
 
56
  # =====================================================================
57
  # Vectorized Metric Evaluation (Fixed IoU and Bounds)
58
  # =====================================================================
 
61
  pred_mask, gt_mask = pred_mask.astype(np.int32), gt_mask.astype(np.int32)
62
  pred_labels = np.unique(pred_mask)[np.unique(pred_mask) != 0]
63
  gt_labels = np.unique(gt_mask)[np.unique(gt_mask) != 0]
 
64
  num_pred, num_gt = len(pred_labels), len(gt_labels)
65
+
66
  pred_bg, gt_bg = pred_mask > 0, gt_mask > 0
67
  union_pixel = np.logical_or(pred_bg, gt_bg).sum()
68
  global_pixel_iou = np.logical_and(pred_bg, gt_bg).sum() / union_pixel if union_pixel > 0 else 0.0
69
+
70
  if num_gt == 0 and num_pred == 0: return global_pixel_iou, 1.0, 1.0, 1.0, num_pred, num_gt
71
  if num_gt == 0 or num_pred == 0: return global_pixel_iou, 0.0, 0.0, 0.0, num_pred, num_gt
72
+
73
  overlapping = gt_bg & pred_bg
74
  if not np.any(overlapping): return global_pixel_iou, 0.0, 0.0, 0.0, num_pred, num_gt
75
+
76
  gt_id_map = {id_: i for i, id_ in enumerate(gt_labels)}
77
  pred_id_map = {id_: j for j, id_ in enumerate(pred_labels)}
78
+
79
  gt_indices = np.array([gt_id_map[x] for x in gt_mask[overlapping]])
80
  pred_indices = np.array([pred_id_map[x] for x in pred_mask[overlapping]])
 
81
  intersection = csr_matrix((np.ones(len(gt_indices), dtype=np.int32), (gt_indices, pred_indices)), shape=(num_gt, num_pred)).toarray()
82
 
83
  gt_vec = np.array([np.bincount(gt_mask.ravel())[id_] for id_ in gt_labels])[:, None]
 
85
 
86
  union = gt_vec + pred_vec - intersection
87
  iou_matrix = np.divide(intersection, union, out=np.zeros_like(intersection, dtype=float), where=union != 0)
88
+
 
89
  matches = iou_matrix > iou_threshold
90
+ tp = np.sum(np.any(matches, axis=0))
91
 
92
  precision = tp / num_pred if num_pred > 0 else 0.0
93
  recall = tp / num_gt if num_gt > 0 else 0.0
94
  instance_f1 = (2 * precision * recall) / (precision + recall) if (precision + recall) > 0 else 0.0
95
+
96
  return global_pixel_iou, precision, recall, instance_f1, num_pred, num_gt
97
 
 
98
  # =====================================================================
99
  # Dataset Prep & Core Pipeline
100
  # =====================================================================
 
106
  "Prep 1->2": os.path.join(base_dir, "dataset_prep1_2")
107
  }
108
  for d in dirs.values(): os.makedirs(d, exist_ok=True)
109
+
110
  for filepath in tqdm(raw_tif_files, desc="Building Datasets"):
111
  fname = os.path.basename(filepath)
112
  if os.path.exists(os.path.join(dirs["Prep 1->2"], fname)):
 
117
  tiff.imwrite(os.path.join(dirs["Prep 1"], fname), preprocess_method_1(img_raw))
118
  tiff.imwrite(os.path.join(dirs["Prep 2"], fname), preprocess_method_2(img_raw))
119
  tiff.imwrite(os.path.join(dirs["Prep 1->2"], fname), preprocess_combined(img_raw))
120
+
121
  return dirs
122
 
123
  def run_nellie_experiment(dataset_dir, combined_data_path, run_native_filter):
124
  files = sorted(glob.glob(os.path.join(dataset_dir, "*.TIF")))
125
  results, saved_masks = [], {}
126
+
127
  for filepath in tqdm(files, desc=f"Evaluating", leave=False):
128
  filename = os.path.basename(filepath)
129
  gt_mask_path = os.path.join(combined_data_path, filename.replace(".TIF", "_seg.npy"))
130
  if not os.path.exists(gt_mask_path): continue
131
+
132
  f_info = FileInfo(filepath=filepath)
133
  f_info.find_metadata()
134
  f_info.load_metadata()
 
137
  f_info.axes = "ZYX" if img_array.ndim == 3 else "YX"
138
  f_info.good_axes, f_info.good_dims = True, True
139
  im_info = ImInfo(file_info=f_info)
140
+
141
  if run_native_filter:
142
  frangi_filter = Filter(im_info=im_info, device="auto")
143
  frangi_filter.run()
144
  else:
145
  prep_path = im_info.create_output_path(pipeline_path="im_preprocessed", ext=".ome.tif")
146
  im_info.allocate_memory(output_path=prep_path, data=img_array, dtype=str(img_array.dtype))
147
+
148
  label_processor = Label(im_info=im_info, otsu_thresh_intensity=False, min_radius_um=10.0, device="auto")
149
  label_processor.run()
 
150
  pred_mask = np.squeeze(tiff.imread(im_info.pipeline_paths["im_instance_label"]))
151
 
152
  gt_data = np.load(gt_mask_path, allow_pickle=True)
153
  if gt_data.ndim == 0 and isinstance(gt_data.item(), dict):
154
  gt_data = gt_data.item().get("masks", gt_data.item())
155
  gt_to_compare = np.squeeze(gt_data)
156
+
 
157
  metrics = evaluate_predictions(pred_mask, gt_to_compare, iou_threshold=0.5)
158
  results.append(metrics)
159
  saved_masks[filename] = (pred_mask, metrics[4])
160
+
161
  return results, saved_masks
162
 
 
163
  # =====================================================================
164
+ # Plot Single Experiment
165
  # =====================================================================
166
+ def plot_single_experiment_visual(sample_filename, combined_data_path, data_src_dir, raw_data_dir, config_name, pred_mask, pred_count):
167
  logger.info(f"Generating visual plot for: {config_name}")
168
 
169
+ # --- Load Data ---
170
  gt_data = np.load(os.path.join(combined_data_path, sample_filename.replace(".TIF", "_seg.npy")), allow_pickle=True)
171
  if gt_data.ndim == 0 and isinstance(gt_data.item(), dict):
172
  gt_data = gt_data.item().get("masks", gt_data.item())
173
  gt_mask = np.squeeze(gt_data)
 
174
  gt_count = len(np.unique(gt_mask)[np.unique(gt_mask) != 0])
 
175
 
176
  feed_img = tiff.imread(os.path.join(data_src_dir, sample_filename))
177
+ raw_img = tiff.imread(os.path.join(raw_data_dir, sample_filename))
178
+
179
+ # --- Ground Truth High-Def Assets ---
180
+ gt_binary = gt_mask > 0
181
+ gt_boundaries = find_boundaries(gt_binary, mode="outer")
182
+ gt_thick_boundaries = binary_dilation(gt_boundaries, disk(1))
183
+ gt_masked_fill = np.ma.masked_where(~gt_binary, gt_binary)
184
+ gt_masked_edges = np.ma.masked_where(~gt_thick_boundaries, gt_thick_boundaries)
185
+
186
+ # --- Prediction High-Def Assets ---
187
+ pred_binary = pred_mask > 0
188
+ pred_boundaries = find_boundaries(pred_binary, mode="outer")
189
+ pred_thick_boundaries = binary_dilation(pred_boundaries, disk(1))
190
+ pred_masked_fill = np.ma.masked_where(~pred_binary, pred_binary)
191
+ pred_masked_edges = np.ma.masked_where(~pred_thick_boundaries, pred_thick_boundaries)
192
+
193
  cmap_choice = "gray" if "Prep 1" in data_src_dir or "Raw" in data_src_dir else "magma"
 
194
  fig, axes = plt.subplots(1, 3, figsize=(18, 6))
195
+
196
+ # 1. Ground Truth Overlay (on Raw Image)
197
+ axes[0].imshow(raw_img, cmap="gray")
198
+ axes[0].imshow(gt_masked_fill, cmap="summer", alpha=0.25) # Greenish overlay
199
+ axes[0].imshow(gt_masked_edges, cmap="Greens", alpha=0.9)
200
+ axes[0].set_title(f"GT Overlay on Raw (Count: {gt_count})", fontsize=12, fontweight="bold")
201
  axes[0].axis("off")
202
+
203
+ # 2. Input Image Alone (The preprocessed image actually fed to Nellie)
204
  axes[1].imshow(feed_img, cmap=cmap_choice)
205
  axes[1].set_title(f"Data Fed into Nellie", fontsize=12, fontweight="bold")
206
  axes[1].axis("off")
207
+
208
+ # 3. Predicted Mask Overlay (on Raw Image)
209
+ axes[2].imshow(raw_img, cmap="gray")
210
+ axes[2].imshow(pred_masked_fill, cmap="autumn", alpha=0.25) # Reddish/Autumn overlay
211
+ axes[2].imshow(pred_masked_edges, cmap="Wistia", alpha=0.9)
212
+ axes[2].set_title(f"Predicted Mask on Raw (Count: {pred_count})", fontsize=12, fontweight="bold")
 
213
  axes[2].axis("off")
214
+
215
  plt.suptitle(f"EXPERIMENT: {config_name}\nFile: {sample_filename}", fontsize=16, fontweight="bold")
216
  plt.tight_layout()
217
  plt.show()
218
 
 
219
  # =====================================================================
220
  # Main Execution Flow
221
  # =====================================================================
222
  def main():
223
+ n_samples = 10 # Default set to 10
224
+
225
  logger.info("Downloading dataset...")
226
  dataset_dir = snapshot_download(repo_id="champ7/CEllDataW3", repo_type="dataset", allow_patterns="combined_data/*")
227
  combined_data_path = os.path.join(dataset_dir, "combined_data")
228
+
229
+ # Randomly sample N files to drastically speed up testing
230
  raw_tif_files = sorted(glob.glob(os.path.join(combined_data_path, "*.TIF")))
231
+ if n_samples and n_samples < len(raw_tif_files):
232
+ random.seed(42) # Ensure the same random subset is used across runs
233
+ raw_tif_files = random.sample(raw_tif_files, n_samples)
234
+ logger.info(f"Subsampled dataset to {n_samples} random images.")
235
+
236
  sample_filename = os.path.basename(raw_tif_files[0]) # File to plot for each step
 
237
  dirs = prepare_datasets(raw_tif_files)
238
+
239
  configs = [
240
  {"name": "Prep 1 Only (Bypassing Nellie Filter)", "data_src": "Prep 1", "run_filter": False},
241
  {"name": "Prep 2 Only (Bypassing Nellie Filter)", "data_src": "Prep 2", "run_filter": False},
 
245
  {"name": "Prep 2 + Nellie Native Filter", "data_src": "Prep 2", "run_filter": True},
246
  {"name": "Prep 1->2 + Nellie Native Filter", "data_src": "Prep 1->2", "run_filter": True},
247
  ]
248
+
249
  aggregate_table = []
250
  headers = ["Strategy Configuration", "Total GT Cells", "Total Pred Cells", "Mean Pixel IoU", "Mean Precision", "Mean Recall", "Mean F1"]
251
+
252
  for config in configs:
253
  logger.info(f"Running strategy: {config['name']}")
254
 
 
260
 
261
  ious, precs, recs, f1s = [m[0] for m in metrics], [m[1] for m in metrics], [m[2] for m in metrics], [m[3] for m in metrics]
262
  total_pred, total_gt = sum([m[4] for m in metrics]), sum([m[5] for m in metrics])
263
+
264
  current_result_row = [
265
  config["name"],
266
  total_gt, total_pred,
 
274
  print(f"="*80)
275
  print(tabulate([current_result_row], headers=headers, tablefmt="fancy_grid"))
276
  print("\n")
277
+
278
  # 2. Trigger the plot immediately
279
  pred_mask_sample, pred_count_sample = saved_masks[sample_filename]
280
  plot_single_experiment_visual(
281
  sample_filename=sample_filename,
282
  combined_data_path=combined_data_path,
283
  data_src_dir=dirs[config["data_src"]],
284
+ raw_data_dir=dirs["Raw"],
285
  config_name=config["name"],
286
  pred_mask=pred_mask_sample,
287
  pred_count=pred_count_sample