Repository navigation
feat(segmentation): FBL and NPM1 nucleolar workflows on CPU or GPU - #64
Draft
alxndrkalinin wants to merge 7 commits into
Draft
alxndrkalinin wants to merge 7 commits into
alxndrkalinin wants to merge 7 commits into
Conversation
…r workflows Shared primitives for the aicssegmentation FBL and NPM1 ports, running on NumPy or CuPy input with reference-identical rounding: - _label_histograms bins each label as np.histogram(values, 256) does (NumPy's float32 linspace edges, its float32 bin estimate and the one-bin edge correction), voxel-wise on the device; Otsu (through skimage's hist= path) and the triangle threshold (skimage's code after its histogram call) then run on the host per label. - _median reproduces np.percentile(x, 50) from the middle order statistics of a device sort. - _greater and _local_threshold_mask compare against host scalars with NumPy's promotion, so float64 thresholds compare in float64 on GPU too. - dot_slice_by_slice ports seg_dot.dot_slice_by_slice: per-plane gaussian_laplace as 1D passes, float64 on GPU, stored in float32 after each pass as SciPy does. - _low_level_mask: the (triangle + median) / 2 threshold with the 700-voxel size filter. Host extremes use ufunc.at; SciPy's labeled minimum/maximum argsorts the whole image and took 70 s of 138 s on a 31x1024x1024 tile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011hEQ1TZJUbmhYKcC14RYHT
workflow_fbl and workflow_npm1 port aicssegmentation's Workflow_fbl and Workflow_npm1 with their fixed parameters (rescale_ratio=-1), returning a boolean mask on the input's device; a 2D plane is segmented as a one-plane volume. FBL thresholds each low-level object at its own Otsu level and merges 2D spot detections; NPM1 dilates the low level by ball(2), keeps objects whose Otsu level beats 0.333 x the global Otsu, adds bright spots inside the low level and cuts dark spots as holes. On 24 fibroblast tiles (31-38 x 1024 x 1024, slides 157-160; 12 anti-fibrillarin tiles through FBL, 12 EtBr through NPM1) the CPU masks differed from aicssegmentation's by 0 voxels and the GPU masks from the CPU's by 0 voxels. Per tile: reference 23-97 s, CPU 22-125 s, GPU 0.23-1.70 s (single-CPU host, A40 shared with another process). The tests add synthetic nucleoli that reach every branch, and extend the GPU-vs-CPU, aicssegmentation, plane-mode and dimensionality tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011hEQ1TZJUbmhYKcC14RYHT
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011hEQ1TZJUbmhYKcC14RYHT
… GPU _group_extremes passed 0-based groups to cupyx's labeled minimum and maximum. Past 30 labels cupyx switches from a per-label loop to a lexsort that reads extremes at the starts of labels 1..max, so label 0's minimum and maximum stayed at 0. The first object then had an empty histogram and a threshold of 0, so every voxel in it passed FBL's local Otsu step. On a 34-object PC3 tile (171 c1_g004, 52x1024x1024) the GPU mask differed from the CPU mask in 375291 voxels; with 1-based labels it differs in 0, and every intermediate matches. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011hEQ1TZJUbmhYKcC14RYHT
dot_slice_by_slice ran cupyx's gaussian_filter1d on a float64 copy. That accumulates in float64 like SciPy but tap by tap, while SciPy's NI_Correlate1D sums a symmetric kernel as centre * w0 plus (left + right) * w_k from the outermost pair inwards. Where a second-derivative pass cancels to near zero, the two orders round to different float32 values: 3 and 6 voxels in the two order-2 passes of a 52x1024x1024 PC3 tile, and 1 voxel of the final response. A CUDA kernel now replays SciPy's loop with SciPy's own reversed weights, for float32 and float64 input, in reflect mode. The response on that tile and on 16x256x256 uniform noise (seed 0, sigma 2; 1 voxel differed before) is bitwise SciPy's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011hEQ1TZJUbmhYKcC14RYHT
The Allen Cell Segmenter workflows are meant to be re-tuned per dataset, and on the 2018 fibroblast/PC3 confocal tiles the defaults mark noise speckle (FBL) and EtBr-bright cytoplasm (NPM1). workflow_fbl and workflow_npm1 now take keyword-only parameters for their constants: normalization bounds, Gaussian sigma, spot-filter sigma(s) and cutoff (None skips the step), low-level and final minimum sizes, and for NPM1 the hole sigmas and cutoff, dilation radius (0 skips it), local-cutoff factor and local Otsu factor. The defaults are the reference's values and its expressions, so the aicssegmentation parity tests are unchanged. Tests check that disabling or loosening each step changes the mask in the documented direction, and that GPU equals CPU at non-default values. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011hEQ1TZJUbmhYKcC14RYHT
Contributor
Reviewer's GuidePorts the Allen Cell Structure Segmenter FBL and NPM1 nucleolar workflows to the segmentation package for NumPy and CuPy inputs, using host-computed scalar thresholds, device-binned histograms, and a custom SciPy-order CUDA spot filter to maintain reference and CPU/GPU bitwise parity; adds public exports, documentation, and extensive regression coverage. Sequence diagram for CPU or GPU FBL segmentationsequenceDiagram
participant Caller
participant workflow_fbl
participant Device as NumPy_or_CuPy
participant Host as Host_thresholds
participant Spot as SciPy_order_spot_filter
Caller->>workflow_fbl: workflow_fbl(image, parameters)
workflow_fbl->>Device: intensity_normalization()
workflow_fbl->>Device: _gaussian_nearest()
workflow_fbl->>Host: _histogram_thresholds(smooth, _triangle_from_histogram)
Host-->>workflow_fbl: triangle threshold
workflow_fbl->>Host: _median(smooth)
Host-->>workflow_fbl: median threshold
workflow_fbl->>Device: _label_faces() and _group_extremes()
workflow_fbl->>Host: _histogram_thresholds(smooth, _otsu_from_histogram, labels, count)
Host-->>workflow_fbl: per-object Otsu thresholds
workflow_fbl->>Device: _local_threshold_mask()
opt dot_cutoff is not None
workflow_fbl->>Spot: dot_slice_by_slice(smooth, dot_sigma)
Spot-->>workflow_fbl: spot response
workflow_fbl->>Device: remove_small_objects_aics()
end
workflow_fbl-->>Caller: boolean mask
Sequence diagram for CPU or GPU NPM1 segmentationsequenceDiagram
participant Caller
participant workflow_npm1
participant Device as NumPy_or_CuPy
participant Host as Host_thresholds
participant Spot as SciPy_order_spot_filter
Caller->>workflow_npm1: workflow_npm1(image, parameters)
workflow_npm1->>Device: intensity_normalization()
workflow_npm1->>Device: _gaussian_nearest()
workflow_npm1->>Host: _histogram_thresholds(smooth, _triangle_from_histogram)
Host-->>workflow_npm1: triangle threshold
workflow_npm1->>Host: _median(smooth)
Host-->>workflow_npm1: median threshold
workflow_npm1->>Device: _label_faces() and binary_dilation()
workflow_npm1->>Host: _histogram_thresholds(smooth, _otsu_from_histogram)
Host-->>workflow_npm1: global Otsu threshold
workflow_npm1->>Host: _histogram_thresholds(smooth, _otsu_from_histogram, labels, count)
Host-->>workflow_npm1: per-object Otsu thresholds
workflow_npm1->>Device: _local_threshold_mask()
opt dot_cutoff is not None
workflow_npm1->>Spot: dot_slice_by_slice(smooth, dot_sigma)
Spot-->>workflow_npm1: bright spot response
end
opt hole_cutoff is not None
workflow_npm1->>Spot: dot_slice_by_slice(inverted, sigma)
Spot-->>workflow_npm1: dark spot response
end
workflow_npm1->>Device: remove_small_objects_aics()
workflow_npm1-->>Caller: boolean mask
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
On the 2018 confocal tiles FBL's low-level objects span whole cells, so each object's Otsu level separates nucleus from cytoplasm rather than nucleoli from nucleoplasm, and the mask covers ~50% of every nucleus even without the spot step. local_otsu_factor scales each object's Otsu level, as NPM1's 0.98 factor does; None (default) keeps the reference. With sigma 2, no spots and a factor of 2 (fibroblast) or 1.5 (PC3) the mask outlines the nucleolar clusters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011hEQ1TZJUbmhYKcC14RYHT
This branch has not been deployed
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.
Stacked on #63 (base
feat/mesh-gpu). It is independent of the mesh work apart from the shared SciPy-rounding Gaussian helper that #63 added; retarget once the stack merges.Summary
Ports two Allen Cell Structure Segmenter classic workflows to
cubic.segmentation, alongside SEC61B and TOMM20:workflow_fbl:aicssegmentation'sWorkflow_fbl, the fibrillarin / dense fibrillar component of the nucleolus.workflow_npm1:Workflow_npm1, the nucleophosmin / granular component.As with the existing workflows, NumPy input stays on the CPU, CuPy input stays on the GPU, and a 2D plane is segmented as a one-plane volume.
How parity is kept.
np.histogrambins them.NI_Correlate1Dsummation order for symmetric kernels. cupyx sums tap by tap, which rounds near-zero second-derivative values differently.Tunable parameters. Every workflow constant is a keyword-only argument: normalization bounds, smoothing, spot sigma and cutoff (
Noneskips the step), size limits, NPM1's hole filter and dilation, and a factor on each object's local Otsu level for both workflows (local_otsu_factor; FBL's defaults toNone, the reference behaviour). The defaults are the reference's values, so parity holds at the defaults. The classic workflows are meant to be re-tuned per dataset. On the 2018 confocal tiles (immunostained fibrillarin and EtBr):Settings that outline nucleoli on those tiles, judged on renderings across several physical samples:
smoothing_sigma=2, dot_cutoff=None, local_otsu_factor=2.0; NPM1 the same with factor 2.5;Evidence
Bitwise parity on real data. These are 3D confocal tiles from Kalinin et al. 2018, cast from uint16 to float32. The measurement script lives outside the repo.
aicssegmentationSpeed. The GPU takes 0.2–5.8 s per tile (A40), against 22–570 s for the CPU reference.
Two GPU bugs, found on PC3 and fixed. Both are now covered by regression tests.
minimum/maximumswitches to a sort that treats label 0 as background. The first object's histogram came out empty, so its threshold was 0 (41b9235). On one tile this flipped 375,291 voxels; with the fix it flips 0.Test plan
tests/segmentation/test_aics_workflows.py: 70 passed and 0 skipped, in the GPU environment withaicssegmentationanditkNotes
scipy.ndimage._filters._gaussian_kernel1d, which guarantees the same taps as SciPy. A future SciPy may move it; the import would then fail loudly, not silently drift.min_sizesemantics followsize_filter_inclusive, as in the existing workflows.🤖 Generated with Claude Code
https://claude.ai/code/session_011hEQ1TZJUbmhYKcC14RYHT
Summary by Sourcery
Add bitwise-compatible CPU and GPU FBL and NPM1 nucleolar segmentation workflows alongside the existing Allen Cell Structure Segmenter integrations.
New Features:
Bug Fixes:
Enhancements:
Documentation:
Tests: