Skip to content

background_deviation_box discards the xpx.at result — silent no-op on immutable backends #963

Description

@mwcraig

Behavior

On the jax backend, background_deviation_box returns a flat array (every pixel = the global xp.std(data)) instead of per-box sigmas — silently wrong results, no error (verified empirically 2026-07-27). On numpy it works, but only by side effect.

Root cause

The xpx.at(...).set(...) result is never assigned:

ccdproc/ccdproc/core.py

Lines 1341 to 1346 in 736555d

barr = data * 0.0 + xp.std(data)
ylen, xlen = data.shape
for i in range(int(0.5 * bbox), xlen, bbox):
for j in range(int(0.5 * bbox), ylen, bbox):
x1, x2, y1, y2 = setbox(i, j, bbox, xlen, ylen)
xpx.at(barr)[y1:y2, x1:x2].set(float(sigma_func(data[y1:y2, x1:x2])))

On numpy, xpx.at takes its in-place fast path so the discarded return value doesn't matter; on immutable backends (jax) it returns a new array that is thrown away, leaving barr at its initialized value. This is the same discarded-update bug class fixed for flat_correct/ccdmask in #956, and (as far as I can find) the last live instance on main.

Note on #931

The "Suggested fix" in #931 cites this exact line as the correct-pattern exemplar to copy — please don't: the pattern is only correct with the result rebound. I'll drop a note on #931 pointing here.

Suggested fix

One-line rebind:

barr = xpx.at(barr)[y1:y2, x1:x2].set(float(sigma_func(data[y1:y2, x1:x2])))

plus a jax-lane regression test asserting per-box values differ from the global std (the numpy lane can't catch this — the in-place fast path masks it).

Follow-up to #909 / #956. This issue was written by Claude (via Claude Code); Matt reviewed and approved its content before filing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions