cellestial 0.48.0__tar.gz → 0.48.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. {cellestial-0.48.0 → cellestial-0.48.2}/PKG-INFO +1 -1
  2. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/frames/operations.py +3 -5
  3. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/single/core/dimensional.py +4 -6
  4. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/single/heatmap/dotplot.py +5 -1
  5. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/single/heatmap/heatmap.py +10 -1
  6. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/single/heatmap/stacked_violin.py +5 -1
  7. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/single/quick/elbow.py +4 -0
  8. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/spatial/spatial.py +9 -1
  9. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/util/__init__.py +2 -0
  10. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/util/dendrogram.py +9 -10
  11. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/util/utilities.py +92 -1
  12. {cellestial-0.48.0 → cellestial-0.48.2}/pyproject.toml +1 -1
  13. {cellestial-0.48.0 → cellestial-0.48.2}/LICENSE +0 -0
  14. {cellestial-0.48.0 → cellestial-0.48.2}/README.md +0 -0
  15. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/__init__.py +0 -0
  16. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/_version.py +0 -0
  17. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/datasets/__init__.py +0 -0
  18. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/datasets/datasets.py +0 -0
  19. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/frames/__init__.py +0 -0
  20. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/frames/build.py +0 -0
  21. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/layers/__init__.py +0 -0
  22. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/layers/_deferred.py +0 -0
  23. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/layers/arrow.py +0 -0
  24. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/layers/bracket.py +0 -0
  25. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/layers/ondata_legend.py +0 -0
  26. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/layers/outline.py +0 -0
  27. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/layers/stream.py +0 -0
  28. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/layers/utilities.py +0 -0
  29. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/single/__init__.py +0 -0
  30. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/single/base/__init__.py +0 -0
  31. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/single/base/base.py +0 -0
  32. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/single/basic/__init__.py +0 -0
  33. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/single/basic/bar.py +0 -0
  34. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/single/basic/scatter.py +0 -0
  35. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/single/common/__init__.py +0 -0
  36. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/single/common/xyplot.py +0 -0
  37. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/single/common/xyplots.py +0 -0
  38. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/single/core/__init__.py +0 -0
  39. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/single/core/distribution.py +0 -0
  40. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/single/core/distributions.py +0 -0
  41. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/single/core/subdimensional.py +0 -0
  42. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/single/core/subdimensionals.py +0 -0
  43. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/single/differential/__init__.py +0 -0
  44. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/single/differential/markers.py +0 -0
  45. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/single/differential/volcano.py +0 -0
  46. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/single/heatmap/__init__.py +0 -0
  47. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/single/heatmap/_key_groups.py +0 -0
  48. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/single/heatmap/_rank_genes_groups.py +0 -0
  49. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/single/quick/__init__.py +0 -0
  50. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/single/quick/highest.py +0 -0
  51. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/single/quick/ridge.py +0 -0
  52. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/spatial/__init__.py +0 -0
  53. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/spatial/_components.py +0 -0
  54. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/spatial/spatials.py +0 -0
  55. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/themes/__init__.py +0 -0
  56. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/themes/_distributions.py +0 -0
  57. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/themes/_heatmaps.py +0 -0
  58. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/themes/_scatters.py +0 -0
  59. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/util/colors.py +0 -0
  60. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/util/errors.py +0 -0
  61. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/util/operations.py +0 -0
  62. {cellestial-0.48.0 → cellestial-0.48.2}/cellestial/util/save.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cellestial
3
- Version: 0.48.0
3
+ Version: 0.48.2
4
4
  Summary:
5
5
  License-Expression: Apache-2.0
6
6
  License-File: LICENSE
@@ -50,15 +50,13 @@ def _pca_variance_frame(
50
50
  n_pcs: int | None = None,
51
51
  component_column: str = "Principal Component",
52
52
  variance_column: str = "Variance Ratio",
53
+ use_key: str = "pca",
53
54
  ) -> DataFrame:
54
55
  """Get PCA variance ratio per principal component as a polars DataFrame."""
55
56
  if isinstance(data, AnnData):
56
- pca_uns = data.uns.get("pca")
57
+ pca_uns = data.uns.get(use_key)
57
58
  if pca_uns is None or "variance_ratio" not in pca_uns:
