بنقرة واحدة
vessel3d-frangi-bugfix
Frangi vesselness 3D filter: Ra formula correction, scikit-image deprecations, GPU VRAM guard
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Frangi vesselness 3D filter: Ra formula correction, scikit-image deprecations, GPU VRAM guard
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Normalize long-form CODEX cycle folders to short form before notebooks run. Trigger: cyc001_reg001_*, hard-coded cyc paths breaking, staged CODEX raw data failing in Notebooks 1/2.
v5.6.0 joint multi-TF model: single model per symbol with broadcast 1Hour context replaces dual 15Min/1Hour models. Trigger: (1) replacing weighted-voting model aggregation, (2) adding broadcast features to vectorized env, (3) limited training data + worried about overfitting from doubling obs_dim, (4) backtest builder mismatch with newer feature counts.
DEPRECATED in v5.6.0 — see joint-multi-tf-v560 skill. Documents the v5.2.0 dual-model approach (train separate 15Min/1Hour models, combine via weighted voting). Still relevant for: (1) loading legacy v5.5.0 dual models, (2) understanding the historical aggregation layer, (3) resampling pattern via origin='start'.
Surface a shipped-but-undocumented CLI feature in user-facing docs. Trigger: user reports a known feature missing from README/readthedocs even though the CLI command exists.
KINTSUGI Snakefile + CLI changes that route SLURM jobs around accounts saturated by OTHER users on the same QOS pool. Trigger: QOSGrpMemLimit, jobs stuck pending despite available GPU slots in config, noisy neighbor on shared QOS, multi-user investment pool exhaustion, _build_cycle_assignment static-vs-live.
KINTSUGI SLURM batch processing: Maximize throughput using multi-account resource calculation with GPU+CPU pools per account. Trigger: SLURM job submission, batch processing, resource maximization, GPU+CPU concurrent, headless processing, resource pool.
| name | vessel3d-frangi-bugfix |
| description | Frangi vesselness 3D filter: Ra formula correction, scikit-image deprecations, GPU VRAM guard |
| author | KINTSUGI Team |
| date | "2026-02-21T00:00:00.000Z" |
| Item | Details |
|---|---|
| Date | 2026-02-21 |
| Goal | Fix critical bugs in 3D vessel segmentation: wrong Frangi Ra formula, scikit-image deprecations, GPU VRAM safety |
| Environment | HiPerGator HPC, Python 3.11, scikit-image 0.26.0, CuPy 13.x, NVIDIA B200 (192 GB) + L4 (23 GB) |
| Status | Success |
The vessel3d module was added Feb 16, 2026 and attempted on one project with 5 SLURM runs — all failed due to different issues. Investigation revealed three distinct failure modes and one critical algorithmic bug.
| Run | Node | Root Cause | Resolution |
|---|---|---|---|
| 1 | B200 | GPU OOM — old (N,3,3) eigenvalue path (108 GB) | Fixed by Cardano commit same day |
| 2 | B200 | Frangi completed then ImportError: skan | skan 0.13.1 installed |
| 3-4 | B200 | User-cancelled | N/A |
| 5 | L4 (23 GB) | GPU OOM on gaussian_filter — L4 too small | VRAM guard added |
Run 2 proved the Cardano fix works. The remaining issues were the Ra formula bug and small-GPU safety.
The Ra ratio discriminates tubes from plates. With eigenvalues sorted |λ₁| ≤ |λ₂| ≤ |λ₃|:
Wrong (vessel3d.py:552):
Ra = abs_l1 / (abs_l2 + eps) # |λ₁|/|λ₂| — near 0 for tubes!
Correct (Frangi 1998, eq. 11; scikit-image reference):
Ra = abs_l2 / (abs_l3 + eps) # |λ₂|/|λ₃| — near 1 for tubes
Impact: With the bug, the (1 - exp(-Ra²/2α²)) term evaluated to ~0 for tubes (since Ra ≈ 0), killing the vesselness response. The pipeline still partially worked because real data has nonzero λ₁ and Otsu threshold adapts, but small/faint vessels were missed.
Verification: Synthetic tube test — Ra at tube center = 0.91 (correct) vs ~0.02 (buggy). Plate Ra = 0.05 (correctly low in both versions).
| Deprecated | Replacement | Semantics Change |
|---|---|---|
binary_closing(mask, footprint=) | closing(mask, footprint=) | FutureWarning in 0.26, removed in 0.28 |
remove_small_objects(mask, min_size=N) | remove_small_objects(mask, max_size=N-1) | New max_size removes objects <= threshold (old min_size removed < threshold) |
Added to _hessian_eigenvalues_3d():
estimated_vram = volume.nbytes * 15 # Peak: 6 Hessian + input + 8 working arrays
free_vram = cp.cuda.Device(device_id).mem_info[0]
if estimated_vram > free_vram:
logger.warning(f"GPU VRAM insufficient, falling back to CPU")
use_gpu = False
SLURM job script also queries nvidia-smi and forces device='cpu' if VRAM < 40 GB.
| Attempt | Why it Failed | Lesson Learned |
|---|---|---|
Compare tube vs plate vesselness by .max() | Both normalize to 1.0 — max is always 1.0 | Compare unnormalized Ra terms at known structure centers, not normalized vesselness |
Compare tube vs plate by (v > 0.5).sum() | Plates have more edge voxels on small (32³) volumes — boundary effects dominate | Small synthetic volumes have strong boundary effects; test eigenvalue ratios directly |
remove_small_objects(min_size=N) with new API | min_size deprecated in skimage 0.26 | Use max_size=N-1 (note: max_size removes objects with size <= threshold, off-by-one from old min_size which removed < threshold) |
| L4 GPU (23 GB) for isotropic volumes | Isotropic volume of 9x7 tile grid = ~9 GB float32; Cardano needs ~15x = 135 GB peak | Always check free VRAM before GPU eigenvalue path; 40 GB minimum for typical volumes |
Preset-overridable params (sigmas, alpha, beta, min_size, denoise_sigma) default to None in segment_vessels_3d(). Resolution chain: explicit value > preset > hardcoded default.
Why None defaults: The original design used hardcoded defaults (alpha=0.5) and compared against them to detect "caller used default." This failed when SLURM scripts passed values explicitly (e.g., alpha=VESSEL_ALPHA where VESSEL_ALPHA=0.5). The function couldn't distinguish "caller passed 0.5" from "caller didn't specify."
SLURM integration: Build a seg_kwargs dict, only including params whose env vars exist (if 'VESSEL_ALPHA' in os.environ). Unset vars → param stays None → preset fills it.
discover_vessel_markers(channel_names) finds vessel markers ranked by priority:
| Pattern | Role | Priority |
|---|---|---|
| CD34 | endothelial | 1 (best) |
| CD31 | endothelial | 2 |
| aSMA, a-SMA, alpha-SMA, αSMA, Smooth Muscle Actin | smooth_muscle | 3 |
Excludes: DAPI, Blank, Empty, Autofluorescence, generic "Actin" (too ambiguous).
segment_vessels_multichannel({"CD31": vol1, "SMA": vol2}, ...): Runs Frangi independently per channel (avoids inter-cycle misalignment), then combines binary masks via combine_vessel_masks() (union or intersection). Skeletonizes and analyzes the combined result. Stores individual masks in result.per_channel_masks.
| Metric | Default | high_sensitivity |
|---|---|---|
| Segments | 207 | 619 (+199%) |
| Duration | ~25 min | ~24 min |
Presets (VESSEL_PRESETS):
| Preset | sigmas | alpha | beta | min_size | denoise_sigma |
|---|---|---|---|---|---|
default | [1,2,4,8] | 0.5 | 0.5 | 500 | 0.5 |
high_sensitivity | [0.5,1,2,4,8] | 0.3 | 0.3 | 250 | 0.3 |
CD34 | [0.5,1,2,4,8] | 0.3 | 0.3 | 250 | 0.3 |
VRAM guard: 15x volume bytes minimum for GPU path.
Thin-slab note: Datasets are ~10 z-planes (~15 um depth). Topology features (tortuosity, branching angle) are unreliable. Cross-section radii and cleaner masks are the main 3D value.
Test assertions that verify the fix:
| Attempt | Why it Failed | Lesson Learned |
|---|---|---|
Compare tube vs plate vesselness by .max() | Both normalize to 1.0 — max is always 1.0 | Compare unnormalized Ra terms at known structure centers, not normalized vesselness |
Compare tube vs plate by (v > 0.5).sum() | Plates have more edge voxels on small (32³) volumes — boundary effects dominate | Small synthetic volumes have strong boundary effects; test eigenvalue ratios directly |
remove_small_objects(min_size=N) with new API | min_size deprecated in skimage 0.26 | Use max_size=N-1 (note: max_size removes objects with size <= threshold, off-by-one from old min_size which removed < threshold) |
| L4 GPU (23 GB) for isotropic volumes | Isotropic volume of 9x7 tile grid = ~9 GB float32; Cardano needs ~15x = 135 GB peak | Always check free VRAM before GPU eigenvalue path; 40 GB minimum for typical volumes |
Preset with hardcoded defaults (alpha=0.5) | SLURM script always passes explicit values; alpha == 0.5 check can't distinguish "caller passed 0.5" from "default" | Use None defaults for preset-overridable params; resolution: explicit > preset > hardcoded |
SLURM export VESSEL_SIGMAS="1,2,4,8" with preset | Env var exists → script passes it as explicit kwarg → overrides preset | Only export env vars you want to override; leave unset to let preset control |
binary_closing, binary_opening, binary_dilation, binary_erosion all deprecated in favor of generic closing, opening, dilation, erosionNone defaults for any param a preset should control. The if 'ENV_VAR' in os.environ pattern in SLURM scripts cleanly separates "explicitly set" from "use default"src/kintsugi/vessel3d.py — Presets, marker discovery, multichannel, preset override patterntests/test_vessel3d.py — 34 tests validating all fixes and new features