AI-RESEARCHER-2024 commited on
Commit
70fca4b
·
verified ·
1 Parent(s): e4a7008

Upload 317 files

Browse files
README.md CHANGED
@@ -15,16 +15,16 @@ Iman Sabir Ezzat, Randa K Ismail, Ayden Chavez, Marisa Zallocchi, PhD, Steven Fe
15
  A variant of the neuron tracer that applies the **MedCLIPSeg** approach
16
  (Koleilat et al., *Probabilistic Vision–Language Adaptation for Data-Efficient
17
  and Generalizable Medical Image Segmentation*, CVPR 2026): a **text prompt**
18
- describes the target ("nerve fibers …"), a frozen **CLIP** backbone produces a
19
- **fiber-probability map**, and a probabilistic (Monte-Carlo) step yields a
20
- pixel-level **uncertainty map**.
21
 
22
  ## What the app does
23
- - **Classical trace (reliable):** the deterministic 3D tracer produces the
24
- quantification you should trust number of fibers, total length, diameter,
25
- branch points.
26
- - **MedCLIP (text-prompted):** shows the CLIP fiber-probability and uncertainty
27
- maps for the neurofilament channel. The prompt is editable.
 
28
 
29
  ## Few-shot training (included)
30
  A runnable, few-shot version of the MedCLIPSeg approach — **frozen CLIP encoders +
 
15
  A variant of the neuron tracer that applies the **MedCLIPSeg** approach
16
  (Koleilat et al., *Probabilistic Vision–Language Adaptation for Data-Efficient
17
  and Generalizable Medical Image Segmentation*, CVPR 2026): a **text prompt**
18
+ describes the target ("nerve fibers …") and a frozen **CLIP** backbone produces a
19
+ **fiber-probability map**, which is then traced into a skeleton.
 
20
 
21
  ## What the app does
22
+ - **Neurofilament (MIP):** the maximum-intensity projection of the input channel.
23
+ - **Tracked skeleton (white on black):** the MedCLIP fiber-probability map is
24
+ thresholded, skeletonised and pruned into a fiber skeleton.
25
+ - **Total trace length (µm):** the spacing-aware total length of that MedCLIP
26
+ skeleton, reported above the images. The text prompt and probability threshold
27
+ are editable.
28
 
29
  ## Few-shot training (included)
30
  A runnable, few-shot version of the MedCLIPSeg approach — **frozen CLIP encoders +
__pycache__/app.cpython-313.pyc CHANGED
Binary files a/__pycache__/app.cpython-313.pyc and b/__pycache__/app.cpython-313.pyc differ
 
__pycache__/medclipseg.cpython-313.pyc ADDED
Binary file (11.9 kB). View file
 
__pycache__/processing.cpython-313.pyc CHANGED
Binary files a/__pycache__/processing.cpython-313.pyc and b/__pycache__/processing.cpython-313.pyc differ
 
app.py CHANGED
@@ -1,72 +1,48 @@
1
  """Neuron Quantification using AI — MedCLIPSeg variant.
2
 
3
- Same task as the main tracer, applying the MedCLIPSeg (CVPR 2026) vision-language
4
- approach: a text prompt describes the target ("nerve fibers ..."), a frozen CLIP
5
- backbone produces a fiber-probability map and a pixel-level UNCERTAINTY map.
 
6
 
7
- The reliable quantification (length, fibers, diameter, branch points) still comes
8
- from the deterministic tracer. The MedCLIP maps are shown alongside; zero-shot
9
- general CLIP is only a coarse prior on this domain — train the bundled model
10
- (reference_medclipseg/) on a GPU with masks for real segmentation quality.
11
  """
12
  import traceback
13
- import numpy as np
14
- import pandas as pd
15
  import gradio as gr
16
 
17
  import processing as P
18
  import medclipseg as MC
19
 
20
- METRIC_COLS = ["Region", "Number of fibers", "Total length (um)",
21
- "Mean diameter (um)", "Branch points", "Area covered (um^2)"]
22
 
23
-
24
- def _cm(arr, name):
25
- import matplotlib.cm as cm
26
- return (getattr(cm, name)(np.clip(arr, 0, 1))[..., :3] * 255).astype(np.uint8)
27
-
28
-
29
- def analyze(file_obj, fg_prompt, run_medclip):
30
  if file_obj is None:
31
- return None, None, None, None, None, "Upload a CZI or TIFF z-stack."
32
  try:
33
  img = P.load_image(file_obj)
34
  nf, _ = P.guess_channels(img)
35
  nf_mip = P.channel_preview(img.data[nf])
36
 
