Repository navigation
perf(samgeo3): 3.3x faster SamGeo3.save_masks (0.30 wall-time ratio) - #564
Conversation
Reuse masks that are already boolean instead of re-thresholding them, and only count mask pixels (with np.count_nonzero instead of np.sum) when a min_size/max_size filter is active. Iterate with enumerate instead of a manually maintained mask_index. Outputs, mask_scores order, dtype promotion, warnings and errors are unchanged. On a workload based on test_save_masks_promotes_dtype_so_scores_stay_aligned (300 boolean masks of shape (2, 300) plus edge cases), median wall time drops from 279.1 ms to 83.5 ms per sample (median of 3 runs, 9 interleaved pairs each). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: Aiden <aiden@weco.ai>
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Mask iteration and size filtering samgeo/samgeo3.py |
The loop uses enumeration to retain original mask indices for score lookup. When size filtering is active, it counts pixels in Boolean masks directly or thresholds other masks at > 0 before applying the existing minimum and maximum checks. |
Priority:
Estimated code review effort: 2 (Simple) | ~10 minutes
Change: Refactor
Suggested reviewers: giswqs
Merge Risk: 
Minimal · up to 95e77
-
The optimization preserves saved-mask values and size-filter decisions for supported inputs, with no merge-blocking risk identified.
Pre-merge checks: 
5
-
Full details: Docstring Coverage
-
Explanation
Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 1 functions across 1 files.
-
Finishing Touches
-
🧪 Generate unit tests (beta)
-
- Create a new PR
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
A rabbit checks each mask in line
And keeps its score in proper time
When size filters come into play
It counts the pixels on the way
Then hops along to save the day
Comment @coderabbitai help to get the list of available commands.
There was a problem hiding this comment.
Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.
Code reviewI reviewed the diff against the full Bugs: None found. Traced the two behavioral changes carefully:
Security: None applicable — purely numeric array manipulation on in-memory data, no new I/O, parsing, or external input handling. Confidence: high. Performance: The stated optimization is sound: avoiding the boolean copy for already-boolean masks and replacing Quality: Code is clean, comments accurately describe the new behavior, and no dead code or unused variables were introduced. Confidence: high. CLAUDE.md: No CLAUDE.md file exists in this repository, so no project-specific guidelines apply. No findings to flag; this is a correct, well-tested, narrowly-scoped optimization. |
TL;DR
SamGeo3.save_masksso it skips work it does not need: boolean masks are used directly, and pixels are counted withnp.count_nonzeroonly when a size filter is active.SamGeo3.save_masks(samgeo/samgeo3.py), on a workload based ontests/test_samgeo3.py::test_save_masks_promotes_dtype_so_scores_stay_aligned(300 boolean masks of shape (2, 300)) plus edge cases.pytest tests/test_samgeo3.py tests/test_api.py agent-harness/cli_anything/samgeo/tests/test_core.py agent-harness/cli_anything/samgeo/tests/test_full_e2e.pygives 100 passed, the same as onmain. The output of every benchmark case matched the unpatched code exactly.Problem
save_masksloops over every mask. On each pass it creates a new boolean array withmask_np > 0, even when the mask is already boolean. It also always computes the mask size withnp.sum(mask_bool). That size is only used for themin_size/max_sizefilter, which is off by default. In addition,np.sumon a boolean array is much slower thannp.count_nonzero. With hundreds of small masks, this per-mask overhead takes up most of the call's run time.Change
mask_npasmask_boolwhen its dtype is alreadybool. The array is only read, never written.min_size > 0ormax_sizeis set, and count them withnp.count_nonzero. Withmin_size <= 0and nomax_size, the old checks could never drop a mask, so the result is the same.enumerate(self.masks)instead of updatingmask_indexby hand on each branch.Nothing else changes: return values,
self.objects,self.mask_scores(including key order), dtype promotion and its warning, error messages and the arguments passed toarray_to_imageare all the same as before.Benchmark
Benchmark method:
SamGeo3.save_maskswithout running the model.common.array_to_imageis recorded instead of writing files to disk.uint8,int8andfloat32(the last one raises an error), mirroring the repo test.unique=False).Run-to-run noise between two unpatched copies (A/A) was within 2.5%.
Tests
pytest --import-mode=importlib tests/test_samgeo3.py tests/test_api.py agent-harness/cli_anything/samgeo/tests/test_core.py agent-harness/cli_anything/samgeo/tests/test_full_e2e.py: 100 passed (same as onmain).ruff(v0.16.7, as pinned in pre-commit) reports nothing new in the changed lines.test_save_masks_*tests already cover the dtype promotion and score alignment that this change touches.AI assistance
This change was found with AI tools. OpenAI Codex (gpt-6.1-sol) generated candidate edits, and the runs were tracked with Weco Observe. A Claude-based agent picked the smallest candidate, checked its output against the unpatched code, and ran the benchmarks and tests above. Only automated checks were run; no human has reviewed the change yet.
Weco share link: https://dashboard.weco.ai/share/7Q74cmqVKQDGESeD00ahLr4Q10JhVaVy
Summary by CodeRabbit