Skip to content

Add explicit layer argument for counterfactual/perturbation spatial… - #41

Merged
dbdimitrov merged 1 commit into
mainfrom
spatial-x-lognorm
Oct 1, 2026
Merged

dbdimitrov merged 1 commit into
mainfrom
spatial-x-lognorm

Conversation

@dbdimitrov

Copy link
Copy Markdown
Collaborator

… features (v1.1.2)

The training spatial_x is built from a normalized representation (log1p of CP10K) and adata.X is then reset to raw counts, because the model's NB likelihood needs counts. make_counterfactual_adata(precomputed=False) recomputed the counterfactual features with compute_spatial_features(adata), which reads adata.X — so edge-perturbation counterfactuals aggregated raw counts while the model had only ever seen log1p(CP10K) neighbourhood means. That train/inference mismatch silently degraded every precomputed=False counterfactual. make_neighbor_perturbation had the same issue on its input side: it wrote a perturbed layer and aggregated it, but _make_perturbed_expression always read adata.X as the source.

Rather than hard-coding a log1p inside the model module (which would guess at the caller's normalization), this adds an explicit layer argument:

  • make_counterfactual_adata(..., layer=None) passes layer to compute_spatial_features when precomputed=False, threaded through Cellina._make_counterfactual_adata, Cellina.get_counterfactual_latents and Cellina.get_counterfactual_expression.
  • make_neighbor_perturbation, _make_perturbed_expression and make_perturbed_expression take an optional source layer and read adata.layers[layer] instead of adata.X when given.
  • compute_spatial_features warns when layer is None and adata.X looks like raw counts (sampled non-zero entries integer-valued with max > 50).

All new arguments default to None, so existing calls keep their behaviour. docs/tutorial.ipynb now stores adata.layers['lognorm'] before resetting X to counts and passes layer='lognorm' to both perturbation sections.

… features (v1.1.2)

The training `spatial_x` is built from a normalized representation (log1p of
CP10K) and `adata.X` is then reset to raw counts, because the model's NB
likelihood needs counts. `make_counterfactual_adata(precomputed=False)`
recomputed the counterfactual features with `compute_spatial_features(adata)`,
which reads `adata.X` — so edge-perturbation counterfactuals aggregated raw
counts while the model had only ever seen log1p(CP10K) neighbourhood means.
That train/inference mismatch silently degraded every `precomputed=False`
counterfactual. `make_neighbor_perturbation` had the same issue on its input
side: it wrote a perturbed layer and aggregated it, but `_make_perturbed_expression`
always read `adata.X` as the source.

Rather than hard-coding a log1p inside the model module (which would guess at
the caller's normalization), this adds an explicit `layer` argument:

- `make_counterfactual_adata(..., layer=None)` passes `layer` to
  `compute_spatial_features` when `precomputed=False`, threaded through
  `Cellina._make_counterfactual_adata`, `Cellina.get_counterfactual_latents`
  and `Cellina.get_counterfactual_expression`.
- `make_neighbor_perturbation`, `_make_perturbed_expression` and
  `make_perturbed_expression` take an optional source `layer` and read
  `adata.layers[layer]` instead of `adata.X` when given.
- `compute_spatial_features` warns when `layer is None` and `adata.X` looks
  like raw counts (sampled non-zero entries integer-valued with max > 50).

All new arguments default to `None`, so existing calls keep their behaviour.
`docs/tutorial.ipynb` now stores `adata.layers['lognorm']` before resetting X
to counts and passes `layer='lognorm'` to both perturbation sections.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.71429% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.06%. Comparing base (c77b614) to head (fc85cbc).

Files with missing lines Patch % Lines
src/cellina/_spatial_utils.py 85.71% 4 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #41      +/-   ##
==========================================
- Coverage   90.10%   90.06%   -0.04%     
==========================================
  Files          10       10              
  Lines        1394     1419      +25     
==========================================
+ Hits         1256     1278      +22     
- Misses        138      141       +3     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@dbdimitrov
dbdimitrov merged commit 781e408 into main Oct 1, 2026
1 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant