Generated banner for Scvelo
Generated logo for Scvelo

Scvelo

K-DenseCommunityCreated by Kuan-lin HuangDistributed by K-Dense

  • Skill
  • Workflow
  • Science and research
  • BSD-3-Clause
  • Low risk
  • Has scripts

From the author

Performs RNA velocity analysis with scVelo from spliced and unspliced single-cell RNA counts. Fits deterministic or dynamical models, examines gene phase portraits, builds velocity graphs, estimates relative latent time, and ranks velocity-associated genes. Use for directional trajectory hypotheses and kinetic-model diagnostics alongside Scanpy; velocity alone does not establish cell fate or causal drivers.
— the description by Kuan-lin Huang

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill provides a structured workflow for performing RNA velocity analysis using scVelo. It enables researchers to estimate expression derivatives from spliced and unspliced single-cell RNA counts, fitting deterministic or dynamical models to explore kinetic trajectories. The workflow includes data validation, preprocessing, model fitting, and visualization of phase portraits and latent time. It is designed to be used alongside Scanpy for analyzing directional cell state transitions, while emphasizing that velocity estimates are model-dependent and do not constitute proof of cell fate or causal drivers.

What it does

  • Fits deterministic and dynamical kinetic models to spliced/unspliced RNA counts.
  • Generates gene-level phase portraits and velocity graphs for trajectory analysis.
  • Estimates relative latent time and ranks velocity-associated genes.
  • Provides a validated environment configuration to handle specific library compatibility.
  • Includes diagnostic tools to assess model fit and velocity coherence.
  • Projects high-dimensional velocity estimates onto UMAP embeddings.

When to use it

  • When you need to generate directional trajectory hypotheses from single-cell RNA-seq data.
  • When analyzing kinetic models of gene expression using spliced and unspliced counts.
  • When you require gene-level phase portraits to inspect induction and repression dynamics.
  • When you need to estimate relative latent time within a cell population.

Advantages

  • Provides a standardized, tested workflow for complex scVelo analysis.
  • Includes explicit validation steps to ensure input data quality and model compatibility.
  • Supports both deterministic and dynamical modeling approaches.
  • Offers clear diagnostic outputs to help identify potential model misspecification.

Usage examples

“Run a dynamical RNA velocity analysis on my H5AD file using the clusters annotation.”
The script will validate the input, perform dynamical model fitting, compute latent time, and save the results including velocity-annotated H5AD files and plots.
“How do I identify the top genes driving the velocity in my dataset?”
The skill provides a method to rank velocity-associated genes based on group-specific correlations, which can then be inspected via phase portraits.

Install

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/k-dense-ai/scvelo/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip scvelo.zip -d .agents/skills

Documentation by Kuan-lin Huang

scVelo RNA Velocity

When to use

Use for kinetic analysis of aligned spliced/unspliced RNA counts, directional trajectory hypotheses, gene-level phase portraits, and dynamical latent time. Use the model reference for assumptions, transition probabilities, and an optional CellRank handoff.

RNA velocity estimates an expression derivative under a model. Smooth arrows are not observed cell movement, lineage tracing, causal drivers, or proof of future fate. The dynamical estimator is not automatically more accurate than a steady-state estimator on every dataset.

Tested environment and compatibility

The maintained workflow targets scVelo 0.3.4, Scanpy 1.12.4, AnnData 0.13.4, NumPy 2.5.3, pandas 2.3.3, SciPy 1.18.1, Matplotlib 3.11.2, and loompy 3.0.8 on Python 3.13. Use an isolated environment:

uv venv --python 3.13 .venv-velocity
uv pip install --python .venv-velocity/bin/python \
  scvelo==0.3.4 scanpy==1.12.4 anndata==0.13.4 numpy==2.5.3 \
  pandas==2.3.3 scipy==1.18.1 matplotlib==3.11.2 loompy==3.0.8

