Repository navigation
Add explicit layer argument for counterfactual/perturbation spatial… - #41
Merged
Merged
Conversation
… 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 Report❌ Patch coverage is
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. 🚀 New features to boost your workflow:
|
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.
… features (v1.1.2)
The training
spatial_xis built from a normalized representation (log1p of CP10K) andadata.Xis then reset to raw counts, because the model's NB likelihood needs counts.make_counterfactual_adata(precomputed=False)recomputed the counterfactual features withcompute_spatial_features(adata), which readsadata.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 everyprecomputed=Falsecounterfactual.make_neighbor_perturbationhad the same issue on its input side: it wrote a perturbed layer and aggregated it, but_make_perturbed_expressionalways readadata.Xas the source.Rather than hard-coding a log1p inside the model module (which would guess at the caller's normalization), this adds an explicit
layerargument:make_counterfactual_adata(..., layer=None)passeslayertocompute_spatial_featureswhenprecomputed=False, threaded throughCellina._make_counterfactual_adata,Cellina.get_counterfactual_latentsandCellina.get_counterfactual_expression.make_neighbor_perturbation,_make_perturbed_expressionandmake_perturbed_expressiontake an optional sourcelayerand readadata.layers[layer]instead ofadata.Xwhen given.compute_spatial_featureswarns whenlayer is Noneandadata.Xlooks 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.ipynbnow storesadata.layers['lognorm']before resetting X to counts and passeslayer='lognorm'to both perturbation sections.