From 560013473afb8ed6da64369ea179ff5e9ab0e575 Mon Sep 17 00:00:00 2001 From: Sricharan Reddy Varra Date: Thu, 3 Oct 2024 11:36:01 -0700 Subject: [PATCH] =?UTF-8?q?=F0=9F=A7=90=20Initial=20Scores=20for=20NBL=20C?= =?UTF-8?q?ells?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Initial scoring --- .gitignore | 2 + cliff.toml | 78 ++ .../00 - Clean Clinical Data.ipynb | 18 +- .../01 - Add SpatialData.ipynb | 106 +-- .../00 - NBL Cell Clustering.ipynb | 26 +- .../Scoring/00 - Genearte Scores.ipynb | 758 ++++++++++++++++++ .../Scoring/01 - Analyze Scores.ipynb | 30 + pyproject.toml | 20 +- requirements-dev.lock | 413 +++++----- requirements.lock | 413 +++++----- src/nbl/pl/basic.py | 3 + src/nbl/pp/__init__.py | 4 +- src/nbl/pp/_utils.py | 0 src/nbl/pp/basic.py | 178 +++- src/nbl/tl/__init__.py | 26 +- src/nbl/tl/_utils.py | 90 ++- src/nbl/tl/basic.py | 408 ++++++++-- src/nbl/util/__init__.py | 3 +- src/nbl/util/utils.py | 62 +- 19 files changed, 2023 insertions(+), 615 deletions(-) create mode 100644 cliff.toml create mode 100644 docs/notebooks/01 - NBL Cell Analysis/Scoring/00 - Genearte Scores.ipynb create mode 100644 docs/notebooks/01 - NBL Cell Analysis/Scoring/01 - Analyze Scores.ipynb create mode 100644 src/nbl/pp/_utils.py diff --git a/.gitignore b/.gitignore index eeec7e2..72f8598 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,5 @@ __pycache__/ # IDEs /.idea/ /.vscode/ + +/docs/notebooks/00 - Initialize LaminDB/00 - Clean Clinical Data.ipynb diff --git a/cliff.toml b/cliff.toml new file mode 100644 index 0000000..abbedc1 --- /dev/null +++ b/cliff.toml @@ -0,0 +1,78 @@ +# git-cliff ~ configuration file +# https://git-cliff.org/docs/configuration + +[changelog] +# template for the changelog footer +header = """ +# Changelog\n +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n +""" +# template for the changelog body +# https://keats.github.io/tera/docs/#introduction +body = """ +{% if version -%} + ## [{{ version | trim_start_matches(pat="v") }}] - {{ timestamp | date(format="%Y-%m-%d") }} +{% else -%} + ## [Unreleased] +{% endif -%} +{% for group, commits in commits | group_by(attribute="group") %} + ### {{ group | upper_first }} + {% for commit in commits %} + - {{ commit.message | upper_first }}\ + {% endfor %} +{% endfor %}\n +""" +# template for the changelog footer +footer = """ +{% for release in releases -%} + {% if release.version -%} + {% if release.previous.version -%} + [{{ release.version | trim_start_matches(pat="v") }}]: \ + https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}\ + /compare/{{ release.previous.version }}..{{ release.version }} + {% endif -%} + {% else -%} + [unreleased]: https://github.com/{{ remote.github.owner }}/{{ remote.github.repo }}\ + /compare/{{ release.previous.version }}..HEAD + {% endif -%} +{% endfor %} + +""" +# remove the leading and trailing whitespace from the templates +trim = true + +[git] +# parse the commits based on https://www.conventionalcommits.org +conventional_commits = true +# filter out the commits that are not conventional +filter_unconventional = true +# process each line of a commit as an individual commit +split_commits = false +# regex for parsing and grouping commits +commit_parsers = [ + { message = "^.*: add", group = "Added" }, + { message = "^.*: support", group = "Added" }, + { message = "^.*: remove", group = "Removed" }, + { message = "^.*: delete", group = "Removed" }, + { message = "^test", group = "Fixed" }, + { message = "^fix", group = "Fixed" }, + { message = "^.*: fix", group = "Fixed" }, + { message = "^.*", group = "Changed" }, +] +# protect breaking changes from being skipped due to matching a skipping commit_parser +protect_breaking_commits = false +# filter out the commits that are not matched by commit parsers +filter_commits = true +# regex for matching git tags +tag_pattern = "v[0-9].*" +# regex for skipping tags +skip_tags = "v0.1.0-beta.1" +# regex for ignoring tags +ignore_tags = "" +# sort the tags topologically +topo_order = false +# sort the commits inside sections by oldest/newest order +sort_commits = "oldest" diff --git a/docs/notebooks/00 - Initialize LaminDB/00 - Clean Clinical Data.ipynb b/docs/notebooks/00 - Initialize LaminDB/00 - Clean Clinical Data.ipynb index 1165439..10a53da 100644 --- a/docs/notebooks/00 - Initialize LaminDB/00 - Clean Clinical Data.ipynb +++ b/docs/notebooks/00 - Initialize LaminDB/00 - Clean Clinical Data.ipynb @@ -52,7 +52,8 @@ "import natsort as ns\n", "import bionty as bt\n", "import lamindb as ln\n", - "from lnschema_core.models import Registry" + "from lnschema_core.models import Registry\n", + "import buckaroo # noqa: F401" ] }, { @@ -72,11 +73,10 @@ "metadata": {}, "outputs": [], "source": [ - "ln.settings.transform.stem_uid = \"4DLIySb5QY32\"\n", - "ln.settings.transform.version = \"1\"\n", + "ln.context.uid = \"XjYRETQ3dpPB0000\"\n", + "ln.context.version = \"1\"\n", "ln.settings.sync_git_repo = \"https://github.com/karadavis-lab/nbl.git\"\n", - "run = ln.track()\n", - "run.transform" + "ln.context.track()" ] }, { @@ -780,7 +780,6 @@ "clinical_artifact = ln.Artifact.from_df(\n", " df=clinical_data,\n", " key=\"clinical_data.parquet\",\n", - " run=run,\n", " description=\"Contains sample level clinical data\",\n", " version=\"1\",\n", ")\n", @@ -916,13 +915,6 @@ "source": [ "ln.finish()" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/docs/notebooks/00 - Initialize LaminDB/01 - Add SpatialData.ipynb b/docs/notebooks/00 - Initialize LaminDB/01 - Add SpatialData.ipynb index 5c07a79..4731777 100644 --- a/docs/notebooks/00 - Initialize LaminDB/01 - Add SpatialData.ipynb +++ b/docs/notebooks/00 - Initialize LaminDB/01 - Add SpatialData.ipynb @@ -52,8 +52,7 @@ "import natsort as ns\n", "import lamindb as ln\n", "from nbl.util import DaskLocalCluster, reset_table_index\n", - "import nbl\n", - "import spatialdata as sd" + "import nbl" ] }, { @@ -73,11 +72,11 @@ "metadata": {}, "outputs": [], "source": [ - "ln.settings.transform.stem_uid = \"sDPLFLgnLcbi\"\n", - "ln.settings.transform.version = \"1\"\n", + "ln.context.uid = \"FGEcC5bGULbo0000\"\n", + "ln.context.version = \"1\"\n", "ln.settings.sync_git_repo = \"https://github.com/karadavis-lab/nbl.git\"\n", - "run = ln.track()\n", - "run.transform" + "\n", + "ln.context.track()" ] }, { @@ -154,16 +153,9 @@ "metadata": {}, "outputs": [], "source": [ - "nbl.io.convert_cohort(fov_dir=fov_dir, label_dir=label_dir, filter_fovs=r\"Hu-*\", file_path=hu_data_path)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "hu_sdata = sd.read_zarr(store=hu_data_path)" + "hu_sdata = nbl.io.convert_cohort(\n", + " fov_dir=fov_dir, label_dir=label_dir, filter_fovs=r\"Hu-*\", file_path=hu_data_path, return_sdata=True\n", + ")" ] }, { @@ -231,16 +223,9 @@ "metadata": {}, "outputs": [], "source": [ - "nbl.io.convert_cohort(fov_dir=fov_dir, filter_fovs=r\"NBL-\\d+-R\\d+C\\d+\", label_dir=label_dir, file_path=nbl_data_path)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "nbl_sdata = sd.read_zarr(store=nbl_data_path)" + "nbl_sdata = nbl.io.convert_cohort(\n", + " fov_dir=fov_dir, filter_fovs=r\"NBL-\\d+-R\\d+C\\d+\", label_dir=label_dir, file_path=nbl_data_path, return_sdata=True\n", + ")" ] }, { @@ -299,7 +284,7 @@ "metadata": {}, "outputs": [], "source": [ - "pixie_clusters_path = (\n", + "pixie_clusters_path: UPath = (\n", " original_data_path / \"segmentation\" / \"cell_table\" / \"cell_table_size_normalized_cell_labels_noCD117.csv\"\n", ")" ] @@ -385,26 +370,26 @@ ] }, { - "cell_type": "code", - "execution_count": null, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "nbl_sdata.tables[\"whole_cell\"].obs" + "## Add Clinical Information" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Add Clinical Information" + "### Load Clinical Data from LaminDB" ] }, { - "cell_type": "markdown", + "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "### Load Clinical Data from LaminDB" + "clinical_data: pd.DataFrame = ln.Artifact.filter(key__contains=\"clinical_data\").one().load()" ] }, { @@ -413,7 +398,7 @@ "metadata": {}, "outputs": [], "source": [ - "clinical_data: pd.DataFrame = ln.Artifact.filter(key__contains=\"clinical_data\").one().load()" + "clinical_data" ] }, { @@ -422,7 +407,14 @@ "metadata": {}, "outputs": [], "source": [ - "cols_to_drop = [\"Clinical presentation\", \"treatment btw biopsies\"]" + "cols_to_keep = [\n", + " \"fov\",\n", + " \"Risk\",\n", + " \"Classification\",\n", + " \"Sex\",\n", + " \"Ethnicity\",\n", + " \"Tissue\",\n", + "]" ] }, { @@ -431,7 +423,7 @@ "metadata": {}, "outputs": [], "source": [ - "filtered_clincial_data = clinical_data.drop(columns=cols_to_drop)" + "filtered_clincial_data = clinical_data.filter(items=cols_to_keep)" ] }, { @@ -446,6 +438,15 @@ "nbl_sdata.tables[\"whole_cell\"].strings_to_categoricals()" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nbl.util.write_elements(sdata=nbl_sdata, elements={\"tables\": [\"whole_cell\"]})" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -460,7 +461,13 @@ "outputs": [], "source": [ "nbl.pp.arcsinh_transform(\n", - " sdata=nbl_sdata, table_names=\"whole_cell\", shift_factor=0, scale_factor=150, replace_X=True, write=True\n", + " sdata=nbl_sdata,\n", + " table_names=\"whole_cell\",\n", + " shift_factor=0,\n", + " scale_factor=150,\n", + " method=\"new table\",\n", + " write=True,\n", + " inplace=True,\n", ")" ] }, @@ -470,7 +477,13 @@ "metadata": {}, "outputs": [], "source": [ - "hu_artifact = ln.Artifact(data=hu_data_path, type=\"dataset\", key=\"Hu.zarr\", description=\"Control Tissue\")\n", + "hu_artifact = ln.Artifact(\n", + " data=hu_data_path,\n", + " type=\"dataset\",\n", + " key=\"Hu.zarr\",\n", + " description=\"Control Tissue\",\n", + " revises=ln.Artifact.filter(key__contains=\"Hu.zarr\").one(),\n", + ")\n", "\n", "hu_artifact.save(upload=True)" ] @@ -481,7 +494,13 @@ "metadata": {}, "outputs": [], "source": [ - "nbl_artifact = ln.Artifact(data=nbl_data_path, type=\"dataset\", key=\"nbl.zarr\", description=\"NBL Tissue Samples\")\n", + "nbl_artifact = ln.Artifact(\n", + " data=nbl_data_path,\n", + " type=\"dataset\",\n", + " key=\"nbl.zarr\",\n", + " description=\"NBL Tissue Samples\",\n", + " revises=ln.Artifact.filter(key__contains=\"nbl.zarr\").one(),\n", + ")\n", "\n", "nbl_artifact.save(upload=True)" ] @@ -494,13 +513,6 @@ "source": [ "ln.finish()" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/docs/notebooks/01 - NBL Cell Analysis/00 - NBL Cell Clustering.ipynb b/docs/notebooks/01 - NBL Cell Analysis/00 - NBL Cell Clustering.ipynb index 46a7cd3..9feb341 100644 --- a/docs/notebooks/01 - NBL Cell Analysis/00 - NBL Cell Clustering.ipynb +++ b/docs/notebooks/01 - NBL Cell Analysis/00 - NBL Cell Clustering.ipynb @@ -65,11 +65,11 @@ "metadata": {}, "outputs": [], "source": [ - "ln.settings.transform.stem_uid = \"mqEhM9LGLNtM\"\n", - "ln.settings.transform.version = \"1\"\n", - "ln.settings.sync_git_repo = \"https://github.com/karadavis-lab/nbl.git\"\n", - "run = ln.track()\n", - "run.transform" + "# ln.context.uid = \"mqEhM9LGLNtM\"\n", + "# ln.settings.transform.version = \"1\"\n", + "# ln.settings.sync_git_repo = \"https://github.com/karadavis-lab/nbl.git\"\n", + "# ln.track()\n", + "# run.transform" ] }, { @@ -181,7 +181,7 @@ " sum(nbl_wc_NBL.obs[f\"{marker}_95\"]) + sum(nbl_wc_NBL.obs[f\"{marker}_99\"])\n", " )\n", " n__95 = sum(nbl_wc_NBL.obs[f\"{marker}_95\"]) - sum(nbl_wc_NBL.obs[f\"{marker}_99\"])\n", - " n__99 = sum(nbl_wc_NBL.obs[f\"{marker}_99\"])\n", + " n__99: int = sum(nbl_wc_NBL.obs[f\"{marker}_99\"])\n", "\n", " n_nbl_cells_rest = nbl_wc_NBL.n_obs - n__90 - n__95 - n__99\n", "\n", @@ -310,13 +310,6 @@ "print(nbl_wc_NBL.obs[\"Group\"].value_counts())" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, { "cell_type": "code", "execution_count": null, @@ -337,13 +330,6 @@ "source": [ "ln.finish()" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/docs/notebooks/01 - NBL Cell Analysis/Scoring/00 - Genearte Scores.ipynb b/docs/notebooks/01 - NBL Cell Analysis/Scoring/00 - Genearte Scores.ipynb new file mode 100644 index 0000000..dc0a64c --- /dev/null +++ b/docs/notebooks/01 - NBL Cell Analysis/Scoring/00 - Genearte Scores.ipynb @@ -0,0 +1,758 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Generate Scores" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Notebook Preferences" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2\n", + "%config InlineBackend.figure_format=\"retina\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Libraries" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import buckaroo # noqa: F401\n", + "import lamindb as ln\n", + "import spatialdata as sd\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "import nbl\n", + "import anndata as ad\n", + "import scanpy as sc\n", + "import seaborn.objects as so\n", + "import itertools\n", + "from matplotlib.figure import Figure\n", + "from upath import UPath" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pd.set_option(\"future.no_silent_downcasting\", True)\n", + "pd.set_option(\"future.infer_string\", False)\n", + "pd.set_option(\"mode.copy_on_write\", False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from seaborn import axes_style\n", + "\n", + "theme_dict = {**axes_style(\"whitegrid\"), \"grid.linestyle\": \":\", \"axes.facecolor\": \"w\", \"axes.edgecolor\": \"slategray\"}\n", + "so.Plot.config.theme.update(theme_dict)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# ln.context.uid = \"c9lSn4d8xU4f0000\"\n", + "# ln.context.version = \"1\"\n", + "ln.settings.sync_git_repo = \"https://github.com/karadavis-lab/nbl.git\"\n", + "ln.track(uid=\"a5bTVIqiPhn00000\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ln.setup.settings.instance" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Load NBL Sdata" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nbl_sdata = sd.read_zarr(ln.Artifact.filter(key__contains=\"nbl.zarr\", is_latest=True).one().path)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nbl_wc = nbl_sdata.tables[\"arcsinh_shift_0_scale_150\"]\n", + "\n", + "nbl_sdata.tables[\"nbl_wc\"] = nbl_wc[nbl_wc.obs[\"pixie_cluster\"] == \"NBL_cell\"].copy()\n", + "nbl_sdata.tables[\"nbl_wc\"].uns = nbl_sdata.tables[\"whole_cell\"].uns\n", + "\n", + "nbl_sdata.update_annotated_regions_metadata(table=nbl_sdata.tables[\"nbl_wc\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nbl.util.write_elements(sdata=nbl_sdata, elements={\"tables\": \"nbl_wc\"})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nbl_wc = nbl_sdata.tables[\"nbl_wc\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Get Marker Groups" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "adrenergic_markers = nbl.ln.cell_marker_set_catalog(\"adrenergic\", return_type=\"names\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "mesenchymal_markers = nbl.ln.cell_marker_set_catalog(\"mesenchymal\", return_type=\"names\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "neuroblastoma_markers = nbl.ln.cell_marker_set_catalog(\"neuroblastoma\", \"names\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Normalize by Area of each cell" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nbl.pp.normalize_by_area(sdata=nbl_sdata, table_names=\"nbl_wc\", method=\"layer\", write=False, inplace=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Compute Scores" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "marker_groups = {\n", + " \"ADRN\": adrenergic_markers,\n", + " \"ADRN_no_TH\": adrenergic_markers[:2],\n", + " \"MESN\": mesenchymal_markers,\n", + "}\n", + "nbl.tl.compute_marker_means(\n", + " nbl_sdata, table_name=\"nbl_wc\", layer=\"area_normalized\", marker_groups=marker_groups, inplace=True\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for score_method, obs_1 in itertools.product(nbl.tl._scores.keys(), [\"ADRN\", \"ADRN_no_TH\"]):\n", + " nbl.tl.compute_score(\n", + " sdata=nbl_sdata,\n", + " table_name=\"nbl_wc\",\n", + " obs_1=f\"{obs_1}_mean\",\n", + " obs_2=\"MESN_mean\",\n", + " score_method=score_method,\n", + " score_col_name=f\"{score_method}_no_TH\" if \"no_TH\" in obs_1 else f\"{score_method}\",\n", + " eps=1e-4,\n", + " )" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Clustering with Area Normalized Arcsinh Transformed Markers" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nbl.pp.neighbors(\n", + " sdata=nbl_sdata,\n", + " table_name=\"nbl_wc\",\n", + " layer=\"area_normalized\",\n", + " key_added=\"area_norm_neighbors\",\n", + " vars=[*marker_groups[\"ADRN_no_TH\"], *marker_groups[\"MESN\"]],\n", + " inplace=True,\n", + " method=\"umap\",\n", + " metric=\"euclidean\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "q = nbl.tl.diffmap(\n", + " sdata=nbl_sdata,\n", + " table_name=\"nbl_wc\",\n", + " neighbors_key=\"area_norm_neighbors\",\n", + " vars=[*marker_groups[\"ADRN_no_TH\"], *marker_groups[\"MESN\"]],\n", + " layer=\"area_normalized\",\n", + " inplace=False,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nbl.tl.leiden(\n", + " sdata=nbl_sdata,\n", + " table_name=\"nbl_wc\",\n", + " neighbors_key=\"area_norm_neighbors\",\n", + " layer=\"area_normalized\",\n", + " vars=[*marker_groups[\"ADRN_no_TH\"], *marker_groups[\"MESN\"]],\n", + " key_added=\"area_norm_leiden\",\n", + " flavor=\"igraph\",\n", + " n_iterations=-1,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Subset on just the Diagnosis Classification samples." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nbl_wc_diagnosis: ad.AnnData = nbl_sdata.tables[\"nbl_wc\"][\n", + " nbl_sdata.tables[\"nbl_wc\"].obs[\"Classification\"] == \"Diagnosis\", :\n", + "].copy()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nbl_sdata.tables[\"nbl_wc_diagnosis\"] = nbl_sdata.update_annotated_regions_metadata(table=nbl_wc_diagnosis)\n", + "\n", + "\n", + "nbl.util.write_elements(sdata=nbl_sdata, elements={\"tables\": \"nbl_wc_diagnosis\"})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "var_names = {\n", + " \"ADRN_no_TH\": adrenergic_markers[:2],\n", + " \"MESN\": mesenchymal_markers,\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "figures_upath = UPath(\"../../../data/db/figures/scoring\")\n", + "figures_upath.mkdir(exist_ok=True, parents=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "fig = plt.figure(figsize=(6, 16), dpi=300)\n", + "ax = fig.subplots(nrows=1, ncols=1)\n", + "sc.pl.dotplot(\n", + " adata=nbl_sdata.tables[\"nbl_wc\"],\n", + " var_names=var_names,\n", + " groupby=[\"area_norm_leiden\"],\n", + " layer=\"area_normalized\",\n", + " log=True,\n", + " return_fig=True,\n", + " cmap=\"viridis\",\n", + " title=\"Arcsinh Transformed, Area Normalized Leiden Clusters (without TH)\",\n", + " ax=ax,\n", + ").add_totals(show=True, color=\"xkcd:ocean blue\").legend(show=True).make_figure()\n", + "\n", + "fig_path: UPath = figures_upath / \"leiden_clusters_area_norm_without_TH_dotplot.pdf\"\n", + "fig.savefig(fig_path)\n", + "artifact = ln.Artifact(\n", + " data=fig_path,\n", + " description=\"Leiden Clusters Area Normalized without TH Dotplot\",\n", + ")\n", + "artifact.save()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nbl_wc_diagnosis.obs.columns" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# score = \"log_ratio_no_TH\"\n", + "for score in [\"ratio_no_TH\", \"log_ratio_no_TH\", \"normalized_difference_no_TH\", \"scaled_difference_no_TH\"]:\n", + " for risk in [\"High\", \"Intermediate\", \"Low\"]:\n", + " fig: Figure = plt.figure(figsize=(16, 10), layout=\"constrained\")\n", + " fig.suptitle(t=f\"{score} Scores for Diagnosis Samples | Risk: {risk}\")\n", + "\n", + " score_subfig, marker_subfig = fig.subfigures(nrows=1, ncols=2)\n", + "\n", + " markers_subfig, score_v_marker_subfig = marker_subfig.subfigures(nrows=2, ncols=1)\n", + "\n", + " score_ax = score_subfig.subplots(nrows=1, ncols=1)\n", + "\n", + " marker_axes = markers_subfig.subplots(nrows=2, ncols=3, sharex=True, sharey=True)\n", + " svm_subfigures = score_v_marker_subfig.subfigures(nrows=1, ncols=2)\n", + " nbl_wc_risk: ad.AnnData = nbl_wc_diagnosis[nbl_wc_diagnosis.obs[\"Risk\"] == risk, :]\n", + "\n", + " # Plotting histogram on ax1\n", + " plot1 = (\n", + " so.Plot(nbl_wc_risk.obs, x=score)\n", + " .add(so.Bars(), so.Hist())\n", + " .scale(x=\"symlog\")\n", + " .label(\n", + " x=r\"$ \\ln{\\left(\\frac{ \\bar{\\bf{M}} + \\epsilon} { \\bar{\\bf{A}} + \\epsilon}\\right)}$\",\n", + " y=\"Count\",\n", + " title=\"Scores\",\n", + " )\n", + " )\n", + " plot1.on(score_ax).plot()\n", + "\n", + " # Create a secondary y-axis\n", + " ax2 = score_ax.twinx()\n", + "\n", + " # Plotting cumulative KDE on ax2\n", + " plot2 = so.Plot(nbl_wc_risk.obs, x=score).add(so.Line(color=\"xkcd:cerulean\"), so.KDE(cumulative=True))\n", + "\n", + " plot2.on(ax2).plot()\n", + "\n", + " # Set labels for ax2\n", + " ax2.set_ylabel(\"CDF\")\n", + " ax2.yaxis.tick_right()\n", + "\n", + " for ax, marker in zip(marker_axes.flat, [*mesenchymal_markers, *adrenergic_markers], strict=False):\n", + " so.Plot(data=nbl_wc_risk[:, [marker]].to_df(), x=marker).add(so.Bars(), so.Hist()).on(ax).plot()\n", + "\n", + " for sf, marker_group in zip(svm_subfigures.flat, [mesenchymal_markers, adrenergic_markers], strict=False):\n", + " a = nbl_wc_risk.to_df(layer=\"area_normalized\").copy()\n", + " b = nbl_wc_risk.obs[score].copy()\n", + " so.Plot(data=a.join(b), x=score).pair(y=marker_group).add(so.Dots(marker=\".\", pointsize=0.5)).on(sf).label(\n", + " x=score\n", + " ).plot()\n", + "\n", + " markers_subfig.suptitle(t=\"Marker Distribution (arcsinh transformed)\")\n", + " score_v_marker_subfig.suptitle(t=\"Score vs. Marker Area Normalized Arcsinh Transformed Value\")\n", + " fig_path: UPath = figures_upath / f\"{score}_scores_{risk}.pdf\"\n", + " fig.savefig(fig_path)\n", + " artifact = ln.Artifact(\n", + " data=fig_path,\n", + " description=f\"{score} Scores for Diagnosis Samples -- Risk: {risk}\",\n", + " )\n", + " artifact.save()\n", + "\n", + " fig.show(warn=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "for score in [\"ratio_no_TH\", \"log_ratio_no_TH\", \"normalized_difference_no_TH\", \"scaled_difference_no_TH\"]:\n", + " fig = plt.figure(figsize=(16, 10), dpi=300)\n", + " subfigs = fig.subfigures(nrows=1, ncols=2, wspace=0.15)\n", + "\n", + " score_ax = subfigs[0].subplots(nrows=1, ncols=1, sharex=True, sharey=True)\n", + " dist_ax = subfigs[1].subplots(nrows=2, ncols=3, sharex=True, sharey=True)\n", + "\n", + " b = (\n", + " nbl_wc_diagnosis[:, [*mesenchymal_markers, *adrenergic_markers]]\n", + " .to_df()\n", + " .merge(right=nbl_wc_diagnosis.obs[\"Risk\"], left_index=True, right_index=True)\n", + " )\n", + "\n", + " # Plotting histogram on ax1\n", + " plot1 = (\n", + " so.Plot(nbl_wc_diagnosis.obs, x=score, color=\"Risk\")\n", + " .add(so.Bars(), so.Hist())\n", + " .scale(x=\"symlog\")\n", + " .label(\n", + " x=score,\n", + " y=\"Count\",\n", + " title=\"Diagnosis -- Scores\",\n", + " )\n", + " )\n", + " plot1.on(score_ax).plot()\n", + "\n", + " # Create a secondary y-axis\n", + " score_ax2 = score_ax.twinx()\n", + "\n", + " # Plotting cumulative KDE on ax2\n", + " plot2 = (\n", + " so.Plot(nbl_wc_diagnosis.obs, x=score, color=\"Risk\").add(so.Line(), so.KDE(cumulative=True)).scale(x=\"symlog\")\n", + " )\n", + "\n", + " plot2.on(score_ax2).plot()\n", + "\n", + " # Set labels for ax2\n", + " score_ax2.set_ylabel(\"CDF\")\n", + " score_ax2.yaxis.tick_right()\n", + "\n", + " for ax, marker in zip(dist_ax.flat, [*mesenchymal_markers, *adrenergic_markers], strict=False):\n", + " so.Plot(data=b, x=marker, color=\"Risk\").add(so.Bars(), so.Hist()).on(ax).label(x=marker, y=\"Count\").plot()\n", + " subfigs[1].suptitle(t=\"Marker Distributions\")\n", + " subfigs[1].subplots_adjust()\n", + " fig_path: UPath = figures_upath / f\"diagnosis_scores_w_marker_dists_{score}.pdf\"\n", + "\n", + " fig.savefig(fig_path)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "q = nbl.tl.umap(\n", + " sdata=nbl_sdata,\n", + " table_name=\"nbl_wc_diagnosis\",\n", + " layer=\"area_normalized\",\n", + " neighbors_key=\"area_norm_neighbors\",\n", + " vars=[*adrenergic_markers[:2], *mesenchymal_markers],\n", + " gamma=2,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sc.pl.umap(adata=q, color=\"normalized_difference_no_TH\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nbl.util.write_elements(sdata=nbl_sdata, elements={\"tables\": [\"nbl_wc_diagnosis\"]})" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "var_names = {\n", + " \"Adrenergic\": adrenergic_markers[:2],\n", + " \"Mesenchymal\": mesenchymal_markers,\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Quantiles" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "nbl.tl.quantile(\n", + " sdata=nbl_sdata,\n", + " table_name=\"nbl_wc_diagnosis\",\n", + " layer=\"area_normalized\",\n", + " var=\"CD45\",\n", + " q=[0.99, 0.95, 0.90],\n", + " inplace=False,\n", + " write=False,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# for q in [0.99, 0.95, 0.90]:\n", + "# nbl_diagnosis_wc_q: ad.AnnData = nbl.tl.filter_obs_names_by_quantile(\n", + "# sdata=nbl_sdata, table_name=\"nbl_wc_diagnosis\", var=\"CD45\", q=q, method=\"upper\", layer=\"area_normalized\"\n", + "# )\n", + "# for risk in [\"High\", \"Intermediate\", \"Low\"]:\n", + "# fig: Figure = plt.figure(figsize=(16, 10), layout=\"constrained\")\n", + "# fig.suptitle(t=f\"Log Ratio Scores for Diagnosis | Risk: {risk} | Quantile: {q}\")\n", + "\n", + "# score_subfig, marker_subfig = fig.subfigures(nrows=1, ncols=2)\n", + "\n", + "# markers_subfig, score_v_marker_subfig = marker_subfig.subfigures(nrows=2, ncols=1)\n", + "\n", + "# score_ax = score_subfig.subplots(nrows=1, ncols=1)\n", + "\n", + "# marker_axes = markers_subfig.subplots(nrows=2, ncols=3, sharex=True, sharey=True)\n", + "# svm_subfigures = score_v_marker_subfig.subfigures(nrows=1, ncols=2)\n", + "# nbl_wc_risk: ad.AnnData = nbl_diagnosis_wc_q[nbl_diagnosis_wc_q.obs[\"Risk\"] == risk, :]\n", + "\n", + "# # Plotting histogram on ax1\n", + "# plot1 = (\n", + "# so.Plot(nbl_wc_risk.obs, x=\"log_ratio_no_TH\")\n", + "# .add(so.Bars(), so.Hist())\n", + "# .scale(x=\"symlog\")\n", + "# .label(\n", + "# x=r\"$ \\ln{\\left(\\frac{ \\bar{\\bf{M}} + \\epsilon} { \\bar{\\bf{A}} + \\epsilon}\\right)}$\",\n", + "# y=\"Count\",\n", + "# title=\"Scores\",\n", + "# )\n", + "# )\n", + "# plot1.on(score_ax).plot()\n", + "\n", + "# # Create a secondary y-axis\n", + "# ax2 = score_ax.twinx()\n", + "\n", + "# # Plotting cumulative KDE on ax2\n", + "# plot2 = (\n", + "# so.Plot(nbl_wc_risk.obs, x=\"log_ratio_no_TH\")\n", + "# .add(so.Line(color=\"xkcd:cerulean\"), so.KDE(cumulative=True))\n", + "# .scale(x=\"symlog\")\n", + "# )\n", + "\n", + "# plot2.on(ax2).plot()\n", + "\n", + "# # Set labels for ax2\n", + "# ax2.set_ylabel(\"Cumulative Probability\")\n", + "# ax2.yaxis.tick_right()\n", + "\n", + "# for ax, marker in zip(marker_axes.flat, [*mesenchymal_markers, *adrenergic_markers], strict=False):\n", + "# so.Plot(data=nbl_wc_risk[:, [marker]].to_df(), x=marker).add(so.Bars(), so.Hist()).on(ax).plot()\n", + "\n", + "# for sf, marker_group in zip(svm_subfigures.flat, [mesenchymal_markers, adrenergic_markers], strict=False):\n", + "# a = nbl_wc_risk.to_df(layer=\"area_normalized\").copy()\n", + "# b = nbl_wc_risk.obs[\"log_ratio_no_TH\"].copy()\n", + "# so.Plot(data=a.join(b), x=\"log_ratio_no_TH\").pair(y=marker_group).add(so.Dots(marker=\".\", pointsize=5)).on(\n", + "# sf\n", + "# ).plot()\n", + "# markers_subfig.suptitle(t=\"Marker Distribution (arcsinh transformed)\")\n", + "# score_v_marker_subfig.suptitle(t=\"Score vs. Marker\")\n", + "# fig_path: UPath = figures_upath / f\"quantile_{q}_scores_{risk}.pdf\"\n", + "# fig.savefig(fig_path)\n", + "# artifact = ln.Artifact(\n", + "# data=fig_path,\n", + "# description=f\"Quantile {q} Scores for Diagnosis Samples -- Risk: {risk}\",\n", + "# )\n", + "# artifact.save()\n", + "# fig.show(warn=False)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# metric = \"normalized_difference_no_TH\"\n", + "# vmax: float = nbl_sdata.tables[\"nbl_wc_diagnosis\"].obs[metric].max()\n", + "\n", + "# vmin: float = nbl_sdata.tables[\"nbl_wc_diagnosis\"].obs[metric].min()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# nbl_sdata.tables[\"nbl_wc_diagnosis\"].obs.columns" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# # fig, axes = plt.subplots(nrows=4, ncols=4, figsize=(16, 16), dpi=300, layout=\"constrained\", sharex=True, sharey=True)\n", + "\n", + "# # for fov, ax in zip(fovs_for_fig, axes.flat, strict=False):\n", + "# nbl_sdata.filter_by_coordinate_system(fov).pl.render_labels(\n", + "# element=f\"{fov}_whole_cell\",\n", + "# color=\"pixie_cluster\",\n", + "# # groups=a,\n", + "# table_name=\"whole_cell\",\n", + "# outline_alpha=1,\n", + "# method=\"datashader\",\n", + "# fill_alpha=0.99,\n", + "# scale=\"full\",\n", + "# ).pl.render_labels(\n", + "# element=f\"{fov}_whole_cell\",\n", + "# color=metric,\n", + "# outline_alpha=1,\n", + "# table_name=\"nbl_wc_diagnosis\",\n", + "# method=\"datashader\",\n", + "# scale=\"full\",\n", + "# cmap=\"viridis\",\n", + "# fill_alpha=0.99,\n", + "# norm=colors.Normalize(vmin=vmin, vmax=vmax),\n", + "# ).pl.show()\n", + "\n", + "# # nbl.util.remove_ticks(f=ax, axis=\"xy\")\n", + "# # fig.savefig(\"many_fovs.png\", dpi=300)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# nbl_sdata.filter_by_coordinate_system(fov).pl.render_labels(\n", + "# element=f\"{fov}_whole_cell\",\n", + "# color=\"pixie_cluster\",\n", + "# table_name=\"whole_cell\",\n", + "# outline_alpha=1,\n", + "# method=\"datashader\",\n", + "# fill_alpha=0.99,\n", + "# scale=\"full\",\n", + "# ).pl.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ln.finish()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/docs/notebooks/01 - NBL Cell Analysis/Scoring/01 - Analyze Scores.ipynb b/docs/notebooks/01 - NBL Cell Analysis/Scoring/01 - Analyze Scores.ipynb new file mode 100644 index 0000000..7d30ef4 --- /dev/null +++ b/docs/notebooks/01 - NBL Cell Analysis/Scoring/01 - Analyze Scores.ipynb @@ -0,0 +1,30 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Analyze Scores" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Analyze scores" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/pyproject.toml b/pyproject.toml index 3918b38..76cf437 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -21,7 +21,7 @@ dependencies = [ # for debug logging (referenced from the issue template) "session-info", "spatialdata @ git+https://github.com/srivarra/spatialdata@fsspec_bump", - "spatialdata-plot>=0.2.4", + "spatialdata-plot @ git+https://github.com/scverse/spatialdata-plot@main", "xarray[accel,parallel]>=2024.7.0", "dask[array,distributed,diagnostics]>=2024.8.0", "dask-image>=2024.5.3", @@ -43,7 +43,9 @@ dependencies = [ "altair[all]>=5.3.0", "great-tables>=0.10.0", "numpydantic[zarr,dask]>=1.3.0", - # "virtualizarr>=1.0.0", + "sklearn-ann[annlibs]>=0.1.2", + "leidenalg>=0.10.2", + "hvplot>=0.10.0", ] [project.optional-dependencies] @@ -85,19 +87,7 @@ db = [ "graphviz>=0.20.3", "ipylab>=1.0.0", ] -jax = [ - "jax-metal>=0.1.0", - "jax>=0.4.31", - "jaxlib>=0.4.31", - "equinox>=0.11.4", - "somap>=0.1.1", -] -torch = [ - "torch>=2.4.0", - "lightning>=2.4.0", - "pykan>=0.2.4", - "torchdata>=0.8.0", -] +torch = ["torch>=2.4.0", "lightning>=2.4.0", "pykan>=0.2.4", "torchdata>=0.8.0"] [tool.coverage.run] source = ["nbl"] diff --git a/requirements-dev.lock b/requirements-dev.lock index 7974960..ad9e8b3 100644 --- a/requirements-dev.lock +++ b/requirements-dev.lock @@ -12,27 +12,25 @@ -e file:. accessible-pygments==0.0.5 # via pydata-sphinx-theme -aiobotocore==2.13.1 +aiobotocore==2.15.1 # via lamindb-setup # via s3fs -aiohappyeyeballs==2.3.5 +aiohappyeyeballs==2.4.3 # via aiohttp -aiohttp==3.10.2 +aiohttp==3.10.8 # via aiobotocore - # via datasets # via fsspec # via ome-zarr # via s3fs -aioitertools==0.11.0 +aioitertools==0.12.0 # via aiobotocore aiosignal==1.3.1 # via aiohttp alabaster==1.0.0 # via sphinx -altair==5.3.0 +altair==5.4.1 # via altair-tiles # via nbl - # via somap # via vegafusion altair-tiles==0.3.0 # via altair @@ -41,13 +39,14 @@ anndata==0.10.8 # via lamindb # via mudata # via nbl - # via pytometry # via readfcs # via scanpy # via spatialdata annotated-types==0.7.0 # via pydantic -anyio==4.4.0 +annoy==1.17.3 + # via sklearn-ann +anyio==4.6.0 # via httpcore # via jupyter-server # via starlette @@ -66,10 +65,8 @@ argon2-cffi==23.1.0 # via jupyter-server argon2-cffi-bindings==21.2.0 # via argon2-cffi -array-api-compat==1.8 +array-api-compat==1.9 # via anndata -array2image==0.1.0 - # via somap arrow==1.3.0 # via isoduration asciitree==0.3.3 @@ -97,21 +94,23 @@ babel==2.16.0 # via sphinx backports-tarfile==1.2.0 # via jaraco-context -beartype==0.18.5 - # via somap beautifulsoup4==4.12.3 # via nbconvert # via pydata-sphinx-theme # via sphinx-toolbox -bionty==0.48.0 +bionty==0.51.1 # via lamindb bleach==6.1.0 # via nbconvert -bokeh==3.5.1 + # via panel +bokeh==3.6.0 # via dask -boto3==1.34.131 + # via holoviews + # via hvplot + # via panel +boto3==1.35.23 # via aiobotocore -botocore==1.34.131 +botocore==1.35.23 # via aiobotocore # via boto3 # via lamindb-setup @@ -119,15 +118,15 @@ botocore==1.34.131 bottleneck==1.4.0 # via pandas # via xarray -buckaroo==0.6.12 +buckaroo==0.7.3 # via nbl cachecontrol==0.14.0 # via sphinx-toolbox -cattrs==23.2.3 +cattrs==24.1.2 # via django-schema-graph # via lsprotocol # via pygls -certifi==2024.7.4 +certifi==2024.8.30 # via httpcore # via httpx # via pyogrio @@ -135,7 +134,7 @@ certifi==2024.7.4 # via requests # via sentry-sdk # via sphinx-prompt -cffi==1.17.0 +cffi==1.17.1 # via argon2-cffi-bindings cfgv==3.4.0 # via pre-commit @@ -160,14 +159,14 @@ colorama==0.4.6 # via sphinx-autobuild colorcet==3.1.0 # via datashader -colormap2d==0.2.0 - # via somap + # via holoviews + # via hvplot comm==0.2.2 # via ipykernel # via ipywidgets commonmark==0.9.1 # via great-tables -contourpy==1.2.1 +contourpy==1.3.0 # via bokeh # via matplotlib coverage==7.6.1 @@ -176,7 +175,7 @@ cssutils==2.11.1 # via dict2css cycler==0.12.1 # via matplotlib -dask==2024.8.0 +dask==2024.9.1 # via dask-expr # via dask-image # via datashader @@ -188,17 +187,14 @@ dask==2024.8.0 # via spatialdata # via xarray # via xbatcher -dask-expr==1.1.10 +dask-expr==1.1.15 # via dask dask-image==2024.5.3 # via nbl # via spatialdata -datasets==2.20.0 - # via somap datashader==0.16.3 - # via pytometry # via xarray-spatial -debugpy==1.8.5 +debugpy==1.8.6 # via ipykernel decorator==5.1.1 # via ipython @@ -209,17 +205,14 @@ deprecation==2.1.0 # via postgrest dict2css==0.3.0.post1 # via sphinx-toolbox -dill==0.3.8 - # via datasets - # via multiprocess distlib==0.3.8 # via virtualenv -distributed==2024.8.0 +distributed==2024.9.1 # via dask # via ome-zarr dj-database-url==2.2.0 # via lamindb-setup -django==5.1 +django==5.1.1 # via dj-database-url # via lamindb-setup django-schema-graph==3.1.0 @@ -249,16 +242,15 @@ einops==0.8.0 # via xarray-einstats einx==0.3.0 # via nbl -equinox==0.11.4 - # via nbl - # via somap -esbonio==0.16.4 +esbonio==0.16.5 # via nbl et-xmlfile==1.1.0 # via openpyxl -executing==2.0.1 +executing==2.1.0 # via stack-data -faker==26.3.0 +faiss-cpu==1.8.0.post1 + # via sklearn-ann +faker==30.1.0 # via faker-biology # via nbl faker-biology==0.6.4 @@ -273,20 +265,18 @@ fastobo==0.12.3 # via pronto fcsparser==0.2.8 # via readfcs -filelock==3.15.4 +filelock==3.16.1 # via bionty # via cachecontrol - # via datasets - # via huggingface-hub # via sphinx-toolbox # via torch # via virtualenv -flowsom @ git+https://github.com/saeyslab/FlowSOM_Python@80529c6b7a1747e8e71042102ac8762c3bfbaa1b +flowsom @ git+https://github.com/saeyslab/FlowSOM_Python@211ad7cd083141f4a7541b217d009ee5c7da00f6 # via nbl -flox==0.9.9 +flox==0.9.13 # via nbl # via xarray -fonttools==4.53.1 +fonttools==4.54.1 # via matplotlib fqdn==1.5.1 # via jsonschema @@ -295,10 +285,8 @@ frozendict==2.4.4 frozenlist==1.4.1 # via aiohttp # via aiosignal -fsspec==2024.3.1 +fsspec==2024.6.1 # via dask - # via datasets - # via huggingface-hub # via lamindb # via lightning # via ome-zarr @@ -314,23 +302,27 @@ gitdb==4.0.11 gitpython==3.1.43 # via tach # via wandb -gotrue==2.6.1 +gotrue==2.8.1 + # via lamindb-setup # via supabase graphlib-backport==1.1.0 # via buckaroo graphviz==0.20.3 # via lamindb # via nbl -great-tables==0.10.0 +great-tables==0.12.0 # via nbl h11==0.14.0 # via httpcore # via uvicorn h2==4.1.0 # via httpx -h5py==3.11.0 +h5py==3.12.1 # via anndata + # via mudata # via scanpy +holoviews==1.19.1 + # via hvplot hpack==4.0.0 # via h2 html5lib==1.1 @@ -345,13 +337,13 @@ httpx==0.24.1 # via storage3 # via supabase # via supafunc -huggingface-hub==0.24.5 - # via datasets +hvplot==0.11.0 + # via nbl hyperframe==6.0.1 # via h2 -identify==2.6.0 +identify==2.6.1 # via pre-commit -idna==3.7 +idna==3.10 # via anyio # via apeye-core # via httpx @@ -361,12 +353,13 @@ idna==3.7 # via yarl igraph==0.11.6 # via flowsom -imageio==2.34.2 + # via leidenalg +imageio==2.35.1 # via pims # via scikit-image imagesize==1.4.1 # via sphinx -importlib-metadata==8.2.0 +importlib-metadata==8.5.0 # via dask # via doit # via great-tables @@ -375,7 +368,7 @@ importlib-metadata==8.2.0 # via myst-nb # via nbproject # via twine -importlib-resources==6.4.0 +importlib-resources==6.4.5 # via great-tables iniconfig==2.0.0 # via pytest @@ -387,12 +380,12 @@ ipykernel==6.29.5 # via nbl ipylab==1.0.0 # via nbl -ipython==8.26.0 +ipython==8.28.0 # via ipykernel # via ipywidgets # via myst-nb # via nbl -ipywidgets==8.1.3 +ipywidgets==8.1.5 # via anywidget # via buckaroo # via ipycytoscape @@ -401,24 +394,10 @@ isoduration==20.11.0 # via jsonschema jaraco-classes==3.4.0 # via keyring -jaraco-context==5.3.0 +jaraco-context==6.0.1 # via keyring -jaraco-functools==4.0.2 +jaraco-functools==4.1.0 # via keyring -jax==0.4.31 - # via equinox - # via jax-metal - # via nbl - # via somap -jax-metal==0.1.0 - # via nbl -jaxlib==0.4.31 - # via jax - # via jax-metal - # via nbl -jaxtyping==0.2.33 - # via equinox - # via somap jedi==0.19.1 # via ipython jinja2==3.1.4 @@ -454,7 +433,7 @@ jsonschema-specifications==2023.12.1 # via jsonschema jupyter-cache==1.0.0 # via myst-nb -jupyter-client==8.6.2 +jupyter-client==8.6.3 # via ipykernel # via jupyter-server # via nbclient @@ -489,30 +468,33 @@ jupyterlab-server==2.27.3 # via jupyterlab # via jupyterlite-sphinx # via notebook -jupyterlab-widgets==3.0.11 +jupyterlab-widgets==3.0.13 # via ipywidgets -jupyterlite-core==0.4.0 +jupyterlite-core==0.4.1 # via jupyterlite-pyodide-kernel # via jupyterlite-sphinx -jupyterlite-pyodide-kernel==0.4.1 +jupyterlite-pyodide-kernel==0.4.2 # via nbl jupyterlite-sphinx==0.16.5 # via nbl -keyring==25.3.0 +jupytext==1.16.4 + # via lamindb +keyring==25.4.1 # via twine -kiwisolver==1.4.5 +kiwisolver==1.4.7 # via matplotlib -lamin-cli==0.16.0 +lamin-cli==0.17.8 # via lamindb -lamin-utils==0.13.2 +lamin-utils==0.13.6 # via lamindb # via lamindb-setup # via nbproject # via readfcs -lamindb==0.75.1 +lamindb==0.76.11 # via bionty # via nbl -lamindb-setup==0.76.3 +lamindb-setup==0.77.7 + # via bionty # via lamindb latexcodec==3.0.0 # via pybtex @@ -522,16 +504,20 @@ legacy-api-wrap==1.4 # via scanpy legendkit==0.3.4 # via marsilea +leidenalg==0.10.2 + # via nbl lightning==2.4.0 # via nbl -lightning-utilities==0.11.6 +lightning-utilities==0.11.7 # via lightning # via pytorch-lightning # via torchmetrics +linkify-it-py==2.0.3 + # via panel llvmlite==0.43.0 # via numba # via pynndescent -lnschema-core==0.72.2 +lnschema-core==0.74.6 # via lamindb # via lamindb-setup locket==1.0.0 @@ -543,67 +529,68 @@ lsprotocol==2023.0.1 # via pygls lz4==4.3.3 # via dask +markdown==3.7 + # via panel markdown-it-py==3.0.0 + # via jupytext # via mdit-py-plugins # via myst-parser + # via panel # via rich markupsafe==2.1.5 # via jinja2 # via nbconvert # via sphinx-jinja2-compat -marsilea==0.4.4 +marsilea==0.4.6 # via nbl -matplotlib==3.9.1.post1 +matplotlib==3.9.2 # via flowsom # via legendkit # via marsilea # via matplotlib-scalebar # via nbl - # via pytometry # via scanpy # via seaborn - # via somap # via spatialdata-plot matplotlib-inline==0.1.7 # via ipykernel # via ipython matplotlib-scalebar==0.8.1 # via spatialdata-plot -mdit-py-plugins==0.4.1 +mdit-py-plugins==0.4.2 + # via jupytext # via myst-parser + # via panel mdurl==0.1.2 # via markdown-it-py mercantile==1.2.1 # via altair-tiles mistune==3.0.2 # via nbconvert -ml-dtypes==0.4.0 - # via jax - # via jaxlib -more-itertools==10.4.0 +more-itertools==10.5.0 # via cssutils # via jaraco-classes # via jaraco-functools mpmath==1.3.0 # via sympy -msgpack==1.0.8 +msgpack==1.1.0 # via cachecontrol # via distributed -mudata==0.3.0 +mudata==0.2.4 # via flowsom -multidict==6.0.5 +multidict==6.1.0 # via aiohttp # via yarl multipledispatch==1.0.0 # via datashader -multiprocess==0.70.16 - # via datasets multiscale-spatial-image==1.0.1 # via spatialdata -myst-nb==1.1.1 +myst-nb==1.1.2 # via nbl myst-parser==4.0.0 # via myst-nb +narwhals==1.9.0 + # via altair natsort==8.4.0 # via anndata # via domdf-python-tools @@ -622,16 +609,13 @@ nbformat==5.10.4 # via jupyter-cache # via jupyter-server # via jupyterlite-sphinx + # via jupytext # via myst-nb # via nb-clean # via nbclient # via nbconvert - # via nbstripout nbproject==0.10.4 # via lamindb - # via pytometry -nbstripout==0.6.1 - # via lamindb nest-asyncio==1.6.0 # via ipykernel networkx==3.3 @@ -661,7 +645,7 @@ numba==0.60.0 # via umap-learn # via xarray-einstats # via xarray-spatial -numbagg==0.8.1 +numbagg==0.8.2 # via xarray numcodecs==0.13.0 # via zarr @@ -670,28 +654,26 @@ numexpr==2.10.1 numpy==1.26.4 # via altair # via anndata - # via array2image # via bokeh # via bottleneck - # via colormap2d # via contourpy # via dask # via dask-image - # via datasets # via datashader # via einx + # via faiss-cpu # via fcsparser # via flowsom # via flox # via geopandas # via great-tables # via h5py + # via holoviews + # via hvplot # via imageio - # via jax - # via jaxlib # via marsilea # via matplotlib - # via ml-dtypes + # via mudata # via multiscale-spatial-image # via nbl # via numba @@ -701,13 +683,11 @@ numpy==1.26.4 # via numpy-groupies # via numpydantic # via ome-zarr - # via opt-einsum # via pandas # via patsy # via pims # via pyarrow # via pyogrio - # via pytometry # via scanpy # via scikit-image # via scikit-learn @@ -730,7 +710,7 @@ numpy==1.26.4 # via zarr numpy-groupies==0.11.2 # via flox -numpydantic==1.3.0 +numpydantic==1.6.3 # via nbl odfpy==1.4.1 # via pandas @@ -738,8 +718,7 @@ ome-zarr==0.9.0 # via spatialdata openpyxl==3.1.5 # via pandas -opt-einsum==3.3.0 - # via jax +opt-einsum==3.4.0 # via xarray orjson==3.10.7 # via nbproject @@ -751,24 +730,27 @@ packaging==24.1 # via bokeh # via buckaroo # via dask - # via datasets # via datashader # via deprecation # via distributed + # via faiss-cpu # via flox # via geopandas + # via holoviews # via htmltools - # via huggingface-hub + # via hvplot # via ipykernel # via jupyter-server # via jupyterlab # via jupyterlab-server + # via jupytext # via lazy-loader # via lightning # via lightning-utilities # via matplotlib # via nbconvert # via nbproject + # via panel # via pims # via pooch # via pydata-sphinx-theme @@ -782,23 +764,25 @@ packaging==24.1 # via torchmetrics # via xarray # via xarray-datatree -pandas==2.2.2 +pandas==2.2.3 # via altair # via anndata # via bokeh # via dask # via dask-expr # via dask-image - # via datasets # via datashader # via fcsparser # via flowsom # via flox # via geopandas + # via holoviews + # via hvplot # via lamindb # via marsilea + # via mudata # via nbl - # via pytometry + # via panel # via scanpy # via seaborn # via spatialdata @@ -808,9 +792,16 @@ pandas==2.2.2 # via xarray pandocfilters==1.5.1 # via nbconvert +panel==1.5.1 + # via holoviews + # via hvplot param==2.1.1 # via datashader + # via holoviews + # via hvplot + # via panel # via pyct + # via pyviz-comms parso==0.8.4 # via jedi partd==1.4.2 @@ -821,7 +812,6 @@ patsy==0.5.6 pexpect==4.9.0 # via ipython pillow==10.4.0 - # via array2image # via bokeh # via datashader # via imageio @@ -832,7 +822,7 @@ pims==0.7 pkginfo==1.10.0 # via jupyterlite-pyodide-kernel # via twine -platformdirs==4.2.2 +platformdirs==4.3.6 # via apeye # via esbonio # via jupyter-core @@ -848,14 +838,14 @@ postgrest==0.13.2 # via supabase pre-commit==3.8.0 # via nbl -prometheus-client==0.20.0 +prometheus-client==0.21.0 # via jupyter-server -prompt-toolkit==3.0.47 +prompt-toolkit==3.0.48 # via ipython # via tach pronto==2.5.5 # via bionty -protobuf==5.27.3 +protobuf==5.28.2 # via vegafusion # via wandb psutil==6.0.0 @@ -877,13 +867,10 @@ pyarrow==17.0.0 # via altair # via dask # via dask-expr - # via datasets # via lamindb + # via pandas # via spatialdata # via vegafusion -pyarrow-hotfix==0.6 - # via dask - # via datasets pybtex==0.24.0 # via pybtex-docutils # via sphinxcontrib-bibtex @@ -893,20 +880,19 @@ pycparser==2.22 # via cffi pyct==0.5.0 # via datashader -pydantic==2.8.2 +pydantic==2.9.2 # via gotrue # via nbproject # via numpydantic # via postgrest # via pydantic-settings - # via tach -pydantic-core==2.20.1 +pydantic-core==2.23.4 # via pydantic -pydantic-settings==2.4.0 +pydantic-settings==2.5.2 # via lamindb-setup pydata-sphinx-theme==0.15.4 # via sphinx-book-theme -pydot==2.0.0 +pydot==3.0.2 # via tach pygls==1.3.1 # via esbonio @@ -920,21 +906,22 @@ pygments==2.18.0 # via sphinx # via sphinx-prompt # via sphinx-tabs -pykan==0.2.4 +pykan==0.2.6 # via nbl pynndescent==0.5.13 # via scanpy + # via sklearn-ann # via umap-learn -pyogrio==0.9.0 +pyogrio==0.10.0 # via geopandas -pyparsing==3.1.2 +pyparsing==3.1.4 # via matplotlib # via pydot -pyproj==3.6.1 +pyproj==3.7.0 # via geopandas pyspellchecker==0.8.1 # via esbonio -pytest==8.3.2 +pytest==8.3.3 # via nbl python-calamine==0.2.3 # via pandas @@ -954,23 +941,23 @@ python-dotenv==1.0.1 # via pydantic-settings python-json-logger==2.0.7 # via jupyter-events -pytometry==0.1.4 - # via flowsom pytorch-lightning==2.4.0 # via lightning -pytz==2024.1 +pytz==2024.2 # via pandas +pyviz-comms==3.0.3 + # via holoviews + # via panel pyxlsb==1.0.10 # via pandas pyyaml==6.0.2 # via bionty # via bokeh # via dask - # via datasets # via distributed - # via huggingface-hub # via jupyter-cache # via jupyter-events + # via jupytext # via lightning # via myst-nb # via myst-parser @@ -980,15 +967,14 @@ pyyaml==6.0.2 # via pytorch-lightning # via tach # via wandb -pyzmq==26.1.0 +pyzmq==26.2.0 # via ipykernel # via jupyter-client # via jupyter-server -rapidfuzz==3.9.6 +rapidfuzz==3.10.0 # via lamindb readfcs==1.1.8 # via flowsom - # via pytometry readme-renderer==44.0 # via twine realtime==1.0.6 @@ -1001,12 +987,11 @@ requests==2.32.3 # via apeye # via bionty # via cachecontrol - # via datasets # via datashader - # via huggingface-hub # via jupyterlab-server # via lamindb-setup # via ome-zarr + # via panel # via pooch # via requests-toolbelt # via sphinx @@ -1023,7 +1008,7 @@ rfc3986==2.0.0 rfc3986-validator==0.1.1 # via jsonschema # via jupyter-events -rich==13.7.1 +rich==13.9.1 # via rich-click # via spatialdata # via tach @@ -1037,24 +1022,24 @@ ruamel-yaml==0.18.6 # via sphinx-toolbox ruamel-yaml-clib==0.2.8 # via ruamel-yaml -ruff==0.5.7 +ruff==0.6.8 # via nbl -s3fs==2024.3.1 +s3fs==2024.6.1 # via fsspec # via lamindb-setup s3transfer==0.10.2 # via boto3 -scanpy==1.10.2 +scanpy==1.10.3 # via flowsom - # via pytometry # via spatialdata-plot scikit-image==0.24.0 # via ome-zarr # via spatialdata -scikit-learn==1.5.1 +scikit-learn==1.5.2 # via flowsom # via pynndescent # via scanpy + # via sklearn-ann # via spatialdata-plot # via umap-learn scipy==1.12.0 @@ -1063,15 +1048,13 @@ scipy==1.12.0 # via datashader # via flowsom # via flox - # via jax - # via jaxlib # via lamindb # via marsilea # via pynndescent - # via pytometry # via scanpy # via scikit-image # via scikit-learn + # via sklearn-ann # via spatialdata # via statsmodels # via umap-learn @@ -1080,11 +1063,10 @@ scipy==1.12.0 seaborn==0.13.2 # via flowsom # via marsilea - # via pytometry # via scanpy send2trash==1.8.3 # via jupyter-server -sentry-sdk==2.12.0 +sentry-sdk==2.15.0 # via wandb session-info==1.0.0 # via flowsom @@ -1092,12 +1074,12 @@ session-info==1.0.0 # via scanpy setproctitle==1.3.3 # via wandb -setuptools==72.1.0 +setuptools==75.1.0 # via lightning-utilities # via nbl # via spatialdata # via wandb -shapely==2.0.5 +shapely==2.0.6 # via geopandas # via spatialdata six==1.16.0 @@ -1105,11 +1087,12 @@ six==1.16.0 # via bleach # via docker-pycreds # via html5lib - # via jax-metal # via patsy # via pybtex # via python-dateutil # via rfc3339-validator +sklearn-ann==0.1.2 + # via nbl slicerator==1.1.0 # via pims smmap==5.0.1 @@ -1120,19 +1103,17 @@ sniffio==1.3.1 # via httpx snowballstemmer==2.2.0 # via sphinx -somap==0.1.1 - # via nbl sortedcontainers==2.4.0 # via distributed -soupsieve==2.5 +soupsieve==2.6 # via beautifulsoup4 spatial-image==1.1.0 # via multiscale-spatial-image # via spatialdata -spatialdata @ git+https://github.com/srivarra/spatialdata@a193a256376552db935e735a94ea5be646eb7506 +spatialdata @ git+https://github.com/srivarra/spatialdata@ebed847007cc96d41f2cb39ab46b7471acc0fecd # via nbl # via spatialdata-plot -spatialdata-plot==0.2.4 +spatialdata-plot @ git+https://github.com/scverse/spatialdata-plot@6ffe22bb57748fb553807761e9771ff9ff1b015f # via nbl spectate==1.0.1 # via ipycytoscape @@ -1154,9 +1135,9 @@ sphinx==8.0.2 # via sphinx-toolbox # via sphinxcontrib-bibtex # via sphinxext-opengraph -sphinx-autobuild==2024.4.16 +sphinx-autobuild==2024.10.3 # via nbl -sphinx-autodoc-typehints==2.2.3 +sphinx-autodoc-typehints==2.4.4 # via nbl # via sphinx-toolbox sphinx-autosummary-accessors==2023.4.0 @@ -1171,11 +1152,11 @@ sphinx-prompt==1.9.0 # via sphinx-toolbox sphinx-tabs==3.4.5 # via sphinx-toolbox -sphinx-toolbox==3.7.0 +sphinx-toolbox==3.8.0 # via nbl sphinxcontrib-applehelp==2.0.0 # via sphinx -sphinxcontrib-bibtex==2.6.2 +sphinxcontrib-bibtex==2.6.3 # via nbl sphinxcontrib-devhelp==2.0.0 # via sphinx @@ -1189,15 +1170,15 @@ sphinxcontrib-serializinghtml==2.0.0 # via sphinx sphinxext-opengraph==0.9.1 # via nbl -sqlalchemy==2.0.32 +sqlalchemy==2.0.35 # via jupyter-cache sqlparse==0.5.1 # via django stack-data==0.6.3 # via ipython -starlette==0.38.2 +starlette==0.39.2 # via sphinx-autobuild -statsmodels==0.14.2 +statsmodels==0.14.4 # via scanpy stdlib-list==0.10.0 # via session-info @@ -1209,13 +1190,13 @@ supabase==2.2.1 # via lamindb-setup supafunc==0.3.3 # via supabase -sympy==1.13.1 +sympy==1.13.3 # via einx # via torch tabulate==0.9.0 # via jupyter-cache # via sphinx-toolbox -tach==0.10.0 +tach==0.12.0 # via nbl tblib==3.0.0 # via distributed @@ -1226,23 +1207,24 @@ texttable==1.7.0 # via igraph threadpoolctl==3.5.0 # via scikit-learn -tifffile==2024.7.24 +tifffile==2024.9.20 # via dask-image # via pims # via scikit-image tinycss2==1.3.0 # via nbconvert +tomli==2.0.2 + # via tach tomli-w==1.0.0 # via tach toolz==0.12.1 - # via altair # via dask # via datashader # via distributed # via flox # via ome-zarr # via partd -torch==2.4.0 +torch==2.4.1 # via lightning # via nbl # via pytorch-lightning @@ -1250,7 +1232,7 @@ torch==2.4.0 # via torchmetrics torchdata==0.8.0 # via nbl -torchmetrics==1.4.1 +torchmetrics==1.4.2 # via lightning # via pytorch-lightning tornado==6.4.1 @@ -1263,9 +1245,8 @@ tornado==6.4.1 # via notebook # via terminado tqdm==4.66.5 - # via datasets - # via huggingface-hub # via lightning + # via panel # via pytorch-lightning # via scanpy # via umap-learn @@ -1283,27 +1264,26 @@ traitlets==5.14.3 # via nbclient # via nbconvert # via nbformat -treescope==0.1.1 +treescope==0.1.5 # via nbl twine==5.1.1 # via nbl -typeguard==2.13.3 - # via jaxtyping -types-python-dateutil==2.9.0.20240316 +types-python-dateutil==2.9.0.20241003 # via arrow typing-extensions==4.12.2 + # via altair # via anywidget # via dj-database-url # via domdf-python-tools - # via equinox + # via faker # via great-tables # via htmltools - # via huggingface-hub # via ipython # via lamindb # via lightning # via lightning-utilities # via myst-nb + # via panel # via pydantic # via pydantic-core # via pydata-sphinx-theme @@ -1316,16 +1296,18 @@ typing-extensions==4.12.2 # via storage3 # via torch # via xarray-dataclasses -tzdata==2024.1 +tzdata==2024.2 # via pandas +uc-micro-py==1.0.3 + # via linkify-it-py umap-learn==0.5.6 # via scanpy -universal-pathlib==0.2.2 +universal-pathlib==0.2.5 # via lamindb-setup # via nbl uri-template==1.3.0 # via jsonschema -urllib3==1.26.19 +urllib3==1.26.20 # via botocore # via distributed # via lamindb-setup @@ -1334,7 +1316,7 @@ urllib3==1.26.19 # via sphinx-prompt # via torchdata # via twine -uvicorn==0.30.5 +uvicorn==0.31.0 # via sphinx-autobuild vega-datasets==0.9.0 # via altair @@ -1342,19 +1324,18 @@ vegafusion==1.6.9 # via altair vegafusion-python-embed==1.6.9 # via vegafusion -virtualenv==20.26.3 +virtualenv==20.26.6 # via pre-commit -vl-convert-python==1.6.0 +vl-convert-python==1.6.1 # via altair - # via somap # via vegafusion -wandb==0.17.6 +wandb==0.18.3 # via nbl -watchfiles==0.23.0 +watchfiles==0.24.0 # via sphinx-autobuild wcwidth==0.2.13 # via prompt-toolkit -webcolors==24.6.0 +webcolors==24.8.0 # via jsonschema webencodings==0.5.1 # via bleach @@ -1365,13 +1346,11 @@ websocket-client==1.8.0 websockets==12.0 # via realtime # via sphinx-autobuild -wheel==0.44.0 - # via jax-metal -widgetsnbextension==4.0.11 +widgetsnbextension==4.0.13 # via ipywidgets wrapt==1.16.0 # via aiobotocore -xarray==2024.7.0 +xarray==2024.9.0 # via datashader # via multiscale-spatial-image # via nbl @@ -1388,26 +1367,24 @@ xarray-dataclasses==1.8.0 xarray-datatree==0.0.14 # via multiscale-spatial-image # via spatialdata -xarray-einstats==0.7.0 +xarray-einstats==0.8.0 # via nbl xarray-schema==0.0.3 # via spatialdata xarray-spatial==0.4.0 # via spatialdata -xbatcher==0.3.0 +xbatcher==0.4.0 # via nbl xlrd==2.0.1 # via pandas xlsxwriter==3.2.0 # via pandas -xxhash==3.4.1 - # via datasets -xyzservices==2024.6.0 +xyzservices==2024.9.0 # via altair-tiles # via bokeh -yarl==1.9.4 +yarl==1.13.1 # via aiohttp -zarr==2.18.2 +zarr==2.18.3 # via lamindb # via multiscale-spatial-image # via numpydantic @@ -1415,5 +1392,5 @@ zarr==2.18.2 # via spatialdata zict==3.0.0 # via distributed -zipp==3.19.2 +zipp==3.20.2 # via importlib-metadata diff --git a/requirements.lock b/requirements.lock index 7974960..ad9e8b3 100644 --- a/requirements.lock +++ b/requirements.lock @@ -12,27 +12,25 @@ -e file:. accessible-pygments==0.0.5 # via pydata-sphinx-theme -aiobotocore==2.13.1 +aiobotocore==2.15.1 # via lamindb-setup # via s3fs -aiohappyeyeballs==2.3.5 +aiohappyeyeballs==2.4.3 # via aiohttp -aiohttp==3.10.2 +aiohttp==3.10.8 # via aiobotocore - # via datasets # via fsspec # via ome-zarr # via s3fs -aioitertools==0.11.0 +aioitertools==0.12.0 # via aiobotocore aiosignal==1.3.1 # via aiohttp alabaster==1.0.0 # via sphinx -altair==5.3.0 +altair==5.4.1 # via altair-tiles # via nbl - # via somap # via vegafusion altair-tiles==0.3.0 # via altair @@ -41,13 +39,14 @@ anndata==0.10.8 # via lamindb # via mudata # via nbl - # via pytometry # via readfcs # via scanpy # via spatialdata annotated-types==0.7.0 # via pydantic -anyio==4.4.0 +annoy==1.17.3 + # via sklearn-ann +anyio==4.6.0 # via httpcore # via jupyter-server # via starlette @@ -66,10 +65,8 @@ argon2-cffi==23.1.0 # via jupyter-server argon2-cffi-bindings==21.2.0 # via argon2-cffi -array-api-compat==1.8 +array-api-compat==1.9 # via anndata -array2image==0.1.0 - # via somap arrow==1.3.0 # via isoduration asciitree==0.3.3 @@ -97,21 +94,23 @@ babel==2.16.0 # via sphinx backports-tarfile==1.2.0 # via jaraco-context -beartype==0.18.5 - # via somap beautifulsoup4==4.12.3 # via nbconvert # via pydata-sphinx-theme # via sphinx-toolbox -bionty==0.48.0 +bionty==0.51.1 # via lamindb bleach==6.1.0 # via nbconvert -bokeh==3.5.1 + # via panel +bokeh==3.6.0 # via dask -boto3==1.34.131 + # via holoviews + # via hvplot + # via panel +boto3==1.35.23 # via aiobotocore -botocore==1.34.131 +botocore==1.35.23 # via aiobotocore # via boto3 # via lamindb-setup @@ -119,15 +118,15 @@ botocore==1.34.131 bottleneck==1.4.0 # via pandas # via xarray -buckaroo==0.6.12 +buckaroo==0.7.3 # via nbl cachecontrol==0.14.0 # via sphinx-toolbox -cattrs==23.2.3 +cattrs==24.1.2 # via django-schema-graph # via lsprotocol # via pygls -certifi==2024.7.4 +certifi==2024.8.30 # via httpcore # via httpx # via pyogrio @@ -135,7 +134,7 @@ certifi==2024.7.4 # via requests # via sentry-sdk # via sphinx-prompt -cffi==1.17.0 +cffi==1.17.1 # via argon2-cffi-bindings cfgv==3.4.0 # via pre-commit @@ -160,14 +159,14 @@ colorama==0.4.6 # via sphinx-autobuild colorcet==3.1.0 # via datashader -colormap2d==0.2.0 - # via somap + # via holoviews + # via hvplot comm==0.2.2 # via ipykernel # via ipywidgets commonmark==0.9.1 # via great-tables -contourpy==1.2.1 +contourpy==1.3.0 # via bokeh # via matplotlib coverage==7.6.1 @@ -176,7 +175,7 @@ cssutils==2.11.1 # via dict2css cycler==0.12.1 # via matplotlib -dask==2024.8.0 +dask==2024.9.1 # via dask-expr # via dask-image # via datashader @@ -188,17 +187,14 @@ dask==2024.8.0 # via spatialdata # via xarray # via xbatcher -dask-expr==1.1.10 +dask-expr==1.1.15 # via dask dask-image==2024.5.3 # via nbl # via spatialdata -datasets==2.20.0 - # via somap datashader==0.16.3 - # via pytometry # via xarray-spatial -debugpy==1.8.5 +debugpy==1.8.6 # via ipykernel decorator==5.1.1 # via ipython @@ -209,17 +205,14 @@ deprecation==2.1.0 # via postgrest dict2css==0.3.0.post1 # via sphinx-toolbox -dill==0.3.8 - # via datasets - # via multiprocess distlib==0.3.8 # via virtualenv -distributed==2024.8.0 +distributed==2024.9.1 # via dask # via ome-zarr dj-database-url==2.2.0 # via lamindb-setup -django==5.1 +django==5.1.1 # via dj-database-url # via lamindb-setup django-schema-graph==3.1.0 @@ -249,16 +242,15 @@ einops==0.8.0 # via xarray-einstats einx==0.3.0 # via nbl -equinox==0.11.4 - # via nbl - # via somap -esbonio==0.16.4 +esbonio==0.16.5 # via nbl et-xmlfile==1.1.0 # via openpyxl -executing==2.0.1 +executing==2.1.0 # via stack-data -faker==26.3.0 +faiss-cpu==1.8.0.post1 + # via sklearn-ann +faker==30.1.0 # via faker-biology # via nbl faker-biology==0.6.4 @@ -273,20 +265,18 @@ fastobo==0.12.3 # via pronto fcsparser==0.2.8 # via readfcs -filelock==3.15.4 +filelock==3.16.1 # via bionty # via cachecontrol - # via datasets - # via huggingface-hub # via sphinx-toolbox # via torch # via virtualenv -flowsom @ git+https://github.com/saeyslab/FlowSOM_Python@80529c6b7a1747e8e71042102ac8762c3bfbaa1b +flowsom @ git+https://github.com/saeyslab/FlowSOM_Python@211ad7cd083141f4a7541b217d009ee5c7da00f6 # via nbl -flox==0.9.9 +flox==0.9.13 # via nbl # via xarray -fonttools==4.53.1 +fonttools==4.54.1 # via matplotlib fqdn==1.5.1 # via jsonschema @@ -295,10 +285,8 @@ frozendict==2.4.4 frozenlist==1.4.1 # via aiohttp # via aiosignal -fsspec==2024.3.1 +fsspec==2024.6.1 # via dask - # via datasets - # via huggingface-hub # via lamindb # via lightning # via ome-zarr @@ -314,23 +302,27 @@ gitdb==4.0.11 gitpython==3.1.43 # via tach # via wandb -gotrue==2.6.1 +gotrue==2.8.1 + # via lamindb-setup # via supabase graphlib-backport==1.1.0 # via buckaroo graphviz==0.20.3 # via lamindb # via nbl -great-tables==0.10.0 +great-tables==0.12.0 # via nbl h11==0.14.0 # via httpcore # via uvicorn h2==4.1.0 # via httpx -h5py==3.11.0 +h5py==3.12.1 # via anndata + # via mudata # via scanpy +holoviews==1.19.1 + # via hvplot hpack==4.0.0 # via h2 html5lib==1.1 @@ -345,13 +337,13 @@ httpx==0.24.1 # via storage3 # via supabase # via supafunc -huggingface-hub==0.24.5 - # via datasets +hvplot==0.11.0 + # via nbl hyperframe==6.0.1 # via h2 -identify==2.6.0 +identify==2.6.1 # via pre-commit -idna==3.7 +idna==3.10 # via anyio # via apeye-core # via httpx @@ -361,12 +353,13 @@ idna==3.7 # via yarl igraph==0.11.6 # via flowsom -imageio==2.34.2 + # via leidenalg +imageio==2.35.1 # via pims # via scikit-image imagesize==1.4.1 # via sphinx -importlib-metadata==8.2.0 +importlib-metadata==8.5.0 # via dask # via doit # via great-tables @@ -375,7 +368,7 @@ importlib-metadata==8.2.0 # via myst-nb # via nbproject # via twine -importlib-resources==6.4.0 +importlib-resources==6.4.5 # via great-tables iniconfig==2.0.0 # via pytest @@ -387,12 +380,12 @@ ipykernel==6.29.5 # via nbl ipylab==1.0.0 # via nbl -ipython==8.26.0 +ipython==8.28.0 # via ipykernel # via ipywidgets # via myst-nb # via nbl -ipywidgets==8.1.3 +ipywidgets==8.1.5 # via anywidget # via buckaroo # via ipycytoscape @@ -401,24 +394,10 @@ isoduration==20.11.0 # via jsonschema jaraco-classes==3.4.0 # via keyring -jaraco-context==5.3.0 +jaraco-context==6.0.1 # via keyring -jaraco-functools==4.0.2 +jaraco-functools==4.1.0 # via keyring -jax==0.4.31 - # via equinox - # via jax-metal - # via nbl - # via somap -jax-metal==0.1.0 - # via nbl -jaxlib==0.4.31 - # via jax - # via jax-metal - # via nbl -jaxtyping==0.2.33 - # via equinox - # via somap jedi==0.19.1 # via ipython jinja2==3.1.4 @@ -454,7 +433,7 @@ jsonschema-specifications==2023.12.1 # via jsonschema jupyter-cache==1.0.0 # via myst-nb -jupyter-client==8.6.2 +jupyter-client==8.6.3 # via ipykernel # via jupyter-server # via nbclient @@ -489,30 +468,33 @@ jupyterlab-server==2.27.3 # via jupyterlab # via jupyterlite-sphinx # via notebook -jupyterlab-widgets==3.0.11 +jupyterlab-widgets==3.0.13 # via ipywidgets -jupyterlite-core==0.4.0 +jupyterlite-core==0.4.1 # via jupyterlite-pyodide-kernel # via jupyterlite-sphinx -jupyterlite-pyodide-kernel==0.4.1 +jupyterlite-pyodide-kernel==0.4.2 # via nbl jupyterlite-sphinx==0.16.5 # via nbl -keyring==25.3.0 +jupytext==1.16.4 + # via lamindb +keyring==25.4.1 # via twine -kiwisolver==1.4.5 +kiwisolver==1.4.7 # via matplotlib -lamin-cli==0.16.0 +lamin-cli==0.17.8 # via lamindb -lamin-utils==0.13.2 +lamin-utils==0.13.6 # via lamindb # via lamindb-setup # via nbproject # via readfcs -lamindb==0.75.1 +lamindb==0.76.11 # via bionty # via nbl -lamindb-setup==0.76.3 +lamindb-setup==0.77.7 + # via bionty # via lamindb latexcodec==3.0.0 # via pybtex @@ -522,16 +504,20 @@ legacy-api-wrap==1.4 # via scanpy legendkit==0.3.4 # via marsilea +leidenalg==0.10.2 + # via nbl lightning==2.4.0 # via nbl -lightning-utilities==0.11.6 +lightning-utilities==0.11.7 # via lightning # via pytorch-lightning # via torchmetrics +linkify-it-py==2.0.3 + # via panel llvmlite==0.43.0 # via numba # via pynndescent -lnschema-core==0.72.2 +lnschema-core==0.74.6 # via lamindb # via lamindb-setup locket==1.0.0 @@ -543,67 +529,68 @@ lsprotocol==2023.0.1 # via pygls lz4==4.3.3 # via dask +markdown==3.7 + # via panel markdown-it-py==3.0.0 + # via jupytext # via mdit-py-plugins # via myst-parser + # via panel # via rich markupsafe==2.1.5 # via jinja2 # via nbconvert # via sphinx-jinja2-compat -marsilea==0.4.4 +marsilea==0.4.6 # via nbl -matplotlib==3.9.1.post1 +matplotlib==3.9.2 # via flowsom # via legendkit # via marsilea # via matplotlib-scalebar # via nbl - # via pytometry # via scanpy # via seaborn - # via somap # via spatialdata-plot matplotlib-inline==0.1.7 # via ipykernel # via ipython matplotlib-scalebar==0.8.1 # via spatialdata-plot -mdit-py-plugins==0.4.1 +mdit-py-plugins==0.4.2 + # via jupytext # via myst-parser + # via panel mdurl==0.1.2 # via markdown-it-py mercantile==1.2.1 # via altair-tiles mistune==3.0.2 # via nbconvert -ml-dtypes==0.4.0 - # via jax - # via jaxlib -more-itertools==10.4.0 +more-itertools==10.5.0 # via cssutils # via jaraco-classes # via jaraco-functools mpmath==1.3.0 # via sympy -msgpack==1.0.8 +msgpack==1.1.0 # via cachecontrol # via distributed -mudata==0.3.0 +mudata==0.2.4 # via flowsom -multidict==6.0.5 +multidict==6.1.0 # via aiohttp # via yarl multipledispatch==1.0.0 # via datashader -multiprocess==0.70.16 - # via datasets multiscale-spatial-image==1.0.1 # via spatialdata -myst-nb==1.1.1 +myst-nb==1.1.2 # via nbl myst-parser==4.0.0 # via myst-nb +narwhals==1.9.0 + # via altair natsort==8.4.0 # via anndata # via domdf-python-tools @@ -622,16 +609,13 @@ nbformat==5.10.4 # via jupyter-cache # via jupyter-server # via jupyterlite-sphinx + # via jupytext # via myst-nb # via nb-clean # via nbclient # via nbconvert - # via nbstripout nbproject==0.10.4 # via lamindb - # via pytometry -nbstripout==0.6.1 - # via lamindb nest-asyncio==1.6.0 # via ipykernel networkx==3.3 @@ -661,7 +645,7 @@ numba==0.60.0 # via umap-learn # via xarray-einstats # via xarray-spatial -numbagg==0.8.1 +numbagg==0.8.2 # via xarray numcodecs==0.13.0 # via zarr @@ -670,28 +654,26 @@ numexpr==2.10.1 numpy==1.26.4 # via altair # via anndata - # via array2image # via bokeh # via bottleneck - # via colormap2d # via contourpy # via dask # via dask-image - # via datasets # via datashader # via einx + # via faiss-cpu # via fcsparser # via flowsom # via flox # via geopandas # via great-tables # via h5py + # via holoviews + # via hvplot # via imageio - # via jax - # via jaxlib # via marsilea # via matplotlib - # via ml-dtypes + # via mudata # via multiscale-spatial-image # via nbl # via numba @@ -701,13 +683,11 @@ numpy==1.26.4 # via numpy-groupies # via numpydantic # via ome-zarr - # via opt-einsum # via pandas # via patsy # via pims # via pyarrow # via pyogrio - # via pytometry # via scanpy # via scikit-image # via scikit-learn @@ -730,7 +710,7 @@ numpy==1.26.4 # via zarr numpy-groupies==0.11.2 # via flox -numpydantic==1.3.0 +numpydantic==1.6.3 # via nbl odfpy==1.4.1 # via pandas @@ -738,8 +718,7 @@ ome-zarr==0.9.0 # via spatialdata openpyxl==3.1.5 # via pandas -opt-einsum==3.3.0 - # via jax +opt-einsum==3.4.0 # via xarray orjson==3.10.7 # via nbproject @@ -751,24 +730,27 @@ packaging==24.1 # via bokeh # via buckaroo # via dask - # via datasets # via datashader # via deprecation # via distributed + # via faiss-cpu # via flox # via geopandas + # via holoviews # via htmltools - # via huggingface-hub + # via hvplot # via ipykernel # via jupyter-server # via jupyterlab # via jupyterlab-server + # via jupytext # via lazy-loader # via lightning # via lightning-utilities # via matplotlib # via nbconvert # via nbproject + # via panel # via pims # via pooch # via pydata-sphinx-theme @@ -782,23 +764,25 @@ packaging==24.1 # via torchmetrics # via xarray # via xarray-datatree -pandas==2.2.2 +pandas==2.2.3 # via altair # via anndata # via bokeh # via dask # via dask-expr # via dask-image - # via datasets # via datashader # via fcsparser # via flowsom # via flox # via geopandas + # via holoviews + # via hvplot # via lamindb # via marsilea + # via mudata # via nbl - # via pytometry + # via panel # via scanpy # via seaborn # via spatialdata @@ -808,9 +792,16 @@ pandas==2.2.2 # via xarray pandocfilters==1.5.1 # via nbconvert +panel==1.5.1 + # via holoviews + # via hvplot param==2.1.1 # via datashader + # via holoviews + # via hvplot + # via panel # via pyct + # via pyviz-comms parso==0.8.4 # via jedi partd==1.4.2 @@ -821,7 +812,6 @@ patsy==0.5.6 pexpect==4.9.0 # via ipython pillow==10.4.0 - # via array2image # via bokeh # via datashader # via imageio @@ -832,7 +822,7 @@ pims==0.7 pkginfo==1.10.0 # via jupyterlite-pyodide-kernel # via twine -platformdirs==4.2.2 +platformdirs==4.3.6 # via apeye # via esbonio # via jupyter-core @@ -848,14 +838,14 @@ postgrest==0.13.2 # via supabase pre-commit==3.8.0 # via nbl -prometheus-client==0.20.0 +prometheus-client==0.21.0 # via jupyter-server -prompt-toolkit==3.0.47 +prompt-toolkit==3.0.48 # via ipython # via tach pronto==2.5.5 # via bionty -protobuf==5.27.3 +protobuf==5.28.2 # via vegafusion # via wandb psutil==6.0.0 @@ -877,13 +867,10 @@ pyarrow==17.0.0 # via altair # via dask # via dask-expr - # via datasets # via lamindb + # via pandas # via spatialdata # via vegafusion -pyarrow-hotfix==0.6 - # via dask - # via datasets pybtex==0.24.0 # via pybtex-docutils # via sphinxcontrib-bibtex @@ -893,20 +880,19 @@ pycparser==2.22 # via cffi pyct==0.5.0 # via datashader -pydantic==2.8.2 +pydantic==2.9.2 # via gotrue # via nbproject # via numpydantic # via postgrest # via pydantic-settings - # via tach -pydantic-core==2.20.1 +pydantic-core==2.23.4 # via pydantic -pydantic-settings==2.4.0 +pydantic-settings==2.5.2 # via lamindb-setup pydata-sphinx-theme==0.15.4 # via sphinx-book-theme -pydot==2.0.0 +pydot==3.0.2 # via tach pygls==1.3.1 # via esbonio @@ -920,21 +906,22 @@ pygments==2.18.0 # via sphinx # via sphinx-prompt # via sphinx-tabs -pykan==0.2.4 +pykan==0.2.6 # via nbl pynndescent==0.5.13 # via scanpy + # via sklearn-ann # via umap-learn -pyogrio==0.9.0 +pyogrio==0.10.0 # via geopandas -pyparsing==3.1.2 +pyparsing==3.1.4 # via matplotlib # via pydot -pyproj==3.6.1 +pyproj==3.7.0 # via geopandas pyspellchecker==0.8.1 # via esbonio -pytest==8.3.2 +pytest==8.3.3 # via nbl python-calamine==0.2.3 # via pandas @@ -954,23 +941,23 @@ python-dotenv==1.0.1 # via pydantic-settings python-json-logger==2.0.7 # via jupyter-events -pytometry==0.1.4 - # via flowsom pytorch-lightning==2.4.0 # via lightning -pytz==2024.1 +pytz==2024.2 # via pandas +pyviz-comms==3.0.3 + # via holoviews + # via panel pyxlsb==1.0.10 # via pandas pyyaml==6.0.2 # via bionty # via bokeh # via dask - # via datasets # via distributed - # via huggingface-hub # via jupyter-cache # via jupyter-events + # via jupytext # via lightning # via myst-nb # via myst-parser @@ -980,15 +967,14 @@ pyyaml==6.0.2 # via pytorch-lightning # via tach # via wandb -pyzmq==26.1.0 +pyzmq==26.2.0 # via ipykernel # via jupyter-client # via jupyter-server -rapidfuzz==3.9.6 +rapidfuzz==3.10.0 # via lamindb readfcs==1.1.8 # via flowsom - # via pytometry readme-renderer==44.0 # via twine realtime==1.0.6 @@ -1001,12 +987,11 @@ requests==2.32.3 # via apeye # via bionty # via cachecontrol - # via datasets # via datashader - # via huggingface-hub # via jupyterlab-server # via lamindb-setup # via ome-zarr + # via panel # via pooch # via requests-toolbelt # via sphinx @@ -1023,7 +1008,7 @@ rfc3986==2.0.0 rfc3986-validator==0.1.1 # via jsonschema # via jupyter-events -rich==13.7.1 +rich==13.9.1 # via rich-click # via spatialdata # via tach @@ -1037,24 +1022,24 @@ ruamel-yaml==0.18.6 # via sphinx-toolbox ruamel-yaml-clib==0.2.8 # via ruamel-yaml -ruff==0.5.7 +ruff==0.6.8 # via nbl -s3fs==2024.3.1 +s3fs==2024.6.1 # via fsspec # via lamindb-setup s3transfer==0.10.2 # via boto3 -scanpy==1.10.2 +scanpy==1.10.3 # via flowsom - # via pytometry # via spatialdata-plot scikit-image==0.24.0 # via ome-zarr # via spatialdata -scikit-learn==1.5.1 +scikit-learn==1.5.2 # via flowsom # via pynndescent # via scanpy + # via sklearn-ann # via spatialdata-plot # via umap-learn scipy==1.12.0 @@ -1063,15 +1048,13 @@ scipy==1.12.0 # via datashader # via flowsom # via flox - # via jax - # via jaxlib # via lamindb # via marsilea # via pynndescent - # via pytometry # via scanpy # via scikit-image # via scikit-learn + # via sklearn-ann # via spatialdata # via statsmodels # via umap-learn @@ -1080,11 +1063,10 @@ scipy==1.12.0 seaborn==0.13.2 # via flowsom # via marsilea - # via pytometry # via scanpy send2trash==1.8.3 # via jupyter-server -sentry-sdk==2.12.0 +sentry-sdk==2.15.0 # via wandb session-info==1.0.0 # via flowsom @@ -1092,12 +1074,12 @@ session-info==1.0.0 # via scanpy setproctitle==1.3.3 # via wandb -setuptools==72.1.0 +setuptools==75.1.0 # via lightning-utilities # via nbl # via spatialdata # via wandb -shapely==2.0.5 +shapely==2.0.6 # via geopandas # via spatialdata six==1.16.0 @@ -1105,11 +1087,12 @@ six==1.16.0 # via bleach # via docker-pycreds # via html5lib - # via jax-metal # via patsy # via pybtex # via python-dateutil # via rfc3339-validator +sklearn-ann==0.1.2 + # via nbl slicerator==1.1.0 # via pims smmap==5.0.1 @@ -1120,19 +1103,17 @@ sniffio==1.3.1 # via httpx snowballstemmer==2.2.0 # via sphinx -somap==0.1.1 - # via nbl sortedcontainers==2.4.0 # via distributed -soupsieve==2.5 +soupsieve==2.6 # via beautifulsoup4 spatial-image==1.1.0 # via multiscale-spatial-image # via spatialdata -spatialdata @ git+https://github.com/srivarra/spatialdata@a193a256376552db935e735a94ea5be646eb7506 +spatialdata @ git+https://github.com/srivarra/spatialdata@ebed847007cc96d41f2cb39ab46b7471acc0fecd # via nbl # via spatialdata-plot -spatialdata-plot==0.2.4 +spatialdata-plot @ git+https://github.com/scverse/spatialdata-plot@6ffe22bb57748fb553807761e9771ff9ff1b015f # via nbl spectate==1.0.1 # via ipycytoscape @@ -1154,9 +1135,9 @@ sphinx==8.0.2 # via sphinx-toolbox # via sphinxcontrib-bibtex # via sphinxext-opengraph -sphinx-autobuild==2024.4.16 +sphinx-autobuild==2024.10.3 # via nbl -sphinx-autodoc-typehints==2.2.3 +sphinx-autodoc-typehints==2.4.4 # via nbl # via sphinx-toolbox sphinx-autosummary-accessors==2023.4.0 @@ -1171,11 +1152,11 @@ sphinx-prompt==1.9.0 # via sphinx-toolbox sphinx-tabs==3.4.5 # via sphinx-toolbox -sphinx-toolbox==3.7.0 +sphinx-toolbox==3.8.0 # via nbl sphinxcontrib-applehelp==2.0.0 # via sphinx -sphinxcontrib-bibtex==2.6.2 +sphinxcontrib-bibtex==2.6.3 # via nbl sphinxcontrib-devhelp==2.0.0 # via sphinx @@ -1189,15 +1170,15 @@ sphinxcontrib-serializinghtml==2.0.0 # via sphinx sphinxext-opengraph==0.9.1 # via nbl -sqlalchemy==2.0.32 +sqlalchemy==2.0.35 # via jupyter-cache sqlparse==0.5.1 # via django stack-data==0.6.3 # via ipython -starlette==0.38.2 +starlette==0.39.2 # via sphinx-autobuild -statsmodels==0.14.2 +statsmodels==0.14.4 # via scanpy stdlib-list==0.10.0 # via session-info @@ -1209,13 +1190,13 @@ supabase==2.2.1 # via lamindb-setup supafunc==0.3.3 # via supabase -sympy==1.13.1 +sympy==1.13.3 # via einx # via torch tabulate==0.9.0 # via jupyter-cache # via sphinx-toolbox -tach==0.10.0 +tach==0.12.0 # via nbl tblib==3.0.0 # via distributed @@ -1226,23 +1207,24 @@ texttable==1.7.0 # via igraph threadpoolctl==3.5.0 # via scikit-learn -tifffile==2024.7.24 +tifffile==2024.9.20 # via dask-image # via pims # via scikit-image tinycss2==1.3.0 # via nbconvert +tomli==2.0.2 + # via tach tomli-w==1.0.0 # via tach toolz==0.12.1 - # via altair # via dask # via datashader # via distributed # via flox # via ome-zarr # via partd -torch==2.4.0 +torch==2.4.1 # via lightning # via nbl # via pytorch-lightning @@ -1250,7 +1232,7 @@ torch==2.4.0 # via torchmetrics torchdata==0.8.0 # via nbl -torchmetrics==1.4.1 +torchmetrics==1.4.2 # via lightning # via pytorch-lightning tornado==6.4.1 @@ -1263,9 +1245,8 @@ tornado==6.4.1 # via notebook # via terminado tqdm==4.66.5 - # via datasets - # via huggingface-hub # via lightning + # via panel # via pytorch-lightning # via scanpy # via umap-learn @@ -1283,27 +1264,26 @@ traitlets==5.14.3 # via nbclient # via nbconvert # via nbformat -treescope==0.1.1 +treescope==0.1.5 # via nbl twine==5.1.1 # via nbl -typeguard==2.13.3 - # via jaxtyping -types-python-dateutil==2.9.0.20240316 +types-python-dateutil==2.9.0.20241003 # via arrow typing-extensions==4.12.2 + # via altair # via anywidget # via dj-database-url # via domdf-python-tools - # via equinox + # via faker # via great-tables # via htmltools - # via huggingface-hub # via ipython # via lamindb # via lightning # via lightning-utilities # via myst-nb + # via panel # via pydantic # via pydantic-core # via pydata-sphinx-theme @@ -1316,16 +1296,18 @@ typing-extensions==4.12.2 # via storage3 # via torch # via xarray-dataclasses -tzdata==2024.1 +tzdata==2024.2 # via pandas +uc-micro-py==1.0.3 + # via linkify-it-py umap-learn==0.5.6 # via scanpy -universal-pathlib==0.2.2 +universal-pathlib==0.2.5 # via lamindb-setup # via nbl uri-template==1.3.0 # via jsonschema -urllib3==1.26.19 +urllib3==1.26.20 # via botocore # via distributed # via lamindb-setup @@ -1334,7 +1316,7 @@ urllib3==1.26.19 # via sphinx-prompt # via torchdata # via twine -uvicorn==0.30.5 +uvicorn==0.31.0 # via sphinx-autobuild vega-datasets==0.9.0 # via altair @@ -1342,19 +1324,18 @@ vegafusion==1.6.9 # via altair vegafusion-python-embed==1.6.9 # via vegafusion -virtualenv==20.26.3 +virtualenv==20.26.6 # via pre-commit -vl-convert-python==1.6.0 +vl-convert-python==1.6.1 # via altair - # via somap # via vegafusion -wandb==0.17.6 +wandb==0.18.3 # via nbl -watchfiles==0.23.0 +watchfiles==0.24.0 # via sphinx-autobuild wcwidth==0.2.13 # via prompt-toolkit -webcolors==24.6.0 +webcolors==24.8.0 # via jsonschema webencodings==0.5.1 # via bleach @@ -1365,13 +1346,11 @@ websocket-client==1.8.0 websockets==12.0 # via realtime # via sphinx-autobuild -wheel==0.44.0 - # via jax-metal -widgetsnbextension==4.0.11 +widgetsnbextension==4.0.13 # via ipywidgets wrapt==1.16.0 # via aiobotocore -xarray==2024.7.0 +xarray==2024.9.0 # via datashader # via multiscale-spatial-image # via nbl @@ -1388,26 +1367,24 @@ xarray-dataclasses==1.8.0 xarray-datatree==0.0.14 # via multiscale-spatial-image # via spatialdata -xarray-einstats==0.7.0 +xarray-einstats==0.8.0 # via nbl xarray-schema==0.0.3 # via spatialdata xarray-spatial==0.4.0 # via spatialdata -xbatcher==0.3.0 +xbatcher==0.4.0 # via nbl xlrd==2.0.1 # via pandas xlsxwriter==3.2.0 # via pandas -xxhash==3.4.1 - # via datasets -xyzservices==2024.6.0 +xyzservices==2024.9.0 # via altair-tiles # via bokeh -yarl==1.9.4 +yarl==1.13.1 # via aiohttp -zarr==2.18.2 +zarr==2.18.3 # via lamindb # via multiscale-spatial-image # via numpydantic @@ -1415,5 +1392,5 @@ zarr==2.18.2 # via spatialdata zict==3.0.0 # via distributed -zipp==3.19.2 +zipp==3.20.2 # via importlib-metadata diff --git a/src/nbl/pl/basic.py b/src/nbl/pl/basic.py index ed390ef..a6077c2 100644 --- a/src/nbl/pl/basic.py +++ b/src/nbl/pl/basic.py @@ -61,3 +61,6 @@ def my_other_method(self, param: str) -> str: """ print("Implement a method here.") return "" + + +# def diff --git a/src/nbl/pp/__init__.py b/src/nbl/pp/__init__.py index 865f0c6..18d04bd 100644 --- a/src/nbl/pp/__init__.py +++ b/src/nbl/pp/__init__.py @@ -1,3 +1,3 @@ -from .basic import arcsinh_transform +from .basic import arcsinh_transform, neighbors, normalize_by_area -__all__ = ["arcsinh_transform"] +__all__ = ["arcsinh_transform", "normalize_by_area", "neighbors"] diff --git a/src/nbl/pp/_utils.py b/src/nbl/pp/_utils.py new file mode 100644 index 0000000..e69de29 diff --git a/src/nbl/pp/basic.py b/src/nbl/pp/basic.py index f937749..4126370 100644 --- a/src/nbl/pp/basic.py +++ b/src/nbl/pp/basic.py @@ -1,18 +1,22 @@ +from collections.abc import Mapping, Sequence +from typing import Any, Literal, TypedDict, Unpack + import anndata as ad import numpy as np +import scanpy as sc import spatialdata as sd -from numpy.typing import NDArray +from numpydantic import NDArray from scipy import sparse -from nbl.util import write_elements +from nbl.util import _extract_layer_from_sdata, write_elements def arcsinh_transform( sdata: sd.SpatialData, table_names: str | list[str] = None, - shift_factor: np.integer | np.floating = 0, - scale_factor: np.integer | np.floating = 150, - replace_X: bool = False, + shift_factor: int | float | Mapping[str, int | float] = 0, + scale_factor: int | float | Mapping[str, int | float] = 150, + method: Literal["replace", "layer", "new table"] = "new table", write: bool = True, inplace: bool = True, ) -> None | sd.SpatialData: @@ -20,25 +24,24 @@ def arcsinh_transform( Parameters ---------- - sdata : sd.SpatialData + sdata The SpatialData object containing the data. - table_names : str | list[str], optional + table_names The name(s) of the table(s) to transform. If None, all tables will be transformed, by default None. - shift_factor : np.integer | np.floating, optional + shift_factor The shift factor to be added to the data before applying the transformation, by default 0. - scale_factor : np.integer | np.floating, optional + scale_factor The scale factor to be multiplied with the data after applying the transformation, by default 150. - replace_X : bool, optional - Whether to replace the original data in `AnnData.X` with the transformed data, by default False. - write : bool, optional + method + Whether to replace the original in `AnnData.X` with the transformed data, to add it as a new layer, or to create a new table, by default "new table". + write Whether to write the transformed tables back to the SpatialData object's Zarr store, by default True. - inplace : bool, optional + inplace Whether to modify the SpatialData object in-place or return a new object, by default True. Returns ------- - None | sd.SpatialData - If inplace is `True`, returns `None`. If inplace is `False`, returns the modified `SpatialData` object. + If inplace is `True`, returns `None`. If inplace is `False`, returns the modified `SpatialData` object. Notes @@ -62,15 +65,150 @@ def arcsinh_transform( for table in table_keys: adata: ad.AnnData = sdata.tables[table] if sparse.issparse(adata.X): - transformed_X: NDArray = sparse.csr_matrix(np.arcsinh(shift_factor + (adata.X.toarray() * scale_factor))) + transformed_X: NDArray = np.arcsinh(shift_factor + (adata.X.toarray() * scale_factor)) else: - transformed_X: NDArray = sparse.csr_matrix(np.arcsinh(shift_factor + (adata.X * scale_factor))) + transformed_X: NDArray = np.arcsinh(shift_factor + (adata.X * scale_factor)) - if replace_X: + if method == "replace": adata.X = transformed_X - else: + elif method == "layer": adata.layers[f"arcsinh_shift_{shift_factor}_scale_{scale_factor}"] = transformed_X + elif method == "new table": + new_table: ad.AnnData = adata.copy() + new_table.X = transformed_X + sdata.tables[f"arcsinh_shift_{shift_factor}_scale_{scale_factor}"] = new_table + + if write: + if method in ["replace", "layer"]: + write_elements(sdata=sdata, elements={"tables": table_keys}) + elif method == "new table": + write_elements(sdata=sdata, elements={"tables": [f"arcsinh_shift_{shift_factor}_scale_{scale_factor}"]}) + return None if inplace else sdata + + +def normalize_by_area( + sdata: sd.SpatialData, + table_names: str | list[str] = None, + method: Literal["replace", "layer", "new table"] = "new table", + inplace: bool = True, + write: bool = True, +): + """Normalize the data by the area of each cell. + + Parameters + ---------- + sdata + The SpatialData object. + table_names + The name(s) of the table(s) to transform. If None, all tables will be transformed, by default None. + inplace + Whether to modify the SpatialData object in-place or return a new object, by default True. + write + Whether to write the transformed tables back to the SpatialData object's Zarr store, by default True. + + Returns + ------- + The updated SpatialData object if inplace is False, otherwise None. + """ + table_keys = [table_names] if isinstance(table_names, str) else table_names + + for table in table_keys: + adata: ad.AnnData = sdata.tables[table] + area_normalized_X = sparse.csr_matrix(adata.X / adata.obs["area"].to_numpy().reshape(-1, 1)) + + if method == "replace": + adata.X = area_normalized_X + elif method == "layer": + adata.layers["area_normalized"] = area_normalized_X + elif method == "new table": + new_table = adata.copy() + new_table.X = area_normalized_X + sdata.tables["area_normalized"] = new_table + return None if inplace else sdata + + +class NeighborsKwargs(TypedDict): + """Keyword arguments for the Neighbors function from Scanpy.""" + + n_neighbors: int + n_pcs: int + use_rep: str + knn: bool = True + method: Literal["umap", "gauss"] = "umap" + transformer: str | None | Any = None + metric: Literal[ + "cityblock", + "cosine", + "euclidean", + "l1", + "l2", + "manhattan", + "braycurtis", + "canberra", + "chebyshev", + "correlation", + "dice", + "hamming", + "jaccard", + "kulsinski", + "mahalanobis", + "minkowski", + "rogerstanimoto", + "russellrao", + "seuclidean", + "sokalmichener", + "sokalsneath", + "sqeuclidean", + "yule", + ] = "euclidean" + metric_kwds: Mapping + random_state: int | None | Any = 0 + + +def neighbors( + sdata: sd.SpatialData, + table_name: str, + layer: str | None = None, + vars: Sequence[str] | None = None, + key_added: str | None = None, + inplace: bool = True, + write: bool = True, + **sc_neighbors_kwargs: Unpack[NeighborsKwargs], +) -> sd.SpatialData: + """Computes the neighbors of a table in a SpatialData object. + + Parameters + ---------- + sdata + _description_ + table_name + _description_ + layer, optional + _description_, by default None + vars, optional + _description_, by default None + key_added, optional + _description_, by default None + inplace, optional + _description_, by default True + write, optional + _description_, by default True + + Returns + ------- + _description_ + """ + q: ad.AnnData = sc.pp.neighbors( + adata=_extract_layer_from_sdata(sdata=sdata, vars=vars, table_name=table_name, layer=layer), + key_added=key_added, + copy=True, + **sc_neighbors_kwargs, + ) + + sdata.tables[table_name].obsp[f"{key_added}_connectivities"] = q.obsp[f"{key_added}_connectivities"] + sdata.tables[table_name].obsp[f"{key_added}_distances"] = q.obsp[f"{key_added}_distances"] + sdata.tables[table_name].uns[key_added] = q.uns[key_added] if write: - write_elements(sdata=sdata, elements={"tables": table_keys}) + write_elements(sdata=sdata, elements={"tables": table_name}) return None if inplace else sdata diff --git a/src/nbl/tl/__init__.py b/src/nbl/tl/__init__.py index 262661a..c1c2764 100644 --- a/src/nbl/tl/__init__.py +++ b/src/nbl/tl/__init__.py @@ -1,3 +1,25 @@ -from .basic import aggregate_images_by_labels, filter_obs_names_by_quantile, quantile, regionprops +from ._utils import _scores +from .basic import ( + aggregate_images_by_labels, + compute_marker_means, + compute_score, + diffmap, + filter_obs_names_by_quantile, + leiden, + quantile, + regionprops, + umap, +) -__all__ = ["aggregate_images_by_labels", "regionprops", "quantile", "filter_obs_names_by_quantile"] +__all__ = [ + "_scores", + "aggregate_images_by_labels", + "compute_marker_means", + "compute_score", + "diffmap", + "filter_obs_names_by_quantile", + "leiden", + "quantile", + "regionprops", + "umap", +] diff --git a/src/nbl/tl/_utils.py b/src/nbl/tl/_utils.py index c3e9060..7cd48ae 100644 --- a/src/nbl/tl/_utils.py +++ b/src/nbl/tl/_utils.py @@ -7,7 +7,7 @@ import spatialdata as sd import xarray as xr from more_itertools import one -from numpy.typing import NDArray +from numpydantic import NDArray from skimage.measure import regionprops_table from spatialdata.models import X, Y @@ -73,8 +73,8 @@ def _rp_stats_table_fov( - It uses xarray's `apply_ufunc` to parallelize the calculation. - The computed properties are merged with the existing table in the SpatialData object. """ - n_obs = sdata.tables[table_name].n_obs - coord = one(sdata.coordinate_systems) + n_obs: int = sdata.tables[table_name].n_obs + coord: str = one(sdata.coordinate_systems) n_properties = len(properties_names) rp = xr.apply_ufunc( @@ -161,3 +161,87 @@ def _strip_var_names(sdata: sd.SpatialData, table_name: str, agg_func: str) -> s var.removeprefix("channel_").removesuffix(f"_{agg_func}") for var in sdata.tables[table_name].var_names ] return sdata + + +def _ratio_score(x1: NDArray, x2: NDArray, eps: float = 1e-10) -> NDArray: + """Computes the ratio score between two arrays. + + Parameters + ---------- + x1 + The first array. + x2 + The second array. + + Returns + ------- + NDArray + The ratio score between the two arrays. + """ + return x1 / (x2 + eps) + + +def _normalized_difference_score(x1: NDArray, x2: NDArray, eps: float = 1e-10) -> NDArray: + """Computes the normalized difference score between two arrays. + + Parameters + ---------- + x1 + The first array. + x2 + The second array. + eps + The epsilon value to use for numerical stability, by default 1e-10. + + Returns + ------- + The normalized difference score between the two arrays. + """ + return (x1 - x2) / (x1 + x2 + eps) + + +def _log_ratio_score(x1: NDArray, x2: NDArray, eps: float = 1e-10) -> NDArray: + """Computes the log ratio score between two arrays. + + Parameters + ---------- + x1 + The first array. + x2 + The second array. + eps + The epsilon value to use for numerical stability, by default 1e-10. + + Returns + ------- + The log ratio score between the two arrays. + """ + return np.log((x1 + eps) / (x2 + eps)) + + +def _scaled_difference_score(x1: NDArray, x2: NDArray, eps: float = 1e-10) -> NDArray: + """Computes the scaled difference score between two arrays. + + Parameters + ---------- + x1 + The first array. + x2 + The second array. + eps + The epsilon value to use for numerical stability, by default 1e-10. + + Returns + ------- + The scaled difference score between the two arrays. + """ + return (x1 - x2) / (np.abs(x1 - x2) + eps) + + +_scores = { + "ratio": _ratio_score, + "normalized_difference": _normalized_difference_score, + "log_ratio": _log_ratio_score, + "scaled_difference": _scaled_difference_score, +} +"""Mapping of score functions to their corresponding names.""" diff --git a/src/nbl/tl/basic.py b/src/nbl/tl/basic.py index 2306ecf..130886a 100644 --- a/src/nbl/tl/basic.py +++ b/src/nbl/tl/basic.py @@ -1,14 +1,22 @@ +from collections.abc import Mapping, Sequence from itertools import chain +from typing import Any, Literal, TypedDict, Unpack import anndata as ad import natsort as ns import numpy as np +import pandas as pd +import scanpy as sc import spatialdata as sd from dask import delayed +from leidenalg.VertexPartition import MutableVertexPartition +from numpydantic import NDArray -from nbl.tl._utils import _rp_stats_table_fov, _set_unique_obs_names, _strip_var_names from nbl.util import DaskSetupDelayed from nbl.util.decorators import catch_warnings +from nbl.util.utils import _extract_layer_from_sdata + +from ._utils import _rp_stats_table_fov, _scores, _set_unique_obs_names, _strip_var_names @catch_warnings @@ -27,28 +35,27 @@ def aggregate_images_by_labels( Parameters ---------- - sdata : sd.SpatialData + sdata A SpatialData object. - label_type : str + label_type The type of label used to identify regions of interest (e.g., 'whole_cell', 'nucleus'). - table_name : str, optional + table_name The name of the table within the SpatialData object where the aggregated data will be stored. Default is None. - region_key : str, optional + region_key The key that represents regions in the concatenated data. Default is "region". - instance_key : str, optional + instance_key The key that uniquely identifies instances within the label type. Default is "instance_id". - agg_func : str, optional + agg_func The aggregation function to be applied (e.g., 'sum', 'mean'). Default is "sum". - inplace : bool, optional + inplace If True, modifies the input SpatialData object in place. Otherwise, returns a new SpatialData object. Default is True. - write : bool, optional + write If True, writes the updated table to disk. Default is True. Returns ------- - None | sd.SpatialData - The updated SpatialData object if inplace is False, otherwise None. + The updated SpatialData object if inplace is False, otherwise None. Notes ----- @@ -72,17 +79,15 @@ def aggregate_images_by_labels( region_key=region_key, instance_key=instance_key, ) - t1 = t1.persist() - t2_t3 = delayed(_strip_var_names)( - sdata=delayed(_set_unique_obs_names)(sdata=t1, coord=coord, table_name=table_name), - table_name=table_name, - agg_func=agg_func, - ) - _tasks.append(t2_t3) + t2 = delayed(_set_unique_obs_names)(sdata=t1, coord=coord, table_name=table_name) + t3 = delayed(_strip_var_names)(sdata=t2, table_name=table_name, agg_func=agg_func) + _tasks.append(t3) + # Initialize Dask Runner and compute tasks dask_runner = DaskSetupDelayed(delayed_objects=_tasks) _sdatas = dask_runner.compute() + # Concatenate the results and update the sdata table adata = sd.concatenate( sdatas=_sdatas, region_key=region_key, instance_key=instance_key, concatenate_tables=True ).tables[table_name] @@ -191,64 +196,357 @@ def regionprops( return None if inplace else sdata -def quantile(adata: ad.AnnData, var: str, q: float, filter_adata: bool = False, **kwargs) -> np.floating | ad.AnnData: +def quantile( + sdata: sd.SpatialData, + table_name: str, + var: str, + q: float | list[float], + layer: str | None = None, + inplace: bool = True, + write: bool = True, +) -> sd.SpatialData | None: """Computes the qth quantile of a particular `var` in the `AnnData` object. Parameters ---------- - adata : ad.AnnData - The `AnnData` object. - var : str - The variable to compute the quantile for. - q : float - Percentage or sequence of percentages for the percentiles to compute. - Must be between 0 and 1 inclusive. - filter_adata: bool - Whether or not to filter the `obs_names` by the value `v` associated to the qth quantile. - kwargs: Mapping[str, Any] - All other keyword arguments are passed to `np.quantile` + sdata + The SpatialData object. + table_name + The name of the table within the SpatialData object. + layer + The name of the layer within the AnnData table object. + var + The variable / marker to compute the quantile for. + q + The quantile to compute. + inplace, optional + If True modifies the input SpatialData object in place. Otherwise, returns a new SpatialData object. + Default is True. + write, optional + If True, writes the updated table to disk. Default is True. Returns ------- - np.floating - The value corresponding to the qth quantile. + The updated SpatialData object if inplace is False, otherwise None. """ - v: np.floating = np.quantile(a=adata[:, var].X.toarray().squeeze(), q=q, **kwargs) + table: ad.AnnData = sdata.tables[table_name] + + if layer: + v: np.floating = np.quantile(a=table[:, var].layers[layer].toarray().squeeze(), q=q) + else: + v: np.floating = np.quantile(a=table[:, var].X.toarray().squeeze(), q=q) + _add_quantile_to_uns(adata=table, var=var, q=q, v=v) + + if write: + from nbl.util import write_elements + + write_elements(sdata=sdata, elements={"tables": table_name}) + + return None if inplace else sdata + + +def _add_quantile_to_uns(adata: ad.AnnData, var: str, q: float | list[float], v: float | list[float]) -> None: if "quantiles" not in adata.uns.keys(): adata.uns["quantiles"] = {} if var not in adata.uns["quantiles"].keys(): adata.uns["quantiles"] = {var: {}} - adata.uns["quantiles"][var].update({"quantile": q, "value": v}) - if filter_adata: - filtered_adata: ad.AnnData = filter_obs_names_by_quantile(adata=adata, var=var, v=v, copy=False) - return filtered_adata - else: - return v + for q_val, v_val in zip(q, v, strict=False): + adata.uns["quantiles"][var].update({f"{q_val}": v_val}) -def filter_obs_names_by_quantile(adata: ad.AnnData, var: str, v: float, copy: bool = False) -> ad.AnnData: +def filter_obs_names_by_quantile( + sdata: sd.SpatialData, + table_name: str, + var: str, + q: float, + layer: str | None = None, + method: Literal["lower", "upper"] = "lower", +) -> ad.AnnData: """Filters the `obs_names` index based on the value (v) corresponding to the qth quantile. - Given an AnnData object A (n_obs x n_vars) we can select the observations with a value greater than or equal to v. - Parameters ---------- - adata : ad.AnnData - The `AnnData` object. - var : str + sdata + The SpatialData object. + table_name + The name of the table within the SpatialData object. + var The variable to filter on. - v : float - The value to filter by. - copy: bool - Whether or not to make a full copy of the subseted AnnData object + q + The quantile to filter on. + layer + The name of the layer within the AnnData table object. + method + The method to use for filtering. Either "lower" or "upper". Default is "lower". + + Returns + ------- + A filtered AnnData object. + """ + table: ad.AnnData = sdata.tables[table_name] + v: float = table.uns["quantiles"][var][f"{q}"] + if layer: + X: NDArray = table[:, var].layers[layer].toarray().squeeze() + else: + X: NDArray = table[:, var].X.toarray().squeeze() + + if method == "lower": + filtered_obs_names: pd.Index = table.obs_names[X < v] + elif method == "upper": + filtered_obs_names: pd.Index = table.obs_names[X >= v] + + subset_table: ad.AnnData = table[filtered_obs_names, :] + sdata.update_annotated_regions_metadata(table=subset_table) + return subset_table + + +def compute_marker_means( + element: sd.SpatialData | ad.AnnData, + table_name: str, + marker_groups: Mapping[str, str | list[str]], + layer: str | None = None, + inplace: bool = False, +) -> sd.SpatialData | ad.AnnData: + """Computes the mean expression for each marker group. + + Parameters + ---------- + element + The SpatialData or AnnData object. + table_name + The name of the table within the SpatialData object. + marker_groups + A mapping of marker groups to marker names. + layer + The name of the layer within the AnnData table object. + inplace + If True modifies the input SpatialData object in place. Otherwise, returns a new SpatialData object. + Default is False. Returns ------- - ad.AnnData - A subset of the `AnnData` object. + The updated SpatialData object if inplace is False, otherwise None. """ - filtered_idx = adata[:, var].X.toarray().squeeze() >= v - if copy: - return adata[filtered_idx, :].copy() + if isinstance(element, sd.SpatialData): + table: ad.AnnData = element.tables[table_name] else: - return adata[filtered_idx, :] + table: ad.AnnData = element + + for marker_group, markers in marker_groups.items(): + if layer: + X: NDArray = table[:, markers].layers[layer].toarray().squeeze() + else: + X: NDArray = table[:, markers].X.toarray().squeeze() + mean_score: NDArray = np.mean(X, axis=1) + table.obs[f"{marker_group}_mean"] = mean_score + + return None if inplace else element + + +def compute_score( + sdata: sd.SpatialData, + table_name: str, + obs_1: str, + obs_2: str, + score_method: str, + score_col_name: str | None = None, + inplace: bool = False, + eps: float = 1e-10, +) -> sd.SpatialData | None: + """Computes a score from two observation columns in a given table of the SpatialData object. + + Parameters + ---------- + sdata + The SpatialData object. + table_name + The name of the table within the SpatialData object. + obs_1 + The name of the first observation column. + obs_2 + The name of the second observation column. + score_method + The name of the score method to use. Options are "ratio", "normalized_difference", "log_ratio", "scaled_difference". + score_col_name + The name of the score column to use. If None, defaults to "{score_method}_{obs_1}_{obs_2}_score". + inplace + If True modifies the input SpatialData object in place. Otherwise, returns a new SpatialData object. + Default is False. + eps + The epsilon value to use for numerical stability, by default 1e-10. + + Returns + ------- + The updated SpatialData object if inplace is False, otherwise None. + """ + table: ad.AnnData = sdata.tables[table_name] + _score_func = _scores[score_method] + if score_col_name is None: + score_col_name = f"{score_method}_{obs_1}_{obs_2}_score" + table.obs[score_col_name] = _score_func(x1=table.obs[obs_1], x2=table.obs[obs_2], eps=eps) + return None if inplace else sdata + + +class DiffmapKwargs(TypedDict): + """Keyword arguments for the Diffmap function from Scanpy.""" + + n_comps: int = 2 + random_state: int | None | Any = 0 + + +def diffmap( + sdata: sd.SpatialData, + table_name: str, + layer: str | None = None, + neighbors_key: str | None = None, + vars: Sequence[str] | None = None, + inplace: bool = True, + write: bool = True, + **sc_diffmap_kwargs: Unpack[DiffmapKwargs], +) -> None | sd.SpatialData: + """Computes the diffusion maps for a given table in the SpatialData object. + + Parameters + ---------- + sdata + _description_ + table_name + _description_ + layer, optional + _description_, by default None + neighbors_key, optional + _description_, by default None + inplace, optional + _description_, by default True + write, optional + _description_, by default True + + Returns + ------- + _description_ + """ + table: ad.AnnData = _extract_layer_from_sdata(sdata=sdata, vars=vars, table_name=table_name, layer=layer) + + q: ad.AnnData = sc.tl.diffmap(adata=table, neighbors_key=neighbors_key, copy=True, **sc_diffmap_kwargs) + + sdata.tables[table_name].obsm[f"{layer}_diffmap"] = q.obsm["X_diffmap"] + sdata.tables[table_name].uns[f"{layer}_diffmap_evals"] = q.uns["diffmap_evals"] + + return None if inplace else sdata + + +class LeidenKwargs(TypedDict): + """Keyword arguments for the Leiden function from Scanpy.""" + + resolution: float = 1 + restrict_to: tuple[str, Sequence[str]] | None = None + random_state: int | None | Any = 0 + adjacency: Any | None = None + directed: bool | None = None + use_weights: bool = True + n_iterations: int = -1 + partition_type: type[MutableVertexPartition] | None = None + flavor: Literal["leidenalg", "igraph"] = "leidenalg" + + +def leiden( + sdata: sd.SpatialData, + table_name: str, + layer: str, + neighbors_key: str, + key_added: str, + vars: Sequence[str] | None = None, + inplace: bool = True, + write: bool = True, + **sc_leiden_kwargs: Unpack[LeidenKwargs], +) -> None | sd.SpatialData: + """Compute Leiden clustering algorithm for a given table in the SpatialData object. + + Parameters + ---------- + sdata + _description_ + table_name + _description_ + layer + _description_ + neighbors_key + _description_ + key_added + _description_ + vars, optional + _description_, by default None + inplace, optional + _description_, by default True + write, optional + _description_, by default True + + Returns + ------- + _description_ + """ + table: ad.AnnData = _extract_layer_from_sdata(sdata=sdata, vars=vars, table_name=table_name, layer=layer) + q: ad.AnnData = sc.tl.leiden( + adata=table, key_added=key_added, neighbors_key=neighbors_key, copy=True, **sc_leiden_kwargs + ) + + sdata.tables[table_name].obs = sdata.tables[table_name].obs.merge( + right=q.obs[key_added], left_index=True, right_index=True + ) + sdata.tables[table_name].uns[key_added] = q.uns[key_added] + + return None if inplace else sdata + + +class UmapKwargs(TypedDict): + """Keyword arguments for the Umap function from Scanpy.""" + + min_dist: float = 0.5 + spread: float = 1 + n_components: int = 2 + maxiter: int | None = None + alpha: float = 1 + gamma: float = 1 + negative_sample_rate: int = 5 + init_pos: NDArray | None = "spectral" + random_state: Any = 0 + a: float | None = None + b: float | None = None + + +def umap( + sdata: sd.SpatialData, + table_name: str, + layer: str, + neighbors_key: str, + vars: Sequence[str] | None = None, + inplace: bool = True, + write: bool = True, + **sc_umap_kwargs: Unpack[UmapKwargs], +) -> ad.AnnData | None: + """Compute UMAP embeddings for a given table in the SpatialData object. + + Parameters + ---------- + sdata + _description_ + table_name + _description_ + layer + _description_ + neighbors_key + _description_ + vars, optional + _description_, by default None + inplace, optional + _description_, by default True + write, optional + _description_, by default True + + Returns + ------- + _description_ + """ + table = _extract_layer_from_sdata(sdata=sdata, vars=vars, table_name=table_name, layer=layer) + q = sc.tl.umap(adata=table, neighbors_key=neighbors_key, copy=True, **sc_umap_kwargs) + return q diff --git a/src/nbl/util/__init__.py b/src/nbl/util/__init__.py index f47b3cf..29b07c5 100644 --- a/src/nbl/util/__init__.py +++ b/src/nbl/util/__init__.py @@ -1,6 +1,6 @@ from .dask import DaskLocalCluster, DaskSetupDelayed from .decorators import catch_warnings, check_inplace, deprecation_alias, path_alias -from .utils import remove_ticks, reset_table_index, write_elements +from .utils import _extract_layer_from_sdata, remove_ticks, reset_table_index, write_elements __all__: list[str] = [ "DaskLocalCluster", @@ -12,4 +12,5 @@ "catch_warnings", "deprecation_alias", "path_alias", + "_extract_layer_from_sdata", ] diff --git a/src/nbl/util/utils.py b/src/nbl/util/utils.py index 85e8429..2a14939 100644 --- a/src/nbl/util/utils.py +++ b/src/nbl/util/utils.py @@ -1,4 +1,4 @@ -from collections.abc import Iterable, Mapping +from collections.abc import Iterable, Mapping, Sequence from typing import Literal import anndata as ad @@ -56,12 +56,19 @@ def _write_element( in_zarr_store : list[str] _description_ """ + if element_type == "tables": + _update_and_sync_table(sdata=sdata, table_name=element_name) + if f"{element_type}/{element_name}" in not_in_zarr_store and not in_zarr_store: sdata.write_element(element_name=element_name, overwrite=False) else: _replace_element(sdata=sdata, element_name=element_name) +def _update_and_sync_table(sdata: sd.SpatialData, table_name: str): + sdata.tables[table_name] = sdata.update_annotated_regions_metadata(table=sdata.tables[table_name]) + + def write_elements( sdata: sd.SpatialData, elements: Mapping[str, str | list[str]], @@ -95,6 +102,7 @@ def write_elements( not_in_zarr_store=not_in_zarr_store, in_zarr_store=_in_zarr_store, ) + sdata.write_consolidated_metadata() def _remove_x_axis_ticks(ax: plt.Axes) -> None: @@ -233,3 +241,55 @@ def reset_table_index(element: sd.SpatialData | ad.AnnData | pd.DataFrame, table else: raise ValueError("The DataFrame does not contain the 'region' and 'instance_id' columns.") return element + + +def _extract_layer_from_sdata( + sdata: sd.SpatialData, vars: Sequence[str] | None, table_name: str, layer: str +) -> ad.AnnData: + if layer is not None: + if vars is None: + adata: ad.AnnData = _convert_layer_to_adata(adata=sdata.tables[table_name].copy(), layer=layer) + else: + adata: ad.AnnData = _convert_layer_to_adata(adata=sdata.tables[table_name][:, vars].copy(), layer=layer) + else: + if vars is None: + adata = sdata.tables[table_name] + else: + adata = sdata.tables[table_name][:, vars] + return adata + + +def _extract_layer_from_sdata( + sdata: sd.SpatialData, vars: Sequence[str] | None, table_name: str, layer: str +) -> ad.AnnData: + table: ad.AnnData = sdata.tables[table_name] + adata: ad.AnnData = table.copy() if vars is None else table[:, vars].copy() + return _convert_layer_to_adata(adata=adata, layer=layer) if layer is not None else table + + +def _convert_layer_to_adata(adata: ad.AnnData, layer: str) -> ad.AnnData: + """Converts a layer in an AnnData object to an AnnData object. + + Parameters + ---------- + adata + The AnnData object. + layer + The name of the layer to convert. + + Returns + ------- + A copy of the AnnData object with the layer converted to X. + """ + layer_as_adata = ad.AnnData( + X=adata.layers[layer], + obs=adata.obs, + var=adata.var, + uns=adata.uns, + obsm=adata.obsm, + varm=adata.varm, + obsp=adata.obsp, + varp=adata.varp, + ) + + return layer_as_adata