The packages above were resolved from cache and tested in an isolated uv environment; shell paths above use POSIX syntax. Installation requires network access unless packages are cached. No API key is required.

  • Deterministic and dynamical models: native synthetic fitting, graphs, relative times, plots and H5AD round trips are tested on this stack.

  • Stochastic model: scVelo 0.3.4's default GLS scalar assignment fails with NumPy 2. Scanpy 1.12 requires NumPy 2, so installing numpy<2 beside current Scanpy is not a solution. A separately validated legacy Scanpy/NumPy environment is required; that legacy stack was not executed in this review. The helper rejects this combination before modifying data, without changing the model.

  • pandas 3: scVelo 0.3.4 dynamical fitting and some plots use operations no longer supported by pandas 3. Keep the explicit pandas pin.

  • The upstream stable tutorial still contains removed scv.read and older preprocessing calls. Check the 0.3.4 source when a tutorial disagrees with the installed API.

Input contract

  1. Obtain spliced and unspliced counts from a velocity-aware quantifier. Keep quantifier/version, reference annotation, counting mode, sample IDs and cell barcode mapping. Upstream quantification is outside the bundled script.

  2. Supply cells by genes matrices in adata.layers['spliced'] and adata.layers['unspliced'], with identical cell/gene ordering and unique IDs. Counts must be finite and nonnegative; fractional count estimates are allowed. Never use logged, scaled, residualized, or batch-corrected values as counts. Numerical inspection alone cannot establish that data are raw.

  3. Inspect per-library/cluster coverage, doublets, ambient RNA, zero-count cells, and annotation compatibility before fitting. Preserve the original full-gene count file. Retained-gene backups do not preserve filtered-out genes.

  4. Resolve barcode prefixes and gene identifiers explicitly when combining files. Do not silently strip library IDs, intersect away most cells, or make duplicated biological IDs unique without understanding why they repeat.

Read H5AD through AnnData. scVelo 0.3.4 has no scv.read or scv.DataFrame:

import anndata as ad
adata = ad.read_h5ad("velocity_counts.h5ad")

For legacy loom input use ad.io.read_loom('counts.loom', X_name='spliced', sparse=True). AnnData 0.13 deprecates loom; convert to H5AD for further work. Its layers[None] aliases X: do not delete it or iterate all layers as if every key were a string. The helper accesses only named velocity layers. Native loom reading and exact, reordered metadata alignment are covered by the tests; AnnData 0.13 write_loom is not used.

Run the maintained workflow

The bundled script accepts local files and never downloads a demonstration dataset on startup:

MPLBACKEND=Agg .venv-velocity/bin/python scripts/rna_velocity_workflow.py \
  velocity_counts.h5ad --mode dynamical --groupby clusters \
  --n-top-genes 2000 --n-neighbors 30 --n-jobs 1 --output-dir velocity_results

# Import a loom and align its raw counts to an existing annotation file:
MPLBACKEND=Agg .venv-velocity/bin/python scripts/rna_velocity_workflow.py \
  counts.loom --processed-h5ad annotated.h5ad --groupby clusters \
  --mode deterministic --output-dir velocity_check

Run these from the skill directory. Input filenames and biological annotations are illustrative; the same CLI and functions are tested on small generated kinetic fixtures. Omit --groupby when annotations are unavailable. Use --no-plots for analysis without computing UMAP.