58
- msg = (
59
- "PCA variance ratio is not available on this data. "
60
- "Run PCA first (e.g., `sc.tl.pca(data)`)."
61
- )
59
+ msg = f"PCA variance ratio not found under `{use_key}`. Run PCA first."
62
60
  raise ValueError(msg)
63
61
  variance_ratio = np.asarray(pca_uns["variance_ratio"]).ravel()
64
62
  n_available = variance_ratio.size
@@ -24,6 +24,7 @@ from cellestial.themes import _THEME_DIMENSION
24
24
  from cellestial.util import (
25
25
  _color_gradient,
26
26
  _is_variable_key,
27
+ _resolve_embedding_key,
27
28
  _resolve_tooltips,
28
29
  _validate_tooltips,
29
30
  _warn,
@@ -228,12 +229,9 @@ def dimensional(
228
229
  if len(xy) != 2:
229
230
  msg = f"xy MUST be of length 2, (len(xy)=={len(xy)})"
230
231
  raise KeyError(msg)
231
- if use_key is None:
232
- x = f"X_{dimensions.upper()}{xy[0]}" # e.g. X_UMAP1
233
- y = f"X_{dimensions.upper()}{xy[1]}" # e.g. X_UMAP2
234
- else:
235
- x = f"{use_key}{xy[0]}" # e.g. X_UMAP1
236
- y = f"{use_key}{xy[1]}" # e.g. X_UMAP2
232
+ prefix = _resolve_embedding_key(data=data, dimensions=dimensions, use_key=use_key, xy=xy)
233
+ x = f"{prefix}{xy[0]}" # e.g. X_UMAP1
234
+ y = f"{prefix}{xy[1]}" # e.g. X_UMAP2
237
235
 
238
236
  # HANDLE: variable_keys
239
237
  if variable_keys is None:
@@ -69,6 +69,7 @@ def dotplot(
69
69
  dendrogram: bool = False,
70
70
  dendrogram_color: str = "black",
71
71
  dendrogram_size: float = 0.5,
72
+ dendrogram_key: str | None = None,
72
73
  dendrogram_kwargs: dict | None = None,
73
74
  rectangle_size: float = 0.8,
74
75
  rectangle_color: str = "#3f3f3f",
@@ -144,6 +145,9 @@ def dotplot(
144
145
  Color of the dendrogram segments.
145
146
  dendrogram_size : float, default=0.5
146
147
  Size (thickness) of the dendrogram segments.
148
+ dendrogram_key : str | None, default=None
149
+ Specific key holding the precomputed dendrogram.
150
+ By default, `dendrogram_{group_by}` is used.
147
151
  dendrogram_kwargs : dict | None, default=None
148
152
  Additional parameters to pass to the dendrogram geom_segment.
149
153
  rectangle_size : float, default=0.8
@@ -339,7 +343,7 @@ def dotplot(
339
343
 
340
344
  # DETERMINE: y order of groups
341
345
  if dendrogram:
342
- y_order_groups, paths = _get_dendrogram(data, group_by)
346
+ y_order_groups, paths = _get_dendrogram(data, group_by, use_key=dendrogram_key)
343
347
  else:
344
348
  y_order_groups = (
345
349
  frame.select(group_by).unique(maintain_order=True)[group_by].cast(pl.String).to_list()
@@ -209,6 +209,7 @@ def heatmap(
209
209
  group_lines_size: float = 0.6,
210
210
  dendrogram_color: str = "black",
211
211
  dendrogram_size: float = 0.5,
212
+ dendrogram_key: str | None = None,
212
213
  group_lines_kwargs: dict | None = None,
213
214
  dendrogram_kwargs: dict | None = None,
214
215
  key_labels: bool = True,
@@ -284,6 +285,9 @@ def heatmap(
284
285
  Color of the dendrogram segments.
285
286
  dendrogram_size : float, default=0.5
286
287
  Size (thickness) of the dendrogram segments.
288
+ dendrogram_key : str | None, default=None
289
+ Specific key holding the precomputed dendrogram.
290
+ By default, `dendrogram_{group_by}` is used.
287
291
  group_lines_kwargs : dict | None, default=None
288
292
  Additional parameters to pass to the group separator lines geom_segment.
289
293
  dendrogram_kwargs : dict | None, default=None
@@ -499,7 +503,7 @@ def heatmap(
499
503
 
500
504
  # DETERMINE: y order of groups
501
505
  if dendrogram:
502
- y_order_groups, paths = _get_dendrogram(data, group_by)
506
+ y_order_groups, paths = _get_dendrogram(data, group_by, use_key=dendrogram_key)
503
507
  else:
504
508
  y_order_groups = (
505
509
  frame.select(group_by).unique(maintain_order=True)[group_by].cast(pl.String).to_list()
@@ -662,6 +666,7 @@ def matrixplot(
662
666
  group_lines_size: float = 0.6,
663
667
  dendrogram_color: str = "black",
664
668
  dendrogram_size: float = 0.5,
669
+ dendrogram_key: str | None = None,
665
670
  group_lines_kwargs: dict | None = None,
666
671
  dendrogram_kwargs: dict | None = None,
667
672
  key_labels: bool = True,
@@ -731,6 +736,9 @@ def matrixplot(
731
736
  Color of the dendrogram segments.
732
737
  dendrogram_size : float, default=0.5
733
738
  Size (thickness) of the dendrogram segments.
739
+ dendrogram_key : str | None, default=None
740
+ Specific key holding the precomputed dendrogram.
741
+ By default, `dendrogram_{group_by}` is used.
734
742
  group_lines_kwargs : dict | None, default=None
735
743
  Additional parameters to pass to the group separator lines geom_segment.
736
744
  dendrogram_kwargs : dict | None, default=None
@@ -842,6 +850,7 @@ def matrixplot(
842
850
  group_lines_size=group_lines_size,
843
851
  dendrogram_color=dendrogram_color,
844
852
  dendrogram_size=dendrogram_size,
853
+ dendrogram_key=dendrogram_key,
845
854
  group_lines_kwargs=group_lines_kwargs,
846
855
  dendrogram_kwargs=dendrogram_kwargs,
847
856
  value_column=value_column,
@@ -199,6 +199,7 @@ def stacked_violin(
199
199
  dendrogram: bool = False,
200
200
  dendrogram_color: str = "black",
201
201
  dendrogram_size: float = 0.5,
202
+ dendrogram_key: str | None = None,
202
203
  dendrogram_kwargs: dict | None = None,
203
204
  rectangle: bool = True,
204
205
  rectangle_size: float = 0.8,
@@ -292,6 +293,9 @@ def stacked_violin(
292
293
  Color of the dendrogram segments.
293
294
  dendrogram_size : float, default=0.5
294
295
  Size (thickness) of the dendrogram segments.
296
+ dendrogram_key : str | None, default=None
297
+ Specific key holding the precomputed dendrogram.
298
+ By default, `dendrogram_{group_by}` is used.
295
299
  dendrogram_kwargs : dict | None, default=None
296
300
  Additional parameters to pass to the dendrogram geom_path.
297
301
  rectangle : bool, default=True
@@ -456,7 +460,7 @@ def stacked_violin(
456
460
 
457
461
  # DETERMINE: y order of groups (dendrogram or first-seen order)
458
462
  if dendrogram:
459
- y_order_groups, paths = _get_dendrogram(data, group_by)
463
+ y_order_groups, paths = _get_dendrogram(data, group_by, use_key=dendrogram_key)
460
464
  else:
461
465
  y_order_groups = (
462
466
  frame.select(group_by).unique(maintain_order=True)[group_by].cast(pl.String).to_list()
@@ -36,6 +36,7 @@ def elbow(
36
36
  label: bool = False,
37
37
  label_size: float = 4.0,
38
38
  label_every: int = 5,
39
+ pca_key: str = "pca",
39
40
  **geom_kwargs,
40
41
  ) -> PlotSpec:
41
42
  """
@@ -73,6 +74,8 @@ def elbow(
73
74
  label_every : int, default=5
74
75
  Only components whose number is a multiple of this value are labeled
75
76
  (used when `label=True`). For example, `5` labels PC5, PC10, PC15, ...
77
+ pca_key : str, default='pca'
78
+ Specific key holding the PCA results.
76
79
  **geom_kwargs
77
80
  Additional parameters for the `geom_point` layer.
78
81
  For more information on geom_point parameters, see:
@@ -124,6 +127,7 @@ def elbow(
124
127
  n_pcs=n_pcs,
125
128
  component_column=component_column,
126
129
  variance_column=variance_column,
130
+ use_key=pca_key,
127
131
  )
128
132
 
129
133
  # HANDLE: mapping
@@ -104,7 +104,15 @@ def _spatial_components(
104
104
 
105
105
  library = spatial_uns[library_id]
106
106
  scale_factors = library.get("scalefactors", {})
107
- scale_factor = scale_factors.get(f"tissue_{image_key}_scalef", 1.0)
107
+ scalef_key = f"tissue_{image_key}_scalef"
108
+ scale_factor = scale_factors.get(scalef_key)
109
+ if scale_factor is None:
110
+ if image:
111
+ _warn(
112
+ f"Scale factor `{scalef_key}` missing; "
113
+ "spots may not align with the image."
114
+ )
115
+ scale_factor = 1.0
108
116
  spot_coordinates = data.obsm[spatial_key] * scale_factor
109
117
 
110
118
  image_array = None
@@ -11,6 +11,7 @@ from cellestial.util.utilities import (
11
11
  _is_observation_key,
12
12
  _is_variable_key,
13
13
  _range_inclusive,
14
+ _resolve_embedding_key,
14
15
  _resolve_tooltips,
15
16
  _select_variable_keys,
16
17
  _share_axis,
@@ -30,6 +31,7 @@ __all__ = [
30
31
  "_is_observation_key",
31
32
  "_is_variable_key",
32
33
  "_range_inclusive",
34
+ "_resolve_embedding_key",
33
35
  "_resolve_tooltips",
34
36
  "_select_variable_keys",
35
37
  "_share_axis",
@@ -4,22 +4,21 @@ import polars as pl
4
4
  from anndata import AnnData
5
5
 
6
6
 
7
- def _get_dendrogram(data: AnnData, group_by: str) -> tuple[list[str], pl.DataFrame]:
8
- """
9
- Get or compute the dendrogram for a group_by key and extract paths.
10
-
11
- Checks ``data.uns[f'dendrogram_{group_by}']``.
12
- For AnnData, if not present, runs ``scanpy.tl.dendrogram`` to compute it.
13
- Extracts the icoord/dcoord arrays into a Polars DataFrame of paths with normalized positions.
14
- """
7
+ def _get_dendrogram(
8
+ data: AnnData,
9
+ group_by: str,
10
+ *,
11
+ use_key: str | None = None,
12
+ ) -> tuple[list[str], pl.DataFrame]:
13
+ """Get or compute the dendrogram for `group_by` and extract paths."""
15
14
  import numpy as np
16
15
 
17
16
  if isinstance(data, AnnData):
18
- key = f"dendrogram_{group_by}"
17
+ key = use_key if use_key is not None else f"dendrogram_{group_by}"
19
18
  if key not in data.uns:
20
19
  import scanpy as sc
21
20
 
22
- sc.tl.dendrogram(data, groupby=group_by)
21
+ sc.tl.dendrogram(data, groupby=group_by, key_added=key)
23
22
 
24
23
  dendro = data.uns[key]
25
24
  categories_ordered = list(dendro["categories_ordered"])
@@ -24,7 +24,7 @@ from lets_plot import (
24
24
  from lets_plot.plot.core import FeatureSpec
25
25
  from lets_plot.plot.subplots import SupPlotsSpec
26
26
 
27
- from cellestial.util.errors import CellestialWarning
27
+ from cellestial.util.errors import CellestialWarning, KeyNotFoundError, UnsupportedDataTypeError
28
28
 
29
29
  _PACKAGE_ROOT = str(Path(__file__).parents[1])
30
30
 
@@ -532,6 +532,97 @@ def _are_variable_features(data: AnnData, keys: Sequence[str] | None) -> bool:
532
532
  return result
533
533
 
534
534
 
535
+ def _resolve_embedding_key(
536
+ data: AnnData,
537
+ dimensions: str,
538
+ use_key: str | None,
539
+ xy: Sequence[int],
540
+ ) -> str:
541
+ """
542
+ Resolve the column-name prefix used to index an embedding in the built frame.
543
+
544
+ `build_frame` emits embedding columns as ``f"{KEY.upper()}{n}"``, so callers
545
+ compose axis names like ``X_UMAP1`` / ``X_UMAP2`` by appending the axis
546
+ index to the prefix returned here.
547
+
548
+ Resolution rules
549
+ ----------------
550
+ 1. If ``use_key`` is given, return it uppercased.
551
+ 2. Otherwise, against ``f"X_{dimensions.upper()}"``:
552
+ - exact (case-insensitive) match -> return it silently.
553
+ - no exact, but prefix matches exist -> pick the candidate whose
554
+ dimensionality is sufficient for ``max(xy)`` (shortest name on ties),
555
+ warn the user, and suggest ``use_key`` to silence.
556
+ - no candidate -> raise ``KeyNotFoundError``.
557
+
558
+ Parameters
559
+ ----------
560
+ data : AnnData
561
+ Single-cell data object providing the embedding store.
562
+ dimensions : str
563
+ Embedding family, e.g. ``"umap"``, ``"pca"``, ``"tsne"``.
564
+ use_key : str | None
565
+ Explicit embedding key. When provided, no search is performed.
566
+ xy : Sequence[int]
567
+ Axis indices requested (1-based); used to filter shape-eligible
568
+ candidates during fallback.
569
+
570
+ Returns
571
+ -------
572
+ str
573
+ Uppercase column-name prefix.
574
+
575
+ Raises
576
+ ------
577
+ UnsupportedDataTypeError
578
+ If `data` is not a supported single-cell data object.
579
+ KeyNotFoundError
580
+ If no embedding matches the requested family.
581
+ """
582
+ if use_key is not None:
583
+ return use_key.upper()
584
+
585
+ if isinstance(data, AnnData):
586
+ candidates = list(data.obsm.keys())
587
+ shapes = {key: data.obsm[key].shape[1] for key in candidates}
588
+ else:
589
+ msg = f"Unsupported data type: `{type(data)}`"
590
+ raise UnsupportedDataTypeError(msg)
591
+
592
+ target = f"X_{dimensions.upper()}"
593
+ needed = max(xy)
594
+ search_targets = (target, dimensions.upper()) # accept bare `UMAP` too, not only `X_UMAP`
595
+
596
+ for key in candidates:
597
+ if key.upper() in search_targets:
598
+ return key.upper()
599
+
600
+ prefix_matches = [
601
+ key for key in candidates
602
+ if any(key.upper().startswith(t) for t in search_targets)
603
+ ]
604
+ if not prefix_matches:
605
+ msg = (
606
+ f"No embedding found for `dimensions='{dimensions}'`\n"
607
+ f"(looked for `{target}` or any key starting with it).\n"
608
+ f"Available embeddings: {candidates}."
609
+ )
610
+ raise KeyNotFoundError(msg)
611
+
612
+ eligible = [key for key in prefix_matches if shapes[key] >= needed]
613
+ pool = eligible or prefix_matches
614
+ chosen = sorted(pool, key=lambda key: (len(key), key))[0]
615
+
616
+ alternatives = sorted(prefix_matches)
617
+ _warn(
618
+ f"No embedding named `{target}` found; "
619
+ f"using `{chosen}` with (shape={shapes[chosen]})\n"
620
+ f"Alternatives: {alternatives}, Rest: {list(set(candidates) - set(alternatives))}.\n"
621
+ "Pass `use_key='...'` to use a specific key."
622
+ )
623
+ return chosen.upper()
624
+
625
+
535
626
  def _determine_axis(
536
627
  data: AnnData,
537
628
  keys: str | Sequence[str],
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cellestial"
3
- version = "0.48.0"
3
+ version = "0.48.2"
4
4
  description = ""
5
5
  authors = [
6
6
  { name = "Zaf4", email = "zafermolbio@gmail.com" },
File without changes
File without changes