|
29 | 29 | "id": "38a9546a", |
30 | 30 | "metadata": { |
31 | 31 | "execution": { |
32 | | - "iopub.execute_input": "2026-08-18T23:00:40.958364Z", |
33 | | - "iopub.status.busy": "2026-08-18T23:00:40.958120Z", |
34 | | - "iopub.status.idle": "2026-08-18T23:00:46.730012Z", |
35 | | - "shell.execute_reply": "2026-08-18T23:00:46.729444Z" |
| 32 | + "iopub.execute_input": "2026-08-19T03:32:58.684716Z", |
| 33 | + "iopub.status.busy": "2026-08-19T03:32:58.684535Z", |
| 34 | + "iopub.status.idle": "2026-08-19T03:33:04.321850Z", |
| 35 | + "shell.execute_reply": "2026-08-19T03:33:04.321265Z" |
36 | 36 | } |
37 | 37 | }, |
38 | 38 | "outputs": [ |
|
75 | 75 | "sdata" |
76 | 76 | ] |
77 | 77 | }, |
| 78 | + { |
| 79 | + "cell_type": "markdown", |
| 80 | + "id": "03f14f09", |
| 81 | + "metadata": {}, |
| 82 | + "source": [ |
| 83 | + "## 0. `AttributeError: ... object has no attribute 'pl'`\n", |
| 84 | + "\n", |
| 85 | + "The `.pl` accessor is only attached when you `import spatialdata_plot`. Forget that import and every\n", |
| 86 | + "`sdata.pl.…` call raises `AttributeError` — the first wall most newcomers hit. The `Setup` cell above\n", |
| 87 | + "already does the import; the cell below runs a *fresh* interpreter without it to show the message." |
| 88 | + ] |
| 89 | + }, |
| 90 | + { |
| 91 | + "cell_type": "code", |
| 92 | + "execution_count": 2, |
| 93 | + "id": "d550e432", |
| 94 | + "metadata": { |
| 95 | + "execution": { |
| 96 | + "iopub.execute_input": "2026-08-19T03:33:04.323735Z", |
| 97 | + "iopub.status.busy": "2026-08-19T03:33:04.323350Z", |
| 98 | + "iopub.status.idle": "2026-08-19T03:33:08.844352Z", |
| 99 | + "shell.execute_reply": "2026-08-19T03:33:08.843536Z" |
| 100 | + } |
| 101 | + }, |
| 102 | + "outputs": [ |
| 103 | + { |
| 104 | + "name": "stdout", |
| 105 | + "output_type": "stream", |
| 106 | + "text": [ |
| 107 | + "AttributeError: 'SpatialData' object has no attribute 'pl'\n" |
| 108 | + ] |
| 109 | + } |
| 110 | + ], |
| 111 | + "source": [ |
| 112 | + "import os\n", |
| 113 | + "import subprocess\n", |
| 114 | + "import sys\n", |
| 115 | + "\n", |
| 116 | + "# A fresh interpreter that never imports spatialdata_plot, so `.pl` is unregistered.\n", |
| 117 | + "# PYTHON_COLORS=0 keeps the captured traceback free of ANSI colour codes.\n", |
| 118 | + "snippet = \"import spatialdata as sd; sd.datasets.blobs().pl.render_shapes('blobs_circles')\"\n", |
| 119 | + "result = subprocess.run(\n", |
| 120 | + " [sys.executable, \"-c\", snippet],\n", |
| 121 | + " capture_output=True,\n", |
| 122 | + " text=True,\n", |
| 123 | + " env={**os.environ, \"PYTHON_COLORS\": \"0\", \"NO_COLOR\": \"1\"},\n", |
| 124 | + ")\n", |
| 125 | + "print(result.stderr.strip().splitlines()[-1])" |
| 126 | + ] |
| 127 | + }, |
78 | 128 | { |
79 | 129 | "cell_type": "markdown", |
80 | 130 | "id": "5ec56751", |
|
88 | 138 | }, |
89 | 139 | { |
90 | 140 | "cell_type": "code", |
91 | | - "execution_count": 2, |
| 141 | + "execution_count": 3, |
92 | 142 | "id": "12b055e9", |
93 | 143 | "metadata": { |
94 | 144 | "execution": { |
95 | | - "iopub.execute_input": "2026-08-18T23:00:46.732100Z", |
96 | | - "iopub.status.busy": "2026-08-18T23:00:46.731683Z", |
97 | | - "iopub.status.idle": "2026-08-18T23:00:46.734623Z", |
98 | | - "shell.execute_reply": "2026-08-18T23:00:46.734105Z" |
| 145 | + "iopub.execute_input": "2026-08-19T03:33:08.846271Z", |
| 146 | + "iopub.status.busy": "2026-08-19T03:33:08.846138Z", |
| 147 | + "iopub.status.idle": "2026-08-19T03:33:08.849074Z", |
| 148 | + "shell.execute_reply": "2026-08-19T03:33:08.848642Z" |
99 | 149 | } |
100 | 150 | }, |
101 | 151 | "outputs": [ |
|
128 | 178 | }, |
129 | 179 | { |
130 | 180 | "cell_type": "code", |
131 | | - "execution_count": 3, |
| 181 | + "execution_count": 4, |
132 | 182 | "id": "86745998", |
133 | 183 | "metadata": { |
134 | 184 | "execution": { |
135 | | - "iopub.execute_input": "2026-08-18T23:00:46.735964Z", |
136 | | - "iopub.status.busy": "2026-08-18T23:00:46.735841Z", |
137 | | - "iopub.status.idle": "2026-08-18T23:00:46.738032Z", |
138 | | - "shell.execute_reply": "2026-08-18T23:00:46.737590Z" |
| 185 | + "iopub.execute_input": "2026-08-19T03:33:08.850507Z", |
| 186 | + "iopub.status.busy": "2026-08-19T03:33:08.850389Z", |
| 187 | + "iopub.status.idle": "2026-08-19T03:33:08.852860Z", |
| 188 | + "shell.execute_reply": "2026-08-19T03:33:08.852432Z" |
139 | 189 | } |
140 | 190 | }, |
141 | 191 | "outputs": [ |
|
154 | 204 | " print(\"KeyError:\", e)" |
155 | 205 | ] |
156 | 206 | }, |
| 207 | + { |
| 208 | + "cell_type": "markdown", |
| 209 | + "id": "9c53205c", |
| 210 | + "metadata": {}, |
| 211 | + "source": [ |
| 212 | + "## 3. Ambiguous colour/column name\n", |
| 213 | + "\n", |
| 214 | + "If a `color` string is **both** a valid matplotlib colour name and a column in the element or its\n", |
| 215 | + "annotating table, `spatialdata-plot` cannot tell which you meant and raises. Disambiguate with a hex\n", |
| 216 | + "string or an RGB(A) tuple, or rename the column. Here we deliberately add a column called `red` to\n", |
| 217 | + "force the clash." |
| 218 | + ] |
| 219 | + }, |
| 220 | + { |
| 221 | + "cell_type": "code", |
| 222 | + "execution_count": 5, |
| 223 | + "id": "dac9e6a7", |
| 224 | + "metadata": { |
| 225 | + "execution": { |
| 226 | + "iopub.execute_input": "2026-08-19T03:33:08.854274Z", |
| 227 | + "iopub.status.busy": "2026-08-19T03:33:08.854151Z", |
| 228 | + "iopub.status.idle": "2026-08-19T03:33:08.857544Z", |
| 229 | + "shell.execute_reply": "2026-08-19T03:33:08.857049Z" |
| 230 | + } |
| 231 | + }, |
| 232 | + "outputs": [ |
| 233 | + { |
| 234 | + "name": "stdout", |
| 235 | + "output_type": "stream", |
| 236 | + "text": [ |
| 237 | + "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" |
| 238 | + ] |
| 239 | + } |
| 240 | + ], |
| 241 | + "source": [ |
| 242 | + "circles = sdata[\"blobs_circles\"]\n", |
| 243 | + "circles[\"red\"] = circles[\"radius\"] # a column whose name is also a colour\n", |
| 244 | + "try:\n", |
| 245 | + " sdata.pl.render_shapes(\"blobs_circles\", color=\"red\").pl.show()\n", |
| 246 | + "except ValueError as e:\n", |
| 247 | + " print(\"ValueError:\", e)" |
| 248 | + ] |
| 249 | + }, |
157 | 250 | { |
158 | 251 | "cell_type": "markdown", |
159 | 252 | "id": "555854a5", |
160 | 253 | "metadata": {}, |
161 | 254 | "source": [ |
162 | | - "## 3. Invalid image channel\n", |
| 255 | + "## 4. Invalid image channel\n", |
163 | 256 | "\n", |
164 | 257 | "Selecting a channel that does not exist raises a `ValueError` that lists the valid channels — helpful\n", |
165 | 258 | "when you are unsure how a multichannel image is indexed (see the *Multichannel & fluorescence images*\n", |
|
168 | 261 | }, |
169 | 262 | { |
170 | 263 | "cell_type": "code", |
171 | | - "execution_count": 4, |
| 264 | + "execution_count": 6, |
172 | 265 | "id": "b1164416", |
173 | 266 | "metadata": { |
174 | 267 | "execution": { |
175 | | - "iopub.execute_input": "2026-08-18T23:00:46.739419Z", |
176 | | - "iopub.status.busy": "2026-08-18T23:00:46.739308Z", |
177 | | - "iopub.status.idle": "2026-08-18T23:00:46.741767Z", |
178 | | - "shell.execute_reply": "2026-08-18T23:00:46.741290Z" |
| 268 | + "iopub.execute_input": "2026-08-19T03:33:08.858839Z", |
| 269 | + "iopub.status.busy": "2026-08-19T03:33:08.858735Z", |
| 270 | + "iopub.status.idle": "2026-08-19T03:33:08.861474Z", |
| 271 | + "shell.execute_reply": "2026-08-19T03:33:08.861008Z" |
179 | 272 | } |
180 | 273 | }, |
181 | 274 | "outputs": [ |
|
199 | 292 | "id": "bac6ece4", |
200 | 293 | "metadata": {}, |
201 | 294 | "source": [ |
202 | | - "## 4. A per-channel `norm` list of the wrong length\n", |
| 295 | + "## 5. A per-channel `norm` list of the wrong length\n", |
203 | 296 | "\n", |
204 | 297 | "When you pass a list of norms for an image, its length must match the number of channels you are\n", |
205 | 298 | "rendering (see the *Normalization and contrast* tutorial)." |
206 | 299 | ] |
207 | 300 | }, |
208 | 301 | { |
209 | 302 | "cell_type": "code", |
210 | | - "execution_count": 5, |
| 303 | + "execution_count": 7, |
211 | 304 | "id": "50f8a28b", |
212 | 305 | "metadata": { |
213 | 306 | "execution": { |
214 | | - "iopub.execute_input": "2026-08-18T23:00:46.743101Z", |
215 | | - "iopub.status.busy": "2026-08-18T23:00:46.742992Z", |
216 | | - "iopub.status.idle": "2026-08-18T23:00:46.745491Z", |
217 | | - "shell.execute_reply": "2026-08-18T23:00:46.745025Z" |
| 307 | + "iopub.execute_input": "2026-08-19T03:33:08.862734Z", |
| 308 | + "iopub.status.busy": "2026-08-19T03:33:08.862642Z", |
| 309 | + "iopub.status.idle": "2026-08-19T03:33:08.865037Z", |
| 310 | + "shell.execute_reply": "2026-08-19T03:33:08.864515Z" |
218 | 311 | } |
219 | 312 | }, |
220 | 313 | "outputs": [ |
|
238 | 331 | "id": "4d06836d", |
239 | 332 | "metadata": {}, |
240 | 333 | "source": [ |
241 | | - "## 5. `grayscale` needs exactly three channels\n", |
| 334 | + "## 6. `grayscale` needs exactly three channels\n", |
242 | 335 | "\n", |
243 | 336 | "`grayscale=True` collapses a three-channel selection into one intensity, so it requires exactly three\n", |
244 | 337 | "channels." |
245 | 338 | ] |
246 | 339 | }, |
247 | 340 | { |
248 | 341 | "cell_type": "code", |
249 | | - "execution_count": 6, |
| 342 | + "execution_count": 8, |
250 | 343 | "id": "b77803c7", |
251 | 344 | "metadata": { |
252 | 345 | "execution": { |
253 | | - "iopub.execute_input": "2026-08-18T23:00:46.746983Z", |
254 | | - "iopub.status.busy": "2026-08-18T23:00:46.746876Z", |
255 | | - "iopub.status.idle": "2026-08-18T23:00:46.896268Z", |
256 | | - "shell.execute_reply": "2026-08-18T23:00:46.895864Z" |
| 346 | + "iopub.execute_input": "2026-08-19T03:33:08.866284Z", |
| 347 | + "iopub.status.busy": "2026-08-19T03:33:08.866186Z", |
| 348 | + "iopub.status.idle": "2026-08-19T03:33:09.025021Z", |
| 349 | + "shell.execute_reply": "2026-08-19T03:33:09.024560Z" |
257 | 350 | } |
258 | 351 | }, |
259 | 352 | "outputs": [ |
|
287 | 380 | "id": "52c83cb4", |
288 | 381 | "metadata": {}, |
289 | 382 | "source": [ |
290 | | - "## 6. Invalid `PercentileNormalize` bounds\n", |
| 383 | + "## 7. Invalid `PercentileNormalize` bounds\n", |
291 | 384 | "\n", |
292 | 385 | "Percentile bounds are validated at construction time: they must satisfy `0 <= pmin < pmax <= 100`." |
293 | 386 | ] |
294 | 387 | }, |
295 | 388 | { |
296 | 389 | "cell_type": "code", |
297 | | - "execution_count": 7, |
| 390 | + "execution_count": 9, |
298 | 391 | "id": "c5293c63", |
299 | 392 | "metadata": { |
300 | 393 | "execution": { |
301 | | - "iopub.execute_input": "2026-08-18T23:00:46.897867Z", |
302 | | - "iopub.status.busy": "2026-08-18T23:00:46.897752Z", |
303 | | - "iopub.status.idle": "2026-08-18T23:00:46.900467Z", |
304 | | - "shell.execute_reply": "2026-08-18T23:00:46.899901Z" |
| 394 | + "iopub.execute_input": "2026-08-19T03:33:09.026804Z", |
| 395 | + "iopub.status.busy": "2026-08-19T03:33:09.026686Z", |
| 396 | + "iopub.status.idle": "2026-08-19T03:33:09.029442Z", |
| 397 | + "shell.execute_reply": "2026-08-19T03:33:09.028925Z" |
305 | 398 | } |
306 | 399 | }, |
307 | 400 | "outputs": [ |
|
317 | 410 | } |
318 | 411 | ], |
319 | 412 | "source": [ |
320 | | - "for bad in [(50, 50), (90, 10), (-1, 50), (0, 101)]:\n", |
| 413 | + "bad_bounds = [\n", |
| 414 | + " (50, 50), # pmin == pmax (must be strictly increasing)\n", |
| 415 | + " (90, 10), # pmin > pmax\n", |
| 416 | + " (-1, 50), # pmin < 0\n", |
| 417 | + " (0, 101), # pmax > 100\n", |
| 418 | + "]\n", |
| 419 | + "for bad in bad_bounds:\n", |
321 | 420 | " try:\n", |
322 | 421 | " PercentileNormalize(*bad)\n", |
323 | 422 | " except ValueError as e:\n", |
|
334 | 433 | "`spatialdata-plot` fails fast with messages that name the offending element, column, channel, or\n", |
335 | 434 | "bound:\n", |
336 | 435 | "\n", |
| 436 | + "- **`AttributeError: ... has no attribute 'pl'`** — you forgot `import spatialdata_plot`.\n", |
337 | 437 | "- **Element not found** — check the key in `sdata`.\n", |
338 | 438 | "- **No column to colour by** — put the value on the element or its annotating table.\n", |
| 439 | + "- **Ambiguous colour/column name** — pass a hex/RGB(A) colour, or rename the column.\n", |
339 | 440 | "- **Invalid channel** — the message lists the valid channels.\n", |
340 | 441 | "- **Wrong-length `norm` list** — one norm per rendered channel.\n", |
341 | 442 | "- **`grayscale` needs three channels** — select exactly three.\n", |
|
354 | 455 | }, |
355 | 456 | { |
356 | 457 | "cell_type": "code", |
357 | | - "execution_count": 8, |
| 458 | + "execution_count": 10, |
358 | 459 | "id": "384e30e6", |
359 | 460 | "metadata": { |
360 | 461 | "execution": { |
361 | | - "iopub.execute_input": "2026-08-18T23:00:46.901803Z", |
362 | | - "iopub.status.busy": "2026-08-18T23:00:46.901692Z", |
363 | | - "iopub.status.idle": "2026-08-18T23:00:46.933690Z", |
364 | | - "shell.execute_reply": "2026-08-18T23:00:46.933162Z" |
| 462 | + "iopub.execute_input": "2026-08-19T03:33:09.030827Z", |
| 463 | + "iopub.status.busy": "2026-08-19T03:33:09.030712Z", |
| 464 | + "iopub.status.idle": "2026-08-19T03:33:09.058691Z", |
| 465 | + "shell.execute_reply": "2026-08-19T03:33:09.058073Z" |
365 | 466 | } |
366 | 467 | }, |
367 | 468 | "outputs": [ |
|
0 commit comments