The helper performs these steps:

  1. Validate layers, IDs, model compatibility and grouping; refuse previously generated moments/velocity so preprocessing cannot silently run twice.

  2. Back up retained raw layers as spliced_counts and unspliced_counts and rebuild X from raw spliced counts. Filter genes and normalize X and the two count layers on a linear scale.

  3. Apply sc.pp.log1p only to X, select highly variable genes with Scanpy, and rebuild PCA and neighbors after subsetting. Reusing stale PCA/neighbors from an unrelated feature set can silently change the velocity model.

  4. Compute Ms and Mu with scv.pp.moments(adata, n_neighbors=None) from the explicit Scanpy graph. Moments are dense: budget memory for multiple cells-by-genes arrays, not only the sparse input.

  5. For dynamical mode, call recover_dynamics(var_names='all') on the selected genes before velocity(mode='dynamical'); otherwise fit the selected model. Require usable genes and a nonempty velocity graph.

  6. Compute velocity coherence and velocity pseudotime; add latent time only for dynamical fits. Rank velocity-associated genes only with at least two groups and at least two cells per group. Record/warn about nonfinite or constant time/coherence outputs in uns['velocity_workflow']['diagnostics']; a constant pseudotime does not support a trajectory ordering. Ranking is exploratory.

  7. For plots, recompute UMAP from the rebuilt graph, project velocities, save PNGs directly to the requested directory, then save H5AD with package versions and selected parameters. Existing labels remain annotations, not validated cell identities.

The Python function mutates its argument in place; pass adata.copy() to keep the original object. The CLI writes to output_dir/adata_velocity.h5ad. Version 2 changes the former helper's behavior deliberately: raw layer geometry is rebuilt, missing requested labels are errors, and plots/parallelism can be controlled explicitly. Runtime depends on cells, genes and fit difficulty; there is no universal 10–30 minute expectation.

Inspect the evidence before interpretation

After a dynamical run (dataset-specific gene selection is illustrative):

import pandas as pd
import scvelo as scv

# Candidate kinetic genes, not experimentally established drivers.
candidates = adata.var['fit_likelihood'].dropna().nlargest(6).index.tolist()
scv.pl.velocity(adata, var_names=candidates, basis='umap', show=False)

# This ranks group-associated velocities, not causal influence or condition DE.
scv.tl.rank_velocity_genes(adata, groupby='clusters', min_corr=0.3)
ranked = pd.DataFrame(adata.uns['rank_velocity_genes']['names'])

scv.tl.velocity_confidence(adata)
scv.pl.scatter(adata, color=['velocity_length', 'velocity_confidence'], show=False)

Inspect spliced-versus-unspliced phase portraits, coverage across induction and repression, fitted parameters, failed/NaN fits, and branch-specific kinetics. Check sensitivity to gene set, neighbors, subsampling and model assumptions. Use time-course labels, perturbations, lineage tracing or labeling experiments as independent directional evidence where available. A plausible UMAP alone cannot validate a fit, and tuning until arrows match a desired story is not a validation strategy.

There is no general minimum of 2,000 cells, universal unspliced fraction, or rule that root cells must have the highest unspliced/spliced ratio. Coverage of relevant kinetic states and measurement quality matter. Negative velocity can represent repression or model misspecification; it is not by itself evidence that layers were swapped.

Output interpretation

FieldInterpretation
layers['velocity']Model-estimated derivative in processed gene-expression space
var['velocity_genes']Genes selected for the velocity graph; distinct from all HVGs
layers['Ms'], layers['Mu']Neighbor-averaged linear-scale spliced/unspliced expression
layers['fit_t']Gene-specific fitted time coordinates, dynamical model only
var['fit_alpha/beta/gamma']Fitted rates on the model's inferred scale; not calibrated physical rates
var['fit_likelihood']Relative model-fit diagnostic; not a posterior probability of biological truth
uns['velocity_graph']Sparse positive cosine correlations for candidate transitions; not row-stochastic
obsm['velocity_umap']Projected vectors, only after embedding computation/plotting
obs['velocity_pseudotime']Graph-based relative ordering
obs['latent_time']Coupled dynamical ordering; normally scaled 0–1, not elapsed hours
obs['velocity_length']Processed-space vector magnitude; not physical cell speed
obs['velocity_confidence']Neighbor velocity coherence, not calibrated uncertainty

