From 5e6e5088c4a2a677fa7c78263350c2a379cb3494 Mon Sep 17 00:00:00 2001 From: anon Date: Wed, 19 Aug 2026 01:02:00 +0200 Subject: [PATCH 1/2] Add common errors troubleshooting tutorial New tutorials/common_errors.ipynb turning spatialdata-plot's up-front input validation (0.4.0 hardening) into a troubleshooting reference. Each section triggers a common error via try/except on the blobs dataset, prints the exact message, and gives the one-line fix: - element not found (KeyError) - colouring by a column with no annotating table (KeyError) - invalid image channel (ValueError, lists valid channels) - wrong-length per-channel norm list (ValueError) - grayscale requires exactly 3 channels (ValueError) - invalid PercentileNormalize bounds (ValueError) Executed against spatialdata-plot v0.4.1; committed with outputs. Adds the gallery card + toctree + a terminal-style thumbnail. Closes P3 of the doc-coverage analysis. --- _static/img/common_errors.png | Bin 0 -> 18776 bytes tutorials/common_errors.ipynb | 426 ++++++++++++++++++++++++++++++++++ tutorials/index.md | 11 + 3 files changed, 437 insertions(+) create mode 100644 _static/img/common_errors.png create mode 100644 tutorials/common_errors.ipynb diff --git a/_static/img/common_errors.png b/_static/img/common_errors.png new file mode 100644 index 0000000000000000000000000000000000000000..f886861afc84e127f725926c6565430524e92a90 GIT binary patch literal 18776 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 G{{Iv0L77(o literal 0 HcmV?d00001 diff --git a/tutorials/common_errors.ipynb b/tutorials/common_errors.ipynb new file mode 100644 index 0000000..0d61858 --- /dev/null +++ b/tutorials/common_errors.ipynb @@ -0,0 +1,426 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "dd17a4bc", + "metadata": {}, + "source": [ + "# Common errors and what they mean\n", + "\n", + "`spatialdata-plot` validates its inputs up front and fails with an actionable message rather than deep\n", + "inside matplotlib or datashader. This notebook collects the errors you are most likely to hit, shows\n", + "what triggers each, and gives the one-line fix. Each cell deliberately raises and catches the error so\n", + "you can read the exact message.\n", + "\n", + "We use the synthetic `blobs` dataset throughout." + ] + }, + { + "cell_type": "markdown", + "id": "1079e5ef", + "metadata": {}, + "source": [ + "## Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "38a9546a", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-18T23:00:40.958364Z", + "iopub.status.busy": "2026-08-18T23:00:40.958120Z", + "iopub.status.idle": "2026-08-18T23:00:46.730012Z", + "shell.execute_reply": "2026-08-18T23:00:46.729444Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "SpatialData object\n", + "├── Images\n", + "│ ├── 'blobs_image': DataArray[cyx] (3, 512, 512)\n", + "│ └── 'blobs_multiscale_image': DataTree[cyx] (3, 512, 512), (3, 256, 256), (3, 128, 128)\n", + "├── Labels\n", + "│ ├── 'blobs_labels': DataArray[yx] (512, 512)\n", + "│ └── 'blobs_multiscale_labels': DataTree[yx] (512, 512), (256, 256), (128, 128)\n", + "├── Points\n", + "│ └── 'blobs_points': DataFrame with shape: (, 4) (2D points)\n", + "├── Shapes\n", + "│ ├── 'blobs_circles': GeoDataFrame shape: (5, 2) (2D shapes)\n", + "│ ├── 'blobs_multipolygons': GeoDataFrame shape: (2, 1) (2D shapes)\n", + "│ └── 'blobs_polygons': GeoDataFrame shape: (5, 1) (2D shapes)\n", + "└── Tables\n", + " └── 'table': AnnData (26, 3)\n", + "with coordinate systems:\n", + " ▸ 'global', with elements:\n", + " blobs_image (Images), blobs_multiscale_image (Images), blobs_labels (Labels), blobs_multiscale_labels (Labels), blobs_points (Points), blobs_circles (Shapes), blobs_multipolygons (Shapes), blobs_polygons (Shapes)" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import numpy as np # noqa: F401\n", + "import spatialdata as sd\n", + "import spatialdata_plot # noqa: F401 # registers the .pl accessor\n", + "from matplotlib.colors import Normalize\n", + "from spatialdata_plot import PercentileNormalize\n", + "\n", + "sdata = sd.datasets.blobs()\n", + "sdata" + ] + }, + { + "cell_type": "markdown", + "id": "5ec56751", + "metadata": {}, + "source": [ + "## 1. Element not found\n", + "\n", + "A typo in the element name raises a `KeyError` naming the element it looked for. Check\n", + "`sdata` for the exact key." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "12b055e9", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-18T23:00:46.732100Z", + "iopub.status.busy": "2026-08-18T23:00:46.731683Z", + "iopub.status.idle": "2026-08-18T23:00:46.734623Z", + "shell.execute_reply": "2026-08-18T23:00:46.734105Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "KeyError: \"Could not find element with name 'blobs_circle'\"\n" + ] + } + ], + "source": [ + "try:\n", + " sdata.pl.render_shapes(\"blobs_circle\").pl.show() # missing trailing 's'\n", + "except KeyError as e:\n", + " print(\"KeyError:\", e)" + ] + }, + { + "cell_type": "markdown", + "id": "cf6fc7d0", + "metadata": {}, + "source": [ + "## 2. Colouring by a column with no annotating table\n", + "\n", + "To colour an element by a column, that column must live on the element or on a table annotating it.\n", + "Passing a name that is neither a valid colour nor an available column raises, naming the element and\n", + "the column." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "86745998", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-18T23:00:46.735964Z", + "iopub.status.busy": "2026-08-18T23:00:46.735841Z", + "iopub.status.idle": "2026-08-18T23:00:46.738032Z", + "shell.execute_reply": "2026-08-18T23:00:46.737590Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "KeyError: \"Element 'blobs_circles' has no annotating tables. Cannot use column 'gene_x' for coloring. Please ensure the element is annotated by at least one table.\"\n" + ] + } + ], + "source": [ + "try:\n", + " sdata.pl.render_shapes(\"blobs_circles\", color=\"gene_x\").pl.show()\n", + "except KeyError as e:\n", + " print(\"KeyError:\", e)" + ] + }, + { + "cell_type": "markdown", + "id": "555854a5", + "metadata": {}, + "source": [ + "## 3. Invalid image channel\n", + "\n", + "Selecting a channel that does not exist raises a `ValueError` that lists the valid channels — helpful\n", + "when you are unsure how a multichannel image is indexed (see the *Multichannel & fluorescence images*\n", + "tutorial)." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "b1164416", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-18T23:00:46.739419Z", + "iopub.status.busy": "2026-08-18T23:00:46.739308Z", + "iopub.status.idle": "2026-08-18T23:00:46.741767Z", + "shell.execute_reply": "2026-08-18T23:00:46.741290Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ValueError: Invalid channel(s): DAPI. Valid choices are: [0 1 2]\n" + ] + } + ], + "source": [ + "try:\n", + " sdata.pl.render_images(\"blobs_image\", channel=\"DAPI\").pl.show()\n", + "except ValueError as e:\n", + " print(\"ValueError:\", e)" + ] + }, + { + "cell_type": "markdown", + "id": "bac6ece4", + "metadata": {}, + "source": [ + "## 4. A per-channel `norm` list of the wrong length\n", + "\n", + "When you pass a list of norms for an image, its length must match the number of channels you are\n", + "rendering (see the *Normalization and contrast* tutorial)." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "50f8a28b", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-18T23:00:46.743101Z", + "iopub.status.busy": "2026-08-18T23:00:46.742992Z", + "iopub.status.idle": "2026-08-18T23:00:46.745491Z", + "shell.execute_reply": "2026-08-18T23:00:46.745025Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ValueError: Length of 'norm' list (2) must match the number of channels (3).\n" + ] + } + ], + "source": [ + "try:\n", + " sdata.pl.render_images(\"blobs_image\", channel=[0, 1, 2], norm=[Normalize()] * 2).pl.show()\n", + "except ValueError as e:\n", + " print(\"ValueError:\", e)" + ] + }, + { + "cell_type": "markdown", + "id": "4d06836d", + "metadata": {}, + "source": [ + "## 5. `grayscale` needs exactly three channels\n", + "\n", + "`grayscale=True` collapses a three-channel selection into one intensity, so it requires exactly three\n", + "channels." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "b77803c7", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-18T23:00:46.746983Z", + "iopub.status.busy": "2026-08-18T23:00:46.746876Z", + "iopub.status.idle": "2026-08-18T23:00:46.896268Z", + "shell.execute_reply": "2026-08-18T23:00:46.895864Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ValueError: grayscale=True requires exactly 3 channels, got 1. Select 3 channels via the 'channel' parameter.\n" + ] + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAosAAAHrCAYAAACn9tfQAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjExLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlcelbwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAIEdJREFUeJzt3QeQVeX9+OF3AUVEiiglIBYsECBWsKBGReyNoNiCaKIzsaFGzc9BHesoMdYUoxHbaAQVC4om2HvF3hsqolEsICugSLn/ec/8d2dX+cIuLrsLPs/Mnd172Jd74Oze+9n3lFtWKpVKCQAAFqDJghYCAIBYBABgocwsAgAQEosAAITEIgAAIbEIAEBILAIAEBKLAADUbSzOnTs3ffzxx2nWrFm1GvPFF1+k+fPnL85DAgDQ2GPxs88+S6effnrq1q1b6tq1a7rttttqNG7EiBGpXbt2aa211krt27dPI0eOXNz1BQCgscbiPffck8rKytJTTz1V4zGjR49OZ511Vho7dmyaMWNGuuyyy9Lhhx+eHnroocVZXwAA6lHZ4r43dI7G66+/Pg0ZMmShX7fVVlul1VdfPY0aNapy2TbbbJM6dOiQxowZszgPDQDAsnCCSz4+8bnnnktbbrllteVbb711evbZZ5fkQwMAUAeapSXom2++SbNnz06rrrpqteX5fj7ZJZLH5FvV6Jw6dWpaZZVVihlNAAB+LO8wzv3VuXPn1KRJk8YfixUrmc+ErmrOnDmpadOmCz0h5swzz1ySqwYAsMyaPHlyWm211Rp/LLZq1Sq1bt26OIu6qilTpqQuXbqE44YPH56OP/74yvvTp08vjnvM//D89wEA8GPl5eXFFWtyg9WVZktiJfNZz3n6M/v1r3+d7rvvvnTCCSdUfs348eOL5ZHmzZsXtx/KoSgWAQAWri4P26vVzux8HGG+GHe+ZdOmTSs+zx8rXHTRRalnz56V908++eT0wAMPpHPPPTe9+uqrRTR+8MEH1eIRAIDGqVaxOGHChLT55psXt7wb+bzzzis+z8cYVsgzf1V3MW+xxRbp7rvvLmYXBw4cmF5//fUiHrt37163/xIAABrPdRbrU9613aZNm+LYRbuhAQDqr5mW6HUWAQBYuolFAABCYhEAgJBYBAAgJBYBAAiJRQAAQmIRAICQWAQAICQWAQAIiUUAAEJiEQCAkFgEACAkFgEACIlFAABCYhEAgJBYBAAgJBYBAAiJRQAAQmIRAICQWAQAICQWAQAIiUUAAEJiEQCAkFgEACAkFgEACIlFAABCYhEAgJBYBAAgJBYBAAiJRQAAQmIRAICQWAQAICQWAQAIiUUAAEJiEQCAkFgEACAkFgEACIlFAABCYhEAgJBYBAAgJBYBAAiJRQAAQmIRAICQWAQAICQWAQAIiUUAAEJiEQCAkFgEACAkFgEACIlFAABCYhEAgJBYBAAgJBYBAAiJRQAAQmIRAICQWAQAICQWAQAIiUUAAEJiEQCAkFgEACAkFgEACIlFAABCYhEAgJBYBAAgJBYBAAiJRQAAQmIRAACxCABA7ZlZBAAgJBYBAAiJRQAAQmIRAICQWAQAICQWAQAIiUUAAEJiEQCAkFgEACDULNXS9OnT0w033JAmTZqU1l133fTb3/42tWjRYqFjJk6cmO644470+eefp86dO6e99947denSpbYPDQBAY55ZzLG38cYbp3//+99phRVWSH//+99Tv3790syZM8Mxd955Z+rRo0d68cUXU9u2bdODDz6Y1llnnfTEE0/UxfoDALAElZVKpVJNv/iYY45J99xzT3rllVdS8+bN07Rp09J6662Xjj/++DR8+PAFjhkwYEBq06ZNuvXWWyuX5cDs1q1bEZ01UV5eXvwdeVazdevWNV1dAICflfIl0Ey1mlnMu5IHDx5chGK28sorpz322CONHTs2HNOhQ4c0Y8aMyvvz589Ps2bNSp06dfop6w0AQD2ocSzOnj07ffTRR2nttdeutjzff/fdd8NxF110UWrZsmXaZptt0hFHHJG22GKLtNFGG6XTTjttoY+Vy7jqDQCARhyLeTYwa9WqVbXleYqz4s8WJJ8I89JLLxUntnTt2jV17NgxPfvss+nTTz8Nx4wYMaKYQq245XEAADTiWMyzg2VlZenrr7+utjwft/jDgKxq6NChaaeddkqjR49OJ598cnHCSz7O8fDDDw/H5OMf8772itvkyZNrupoAADRELC6//PLFWcxvvfVWteX5fs+ePRc4Zt68eem9995Lffv2rba8T58+6c033wwfKx8TmWcsq94AAKh/tTrBZd9990033XRTMZuY5Rm/u+66q1heYfz48ZVnRjdt2rS4bM69995b+ef55Ov7778/9erVq+7+FQAANPylc/JZzdtvv3366quv0pZbbpkeeOCB1Lt37zRu3Li03HLLFV9zxhlnpEsuuaRyd/XDDz+cBg0alLp3757WX3/99Mwzz6QpU6YUUbnBBhvU6HFdOgcAoGGaqVaxmM2dO7e41mI+Mzq/g0uOx3wsY4Wnn366uAB3PvO5Qg7HHI05EvM7t/Tv3z+tuOKKNX5MsQgAsJTEYkMQiwAAS8FFuQEA+HkRiwAAhMQiAAAhsQgAQEgsAgAQEosAAITEIgAAIbEIAEBILAIAEBKLAACExCIAACGxCABASCwCABASiwAAhMQiAAAhsQgAQEgsAgAQEosAAITEIgAAIbEIAEBILAIAEBKLAACExCIAACGxCABASCwCABASiwAAhMQiAAAhsQgAQEgsAgAQEosAAITEIgAAIbEIAEBILAIAEBKLAACExCIAACGxCABASCwCABASiwAAhMQiAAAhsQgAQEgsAgAQEosAAITEIgAAIbEIAEBILAIAEBKLAACExCIAACGxCABASCwCABASiwAAhMQiAAAhsQgAQEgsAgAQEosAAITEIgAAIbEIAEBILAIAEBKLAACExCIAACGxCABASCwCABASiwAAhMQiAAAhsQgAQEgsAgAQEosAAITEIgAAIbEIAEBILAIAEBKLAACExCIAACGxCABASCwCABASiwAAhMQiAAAhsQgAQEgsAgBQd7F43XXXpfXWWy81b9489e7dO40bN26RYz755JM0dOjQtOqqq6b27dunY489Ns2cObO2Dw0AQGOOxfHjx6dDDz00nXrqqemzzz5Lv//979OgQYPS888/H46ZOnVq2nLLLdP06dPTc889lz788MPUo0ePdN9999XF+gMAsASVlUqlUk2/eMCAAal169bptttuq1zWt2/fIv6uv/76BY75v//7v+LP3n///dSiRYvFWsny8vLUpk2bIjjz4wMAUD/NVOOZxdyUTz/9dNp2222rLd9+++3Tk08+GY4bO3Zs+s1vfrPYoQgAQMOpcSx+8803xXGG+ZjDqjp06FDsko588MEHqV27dmnXXXctgnGNNdZIJ5xwwkKPWZw9e3ZRxlVvAAAshWdD5xnHsrKyhf75+eefnw455JD05ZdfpltuuSWNGTOmOMklMmLEiGIKteLWtWvXn7qaAAAsyVhs1apVatmyZfriiy+qLc/3O3bsGI7r1KlT2mmnndK+++5bjM/HOB533HFFNEaGDx9e7GuvuE2ePLmmqwkAQEPEYp493HzzzdNDDz1UbfmDDz6Y+vXrF47LZ0IvaLaxSZP4ofNlefJBmVVvAAA08t3QJ554YrrrrruKay1OmzYtXXzxxenFF18sZgornHHGGalt27bVxuTL5Nx8881p1qxZacKECemvf/1rOuCAA+r2XwIAQMPG4s4775yuuuqqdPbZZxe7l/Pnt956a9pkk03CMXm3c97lfM455xQnuuTd0UOGDEkXXnhhXaw/AACN5TqLDcV1FgEAGvl1FgEA+PkRiwAAhMQiAAAhsQgAQEgsAgAQEosAAITEIgAAIbEIAEBILAIAEBKLAACExCIAACGxCABASCwCABASiwAAhMQiAAAhsQgAQEgsAgAQEosAAITEIgAAIbEIAEBILAIAEBKLAACExCIAACGxCABASCwCABASiwAAhMQiAAAhsQgAQEgsAgAQEosAAITEIgAAIbEIAEBILAIAEBKLAACExCIAACGxCABASCwCABASiwAAhMQiAAAhsQgAQEgsAgAgFgEAqD0ziwAAhMQiAAAhsQgAQEgsAgAQEosAAITEIgAAIbEIAEBILAIAEBKLAACExCIAACGxCABASCwCABASiwAAhMQiAAAhsQgAQEgsAgAQEosAAITEIgAAIbEIAEBILAIAEBKLAACExCIAACGxCABASCwCABASiwAAhMQiAAAhsQgAQEgsAgAQEosAAITEIgAAIbEIAEBILAIAEBKLAACExCIAACGxCABAqFmqpTlz5qT//ve/adKkSWnddddNO+64Y2rSpGbN+b///S+NGjUqde/ePe2xxx61fWgAABpzLH7zzTepf//+afr06WnrrbdO559/furRo0e666670vLLL7/QsfPnz08HHnhgev7559NOO+0kFgEAlrVYHDFiRJoyZUp65ZVXUtu2bdMnn3ySevbsma644op09NFHL3Ts2WefnVZZZZW0zTbb/NR1BgCgMR6zOGbMmLTffvsVoZh16dIl7b777unmm29e6LjHHnssXX311WnkyJE/bW0BAGicM4vff/99mjhxYnG8YVV5N/S9994bjps6dWoaMmRIuuqqq1K7du1q9FizZ88ubhXKy8trupoAADTEzOLMmTNTqVSqnFWskO/nYxkjv/vd79I+++yTBgwYUKvd3W3atKm8de3atcZjAQBogFhcccUVFzjLl092admy5QLH3HfffWn8+PHFjOIFF1xQ3N5///30zjvvFJ9HM4bDhw8v/t6K2+TJk2v3rwIAoH53Qzdv3jytscYaxa7oqvL9fAmdBencuXMaNmxYmjZtWuWyvHt53rx56bPPPis+Ro+VbwAANKyyUt63XEPHHntscY3FfDb0CiusUByPuN5666U//elP6aSTTiq+5sknn0wvvPBCeHZ0PiEmj73llltqvJJ5BjLvjs6zjK1bt67xOACAn5PyJdBMtTob+tRTTy2ul5gvf3PKKacUH/NsY9UwzCe75K8DAOBndp3F9u3bpxdffLF4F5aPPvoonXDCCWn//fcvZgor9OvXL9y9nA0aNCg1a1brN44BAKCx74ZuKHZDAwAsBbuhAQD4eRGLAACExCIAACGxCABASCwCABASiwAAhMQiAAAhsQgAQEgsAgAQEosAAITEIgAAIbEIAEBILAIAEBKLAACExCIAACGxCABASCwCABASiwAAhMQiAAAhsQgAQEgsAgAQEosAAITEIgAAIbEIAEBILAIAEBKLAACExCIAACGxCABASCwCABASiwAAhMQiAAAhsQgAQEgsAgAQEosAAITEIgAAIbEIAEBILAIAEBKLAACExCIAACGxCACAWAQAoPbMLAIAEBKLAACExCIAACGxCABASCwCABASiwAAhMQiAAAhsQgAQEgsAgAQEosAAITEIgAAIbEIAEBILAIAEBKLAACExCIAACGxCABASCwCABASiwAAhMQiAAAhsQgAQEgsAgAQEosAAITEIgAAIbEIAEBILAIAEBKLAACExCIAACGxCABASCwCABASiwAAhMQiAAAhsQgAQEgsAgAQEosAAITEIgAAIbEIAEDdxeJ9992X+vfvn9Zee+208847p2eeeWahX//xxx+nE044IfXp0ydtsMEG6bDDDksffvhhbR8WAIDGHotPPfVU2m233dKAAQPS2LFjU69evYpwfPvtt8MxgwcPTquttlq6/PLL07XXXps+//zztNVWW6Uvv/yyLtYfAIAlqKxUKpVq+sV77rln+v7779P48eMrl/Xu3Tv169cvXXHFFQscM2/evNS0adPK+zNmzEht27YtwnHIkCE1etzy8vLUpk2bNH369NS6deuari4AwM9K+RJoplrNLD7yyCNphx12qLYs74p+9NFHwzFVQzGbM2dOyn263HLL1XZdAQCoZ81q+oXffPNNUaudOnWqtrxjx47pk08+qfEDnnrqqWnllVdOO+64Y/g1s2fPLm4V8uMCAFD/ajyzOH/+/OJjs2bV+zLPEOZdzTVx6aWXppEjR6ZRo0YVwRgZMWJEMYVacevatWtNVxMAgIaIxVatWqXmzZv/6MSUfL99+/aLHJ+PaTz++OPTzTffvNBZxWz48OHFvvaK2+TJk2u6mgAANEQsNmnSpLj8zRNPPFFt+WOPPZY23XTThY698sor07Bhw9Lo0aPTwIEDF/lYOUrzQZlVbwAA1L9aneBy1FFHpdtvvz09+OCDxf0xY8akxx9/PB155JGVX3PRRRcVl9SpcM011xTjcigOGjSoLtcdAIDGcoJLdsABBxQX1M6zg/kYxjwDeNlll6Xtttuu2skoVU94yaFYVlaWjjnmmOJWIe+SzjcAAJaR6yxWmDt3bpo2bVpq167djy6Nk2MxX0uxc+fOxf0cjgt6iNrsXnadRQCAhmmmZos1qFmz8KSWH0Zgly5dFn/tAABYut4bGgCAnw+xCABASCwCABASiwAAhMQiAAAhsQgAQEgsAgAQEosAAITEIgAAIbEIAEBILAIAEBKLAACExCIAACGxCABASCwCABASiwAAhMQiAAAhsQgAQEgsAgAQEosAAITEIgAAIbEIAEBILAIAEBKLAACExCIAACGxCABASCwCABASiwAAhMQiAAAhsQgAQEgsAgAQEosAAITEIgAAIbEIAEBILAIAEBKLAACExCIAACGxCABASCwCABASiwAAhMQiAAAhsQgAQEgsAgAQEosAAITEIgAAIbEIAEBILAIAEBKLAACExCIAACGxCABASCwCABASiwAAhMQiAAAhsQgAQEgsAgAQEosAAITEIgAAIbEIAEBILAIAEBKLAACExCIAACGxCABASCwCABASiwAAhMQiAAAhsQgAQEgsAgAQEosAAITEIgAAIbEIAEBILAIAEBKLAACExCIAACGxCABASCwCABASiwAAhJqlxfDKK6+kSZMmpXXXXTf16NFjiY0BAGApmln8/vvv08CBA9N2222XLrnkkrTpppumQw89NJVKpTodAwDAUjizmGPviSeeSC+//HJabbXV0uuvv5769OmTtt1223TQQQfV2RgAAJbCmcXrr78+7bfffkX0Zb169Uq77LJLsbwuxwAAsJTNLM6dOze9+eabadiwYdWWr7/++unyyy+vszHZ7Nmzi1uF6dOnFx/Ly8truroAAD875f+/lerycL8ax+KMGTPSvHnz0sorr1xt+SqrrJK+/vrrOhuTjRgxIp155pk/Wt61a9eari4AwM/WV199ldq0aVO/sdi8efPi46xZs34UhCussEKdjcmGDx+ejj/++Mr7OSzXWGON9NFHH9XZP5zG8dtP/gVg8uTJqXXr1g29OtQB23TZZLsum2zXZdP06dPT6quvntq1a1dnf2eNY7FFixapU6dORbBVle9369atzsZURGZFaFaVQ1FULHvyNrVdly226bLJdl022a7LpiZN6u5S2rX6m/KJKbfddluaP39+cf+7775L48aNK5ZXeOONN9Kdd95ZqzEAADROtYrF0047LX388cdp7733TiNHjky77bZbatasWbVdxjfffHMaOnRorcYAALAMxOKaa66ZXnjhheIdWB5++OG09dZbpwkTJhQnrFTo2bNn2muvvWo1ZlHyLunTTz99gbumWXrZrsse23TZZLsum2zXZVPzJdBMZSVvpQIAQKDujn4EAGCZIxYBAAiJRQAAfvp1Fpek/NZ+TzzxRHGx7k033bS4NuOSGEP9yhdTz9uoadOmaauttkorrbTSIse8/fbb6Z133km/+MUv0sYbb1yn14mibuSLqOeT1vI7M/Xr16+4ukFN5ctq5Z/dwYMH2xyNzGuvvZbefffd4g0Q8s9eTeRt+dRTTxUf8/dCq1atlvh6UnP5knVPP/10+vzzz9OvfvWrtPbaay9yTH4Tjeeeey5Nmzat+F7YcMMN/Zc3MvlUk4ceeqjYrvvuu2+NXifzZQsff/zx9O2336bNN988tW/fvtYP2qDefffd0pprrlnq3r176de//nVpxRVXLF111VV1Pob6dc8995TatGlT2myzzUobbbRRaZVVVik9/vjj4de/9tprpS222KLYprvvvntpjTXWKPXu3bs0ceLEel1vFu6CCy4otWjRotS/f//iZ7Bnz56lTz75pEb/bVdeeWVp+eWXLzVv3tx/cyMyb9680tChQ4uf1x133LG06qqrlnbbbbfSd999t8if8Y4dO5Y23HDD0l577VXq0aNH6cknn6y39Wbhpk6dWurbt2+pS5cupQEDBhSvk6eccspCxzzwwAPFc3XepnvuuWepQ4cO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", + "text/plain": [ + "

" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "try:\n", + " sdata.pl.render_images(\"blobs_image\", channel=[0], grayscale=True).pl.show()\n", + "except ValueError as e:\n", + " print(\"ValueError:\", e)" + ] + }, + { + "cell_type": "markdown", + "id": "52c83cb4", + "metadata": {}, + "source": [ + "## 6. Invalid `PercentileNormalize` bounds\n", + "\n", + "Percentile bounds are validated at construction time: they must satisfy `0 <= pmin < pmax <= 100`." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "c5293c63", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-18T23:00:46.897867Z", + "iopub.status.busy": "2026-08-18T23:00:46.897752Z", + "iopub.status.idle": "2026-08-18T23:00:46.900467Z", + "shell.execute_reply": "2026-08-18T23:00:46.899901Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "PercentileNormalize(50, 50) -> Require 0 <= pmin < pmax <= 100, got pmin=50, pmax=50.\n", + "PercentileNormalize(90, 10) -> Require 0 <= pmin < pmax <= 100, got pmin=90, pmax=10.\n", + "PercentileNormalize(-1, 50) -> Require 0 <= pmin < pmax <= 100, got pmin=-1, pmax=50.\n", + "PercentileNormalize(0, 101) -> Require 0 <= pmin < pmax <= 100, got pmin=0, pmax=101.\n" + ] + } + ], + "source": [ + "for bad in [(50, 50), (90, 10), (-1, 50), (0, 101)]:\n", + " try:\n", + " PercentileNormalize(*bad)\n", + " except ValueError as e:\n", + " print(f\"PercentileNormalize{bad} -> {e}\")" + ] + }, + { + "cell_type": "markdown", + "id": "c21a9c5c", + "metadata": {}, + "source": [ + "## Summary\n", + "\n", + "`spatialdata-plot` fails fast with messages that name the offending element, column, channel, or\n", + "bound:\n", + "\n", + "- **Element not found** — check the key in `sdata`.\n", + "- **No column to colour by** — put the value on the element or its annotating table.\n", + "- **Invalid channel** — the message lists the valid channels.\n", + "- **Wrong-length `norm` list** — one norm per rendered channel.\n", + "- **`grayscale` needs three channels** — select exactly three.\n", + "- **Invalid percentile bounds** — `0 <= pmin < pmax <= 100`.\n", + "\n", + "When a plot fails, read the message first — it usually names the fix." + ] + }, + { + "cell_type": "markdown", + "id": "d3106cc6", + "metadata": {}, + "source": [ + "## For reproducibility" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "384e30e6", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-18T23:00:46.901803Z", + "iopub.status.busy": "2026-08-18T23:00:46.901692Z", + "iopub.status.idle": "2026-08-18T23:00:46.933690Z", + "shell.execute_reply": "2026-08-18T23:00:46.933162Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Python implementation: CPython\n", + "Python version : 3.14.6\n", + "IPython version : 9.14.1\n", + "\n", + "spatialdata : 0.7.3\n", + "spatialdata_plot: 0.4.1\n", + "matplotlib : 3.11.0\n", + "numpy : 2.4.6\n", + "\n", + "Compiler : Clang 20.1.8 \n", + "OS : Darwin\n", + "Release : 25.2.0\n", + "Machine : arm64\n", + "Processor : arm\n", + "CPU cores : 8\n", + "Architecture: 64bit\n", + "\n" + ] + } + ], + "source": [ + "# ruff: noqa: F401, F811, I001, E402\n", + "# fmt: off\n", + "import warnings\n", + "import spatialdata_plot\n", + "\n", + "%load_ext watermark\n", + "# fmt: on\n", + "\n", + "%watermark -v -m -p spatialdata,spatialdata_plot,matplotlib,numpy" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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", + "version": "3.14.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorials/index.md b/tutorials/index.md index a16c1bb..e496d38 100644 --- a/tutorials/index.md +++ b/tutorials/index.md @@ -54,6 +54,16 @@ Add a physical scalebar with `scalebar_dx`, choose units, and style placement, colour, length and fonts through `scalebar_params`. ::: +:::{grid-item-card} Common errors and what they mean +:link: /notebooks/tutorials/common_errors +:link-type: doc +:img-top: /notebooks/_static/img/common_errors.png + +A troubleshooting reference: the errors you are most likely to hit — +missing elements, invalid channels, wrong-length `norm` lists — with what +triggers each and the one-line fix. +::: + :::: @@ -66,4 +76,5 @@ color_and_palette multi_panel_color performance scalebars +common_errors ``` From 92f4ed52127191c0386220789e8248d2d3d044e3 Mon Sep 17 00:00:00 2001 From: anon Date: Wed, 19 Aug 2026 05:33:19 +0200 Subject: [PATCH 2/2] docs(common_errors): add missing-import AttributeError (#0) and ambiguous colour/column cases, annotate percentile-bound rules --- tutorials/common_errors.ipynb | 189 ++++++++++++++++++++++++++-------- 1 file changed, 145 insertions(+), 44 deletions(-) diff --git a/tutorials/common_errors.ipynb b/tutorials/common_errors.ipynb index 0d61858..44cd8b1 100644 --- a/tutorials/common_errors.ipynb +++ b/tutorials/common_errors.ipynb @@ -29,10 +29,10 @@ "id": "38a9546a", "metadata": { "execution": { - "iopub.execute_input": "2026-08-18T23:00:40.958364Z", - "iopub.status.busy": "2026-08-18T23:00:40.958120Z", - "iopub.status.idle": "2026-08-18T23:00:46.730012Z", - "shell.execute_reply": "2026-08-18T23:00:46.729444Z" + "iopub.execute_input": "2026-08-19T03:32:58.684716Z", + "iopub.status.busy": "2026-08-19T03:32:58.684535Z", + "iopub.status.idle": "2026-08-19T03:33:04.321850Z", + "shell.execute_reply": "2026-08-19T03:33:04.321265Z" } }, "outputs": [ @@ -75,6 +75,56 @@ "sdata" ] }, + { + "cell_type": "markdown", + "id": "03f14f09", + "metadata": {}, + "source": [ + "## 0. `AttributeError: ... object has no attribute 'pl'`\n", + "\n", + "The `.pl` accessor is only attached when you `import spatialdata_plot`. Forget that import and every\n", + "`sdata.pl.…` call raises `AttributeError` — the first wall most newcomers hit. The `Setup` cell above\n", + "already does the import; the cell below runs a *fresh* interpreter without it to show the message." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "d550e432", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-19T03:33:04.323735Z", + "iopub.status.busy": "2026-08-19T03:33:04.323350Z", + "iopub.status.idle": "2026-08-19T03:33:08.844352Z", + "shell.execute_reply": "2026-08-19T03:33:08.843536Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "AttributeError: 'SpatialData' object has no attribute 'pl'\n" + ] + } + ], + "source": [ + "import os\n", + "import subprocess\n", + "import sys\n", + "\n", + "# A fresh interpreter that never imports spatialdata_plot, so `.pl` is unregistered.\n", + "# PYTHON_COLORS=0 keeps the captured traceback free of ANSI colour codes.\n", + "snippet = \"import spatialdata as sd; sd.datasets.blobs().pl.render_shapes('blobs_circles')\"\n", + "result = subprocess.run(\n", + " [sys.executable, \"-c\", snippet],\n", + " capture_output=True,\n", + " text=True,\n", + " env={**os.environ, \"PYTHON_COLORS\": \"0\", \"NO_COLOR\": \"1\"},\n", + ")\n", + "print(result.stderr.strip().splitlines()[-1])" + ] + }, { "cell_type": "markdown", "id": "5ec56751", @@ -88,14 +138,14 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "id": "12b055e9", "metadata": { "execution": { - "iopub.execute_input": "2026-08-18T23:00:46.732100Z", - "iopub.status.busy": "2026-08-18T23:00:46.731683Z", - "iopub.status.idle": "2026-08-18T23:00:46.734623Z", - "shell.execute_reply": "2026-08-18T23:00:46.734105Z" + "iopub.execute_input": "2026-08-19T03:33:08.846271Z", + "iopub.status.busy": "2026-08-19T03:33:08.846138Z", + "iopub.status.idle": "2026-08-19T03:33:08.849074Z", + "shell.execute_reply": "2026-08-19T03:33:08.848642Z" } }, "outputs": [ @@ -128,14 +178,14 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "id": "86745998", "metadata": { "execution": { - "iopub.execute_input": "2026-08-18T23:00:46.735964Z", - "iopub.status.busy": "2026-08-18T23:00:46.735841Z", - "iopub.status.idle": "2026-08-18T23:00:46.738032Z", - "shell.execute_reply": "2026-08-18T23:00:46.737590Z" + "iopub.execute_input": "2026-08-19T03:33:08.850507Z", + "iopub.status.busy": "2026-08-19T03:33:08.850389Z", + "iopub.status.idle": "2026-08-19T03:33:08.852860Z", + "shell.execute_reply": "2026-08-19T03:33:08.852432Z" } }, "outputs": [ @@ -154,12 +204,55 @@ " print(\"KeyError:\", e)" ] }, + { + "cell_type": "markdown", + "id": "9c53205c", + "metadata": {}, + "source": [ + "## 3. Ambiguous colour/column name\n", + "\n", + "If a `color` string is **both** a valid matplotlib colour name and a column in the element or its\n", + "annotating table, `spatialdata-plot` cannot tell which you meant and raises. Disambiguate with a hex\n", + "string or an RGB(A) tuple, or rename the column. Here we deliberately add a column called `red` to\n", + "force the clash." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "dac9e6a7", + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-19T03:33:08.854274Z", + "iopub.status.busy": "2026-08-19T03:33:08.854151Z", + "iopub.status.idle": "2026-08-19T03:33:08.857544Z", + "shell.execute_reply": "2026-08-19T03:33:08.857049Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ValueError: `color='red'` is ambiguous: it is a valid matplotlib color name AND a column name in element 'blobs_circles'. Disambiguate by either passing an unambiguous color form (hex string like '#ffa500' or an RGB(A) tuple), or by renaming the column.\n" + ] + } + ], + "source": [ + "circles = sdata[\"blobs_circles\"]\n", + "circles[\"red\"] = circles[\"radius\"] # a column whose name is also a colour\n", + "try:\n", + " sdata.pl.render_shapes(\"blobs_circles\", color=\"red\").pl.show()\n", + "except ValueError as e:\n", + " print(\"ValueError:\", e)" + ] + }, { "cell_type": "markdown", "id": "555854a5", "metadata": {}, "source": [ - "## 3. Invalid image channel\n", + "## 4. Invalid image channel\n", "\n", "Selecting a channel that does not exist raises a `ValueError` that lists the valid channels — helpful\n", "when you are unsure how a multichannel image is indexed (see the *Multichannel & fluorescence images*\n", @@ -168,14 +261,14 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 6, "id": "b1164416", "metadata": { "execution": { - "iopub.execute_input": "2026-08-18T23:00:46.739419Z", - "iopub.status.busy": "2026-08-18T23:00:46.739308Z", - "iopub.status.idle": "2026-08-18T23:00:46.741767Z", - "shell.execute_reply": "2026-08-18T23:00:46.741290Z" + "iopub.execute_input": "2026-08-19T03:33:08.858839Z", + "iopub.status.busy": "2026-08-19T03:33:08.858735Z", + "iopub.status.idle": "2026-08-19T03:33:08.861474Z", + "shell.execute_reply": "2026-08-19T03:33:08.861008Z" } }, "outputs": [ @@ -199,7 +292,7 @@ "id": "bac6ece4", "metadata": {}, "source": [ - "## 4. A per-channel `norm` list of the wrong length\n", + "## 5. A per-channel `norm` list of the wrong length\n", "\n", "When you pass a list of norms for an image, its length must match the number of channels you are\n", "rendering (see the *Normalization and contrast* tutorial)." @@ -207,14 +300,14 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 7, "id": "50f8a28b", "metadata": { "execution": { - "iopub.execute_input": "2026-08-18T23:00:46.743101Z", - "iopub.status.busy": "2026-08-18T23:00:46.742992Z", - "iopub.status.idle": "2026-08-18T23:00:46.745491Z", - "shell.execute_reply": "2026-08-18T23:00:46.745025Z" + "iopub.execute_input": "2026-08-19T03:33:08.862734Z", + "iopub.status.busy": "2026-08-19T03:33:08.862642Z", + "iopub.status.idle": "2026-08-19T03:33:08.865037Z", + "shell.execute_reply": "2026-08-19T03:33:08.864515Z" } }, "outputs": [ @@ -238,7 +331,7 @@ "id": "4d06836d", "metadata": {}, "source": [ - "## 5. `grayscale` needs exactly three channels\n", + "## 6. `grayscale` needs exactly three channels\n", "\n", "`grayscale=True` collapses a three-channel selection into one intensity, so it requires exactly three\n", "channels." @@ -246,14 +339,14 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 8, "id": "b77803c7", "metadata": { "execution": { - "iopub.execute_input": "2026-08-18T23:00:46.746983Z", - "iopub.status.busy": "2026-08-18T23:00:46.746876Z", - "iopub.status.idle": "2026-08-18T23:00:46.896268Z", - "shell.execute_reply": "2026-08-18T23:00:46.895864Z" + "iopub.execute_input": "2026-08-19T03:33:08.866284Z", + "iopub.status.busy": "2026-08-19T03:33:08.866186Z", + "iopub.status.idle": "2026-08-19T03:33:09.025021Z", + "shell.execute_reply": "2026-08-19T03:33:09.024560Z" } }, "outputs": [ @@ -287,21 +380,21 @@ "id": "52c83cb4", "metadata": {}, "source": [ - "## 6. Invalid `PercentileNormalize` bounds\n", + "## 7. Invalid `PercentileNormalize` bounds\n", "\n", "Percentile bounds are validated at construction time: they must satisfy `0 <= pmin < pmax <= 100`." ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 9, "id": "c5293c63", "metadata": { "execution": { - "iopub.execute_input": "2026-08-18T23:00:46.897867Z", - "iopub.status.busy": "2026-08-18T23:00:46.897752Z", - "iopub.status.idle": "2026-08-18T23:00:46.900467Z", - "shell.execute_reply": "2026-08-18T23:00:46.899901Z" + "iopub.execute_input": "2026-08-19T03:33:09.026804Z", + "iopub.status.busy": "2026-08-19T03:33:09.026686Z", + "iopub.status.idle": "2026-08-19T03:33:09.029442Z", + "shell.execute_reply": "2026-08-19T03:33:09.028925Z" } }, "outputs": [ @@ -317,7 +410,13 @@ } ], "source": [ - "for bad in [(50, 50), (90, 10), (-1, 50), (0, 101)]:\n", + "bad_bounds = [\n", + " (50, 50), # pmin == pmax (must be strictly increasing)\n", + " (90, 10), # pmin > pmax\n", + " (-1, 50), # pmin < 0\n", + " (0, 101), # pmax > 100\n", + "]\n", + "for bad in bad_bounds:\n", " try:\n", " PercentileNormalize(*bad)\n", " except ValueError as e:\n", @@ -334,8 +433,10 @@ "`spatialdata-plot` fails fast with messages that name the offending element, column, channel, or\n", "bound:\n", "\n", + "- **`AttributeError: ... has no attribute 'pl'`** — you forgot `import spatialdata_plot`.\n", "- **Element not found** — check the key in `sdata`.\n", "- **No column to colour by** — put the value on the element or its annotating table.\n", + "- **Ambiguous colour/column name** — pass a hex/RGB(A) colour, or rename the column.\n", "- **Invalid channel** — the message lists the valid channels.\n", "- **Wrong-length `norm` list** — one norm per rendered channel.\n", "- **`grayscale` needs three channels** — select exactly three.\n", @@ -354,14 +455,14 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 10, "id": "384e30e6", "metadata": { "execution": { - "iopub.execute_input": "2026-08-18T23:00:46.901803Z", - "iopub.status.busy": "2026-08-18T23:00:46.901692Z", - "iopub.status.idle": "2026-08-18T23:00:46.933690Z", - "shell.execute_reply": "2026-08-18T23:00:46.933162Z" + "iopub.execute_input": "2026-08-19T03:33:09.030827Z", + "iopub.status.busy": "2026-08-19T03:33:09.030712Z", + "iopub.status.idle": "2026-08-19T03:33:09.058691Z", + "shell.execute_reply": "2026-08-19T03:33:09.058073Z" } }, "outputs": [