Replace scv.pl.scatter and scvelo paga with scanpy equivalents#1302
Replace scv.pl.scatter and scvelo paga with scanpy equivalents#1302Marius1311 wants to merge 19 commits intomainfrom
Conversation
|
@Marius1311, we cannot simply replace scvelo with scanpy for plotting macro- and terminal states. Highlighting them is one of the essential features of the function and the reason why we implemented them as such. |
- _lineage_drivers.py: scv.pl.scatter -> sc.pl.embedding (gene-on-embedding)
- _circular_projection.py: scv.pl.scatter -> sc.pl.embedding (custom basis)
- _aggregate_fate_probs.py: scvelo.plotting.paga -> sc.pl.paga for both
PAGA and PAGA_PIE modes; handle scatter_flag/basis by drawing embedding
separately; transitions_confidence only used when available
- _term_states_estimator.py:
- _plot_discrete: scv.pl.scatter -> sc.pl.embedding (categorical states)
- _plot_continuous EMBEDDING mode: color_gradients reimplemented as
alpha-blended matplotlib scatter; multi-panel uses temp obs columns
- _plot_continuous TIME mode: reimplemented as matplotlib scatter
- singleton perc -> vmin/vmax percentile conversion
- add_outline (group-specific) dropped (scanpy only supports boolean)
- dpi/perc/color_map cleaned from kwargs before sc.pl.embedding calls
…nsitions_confidence - Move _plot_time_scatter, _plot_color_gradients from inline in _term_states_estimator.py to pl/_utils.py with proper docstrings - Add _add_outline_to_groups helper to pl/_utils.py that replicates scVelo's group-specific add_outline (3-layer scatter: bg/gap/dot) - Wire up _add_outline_to_groups in _plot_discrete for same_plot mode - Drop all perc handling (backwards-breaking; users should use vmin/vmax directly) - Keep dpi pop (sc.pl.embedding doesn't accept it) - Document transitions_confidence in aggregate_fate_probabilities docstring (directed edges require scVelo's PAGA extension)
sc.pl.embedding saves to sc.settings.figdir with a basis prefix (e.g. "umap"), which doesn't match the test infrastructure expectations. Pop save/show from kwargs and handle them via CellRank's save_fig instead. - _plot_discrete: pop save/show, call save_fig + plt.show after outline - _plot_continuous: pop save/show, handle in each branch (time/embedding) - _plot_time_scatter, _plot_color_gradients: add save support via save_fig - show defaults to None (auto: show when save is None, matching scanpy convention) - Simplify _prepare_fname: stop stripping "scvelo_" prefix (no longer needed since CellRank saves without adding any prefix) - For 5 plot_projection tests that still use scVelo's save (which re-adds "scvelo_" prefix): strip prefix + add ".png" explicitly - Add default-groups = ["dev", "test"] to [tool.uv] so `uv sync` installs test deps (adjusttext, pytest, etc.) in the local venv
- Move figure settings (figdir, transparent) from test_plotting.py module-level to conftest.py pytest_sessionstart - Remove `import scvelo as scv` from test_plotting.py; set scv.settings.figdir via try/except in conftest instead - Drop `scvelo_` prefix from 27 test methods and ground truth files (keep prefix for 5 genuine scVelo projection tests) - Rename test_proj_scvelo_kwargs -> test_proj_legend_loc - Regenerate all ground truth images (removing scv.set_figure_params changes matplotlib defaults globally)
0d030f1 to
10705c7
Compare
|
This should be ready for your review @WeilerP, would be great if you could test some of the plotting functions on some real data if you have some suitable examples. |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #1302 +/- ##
==========================================
- Coverage 80.83% 80.77% -0.07%
==========================================
Files 53 53
Lines 8703 8799 +96
Branches 1490 1512 +22
==========================================
+ Hits 7035 7107 +72
- Misses 1090 1102 +12
- Partials 578 590 +12
🚀 New features to boost your workflow:
|
| axes = sc.pl.embedding( | ||
| self.adata, | ||
| basis=basis, | ||
| color=color + keys, | ||
| title=color + title, | ||
| add_outline=outline, | ||
| show=False, | ||
| return_fig=False, | ||
| **kwargs, | ||
| ) | ||
|
|
||
| # Draw group-specific outlines (scVelo's add_outline with group names) | ||
| if outline is not None: | ||
| coords = self.adata.obsm[f"X_{basis}"] | ||
| axes_list = [axes] if not isinstance(axes, list | np.ndarray) else list(np.ravel(axes)) | ||
| for ax, key in zip(axes_list[len(color):], keys): | ||
| if key in self.adata.obs: | ||
| _add_outline_to_groups( | ||
| ax, coords, outline, self.adata.obs[key], size=size, | ||
| ) |
There was a problem hiding this comment.
The states are plotted in the background, and all cells not assigned to a state on top and included in the legend as nan. Using the CellRank pseudotime protocol:
I suggest we try not using _add_outline_to_groups and call sc.pl.embedding first with the entire dataset, but no coloring, followed by calling sc.pl.embedding with the data subsetted to the states and plotting them with an outline.
|
We were discussing at scverse whether some of scvelo's plottung functionality should be implemented into scanpy. Would this be of interest and a good idea? |
It would definitively be of interest - we're currently re-implementing them here, based on scanpy + some custom tweaks. Maybe some velocity plotting could be useful in scanpy, given that there are now plenty of velocity approaches? |
|
We'll keep having on optional scVelo dependency just for the velocity plotting functions. |
The previous approach added "nan" as an explicit category and relied on a custom `_add_outline_to_groups` helper, but scanpy drew NaN cells on top of the state cells, making states invisible. Now: 1. Leave unassigned cells as actual NaN (drawn by scanpy as na_color). 2. Overlay state-assigned cells via a second `sc.pl.embedding` call with `add_outline=True`, so they always appear on top. 3. Remove the custom `_add_outline_to_groups` helper (no longer needed).
`_plot_outline` used simple linear size multipliers and the default "o" marker, producing oversized waypoint dots compared to the old scVelo scatter. Restore scVelo's quadratic outline-width formula and use `marker="."` so waypoints are correctly sized again. Closes #1303
|
Yes exactly, that's the motivation. CC @flying-sheep |
- Add `basis` as a named parameter to `plot_macrostates`, `plot_fate_probabilities`, `_plot_discrete`, and `_plot_continuous` (previously hidden in **kwargs, defaulting to "umap") - Set `propagate = False` on the cellrank logger to prevent messages from bubbling to the root logger (which caused duplicate output with a red background in Jupyter notebooks) - Fix stale docstring in `plot_fate_probabilities` referencing `scvelo.pl.scatter` instead of `scanpy.pl.embedding`
When plotting macrostates/terminal states in discrete mode, cells not belonging to any state (NaN) produced an "NA" entry in the legend. Set na_in_legend=False by default so only actual states appear.
5eb0e88 to
dc27682
Compare
- Respect legend_loc parameter in _plot_color_gradients (was hard-coded) - Support "right", "right margin", "on data", and matplotlib loc strings - Default point size to 120_000/n_obs (scanpy convention) instead of 1 - Preserve dpi kwarg for same_plot path (only pop for sc.pl.embedding)
1e064d7 to
8f9375f
Compare
Reimplement _plot_color_gradients using scvelo's technique: for each pair of lineages, create a diverging colormap (color_A → transparent → color_B). Uncertain cells become transparent, letting the grey background show through. Only cells whose top-2 lineages match each pair are drawn, so colors don't stack up and produce dark overlaps.
8f9375f to
64382c4
Compare
- circular_projection: widen figure when legend_loc contains "right" so the scanpy legend placed via bbox_to_anchor is not clipped - _plot_time_scatter: pop legend_loc from kwargs and pass it to ax.legend(), supporting "right", "right margin", "none", and any matplotlib loc string (was hard-coded to "best")
67622cb to
3ad5381
Compare
|
Hi @WeilerP, the issues you raised should be fixed now - could you take another look please? I checked using the getting started tutorial and things seemed to work there. There's a separate notebooks PR here: scverse/cellrank_notebooks#77 It's unfinished, only contains changes for the getting started tutorial. |
Rename local `colors` to `lin_colors` to avoid shadowing the `matplotlib.colors` module import, which caused an AttributeError when calling `colors.LinearSegmentedColormap`.
pyGAM triggers harmless RuntimeWarnings (divide by zero in log, invalid value in reduce/divide) for degenerate genes. Show each unique warning once instead of flooding notebook output.


Remove
scv.pl.scatterandscvelo.plotting.pagausage (scVelo removal PR 2)Part of the scVelo dependency removal effort — see plan in
.github/prompts/PLAN_scvelo_removal.md.Builds on PR #1301 (now merged).
Changes
_lineage_drivers.py—plot_lineage_drivers()scv.pl.scatter→sc.pl.embeddingfor gene-on-embedding scatterbasiskwarg (default"umap") extracted from**kwargs_circular_projection.py—circular_projection()scv.pl.scatter→sc.pl.embeddingfor custom-basis scattercolorbar=→colorbar_loc="right" | None_aggregate_fate_probs.py— PAGA and PAGA_PIE modesfrom scvelo.plotting import paga→sc.pl.pagacolors=→color=(node colors); scatter background drawn viasc.pl.embeddingwhenbasisis setnode_colors=→color=(pie chart dict);scatter_flag/basishandled separately;legend_locpopped (scanpy doesn't accept it)transitions_confidenceonly used when available inadata.uns["paga"]_term_states_estimator.py—_plot_discrete()and_plot_continuous()_plot_discrete:scv.pl.scatter→sc.pl.embedding; group-specificadd_outlinereimplemented via new_add_outline_to_groupshelper (three-layer scatter: background → gap → foreground)_plot_continuousEMBEDDING mode:color_gradientsreimplemented as alpha-blended matplotlib scatter (new_plot_color_gradientshelper)RandomKeys(scanpy requires column names, not raw arrays)percdropped;dpi,color_mapcleaned from kwargs beforesc.pl.embeddingcalls_plot_continuousTIME mode: reimplemented as multi-panel matplotlib scatter (new_plot_time_scatterhelper)New helpers (in
pl/_utils.py):_add_outline_to_groups— group-specific outline via double-scatter, replacing scVelo'sadd_outline=["Alpha", ...]_plot_color_gradients— alpha-blended lineage overlay, replacing scVelo'scolor_gradientsparameter_plot_time_scatter— fate probability vs pseudotime scatter panelsTests
import scvelo as scvfromtest_plotting.py; movedfigdirandtransparentsettings toconftest.py::pytest_sessionstartscvelo_prefix (5 genuine scVelo projection tests keep their names)scvelo>=0.3to thetestdependency group (needed for projection tests)from __future__ import annotationsfrom PR 1 filesBehavioral changes
perckwarg is no longer passed through; usevmin/vmaxinsteadtransitions_confidence) only shown when scVelo's PAGA was usedsave/showare handled by CellRank (popped from kwargs before calling scanpy), matching the pattern used elsewhere in the codebaseTest results