For fate probabilities use an explicitly normalized transition kernel and a validated terminal-state definition; see the optional CellRank example in the reference. PAGA requires optional igraph and compatible Scanpy internals; it is not part of the tested core workflow or a substitute for fate inference.

Troubleshooting

ProblemAction
Missing or mismatched layersRevisit quantification and explicit ID alignment; X cannot substitute for unspliced counts
Very few velocity genesInspect depth, state coverage and phase portraits before altering thresholds
Smooth but implausible arrowsCheck model assumptions, batch geometry and individual genes; compare independent evidence
Nonfinite fit or empty graphStop interpretation and investigate degenerate features/coverage
Excessive memory or fitting timeUse a justified gene set and n_jobs=1; account for dense moments and fit arrays
Stochastic failure on NumPy 2Use a separately validated compatible legacy stack or explicitly reconsider the model
pandas unique error in fit/plotUse the tested pandas 2.3.3 pin

Sources and verification scope

Reviewed 2026-10-01 against the released source, API, kinetic-model caveats, and AnnData loom reader. The main paper is Bergen et al., 2020. Synthetic execution tests verify software contracts and file/figure production; they do not establish biological accuracy, identifiability or dataset-specific parameter recovery. Optional CellRank/PAGA analyses remain source-reviewed, illustrative extensions. No authenticated remote service is used.

Files

scripts/rna_velocity_workflow.py

"""RNA velocity from raw, aligned spliced/unspliced layers (scVelo 0.3.4).

The workflow mutates AnnData in place; pass a copy to retain all input genes.
Run --help for a local-file CLI. No datasets are downloaded automatically.
"""
from __future__ import annotations

import argparse
from importlib.metadata import version
from pathlib import Path

import anndata as ad
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import scanpy as sc
import scvelo as scv
from packaging.version import Version
from scipy import sparse


def validate_layers(adata):
    """Reject missing, nonfinite, negative, empty or ambiguously indexed counts."""
    if not adata.obs_names.is_unique or not adata.var_names.is_unique:
        raise ValueError("Cell and gene identifiers must be unique before alignment.")
    if adata.n_obs < 5 or adata.n_vars < 3:
        raise ValueError("This workflow needs at least 5 cells and 3 genes to run.")
    for name in ("spliced", "unspliced"):
        if name not in adata.layers:
            raise ValueError(f"Missing '{name}' layer; supply raw quantifier counts.")
        matrix = adata.layers[name]
        values = matrix.data if sparse.issparse(matrix) else np.asarray(matrix)
        if not np.isfinite(values).all() or (values < 0).any():
            raise ValueError(f"'{name}' must contain finite nonnegative counts.")
        if not np.any(values > 0):
            raise ValueError(f"'{name}' contains no positive counts.")
    if np.any(np.asarray(adata.layers["spliced"].sum(axis=1)).ravel() == 0):
        raise ValueError("Remove cells with zero spliced library size before analysis.")


def _check_versions(mode, make_plots):
    """Reject confirmed upstream failures, without silently changing estimators."""
    if Version(scv.__version__) == Version("0.3.4"):
        if mode == "stochastic" and Version(np.__version__) >= Version("2"):
            raise RuntimeError(
                "scVelo 0.3.4 default stochastic GLS fails with NumPy >=2. "
                "Use a separately validated legacy NumPy<2/Scanpy<1.12 environment, "
                "or explicitly choose a different scientific model."
            )
        if (mode == "dynamical" or make_plots) and Version(pd.__version__) >= Version("3"):
            raise RuntimeError("scVelo 0.3.4 dynamical fitting/plots require pandas<3.")


def _save_plot(function, adata, destination, **kwargs):
    """Own figure paths directly; scVelo's figdir/save prefix is not a path API."""
    before = set(plt.get_fignums())
    try:
        function(adata, show=False, save=False, **kwargs)
        figure = plt.gcf()
        figure.savefig(destination, dpi=150, bbox_inches="tight")
    finally:
        for number in set(plt.get_fignums()) - before:
            plt.close(number)


