# Updated app — summary of changes Verified end-to-end against `Untitled1592_c1-4.tif` (Gradio graph builds, behaviors tested via the app's own functions, and the UI confirmed in a browser). ## Round 4 (2026-07-12) — raise traced length toward Imaris **Problem:** total traced fiber length was ~1/3 of Imaris (median ratio **0.31** across 6 CZI stacks that have Imaris `_Statistics`); the skeleton looked right but under-captured dim/thin fibers. **Fix (found via a measured agentic sweep of 7 segmentation approaches scored against Imaris ground truth):** `_threshold_volume` gained a **`capture`** step — a per-plane **white top-hat** (flattens background, boosts thin faint fibers) + lighter smoothing + a lower Otsu cut. This recovers the faint fibers Otsu drops **while keeping fibers thin/tubular** (no filled mesh). Default `capture=0.40`. - **Result:** median length ratio **0.31 → 0.88** (~2.8×, zero over-tracing on any image). Your ~2,500-vs-5,500 image lands near parity. - **`capture=1.0` (slider max) reproduces the EXACT legacy result** (verified byte-identical: 2,156 µm / 1,123 branch points), so nothing is lost — the old behaviour is one slider-click away. - Exposed as a **"Faint-fiber capture" slider** (0.20 aggressive … 1.0 = legacy, default 0.40) on both tabs, alongside Sensitivity. - **Trade-offs (honest):** capturing ~2.8× more real fiber also raises **branch points ~3.5×** (still well below Imaris, and *not* the fused "tangle" — a rejected hysteresis approach exploded them 13–18×) and lowers **mean diameter ~30%** (thinner, arguably more accurate for fine NF fibers). Fiber tracing is independent of hair-cell detection, so those metrics are unaffected. - The `0.20` end can over-trace on sparse images (ratio > 1.3); `0.40` is the safe default. Full per-image numbers are in the tuning notes. ## Round 3 (2026-07-12) — Cellpose-SAM only + Step 2 → Step 3 dependency **1. Step 2 always uses Cellpose-SAM (never watershed).** `detect_hair_cells` now always runs Cellpose-SAM (the `cpsam` model in cellpose 4.x) with **no watershed fallback** — it raises a clear error if cellpose isn't installed. UI labels now read "Cellpose-SAM". `requirements.txt` makes it a hard dependency for the Hugging Face Space: `cellpose>=4.0`, `torch>=2.3`, `torchvision>=0.18`. - *Fine-tuned model:* the code uses a fine-tuned model automatically **if** one is present at `models/hair_cell_cpsam` (or the `NEURON_TRACER_CP_MODEL` env var). **No fine-tuned model is bundled** — as shipped it runs *stock* Cellpose-SAM. Drop your fine-tuned weights at `models/hair_cell_cpsam` (or upload them to the Space) to use them; the training scripts are in `training/` in the worktree. **2. Step 3 runs only after Step 2 (results feed in).** "Step 3 — Run analysis" now refuses until Step 2 has been run, with the message "Please run Step 2 — Detect hair cells (Cellpose-SAM) first." Step 2's detected cells then feed Step 3's per-region hair-cell counts and normalization. - The **neurofilament skeleton is unchanged** — tracing is independent of hair cells; verified identical (9864 skeleton voxels on the test image, same as before). The gate is a pre-tracing early return. - *Exception:* a manual **Z (depth) split** is exempt from the gate, because detection is an in-plane (Y/X) operation and refuses on Z (requiring it would deadlock). - *Trade-off:* because Step 2 is now mandatory, all single-image analysis (including "Whole field only") requires Cellpose-SAM to have run. On a Space without Cellpose/GPU this will surface a clear error rather than silently degrading. Say the word if you'd rather exempt whole-field. **Also fixed (latent bug this change exposed):** the Excel *summary* sheet crashed when one region had 0 hair cells (a column mixing blanks and numbers). Now coerced to numeric before aggregating — the per-region numbers are unchanged. ## Round 2 (2026-07-12) — three more changes **1. Removed the "OHC is BELOW the line (bottom of image)" radio option.** The "Which side is OHC?" radio now offers only **"OHC is ABOVE the line (top of image)"** for the Y (radial) axis. Two safety fixes came with it so nothing breaks: switching the split axis now keeps the radio's value valid (a value picked on the X/Z axis is clamped when returning to Y), and hair-cell detection won't set the radio to a side it no longer offers. **2. Removed the word "optional" from Step 2.** The button now reads **"Step 2 — Detect hair cells (Cellpose)"** (and the method note no longer calls detection "optional"). *Note:* Step 2 is still not hard- required before Run — say the word if you want it enforced. **3. Freehand-drawn curved boundary.** New **"Draw a custom curved boundary (freehand)"** panel under the boundary sliders (single-image tab): press **Load Myo7a here**, draw the boundary freehand across the width with the brush, then **Apply drawn boundary**. The analysis and all overlays then use your drawn curve (for the Y or X split axis) instead of the position/tilt/curvature sliders. **Clear drawn boundary** reverts to the sliders; loading a new image also clears it. Works alongside everything else — if you don't draw, the sliders behave exactly as before. --- ## Round 1 (2026-07-12) All edits are in **`app.py`**. `processing.py` was unchanged (change #1 was already supported there). Verified: the Gradio UI builds and the behaviors below were tested end-to-end against `Untitled1592_c1-4.tif`. ## 1. Channel dropdown — additional dyes (already supported, no code change) `processing.py`'s `KNOWN_DYES` table already recognizes **Alexa Fluor 405, 488, 555, 633, 647** (plus 514/532/546/568/594/680, ATTO 390/488/647, Cy3, Cy5, DAPI, FITC, GFP). The channel dropdown lists whatever channels the loaded file contains and labels each by its recognized dye — so files using those dyes show them automatically. No change was required. ## 2. Removed the Voxel size (dz / dy / dx) inputs - Deleted the "Voxel size (µm)" accordion from **both** the single-image tab and the batch tab. - `load_and_preview(file_obj)` and `run_batch(...)` no longer take dz/dy/dx; they call `P.load_image(...)` with its built-in defaults (dz=0.35, dy=0.0895, dx=0.0895 µm). CZI files still read their own voxel size from metadata. - The read-only "Voxel size: …" line in the status text is kept (it reports the size actually used; it is not an input). ## 3. Myo7a (MIP) + boundary preview no longer auto-loads - On image load, only the **Neurofilament** preview appears. The **Myo7a (MIP) + boundary** preview stays hidden. - It appears only **after** the user picks an analysis scope ("Split IHC vs OHC" / "Inner hair cells only" / "Whole field only"). - Implementation: the scope radio starts unselected (`value=None`); `refresh_boundary_preview(...)` gained a `mode` argument and returns nothing until a scope is chosen; `run_single` prompts to pick a scope if none is set. ## 4. "IHC is ABOVE the line…" → "OHC is ABOVE the line…" (with correct region math) - The side radio was reframed around **OHC** so it stays a coherent binary choice: retitled **"Which side is OHC?"** with options "OHC is ABOVE / BELOW the line" (and LEFT/RIGHT for the X axis, SHALLOW/DEEP for the Z axis). - The region assignment follows the label: selecting **"OHC is ABOVE the line (top of image)"** now analyzes the **top** band as OHC and the **bottom** as IHC (verified in `make_region_masks`). Default = "OHC is ABOVE the line (top)". - Internally the value the pipeline consumes is still the *IHC side* (the opposite of the chosen OHC side), so the tracing/metrics logic is unchanged — only the wording and the value-to-label pairing were flipped. --- ## Run it ```bash cd "hf_neuron_tracer_updated" pip install -r requirements.txt # first time only python3 app.py ``` `DAPI.tif`, `Tuj1.tif`, `Untitled1592_c1-4.tif` are included as sample images.