37
- # --- deterministic trace + quantification (the reliable numbers) ---
38
- trace = P.trace_neurites(img.data[nf], img.voxel, sensitivity=1.0,
39
- prune_um=3.0)
40
- m = P.compute_metrics(trace, "Whole field", min_fiber_um=5.0)
41
- overlay = P.overlay_on_original(nf_mip, trace.skeleton)
42
- df = pd.DataFrame([{
43
- "Region": "Whole field", "Number of fibers": m.n_fibers,
44
- "Total length (um)": round(m.total_length_um, 2),
45
- "Mean diameter (um)": round(m.mean_diameter_um, 3),
46
- "Branch points": m.n_branch_points,
47
- "Area covered (um^2)": round(m.area_covered_um2, 2)}],
48
- columns=METRIC_COLS)
49
- status = (f"**Classical trace:** {m.n_fibers} fibers / "
50
- f"{m.total_length_um:.0f} µm (the reliable quantification).")
51
 
52
- prob_img = unc_img = None
53
- if run_medclip:
54
- if fg_prompt and fg_prompt.strip():
55
- MC.FG_PROMPTS = [p.strip() for p in fg_prompt.split("|") if p.strip()]
56
- prob, unc = MC.segment_best(nf_mip)
57
- prob_img, unc_img = _cm(prob, "viridis"), _cm(unc, "magma")
58
- if MC.has_trained_model():
59
- status += (f"\n\n**MedCLIP (few-shot trained, text-prompted):** "
60
- f"fiber-probability + uncertainty maps (frozen CLIP + "
61
- f"trained decoder; held-out Dice ≈ 0.53). Prompt(s): "
62
- f"*{', '.join(MC.FG_PROMPTS)}*.")
63
- else:
64
- status += (f"\n\n**MedCLIP (zero-shot, text-prompted):** coarse "
65
- f"prior train with train_medclip_fewshot.py for the "
66
- f"few-shot model.")
67
- return overlay, nf_mip, prob_img, unc_img, df, status
68
  except Exception as e: # noqa: BLE001
69
- return None, None, None, None, None, f"Error:\n{e}\n{traceback.format_exc()}"
70
 
71
 
72
  HEADER_HTML = """
@@ -77,7 +53,7 @@ HEADER_HTML = """
77
  Iman Sabir Ezzat, Randa K Ismail, Ayden Chavez, Marisa Zallocchi, PhD, Steven Fernandes, PhD
78
  </div>
79
  <div style="font-weight:600; font-size:0.95rem; color:#4b5563; margin-top:0.25rem;">
80
- MedCLIPSeg variant — text-prompted vision-language segmentation
81
  </div>
82
  </div>
83
  """
@@ -125,20 +101,17 @@ with gr.Blocks(title="Neuron Quantification using AI — MedCLIPSeg",
125
  label="Text prompt(s) for the target (separate with | )",
126
  value="a fluorescence microscopy image of nerve fibers | "
127
  "neurofilament nerve fibers and axons")
128
- use_mc = gr.Checkbox(value=True,
129
- label="Run MedCLIP text-prompted segmentation")
130
  btn = gr.Button("Analyze", variant="primary")
131
  with gr.Column(scale=2):
132
  status = gr.Markdown()
133
  with gr.Row():
134
- out_overlay = gr.Image(label="Traced neurons (classical)", height=250)
135
- out_orig = gr.Image(label="Neurofilament (MIP)", height=250)
136
- with gr.Row():
137
- out_prob = gr.Image(label="MedCLIP fiber probability", height=250)
138
- out_unc = gr.Image(label="MedCLIP uncertainty", height=250)
139
- out_table = gr.Dataframe(label="Quantification", wrap=True)
140
- btn.click(analyze, [file_in, prompt, use_mc],
141
- [out_overlay, out_orig, out_prob, out_unc, out_table, status])
142
 
143
  if __name__ == "__main__":
144
  demo.launch()
 
1
  """Neuron Quantification using AI — MedCLIPSeg variant.
2
 
3
+ Applies the MedCLIPSeg (CVPR 2026) vision-language approach: a text prompt
4
+ describes the target ("nerve fibers ..."), a frozen CLIP backbone produces a
5
+ fiber-probability map, and that map is traced into a skeleton. The app reports a
6
+ white-on-black **tracked skeleton** and its **total trace length** in microns.
7
 