def run_velocity_analysis(
    adata,
    groupby="leiden",
    n_top_genes=2000,
    n_neighbors=30,
    mode="dynamical",
    n_jobs=1,
    output_dir="velocity_results",
    *,
    min_shared_counts=20,
    n_pcs=30,
    random_state=0,
    make_plots=True,
    recover_max_iter=10,
):
    """Analyze raw velocity layers in place, rebuilding X, PCA and neighbors.

    Existing X (including integrated/scaled values) is replaced from spliced
    counts; existing UMAP is recomputed for plotted runs. Cluster annotations
    are preserved, but their biological validity is the caller's responsibility.
    Input layers must be raw counts (fractional count estimates are permitted),
    never logged/scaled/normalized. Numeric inspection cannot establish this
    provenance. Save the original full-gene input before running.
    """
    if mode not in {"deterministic", "stochastic", "dynamical"}:
        raise ValueError("mode must be deterministic, stochastic or dynamical")
    validate_layers(adata)
    if "velocity_workflow" in adata.uns or any(k in adata.layers for k in ("Ms", "Mu", "velocity")):
        raise ValueError("Use fresh raw layers; existing velocity preprocessing must not be repeated.")
    for name, value, minimum in (("n_top_genes", n_top_genes, 3), ("n_neighbors", n_neighbors, 2),
                                 ("n_pcs", n_pcs, 1), ("n_jobs", n_jobs, 1),
                                 ("recover_max_iter", recover_max_iter, 1)):
        if not isinstance(value, int) or isinstance(value, bool) or value < minimum:
            raise ValueError(f"{name} must be an integer >= {minimum}")
    if not np.isfinite(min_shared_counts) or min_shared_counts < 0:
        raise ValueError("min_shared_counts must be finite and nonnegative")
    _check_versions(mode, make_plots)
    if groupby is not None and groupby not in adata.obs:
        raise ValueError(f"Grouping column {groupby!r} is missing; use groupby=None to omit ranking.")
    if groupby is not None and adata.obs[groupby].isna().any():
        raise ValueError("Grouping labels must not be missing.")

    # Rebuild expression geometry from explicit count provenance. Store raw
    # layers before normalization; filtered-out genes remain in the input file.
    adata.X = adata.layers["spliced"].astype(np.float32).copy()
    for key in ("spliced", "unspliced"):
        adata.layers[f"{key}_counts"] = adata.layers[key].copy()
        adata.layers[key] = adata.layers[key].astype(np.float32).copy()
    for key in ("log1p", "neighbors", "pca"):
        adata.uns.pop(key, None)
    for key in ("initial_size", "initial_size_spliced", "initial_size_unspliced"):
        if key in adata.obs:
            del adata.obs[key]
    print("[1/5] Filter counts, normalize layers, select genes and rebuild neighbors")
    scv.pp.filter_and_normalize(adata, min_shared_counts=min_shared_counts,
                                layers_normalize=["spliced", "unspliced"])
    if adata.n_vars < 3:
        raise ValueError("Fewer than 3 genes survived count filtering; inspect coverage and thresholds.")
    sc.pp.log1p(adata)  # only X; spliced/unspliced stay on linear scale
    sc.pp.highly_variable_genes(adata, n_top_genes=min(n_top_genes, adata.n_vars), subset=True)
    if adata.n_vars < 3:
        raise ValueError("Fewer than 3 variable genes remain; inspect the expression distribution.")
    actual_pcs = min(n_pcs, adata.n_obs - 1, adata.n_vars - 1)
    actual_neighbors = min(n_neighbors, adata.n_obs - 1)
    sc.pp.pca(adata, n_comps=actual_pcs, random_state=random_state)
    sc.pp.neighbors(adata, n_neighbors=actual_neighbors, n_pcs=actual_pcs,
                    use_rep="X_pca", random_state=random_state)
    scv.pp.moments(adata, n_neighbors=None)  # consume the explicit Scanpy graph

    print(f"[2/5] Fit {mode} model")
    if mode == "dynamical":
        scv.tl.recover_dynamics(adata, var_names="all", n_jobs=n_jobs,
                                max_iter=recover_max_iter, show_progress_bar=False)
        if not np.isfinite(adata.var["fit_likelihood"]).any():
            raise ValueError("No finite dynamical fits; inspect phase portraits and input coverage.")
    scv.tl.velocity(adata, mode=mode)
    selected = adata.var["velocity_genes"].to_numpy(dtype=bool)
    if selected.sum() < 2 or not np.isfinite(adata.layers["velocity"][:, selected]).all():
        raise ValueError("Fewer than 2 usable velocity genes, or nonfinite fitted velocities.")
    scv.tl.velocity_graph(adata, n_jobs=n_jobs, show_progress_bar=False)
    if not adata.uns["velocity_graph"].nnz:
        raise ValueError("Velocity graph is empty; inspect fitted genes and neighborhood geometry.")

    print("[3/5] Compute coherence and relative time")
    scv.tl.velocity_confidence(adata)
    scv.tl.velocity_pseudotime(adata)
    if mode == "dynamical":
        scv.tl.latent_time(adata)
    diagnostics = {}
    for key in ("velocity_confidence", "velocity_pseudotime", "latent_time"):
        if key not in adata.obs:
            continue
        values = adata.obs[key].to_numpy()
        finite = values[np.isfinite(values)]
        diagnostics[f"{key}_nonfinite"] = int(len(values) - len(finite))
        diagnostics[f"{key}_constant"] = bool(len(finite) == 0 or np.ptp(finite) == 0)
        if len(finite) != len(values) or diagnostics[f"{key}_constant"]:
            print(f"[WARN] {key} is nonfinite or constant; it does not support an ordering/confidence claim.")
    if groupby is not None:
        adata.obs[groupby] = adata.obs[groupby].astype("category")
        sizes = adata.obs[groupby].value_counts()
        if len(sizes) >= 2 and (sizes >= 2).all():
            scv.tl.rank_velocity_genes(adata, groupby=groupby, min_corr=0.3)

    destination = Path(output_dir)
    destination.mkdir(parents=True, exist_ok=True)
    print("[4/5] Render figures" if make_plots else "[4/5] Figures disabled")
    if make_plots:
        sc.tl.umap(adata, random_state=random_state)
        _save_plot(scv.pl.velocity_embedding_stream, adata, destination / "velocity_stream.png",
                   basis="umap", color=groupby, title="RNA velocity (projected)")
        _save_plot(scv.pl.velocity_embedding, adata, destination / "velocity_arrows.png",
                   basis="umap", color=groupby, arrow_length=3, arrow_size=2)
        _save_plot(scv.pl.scatter, adata, destination / "pseudotime.png", basis="umap",
                   color="velocity_pseudotime", cmap="gnuplot")
        _save_plot(scv.pl.scatter, adata, destination / "velocity_quality.png", basis="umap",
                   color=["velocity_length", "velocity_confidence"], cmap="coolwarm", perc=[5, 95])
        if mode == "dynamical":
            _save_plot(scv.pl.scatter, adata, destination / "latent_time.png", basis="umap",
                       color="latent_time", cmap="gnuplot")
            genes = adata.var["fit_likelihood"].dropna().nlargest(min(50, adata.n_vars)).index
            _save_plot(scv.pl.heatmap, adata, destination / "dynamical_gene_heatmap.png",
                       var_names=genes, sortby="latent_time", col_color=groupby,
                       n_convolve=min(30, adata.n_obs))

    adata.uns["velocity_workflow"] = {
        "mode": mode, "n_neighbors": actual_neighbors, "n_pcs": actual_pcs,
        "random_state": random_state, "input_layers": "raw spliced and unspliced counts",
        "diagnostics": diagnostics,
        "packages": {name: version(name) for name in ("scvelo", "scanpy", "anndata", "numpy", "pandas")},
    }
    print("[5/5] Save annotated data")
    adata.write_h5ad(destination / "adata_velocity.h5ad")
    print(f"[OK] Cells: {adata.n_obs}; selected genes: {adata.n_vars}; velocity genes: {selected.sum()}")
    print("Velocity coherence is a descriptive score, not calibrated certainty.")
    return adata


