Repository navigation
Conversation
ziw-liu
marked this pull request as ready for review
May 30, 2023 23:12
alxndrkalinin
added a commit
that referenced
this pull request
Oct 2, 2026
…mito Stage 2 #5 targets thin structures, so the SAUNA-weighted Dice and the soft-clDice arms now cover ER and mito on the fnet3d_vscyto3daug recipe, next to a plain segaux arm on the same cell. Their training mask is the eval's classical segmenter (fg_mask in SEC61B.zarr / TOMM20.zarr). Each weight is the median ||grad base|| / ||grad L|| over 20 seeded val batches at the v2 baseline's best checkpoint, taken mid-training (ER epoch 28, mito epoch 18): ER 0.22 / 0.21 / 0.24 and mito 0.77 / 0.66 / 0.71 for Dice / SAUNA / clDice. Every new train leaf composes, and its data and trainer blocks equal the v2 baseline's except fg_mask_key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gwn9hwgT8uJSoWaCxCAm8J
alxndrkalinin
added a commit
that referenced
this pull request
Oct 5, 2026
Scores the FNet-3D vscyto3daug nucleus SAUNA-map-weighted Dice arm (Stage 2 #5; fit 36906516 reached max_steps=200000; --ckpt best epoch 64) on all four legs. It is read against the wave2q 2x2. All four predicts completed with complete markers on every FOV, and the generator's store gate passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gwn9hwgT8uJSoWaCxCAm8J
alxndrkalinin
added a commit
that referenced
this pull request
Oct 7, 2026
…Diff-2D (#524) * feat(viscy-utils): add SAUNA weighting and soft-clDice to SegAuxDice Spotlight v2 Stage 2 #5 compares two topology-aware variants of the seg-aux Dice on thin structures (ER, mito, nucleus necks): - weighting="sauna" weights both Dice sums by |y~|, SAUNA's combined boundary/thickness "h" map of the patch mask (Oulu-IMEDS/SAUNA), ported to anisotropic 3D. The training venv has no cupy, so the distance transform is an exact separable torch EDT, capped at the distance the map uses; it matches scipy to 1e-4. - topology="cldice" mixes in 1 - soft-clDice (jocpae/clDice skeleton). Each skeleton iteration is activation-checkpointed: at 10 iterations a (8, 1, 32, 384, 384) batch otherwise exceeded 44 GiB on an A40. Both default off, and the defaults stay bit-identical to the plain soft Dice. per_channel keeps its contract, so CellDiff's gated path gets the options too. Tests: test_seg_aux_loss.py 63 passed; test_training_integration.py 66 passed (fast_dev_run of both options in 3D and Z=1). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gwn9hwgT8uJSoWaCxCAm8J * feat(dynacell): add Spotlight v2 topology arms and ER/mito baselines Stage 2 #5 compares SAUNA-weighted Dice against soft-clDice as one-term changes to the segaux arm, on the FNet-3D recipe that works out of domain (fnet3d_vscyto3daug): - nucleus: segaux_sauna (w 5.3) and segaux_cldice (w 0.88, 5 skeleton iterations), both calibrated on the Phase 15 Arm B ckpt like segaux; the plain-Dice row of that calibration reproduces segaux's 4.5. They are scored against the in-flight v2/segaux 2x2. - ER and mito: never-run template leaves of the same recipe (target channel Structure, so the overlay's lists apply unchanged) and the v2/v2_seed1 baseline draws. The topology terms are aimed at these thin organelles; their arms follow once the baselines can be calibrated on. The A549 predict leaf names become per-organelle (nucleus keeps the Phase 15 _h2b_ names). The SAUNA spacing is read from the aics-hipsc manifest. Tests: 2630 passed, 176 skipped (leaf generator, paths, config composition, grouped eval generator). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gwn9hwgT8uJSoWaCxCAm8J * feat(dynacell): add Spotlight v2 segaux and topology arms for ER and mito Stage 2 #5 targets thin structures, so the SAUNA-weighted Dice and the soft-clDice arms now cover ER and mito on the fnet3d_vscyto3daug recipe, next to a plain segaux arm on the same cell. Their training mask is the eval's classical segmenter (fg_mask in SEC61B.zarr / TOMM20.zarr). Each weight is the median ||grad base|| / ||grad L|| over 20 seeded val batches at the v2 baseline's best checkpoint, taken mid-training (ER epoch 28, mito epoch 18): ER 0.22 / 0.21 / 0.24 and mito 0.77 / 0.66 / 0.71 for Dice / SAUNA / clDice. Every new train leaf composes, and its data and trainer blocks equal the v2 baseline's except fg_mask_key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gwn9hwgT8uJSoWaCxCAm8J * feat(viscy-utils): add BackgroundLowPass training-target transform x' = w * x + (1 - w) * G_lp(x), w = clamp(G_f(dilate(fg_mask, r)), 0, 1): the target is unchanged inside the dilated, feathered foreground and a Gaussian low-pass of itself in the background. It changes the target rather than masking the loss, so a flow-matching model is still supervised in the background and learns to remove the initial noise there (masking it out was the Spotlight v1 collapse). The Gaussians are normalized and edge-replicated (scipy mode="nearest", truncate=4.0), so a constant passes through unchanged and the low-pass keeps the local mean; no normalization change is needed. They run as float32 shifted multiply-adds, out of reach of autocast and cuDNN TF32. XY per Z plane by default; sigma_lp_z / sigma_feather_z / dilate_radius_z extend each step to Z. Tests check the op against a scipy.ndimage reference (D=1, per-plane D>1, with Z), identity where w = 1, exact low-pass where w = 0, the local mean, empty/full masks, plane independence, dtype/autocast and CPU vs CUDA. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gwn9hwgT8uJSoWaCxCAm8J * feat(dynacell): train CELL-Diff on a background-low-passed target DynacellFlowMatching gains target_bg_lowpass (default None). When set, the training step replaces the target by target_bg_lowpass(target, fg_mask) after normalization and the GPU augmentations, so the flow learns full-resolution texture inside cells and a smooth, still-supervised field in the background, instead of spending capacity on background noise that phase cannot predict (Spotlight v2 Track H). seg_aux, C-joint and C-cond see the same transformed target; validation, its samples and prediction see the raw target, so loss/validate and --ckpt best keep the baseline's criterion. The op is applied in the engine and fg_mask reaches the wrapper only through seg_aux, so it composes with seg_aux and never trips "fg_mask was passed but seg_aux is not configured". A missing fg_mask raises the shared KeyError, now naming target_bg_lowpass. With None the loss is bit-identical to the baseline's, even on a batch that carries fg_mask. The op holds no parameters, so checkpoints are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gwn9hwgT8uJSoWaCxCAm8J * feat(dynacell): add Spotlight v2 bglp arm for CellDiff-2D Track H H1: celldiff_2d_bglp, nucleus and membrane. The fit leaf is the celldiff_2d baseline plus fg_mask_key: fg_mask and target_bg_lowpass (BackgroundLowPass); normalization, sampling, budget and the monitored metric are the baseline's. Predict leaves for iPSC and the three A549 legs equal the baseline's but for checkpoint, store and names, since the op is training-only. BG_LOWPASS_ARGS (sigma_lp 2, sigma_feather 2, dilate_radius 4 px) is PROVISIONAL: the final values come from the H0 band-power spectra. The new token is registered wherever an arm token must be (PAPER_KEY, the CellDiff arm segments, the grouped generator's code map and CellDiff list) so it never collapses onto celldiff_2d. Regenerating writes the 10 new leaves and leaves the other 372 byte-identical. A test composes both fit leaves, checks their delta against the baseline and builds the model through jsonargparse. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gwn9hwgT8uJSoWaCxCAm8J * fix(viscy-utils): estimate the background from background pixels only G_lp(x) of the whole target bleeds a bright foreground into the background as a halo, so BackgroundLowPass taught the background to carry a ghost of every cell. The estimate is now a normalized convolution over background pixels: m = dilate(fg_mask, r), B = G_lp(x * (1 - m)) / G_lp(1 - m), x' = w * x + (1 - w) * B, w = clamp(G_f(m), 0, 1). Where under 1e-6 of the kernel mass falls on background (e.g. a patch the dilated mask covers entirely) B is undefined and the target is kept, so x' stays finite; w is ~1 there anyway. dilated_mask(), blend_weight(m) and background(x, m) replace lowpass() and blend_weight(fg_mask). A new test shows a blob's halo in a plain blur (> 2 just outside the dilated mask) and none in B (|B| < 0.15 on noise of std 0.1); it fails when B is swapped back to the plain blur. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gwn9hwgT8uJSoWaCxCAm8J * feat(viscy-utils): add a flat background mode to BackgroundLowPass sigma_lp=None (or inf) makes the background estimate flat: per sample and channel, the mean of the target over every background voxel of the patch, the sigma_lp -> inf limit of the normalized convolution. Beyond background high frequencies it also removes autofluorescence and uneven illumination, which phase cannot predict either (Track H revision). A patch with no background keeps its target, as in the smooth mode. sigma_lp_z is rejected with the flat mode, which already spans Z. The mean, not the median, keeps the flat mode the exact limit of the smooth one. Tests: x' is the masked background mean wherever w = 0, across planes and through an illumination gradient (failing for an unmasked or a per-plane mean), plus the scipy reference, the no-background patch and CPU vs CUDA in flat mode. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gwn9hwgT8uJSoWaCxCAm8J * test(dynacell): train CELL-Diff with a flat background target The fast_dev_run of the background target op now also runs the flat mode (sigma_lp=None), 2D and 3D, with and without seg_aux, on real generate_fg_masks output. The engine docstring describes the background-pixel estimate instead of a low-pass of the whole target. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gwn9hwgT8uJSoWaCxCAm8J * feat(dynacell): add Spotlight v2 bgflat arm for CellDiff-2D Track H revision: celldiff_2d_bgflat, nucleus and membrane, next to celldiff_2d_bglp. Its target_bg_lowpass has sigma_lp: null, so the background becomes the patch's background mean, which removes autofluorescence and uneven illumination as well as background high frequencies. Its risk is foreground the mask misses (dim nuclei) becoming an "erase" example, where bglp only blurs it. BG_LOWPASS_ARGS becomes BG_TARGET_ARGS, one entry per arm sharing the feather and dilation (sigma_feather 2, dilate_radius 4 px; bglp sigma_lp 2 px); all values stay PROVISIONAL until the H0 spectra. celldiff_2d_bgflat is registered like bglp. Regenerating writes the 10 new leaves; the other 382, including bglp's, are byte-identical. The composition test now covers both arms. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gwn9hwgT8uJSoWaCxCAm8J * test(dynacell): build the Track H arms' model from a LightningModule base test_lazy_init evicts every dynacell.* module from sys.modules, so when it shares an xdist worker with the composition test, jsonargparse imports a fresh dynacell.engine whose DynacellFlowMatching is not the class the test bound at import ("does not correspond to a subclass"). Run after test_lazy_init, all 4 cases failed. LightningModule as the base, as LightningCLI uses, is never evicted; the test now checks the built class by its import path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gwn9hwgT8uJSoWaCxCAm8J * fix(viscy-utils): make the BackgroundLowPass blend weight exactly 1 inside the mask The normalized Gaussian sums to 1 only up to float32 rounding, so under an all-foreground window w was 0.99999982 (sigma_feather 0.5), 0.99999988 (1.0, 3.0) and exactly 1 only at 2.0 and 4.0 (measured on an all-ones mask). x' = w * x + (1 - w) * B then differs from x by rounding wherever B != x, and "an all-foreground plane comes back unchanged" held by luck. w is now divided by the blur of ones: edge replication makes that one value at every voxel, computed by the same operations as any all-ones window, so w is exactly 1 there and still exactly 0 where the kernel misses the mask. The new test requires w == 1 and a bit-identical plane for sigma_feather 0.5, 1, 2 and 3; without the division it fails at 0.5, 1 and 3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gwn9hwgT8uJSoWaCxCAm8J * fix(viscy-utils): flatten the background per plane and fall back to that mean Two refinements to BackgroundLowPass from the Track H revision: - The flat background (sigma_lp=None) is now the mean over background pixels per sample, channel and Z plane, consistent with the op's no-Z-mixing default, instead of one mean over the whole patch. - Where the normalized convolution has at most 1e-6 of its kernel mass on background (a background ring thinner than its support), B is now that plane's flat mean, not the target itself. Only a plane with no background pixel at all keeps B = x; there w == 1 (exact since the previous commit), so it is returned bit-identical. Tests, each failing under the matching mutation: - a bright blob on a constant background gives B == the constant everywhere, including the ring just outside the dilated mask where a plain blur of the target reaches > 2.7 (fails with G_lp(x)); - the flat output is the masked mean per (b, c, plane) through per-plane offsets and a ramp (fails with a whole-patch mean); - the pixels a thin ring cannot reach take the ring's mean, all finite (fails with the x fallback and with an eps-divide to 0); - a linear backlight ramp is flattened by bgflat and kept by bglp at sigma 8, fitted slope within 20% (fails if either mode is swapped). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gwn9hwgT8uJSoWaCxCAm8J * docs(dynacell): describe bgflat as a per-plane background mean BackgroundLowPass now flattens each Z plane to its own background mean; the generator's docstring and BG_TARGET_ARGS comment said "the patch's". The 2D leaves have one plane, so no leaf changes (--check: 0 stale). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gwn9hwgT8uJSoWaCxCAm8J * fix(dynacell): give the Track H arms their measured mask geometry The bglp and bgflat arms shared a provisional r=4 / sigma_f=2 ring. The H0 step measured each arm's geometry on the GT with the training op (trackh/target_op_check2.csv, mask_recall.csv): - bglp moves to r=2 / sigma_f=1. At r4/f2 the unsmoothed ring keeps 12-15% of the background's 1-px power, vs 3% at r2/f1. A nucleus the mask misses is only blurred, so the tighter ring is safe. - bgflat keeps r=4 / sigma_f=2, for mask recall. Flattening erases any nucleus the mask misses: 1.2% of iPSC cpdino GT nuclei are <50% covered at r4, vs 2.9% at r2. Only the two bglp train leaves change. The bgflat leaves stay byte-identical, and the generator's --check reports 0 stale. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gwn9hwgT8uJSoWaCxCAm8J * fix(dynacell): resolve the Track H arms ahead of the celldiff_2d fallback The merge of dynacell-models took that side's paths.py for the PAPER_KEY conflict and with it dropped this branch's non-conflicting hunk listing celldiff_2d_bglp/bgflat in _CELLDIFF_ARM_MODELS. Without it, a leaf that keeps model_name celldiff_2d resolves a bglp/bgflat checkpoint to the baseline; test_resolve_model_spotlight_v2_arm_not_collapsed caught both cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gwn9hwgT8uJSoWaCxCAm8J * docs(dynacell): describe bgflat as a per-plane background mean in PAPER_KEY BackgroundLowPass with sigma_lp=None takes the mean over each sample, channel and Z plane's background pixels (background_lowpass.py), as 161e098 already says in the leaf generator. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gwn9hwgT8uJSoWaCxCAm8J * docs(viscy-utils): say where BackgroundLowPass leaves the target unchanged The module docstring said the whole dilated foreground is unchanged. w is exactly 1 only where the feather kernel lies inside the dilated mask, as the class docstring states; with a feather radius above the dilation radius the blend reaches the foreground rim. Both Track H arms are in that regime: on a 24x24 square, min w on the original foreground is 0.9909 (bglp) and 0.9772 (bgflat), and a 1-px object gets 0.9818 and 0.9546. The code is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gwn9hwgT8uJSoWaCxCAm8J * test(dynacell): build the Track H model with ArgumentParser.instantiate jsonargparse 4.49 deprecates instantiate_classes in favour of instantiate, and the Track H compose test raised that warning. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gwn9hwgT8uJSoWaCxCAm8J --------- Co-authored-by: Alexandr Kalinin <alxndrkalinin@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Clean up unused code from microDL 1.0.