8
+ Zero-shot general CLIP is only a coarse prior on this domain — train the bundled
9
+ model (reference_medclipseg/) on a GPU with masks for real segmentation quality.
 
 
10
  """
11
  import traceback
 
 
12
  import gradio as gr
13
 
14
  import processing as P
15
  import medclipseg as MC
16
 
 
 
17
 
18
+ def analyze(file_obj, fg_prompt, threshold):
 
 
 
 
 
 
19
  if file_obj is None:
20
+ return None, None, "Upload a CZI or TIFF z-stack."
21
  try:
22
  img = P.load_image(file_obj)
23
  nf, _ = P.guess_channels(img)
24
  nf_mip = P.channel_preview(img.data[nf])
25
 
26
+ if fg_prompt and fg_prompt.strip():
27
+ MC.FG_PROMPTS = [p.strip() for p in fg_prompt.split("|") if p.strip()]
 
 
 
 
 
 
 
 
 
 
 
 
28
 
29
+ # --- MedCLIP text-prompted fiber-probability map -> tracked skeleton ---
30
+ prob, _ = MC.segment_best(nf_mip)
31
+ trace = P.trace_from_probability(prob, img.voxel,
32
+ threshold=float(threshold), prune_um=3.0)
33
+ m = P.compute_metrics(trace, "MedCLIP", min_fiber_um=5.0)
34
+ skel_img = P.skeleton_image(trace.skeleton, dilate=1) # white on black
35
+
36
+ model = ("few-shot trained decoder" if MC.has_trained_model()
37
+ else "zero-shot CLIP prior")
38
+ status = (f"**MedCLIP total trace length: "
39
+ f"{m.total_length_um:,.1f} µm.**\n\n"
40
+ f"Tracked from the MedCLIP fiber-probability map "
41
+ f"({model}, text-prompted) at threshold {float(threshold):.2f}. "
42
+ f"Prompt(s): *{', '.join(MC.FG_PROMPTS)}*.")
43
+ return nf_mip, skel_img, status
 
44
  except Exception as e: # noqa: BLE001
45
+ return None, None, f"Error:\n{e}\n{traceback.format_exc()}"
46
 
47
 
48
  HEADER_HTML = """
 
53
  Iman Sabir Ezzat, Randa K Ismail, Ayden Chavez, Marisa Zallocchi, PhD, Steven Fernandes, PhD
54
  </div>
55
  <div style="font-weight:600; font-size:0.95rem; color:#4b5563; margin-top:0.25rem;">
56
+ MedCLIPSeg variant — text-prompted vision-language fiber tracking
57
  </div>
58
  </div>
59
  """
 
101
  label="Text prompt(s) for the target (separate with | )",
102
  value="a fluorescence microscopy image of nerve fibers | "
103
  "neurofilament nerve fibers and axons")
104
+ thresh = gr.Slider(0.1, 0.9, value=0.5, step=0.05,
105
+ label="Fiber-probability threshold")
106
  btn = gr.Button("Analyze", variant="primary")
107
  with gr.Column(scale=2):
108
  status = gr.Markdown()
109
  with gr.Row():
110
+ out_orig = gr.Image(label="Neurofilament (MIP)", height=280)
111
+ out_skel = gr.Image(label="Tracked skeleton (white on black)",
112
+ height=280)
113
+ btn.click(analyze, [file_in, prompt, thresh],
114
+ [out_orig, out_skel, status])
 
 
 
115
 
116
  if __name__ == "__main__":
117
  demo.launch()
processing.py CHANGED
@@ -568,6 +568,36 @@ def trace_neurites(nf_vol: np.ndarray, voxel: tuple,
568
  return TraceResult(mask, skel, dist, voxel)
569
 
570
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
571
  # --------------------------------------------------------------------------- #
572
  # Region definition (IHC vs OHC) from Myo7a
573
  # --------------------------------------------------------------------------- #
 
568
  return TraceResult(mask, skel, dist, voxel)
569
 
570
 
571
+ def trace_from_probability(prob: np.ndarray, voxel: tuple,
572
+ threshold: float = 0.5,
573
+ min_object_px: int = 32,
574
+ prune_um: float = 3.0) -> TraceResult:
575
+ """Trace a 2D fiber-probability map (e.g. a MedCLIP map) into a skeleton.
576
+
577
+ ``prob`` is a 2D float map in [0, 1] at the MIP resolution; ``threshold``
578
+ binarises it into a fiber mask which is cleaned, skeletonised and pruned.
579
+ The result is wrapped as a 3D (Z=1) ``TraceResult`` so the standard
580
+ ``compute_metrics`` / ``skeleton_image`` helpers apply unchanged and the
581
+ total length is measured with the same (dy, dx) spacing-aware code path.
582
+ """
583
+ dz, dy, dx = voxel
584
+ prob = np.asarray(prob, dtype=np.float32)
585
+ mask = prob >= float(threshold)
586
+ if mask.any():
587
+ mask = remove_small_objects(mask, int(min_object_px))
588
+ mask = ndi.binary_closing(mask, structure=np.ones((3, 3), bool))
589
+ if not mask.any():
590
+ z = np.zeros((1,) + prob.shape, bool)
591
+ return TraceResult(z, z.copy(), np.zeros(z.shape, np.float32), voxel)
592
+ # Wrap the 2D skeleton as a single-plane (Z=1) volume so the 3D-voxel
593
+ # pruning / metrics helpers apply unchanged (dz never affects in-plane steps).
594
+ skel = skeletonize(mask)[None]
595
+ if prune_um and prune_um > 0:
596
+ skel = prune_skeleton(skel, voxel, spur_um=float(prune_um))
597
+ dist = ndi.distance_transform_edt(mask, sampling=(dy, dx)).astype(np.float32)
598
+ return TraceResult(mask[None], skel, dist[None], voxel)
599
+
600
+
601
  # --------------------------------------------------------------------------- #
602
  # Region definition (IHC vs OHC) from Myo7a
603
  # --------------------------------------------------------------------------- #