def load_from_loom(loom_path, processed_h5ad=None):
    """Read local loom counts and optionally align to processed metadata exactly.

    No barcode rewriting or silent intersections. Resolve library prefixes and
    gene IDs explicitly before combining mismatched files. Extra loom cells and
    genes are permitted, but every processed cell/gene must have a match.
    """
    counts = ad.io.read_loom(loom_path, X_name="spliced", sparse=True)
    validate_layers(counts)
    if processed_h5ad is None:
        return counts
    processed = ad.read_h5ad(processed_h5ad)
    if not processed.obs_names.is_unique or not processed.var_names.is_unique:
        raise ValueError("Processed cell and gene identifiers must be unique.")
    if not processed.obs_names.isin(counts.obs_names).all() or not processed.var_names.isin(counts.var_names).all():
        raise ValueError("Processed IDs do not all match loom IDs; align barcodes and gene identifiers explicitly.")
    aligned = counts[processed.obs_names, processed.var_names]
    for key in ("spliced", "unspliced"):
        processed.layers[key] = aligned.layers[key].copy()
    validate_layers(processed)
    return processed


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("input", help="Local .h5ad or .loom with raw spliced/unspliced layers")
    parser.add_argument("--processed-h5ad", help="Metadata/embedding file to align with loom counts")
    parser.add_argument("--groupby", default=None, help="Optional annotation column for coloring/ranking")
    parser.add_argument("--mode", choices=("deterministic", "stochastic", "dynamical"), default="dynamical")
    parser.add_argument("--n-top-genes", type=int, default=2000)
    parser.add_argument("--n-neighbors", type=int, default=30)
    parser.add_argument("--n-jobs", type=int, default=1)
    parser.add_argument("--output-dir", default="velocity_results")
    parser.add_argument("--no-plots", action="store_true")
    args = parser.parse_args()
    if Path(args.input).suffix.lower() == ".loom":
        data = load_from_loom(args.input, args.processed_h5ad)
    else:
        if args.processed_h5ad:
            parser.error("--processed-h5ad is only used with .loom input")
        data = ad.read_h5ad(args.input)
    run_velocity_analysis(data, groupby=args.groupby, n_top_genes=args.n_top_genes,
                          n_neighbors=args.n_neighbors, mode=args.mode, n_jobs=args.n_jobs,
                          output_dir=args.output_dir, make_plots=not args.no_plots)


if __name__ == "__main__":
    main()

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs an API key, Needs Python

The author says: Requires Python 3.13 for the tested stack with scvelo 0.3.4, scanpy 1.12.4, anndata 0.13.4, numpy 2.5.3 and pandas 2.3.3. Loom import requires loompy. Local H5AD analysis needs no network or credentials. The default stochastic solver is incompatible with NumPy 2.

Security

What our scanner noticed· Low risk

  • Instructions that use the network
  • Scripts that your agent may run

This skill includes scripts that your agent may run. Read them before you use it.

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
Kuan-lin Huang
Publisher
K-Dense (Community)
Distributed by
K-Dense
Source
K-Dense-AI/scientific-agent-skills · skills/scvelo · commit a25cf9e
Licence
BSD 3-Clause License (redistribution allowed)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Kuan-lin Huang or K-Dense.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download scvelo.zip