napari-spatialdata 0.3.0__tar.gz → 0.4.0__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 (77) hide show
  1. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/.pre-commit-config.yaml +4 -4
  2. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/.readthedocs.yaml +1 -0
  3. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/CHANGELOG.md +21 -1
  4. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/PKG-INFO +14 -4
  5. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/README.md +9 -0
  6. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/docs/conf.py +1 -1
  7. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/pyproject.toml +6 -5
  8. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/setup.cfg +4 -3
  9. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/src/napari_spatialdata/__main__.py +2 -0
  10. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/src/napari_spatialdata/_constants/_constants.py +1 -0
  11. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/src/napari_spatialdata/_constants/_pkg_constants.py +1 -0
  12. napari-spatialdata-0.4.0/src/napari_spatialdata/_constants/config.py +2 -0
  13. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/src/napari_spatialdata/_interactive.py +16 -0
  14. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/src/napari_spatialdata/_model.py +90 -102
  15. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/src/napari_spatialdata/_reader.py +0 -2
  16. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/src/napari_spatialdata/_scatterwidgets.py +8 -8
  17. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/src/napari_spatialdata/_sdata_widgets.py +6 -4
  18. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/src/napari_spatialdata/_version.py +2 -2
  19. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/src/napari_spatialdata/_view.py +166 -38
  20. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/src/napari_spatialdata/_viewer.py +223 -47
  21. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/src/napari_spatialdata/_widgets.py +88 -70
  22. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/src/napari_spatialdata/utils/_categoricals_utils.py +18 -4
  23. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/src/napari_spatialdata/utils/_test_utils.py +24 -10
  24. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/src/napari_spatialdata/utils/_utils.py +99 -13
  25. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/src/napari_spatialdata/utils/_viewer_utils.py +2 -2
  26. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/src/napari_spatialdata.egg-info/PKG-INFO +14 -4
  27. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/src/napari_spatialdata.egg-info/SOURCES.txt +1 -0
  28. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/src/napari_spatialdata.egg-info/requires.txt +5 -3
  29. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/tests/conftest.py +7 -2
  30. napari-spatialdata-0.4.0/tests/plots/groundtruth/Images_can_add_element_image.png +0 -0
  31. napari-spatialdata-0.4.0/tests/plots/groundtruth/Images_can_add_element_multiple.png +0 -0
  32. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/tests/test_scatterwidgets.py +10 -8
  33. napari-spatialdata-0.4.0/tests/test_spatialdata.py +346 -0
  34. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/tests/test_utils.py +6 -6
  35. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/tests/test_widgets.py +53 -86
  36. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/tox.ini +1 -0
  37. napari-spatialdata-0.3.0/tests/plots/groundtruth/Images_can_add_element_image.png +0 -0
  38. napari-spatialdata-0.3.0/tests/plots/groundtruth/Images_can_add_element_multiple.png +0 -0
  39. napari-spatialdata-0.3.0/tests/test_spatialdata.py +0 -229
  40. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/.github/workflows/test_and_deploy.yml +0 -0
  41. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/.gitignore +0 -0
  42. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/.mypy.ini +0 -0
  43. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/.napari/DESCRIPTION.md +0 -0
  44. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/LICENSE +0 -0
  45. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/MANIFEST.in +0 -0
  46. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/docs/Makefile +0 -0
  47. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/docs/_static/.gitkeep +0 -0
  48. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/docs/_templates/.gitkeep +0 -0
  49. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/docs/_templates/autosummary/class.rst +0 -0
  50. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/docs/api.md +0 -0
  51. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/docs/cli.md +0 -0
  52. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/docs/contributing.md +0 -0
  53. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/docs/extensions/typed_returns.py +0 -0
  54. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/docs/index.md +0 -0
  55. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/docs/make.bat +0 -0
  56. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/docs/notebooks/mibitof_analysis.ipynb +0 -0
  57. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/docs/notebooks/nanostring_analysis.ipynb +0 -0
  58. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/docs/notebooks/scatterwidget.ipynb +0 -0
  59. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/docs/notebooks/spatialdata.ipynb +0 -0
  60. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/docs/references.bib +0 -0
  61. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/docs/references.md +0 -0
  62. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/docs/template_usage.md +0 -0
  63. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/examples/spatialdata_empty.py +0 -0
  64. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/examples/spatialdata_visium.py +0 -0
  65. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/src/napari_spatialdata/__init__.py +0 -0
  66. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/src/napari_spatialdata/_constants/__init__.py +0 -0
  67. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/src/napari_spatialdata/_constants/_utils.py +0 -0
  68. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/src/napari_spatialdata/napari.yaml +0 -0
  69. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/src/napari_spatialdata/utils/__init__.py +0 -0
  70. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/src/napari_spatialdata.egg-info/dependency_links.txt +0 -0
  71. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/src/napari_spatialdata.egg-info/entry_points.txt +0 -0
  72. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/src/napari_spatialdata.egg-info/top_level.txt +0 -0
  73. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/tests/__init__.py +0 -0
  74. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/tests/plots/groundtruth/Images_can_add_element_label.png +0 -0
  75. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/tests/test_cli.py +0 -0
  76. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/tests/test_interactive.py +0 -0
  77. {napari-spatialdata-0.3.0 → napari-spatialdata-0.4.0}/tests/test_viewer.py +0 -0
@@ -7,18 +7,18 @@ default_stages:
7
7
  minimum_pre_commit_version: 2.9.3
8
8
  repos:
9
9
  - repo: https://github.com/pre-commit/mirrors-mypy
10
- rev: v1.6.1
10
+ rev: v1.9.0
11
11
  hooks:
12
12
  - id: mypy
13
13
  additional_dependencies: [numpy>=1.23]
14
14
  exclude: docs
15
15
  - repo: https://github.com/psf/black
16
- rev: 23.10.1
16
+ rev: 24.3.0
17
17
  hooks:
18
18
  - id: black
19
19
  additional_dependencies: [toml]
20
20
  - repo: https://github.com/pre-commit/mirrors-prettier
21
- rev: v3.0.3
21
+ rev: v4.0.0-alpha.8
22
22
  hooks:
23
23
  - id: prettier
24
24
  - repo: https://github.com/pre-commit/pre-commit-hooks
@@ -49,7 +49,7 @@ repos:
49
49
  hooks:
50
50
  - id: blacken-docs
51
51
  - repo: https://github.com/astral-sh/ruff-pre-commit
52
- rev: v0.1.4
52
+ rev: v0.3.3
53
53
  hooks:
54
54
  - id: ruff
55
55
  args: [--fix, --exit-non-zero-on-fix]
@@ -15,3 +15,4 @@ python:
15
15
  - "doc"
16
16
  - "all"
17
17
  - "readthedocs"
18
+ - "pre"
@@ -8,7 +8,27 @@ and this project adheres to [Semantic Versioning][].
8
8
  [keep a changelog]: https://keepachangelog.com/en/1.0.0/
9
9
  [semantic versioning]: https://semver.org/spec/v2.0.0.html
10
10
 
11
- ## [0.3.1] - TBD
11
+ ## [0.4.0] - 2023-03-24
12
+
13
+ ### Added
14
+
15
+ - Multi table support #199 @melonora
16
+ - Color shapes and points by values in dataframe in addition to tables #216
17
+
18
+ ### Fixed
19
+
20
+ - Avoid exception when registering shortcuts #201 @aeisenbarth
21
+ - Fix wrong point size when affine specified #193
22
+ - Wrong element visiblity when changing coordinate system #207
23
+ - Cleaned up data model #180
24
+ - Fixes correct matching of geometies and annoations via join #208
25
+ - Better docstrings #219
26
+
27
+ ## [0.3.1] - 2023-11-11
28
+
29
+ ### Fix
30
+
31
+ - removed npe (napari plugin engine) dependency
12
32
 
13
33
  ## [0.3.0] - 2023-11-09
14
34
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: napari-spatialdata
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Interactive visualization of spatial omics data with napari
5
5
  Home-page: https://github.com/scverse/napari-spatialdata.git
6
6
  Author: giovanni palla
@@ -30,15 +30,15 @@ Requires-Dist: dask
30
30
  Requires-Dist: geopandas
31
31
  Requires-Dist: loguru
32
32
  Requires-Dist: matplotlib
33
- Requires-Dist: napari
33
+ Requires-Dist: napari>=0.4.19
34
34
  Requires-Dist: napari-matplotlib
35
- Requires-Dist: napari-plugin-engine
36
35
  Requires-Dist: numba
37
36
  Requires-Dist: numpy
38
37
  Requires-Dist: packaging
39
38
  Requires-Dist: pandas
40
39
  Requires-Dist: pillow
41
40
  Requires-Dist: qtpy
41
+ Requires-Dist: scanpy
42
42
  Requires-Dist: scipy
43
43
  Requires-Dist: shapely
44
44
  Requires-Dist: scikit-learn
@@ -67,9 +67,10 @@ Requires-Dist: sphinx-copybutton; extra == "doc"
67
67
  Requires-Dist: sphinx-qt-documentation; extra == "doc"
68
68
  Requires-Dist: myst-nb; extra == "doc"
69
69
  Requires-Dist: squidpy; extra == "doc"
70
- Requires-Dist: scanpy; extra == "doc"
71
70
  Provides-Extra: readthedocs
72
71
  Requires-Dist: pydantic<2; extra == "readthedocs"
72
+ Provides-Extra: pre
73
+ Requires-Dist: spatialdata>=0.1.0-pre0; extra == "pre"
73
74
  Provides-Extra: all
74
75
  Requires-Dist: PyQt5; extra == "all"
75
76
 
@@ -110,6 +111,11 @@ pip install napari-spatialdata
110
111
 
111
112
  You can find more details on this in the [installation instructions](https://spatialdata.scverse.org/en/latest/installation.html).
112
113
 
114
+ ## Using napari-spatialdata as default zarr reader
115
+
116
+ If you would like to use the plugin as the default zarr reader, in napari please go to `File` -> `Preferences`
117
+ -> `Plugins` and follow the instructions under `File extension readers`.
118
+
113
119
  ## Development Version
114
120
 
115
121
  You can install `napari-spatialdata` from Github with:
@@ -145,6 +151,10 @@ Distributed under the terms of the [BSD-3] license,
145
151
 
146
152
  If you encounter any problems, please file an [issue] along with a detailed description.
147
153
 
154
+ ## Citation
155
+
156
+ Marconato, L., Palla, G., Yamauchi, K.A. et al. SpatialData: an open and universal data framework for spatial omics. Nat Methods (2024). https://doi.org/10.1038/s41592-024-02212-x
157
+
148
158
  [napari]: https://github.com/napari/napari
149
159
  [cookiecutter]: https://github.com/audreyr/cookiecutter
150
160
  [@napari]: https://github.com/napari
@@ -35,6 +35,11 @@ pip install napari-spatialdata
35
35
 
36
36
  You can find more details on this in the [installation instructions](https://spatialdata.scverse.org/en/latest/installation.html).
37
37
 
38
+ ## Using napari-spatialdata as default zarr reader
39
+
40
+ If you would like to use the plugin as the default zarr reader, in napari please go to `File` -> `Preferences`
41
+ -> `Plugins` and follow the instructions under `File extension readers`.
42
+
38
43
  ## Development Version
39
44
 
40
45
  You can install `napari-spatialdata` from Github with:
@@ -70,6 +75,10 @@ Distributed under the terms of the [BSD-3] license,
70
75
 
71
76
  If you encounter any problems, please file an [issue] along with a detailed description.
72
77
 
78
+ ## Citation
79
+
80
+ Marconato, L., Palla, G., Yamauchi, K.A. et al. SpatialData: an open and universal data framework for spatial omics. Nat Methods (2024). https://doi.org/10.1038/s41592-024-02212-x
81
+
73
82
  [napari]: https://github.com/napari/napari
74
83
  [cookiecutter]: https://github.com/audreyr/cookiecutter
75
84
  [@napari]: https://github.com/napari
@@ -135,7 +135,7 @@ nitpick_ignore = [
135
135
  # ("py:obj", "napari_spatialdata.QtAdataViewWidget.insertAction"),
136
136
  # ("py:obj", "napari_spatialdata.QtAdataViewWidget.scroll"),
137
137
  # ("py:obj", "napari_spatialdata.QtAdataViewWidget.setTabOrder"),
138
- # ("py:class", "napari_spatialdata._model.ImageModel"),
138
+ # ("py:class", "napari_spatialdata._model.DataModel"),
139
139
  # ("py:obj", "napari_spatialdata.QtAdataScatterWidget.scroll"),
140
140
  # ("py:obj", "napari_spatialdata.QtAdataScatterWidget.insertAction"),
141
141
  # If building the documentation fails because of a missing link that is outside your control,
@@ -37,6 +37,9 @@ exclude = [
37
37
  "dist",
38
38
  "setup.py",
39
39
  ]
40
+ line-length = 120
41
+ target-version = "py38"
42
+ [tool.ruff.lint]
40
43
  ignore = [
41
44
  # Do not assign a lambda expression, use a def -> lambda expression assignments are convenient
42
45
  "E731",
@@ -56,7 +59,6 @@ ignore = [
56
59
  # Missing docstring in magic method
57
60
  "D105",
58
61
  ]
59
- line-length = 120
60
62
  select = [
61
63
  "D", # flake8-docstrings
62
64
  "I", # isort
@@ -76,8 +78,9 @@ select = [
76
78
  "RET", # flake8-raise
77
79
  ]
78
80
  unfixable = ["B", "UP", "C4", "BLE", "T20", "RET"]
79
- target-version = "py38"
80
- [tool.ruff.per-file-ignores]
81
+ [tool.ruff.lint.pydocstyle]
82
+ convention = "numpy"
83
+ [tool.ruff.lint.per-file-ignores]
81
84
  "tests/*" = ["D", "PT", "B024"]
82
85
  "*/__init__.py" = ["F401", "D104", "D107"]
83
86
  "docs/*" = ["D","B","E","A"]
@@ -86,5 +89,3 @@ target-version = "py38"
86
89
  "src/napari_spatialdata/_constants/_utils.py" = ["D101","D102", "B024"]
87
90
  "src/napari_spatialdata/_widgets.py" = ["D"]
88
91
  "src/napari_spatialdata/_scatterwidgets.py" = ["D"]
89
- [tool.ruff.pydocstyle]
90
- convention = "numpy"
@@ -37,15 +37,15 @@ install_requires =
37
37
  geopandas
38
38
  loguru
39
39
  matplotlib
40
- napari
40
+ napari>=0.4.19
41
41
  napari-matplotlib
42
- napari-plugin-engine
43
42
  numba
44
43
  numpy
45
44
  packaging
46
45
  pandas
47
46
  pillow
48
47
  qtpy
48
+ scanpy
49
49
  scipy
50
50
  shapely
51
51
  scikit-learn
@@ -78,9 +78,10 @@ doc =
78
78
  sphinx-qt-documentation
79
79
  myst-nb
80
80
  squidpy
81
- scanpy
82
81
  readthedocs =
83
82
  pydantic<2
83
+ pre =
84
+ spatialdata>=0.1.0-pre0
84
85
  all =
85
86
  PyQt5
86
87
 
@@ -32,6 +32,8 @@ def view(path: tuple[str], headless: bool) -> None:
32
32
  ----------
33
33
  path
34
34
  Path to one or more SpatialData datasets.
35
+ headless
36
+ Whether to run the napari application or to run in headless mode.
35
37
  """
36
38
  assert type(path) == tuple
37
39
 
@@ -1,4 +1,5 @@
1
1
  """Constants that user deals with."""
2
+
2
3
  from enum import unique
3
4
 
4
5
  from napari_spatialdata._constants._utils import ModeEnum
@@ -1,4 +1,5 @@
1
1
  """Internal constants not exposed to the user."""
2
+
2
3
  from typing import Any, Callable, Optional
3
4
 
4
5
  from anndata import AnnData
@@ -0,0 +1,2 @@
1
+ POLYGON_THRESHOLD = 100
2
+ POINT_THRESHOLD = 100000
@@ -34,6 +34,19 @@ class Interactive:
34
34
  """
35
35
 
36
36
  def add_element(self, element: str, element_coordinate_system: str, view_element_system: bool = False) -> None:
37
+ """
38
+ Add an element of a spatial data object being visualized with interactive to the viewer.
39
+
40
+ Parameters
41
+ ----------
42
+ element
43
+ The name of the element in the spatial data object to add as napari layer.
44
+ element_coordinate_system
45
+ The coordinate system in which the layer should be visualized.
46
+ view_element_system
47
+ Whether to switch to element_coordinate_system or to switch back to current active coordinate system
48
+ after adding an element as layer.
49
+ """
37
50
  duplicate_element_names, _ = get_duplicate_element_names(self._sdata)
38
51
  elements, name_to_add = get_elements_meta_mapping(
39
52
  self._sdata, element_coordinate_system, duplicate_element_names, element
@@ -58,6 +71,7 @@ class Interactive:
58
71
  raise ValueError(f"Element {element} not found in coordinate system {element_coordinate_system}.")
59
72
 
60
73
  def switch_coordinate_system(self, coordinate_system: str) -> None:
74
+ """Switch to a coordinate system present in the spatialdata object(s)."""
61
75
  widget_item = get_itemindex_by_text(self._sdata_widget.coordinate_system_widget, coordinate_system)
62
76
  if widget_item:
63
77
  self._sdata_widget.coordinate_system_widget.setCurrentItem(widget_item)
@@ -80,7 +94,9 @@ class Interactive:
80
94
  self.run()
81
95
 
82
96
  def run(self) -> None:
97
+ """Run the napari application."""
83
98
  napari.run()
84
99
 
85
100
  def screenshot(self) -> NDArrayA | Any:
101
+ """Take a screenshot of the viewer in its current state."""
86
102
  return self._viewer.screenshot(canvas_only=False)
@@ -1,5 +1,5 @@
1
1
  from dataclasses import dataclass, field
2
- from typing import TYPE_CHECKING, Any, Optional, Tuple, Union
2
+ from typing import Any, Optional, Sequence, Tuple, Union
3
3
 
4
4
  import numpy as np
5
5
  import pandas as pd
@@ -8,65 +8,63 @@ from napari.layers import Layer
8
8
  from napari.utils.events import EmitterGroup, Event
9
9
 
10
10
  from napari_spatialdata._constants._constants import Symbol
11
- from napari_spatialdata._constants._pkg_constants import Key
12
11
  from napari_spatialdata.utils._utils import NDArrayA, _ensure_dense_vector
13
12
 
14
- __all__ = ["ImageModel"]
13
+ __all__ = ["DataModel"]
15
14
 
16
15
 
17
16
  @dataclass
18
- class ImageModel:
17
+ class DataModel:
19
18
  """Model which holds the data for interactive visualization."""
20
19
 
21
20
  events: EmitterGroup = field(init=False, default=None, repr=True)
21
+ _table_names: Sequence[Optional[str]] = field(default_factory=list, init=False)
22
+ _active_table_name: Optional[str] = field(default=None, init=False, repr=True)
22
23
  _layer: Layer = field(init=False, default=None, repr=True)
23
24
  _adata: Optional[AnnData] = field(init=False, default=None, repr=True)
24
- _spatial_key: str = field(default=Key.obsm.spatial, repr=False)
25
25
  _adata_layer: Optional[str] = field(init=False, default=None, repr=False)
26
- _label_key: Optional[str] = field(default=None, repr=True)
27
- _coordinates: Optional[NDArrayA] = field(init=False, default=None, repr=True)
28
- _points_coordinates: Optional[NDArrayA] = field(init=False, default=None, repr=True)
29
- _points_var: Optional[pd.Series] = field(init=False, default=None, repr=True)
30
- _scale: Optional[float] = field(init=False, default=None)
26
+ _region_key: Optional[str] = field(default=None, repr=True)
27
+ _instance_key: Optional[str] = field(default=None, repr=True)
28
+ _color_by: str = field(default="", repr=True, init=False)
31
29
  _system_name: Optional[str] = field(default=None, repr=True)
32
30
 
33
- _spot_diameter: Union[NDArrayA, float] = field(init=False, default=1)
34
- _point_diameter: Union[NDArrayA, float] = field(init=False, default=1)
35
31
  _scale_key: Optional[str] = field(init=False, default="tissue_hires_scalef") # TODO(giovp): use constants for these
36
32
 
37
33
  _palette: Optional[str] = field(init=False, default=None, repr=False)
38
34
  _cmap: str = field(init=False, default="viridis", repr=False)
39
35
  _symbol: str = field(init=False, default=Symbol.DISC, repr=False)
40
36
 
41
- VALID_ATTRIBUTES = ("obs", "var", "obsm", "points")
37
+ VALID_ATTRIBUTES = ("obs", "var", "obsm", "columns_df")
42
38
 
43
39
  def __post_init__(self) -> None:
44
40
  self.events = EmitterGroup(
45
41
  source=self,
46
42
  layer=Event,
47
43
  adata=Event,
44
+ color_by=Event,
48
45
  )
49
46
 
50
- def get_items(self, attr: str) -> Tuple[str, ...]:
47
+ def get_items(self, attr: str) -> Optional[Tuple[str, ...]]:
51
48
  """
52
49
  Return valid keys for an attribute.
53
50
 
54
51
  Parameters
55
52
  ----------
56
53
  attr
57
- Attribute of :mod:`anndata.AnnData` to access.
54
+ Attribute of :mod:`anndata.AnnData` to access or if columns_df indicates
55
+ the column is part of the SpatialElement dataframe and this will be retrieved.
58
56
 
59
57
  Returns
60
58
  -------
61
- The available items.
59
+ The available items coerced to a tuple of strings.
62
60
  """
63
61
  if attr in ("obs", "obsm"):
64
62
  return tuple(map(str, getattr(self.adata, attr).keys()))
65
- if attr == "points":
66
- if self.points_var is not None:
67
- return tuple(map(str, self.points_var.unique()))
68
- return tuple(["No points found."]) # noqa: C409
69
- return tuple(map(str, getattr(self.adata, attr).index))
63
+ if attr == "columns_df" and self.layer is not None and (df_cols := self.layer.metadata.get("_columns_df")):
64
+ return tuple(map(str, df_cols.columns))
65
+ if attr == "var":
66
+ return tuple(map(str, getattr(self.adata, attr).index))
67
+ return None
70
68
 
71
69
  @_ensure_dense_vector
72
70
  def get_obs(
@@ -86,12 +84,35 @@ class ImageModel:
86
84
  """
87
85
  if name not in self.adata.obs.columns:
88
86
  raise KeyError(f"Key `{name}` not found in `adata.obs`.")
89
- return self.adata.obs[name], self._format_key(name)
87
+ if name != self.instance_key:
88
+ adata_obs = self.adata.obs[[self.instance_key, name]]
89
+ adata_obs.set_index(self.instance_key, inplace=True)
90
+ else:
91
+ adata_obs = self.adata.obs
92
+ return adata_obs[name], self._format_key(name)
93
+
94
+ @_ensure_dense_vector
95
+ def get_columns_df(self, name: Union[str, int], **_: Any) -> Tuple[Optional[NDArrayA], str]:
96
+ """
97
+ Return a column of the dataframe of the SpatialElement.
98
+
99
+ Parameters
100
+ ----------
101
+ name
102
+ Name of the column in the dataframe to retrieve.
103
+
104
+ Returns
105
+ -------
106
+ The dataframe column of interest and the formatted name of the column.
107
+ """
108
+ if self.layer is None:
109
+ raise ValueError("Layer must be present")
110
+ return self.layer.metadata["_columns_df"][name], self._format_key(name)
90
111
 
91
112
  @_ensure_dense_vector
92
113
  def get_var(self, name: Union[str, int], **_: Any) -> Tuple[Optional[NDArrayA], str]: # TODO(giovp): fix docstring
93
114
  """
94
- Return a gene.
115
+ Return a column in anndata.var_names.
95
116
 
96
117
  Parameters
97
118
  ----------
@@ -151,25 +172,6 @@ class ImageModel:
151
172
 
152
173
  return (res if res.ndim == 1 else res[:, index]), pretty_name
153
174
 
154
- def get_points(self, name: str, **_: Any) -> Tuple[Optional[NDArrayA], str]: # TODO(giovp): fix docstring
155
- """
156
- Return a gene in spatial coordinates.
157
-
158
- Parameters
159
- ----------
160
- name
161
- Gene name.
162
-
163
- Returns
164
- -------
165
- The values and the formatted ``name``.
166
- """
167
- if name not in self.points_var.unique():
168
- raise KeyError(f"Key `{name}` not found in `adata.uns['points']['gene']`.")
169
- coords = self.points_coordinates
170
- coords = coords[self.points_var == name]
171
- return np.insert(coords[:, ::-1][:, :2] * self.scale, 0, values=0, axis=1), self._format_key(name)
172
-
173
175
  def _format_key(
174
176
  self, key: Union[str, int], index: Optional[Union[int, str]] = None, adata_layer: bool = False
175
177
  ) -> str:
@@ -180,8 +182,28 @@ class ImageModel:
180
182
 
181
183
  return str(key) + (f":{self.layer}" if self.layer is not None else ":X")
182
184
 
185
+ @property
186
+ def color_by(self) -> str:
187
+ """The name by which the layer is currently colored."""
188
+ return self._color_by
189
+
190
+ @color_by.setter
191
+ def color_by(self, color_column: str) -> None:
192
+ self._color_by = color_column
193
+ self.events.color_by()
194
+
195
+ @property
196
+ def table_names(self) -> Sequence[Optional[str]]:
197
+ """The table names annotating the current active napari layer, if any."""
198
+ return self._table_names
199
+
200
+ @table_names.setter
201
+ def table_names(self, table_names: Sequence[Optional[str]]) -> None:
202
+ self._table_names = table_names
203
+
183
204
  @property
184
205
  def layer(self) -> Optional[Layer]: # noqa: D102
206
+ """The current active napari layer."""
185
207
  return self._layer
186
208
 
187
209
  @layer.setter
@@ -189,8 +211,18 @@ class ImageModel:
189
211
  self._layer = layer
190
212
  self.events.layer()
191
213
 
214
+ @property
215
+ def active_table_name(self) -> Optional[str]:
216
+ """The table name currently active in the widget."""
217
+ return self._active_table_name
218
+
219
+ @active_table_name.setter
220
+ def active_table_name(self, active_table_name: Optional[str]) -> None:
221
+ self._active_table_name = active_table_name
222
+
192
223
  @property
193
224
  def adata(self) -> AnnData: # noqa: D102
225
+ """The anndata object corresponding to the current active table name."""
194
226
  return self._adata
195
227
 
196
228
  @adata.setter
@@ -198,16 +230,9 @@ class ImageModel:
198
230
  self._adata = adata
199
231
  self.events.adata()
200
232
 
201
- @property
202
- def spatial_key(self) -> str: # noqa: D102
203
- return self._spatial_key
204
-
205
- @spatial_key.setter
206
- def spatial_key(self, key: str) -> None:
207
- self._spatial_key = key
208
-
209
233
  @property
210
234
  def adata_layer(self) -> Optional[str]: # noqa: D102
235
+ """The current anndata layer."""
211
236
  return self._adata_layer
212
237
 
213
238
  @adata_layer.setter
@@ -215,65 +240,26 @@ class ImageModel:
215
240
  self._adata_layer = adata_layer
216
241
 
217
242
  @property
218
- def coordinates(self) -> NDArrayA: # noqa: D102
219
- return self._coordinates # type: ignore[return-value]
220
-
221
- @coordinates.setter
222
- def coordinates(self, coordinates: NDArrayA) -> None:
223
- self._coordinates = coordinates
224
-
225
- @property
226
- def points_coordinates(self) -> NDArrayA: # noqa: D102
227
- if TYPE_CHECKING:
228
- assert self._points_coordinates is not None
229
- return self._points_coordinates
230
-
231
- @points_coordinates.setter
232
- def points_coordinates(self, points_coordinates: NDArrayA) -> None:
233
- self._points_coordinates = points_coordinates
234
-
235
- @property
236
- def points_var(self) -> pd.Series: # noqa: D102
237
- return self._points_var
238
-
239
- @points_var.setter
240
- def points_var(self, points_var: pd.Series) -> None:
241
- self._points_var = points_var
242
-
243
- @property
244
- def scale(self) -> Optional[float]: # noqa: D102
245
- return self._scale
246
-
247
- @scale.setter
248
- def scale(self, scale: float) -> None:
249
- self._scale = scale
250
-
251
- @property
252
- def spot_diameter(self) -> Union[NDArrayA, float]: # noqa: D102
253
- return self._spot_diameter
254
-
255
- @spot_diameter.setter
256
- def spot_diameter(self, spot_diameter: Union[NDArrayA, float]) -> None:
257
- self._spot_diameter = spot_diameter
258
-
259
- @property
260
- def point_diameter(self) -> Union[NDArrayA, float]: # noqa: D102
261
- return self._point_diameter
243
+ def region_key(self) -> Optional[str]: # noqa: D102
244
+ """The region key of the currently active table in the widget."""
245
+ return self._region_key
262
246
 
263
- @point_diameter.setter
264
- def point_diameter(self, point_diameter: Union[NDArrayA, float]) -> None:
265
- self._point_diameter = point_diameter
247
+ @region_key.setter
248
+ def region_key(self, region_key: str) -> None:
249
+ self._region_key = region_key
266
250
 
267
251
  @property
268
- def labels_key(self) -> Optional[str]: # noqa: D102
269
- return self._labels_key
252
+ def instance_key(self) -> Optional[str]: # noqa: D102
253
+ """The instance key of the currently active table in the widget."""
254
+ return self._instance_key
270
255
 
271
- @labels_key.setter
272
- def labels_key(self, region_key: str) -> None:
273
- self._labels_key = region_key
256
+ @instance_key.setter
257
+ def instance_key(self, instance_key: str) -> None:
258
+ self._instance_key = instance_key
274
259
 
275
260
  @property
276
261
  def palette(self) -> Optional[str]: # noqa: D102
262
+ """The palette from which to draw the colors."""
277
263
  return self._palette
278
264
 
279
265
  @palette.setter
@@ -282,6 +268,7 @@ class ImageModel:
282
268
 
283
269
  @property
284
270
  def cmap(self) -> str: # noqa: D102
271
+ """The continuous color map to draw colors from."""
285
272
  return self._cmap
286
273
 
287
274
  @cmap.setter
@@ -306,6 +293,7 @@ class ImageModel:
306
293
 
307
294
  @property
308
295
  def system_name(self) -> Optional[str]: # noqa: D102
296
+ """The layer name."""
309
297
  return self._system_name
310
298
 
311
299
  @system_name.setter
@@ -3,7 +3,6 @@ from __future__ import annotations
3
3
  from typing import Callable
4
4
 
5
5
  from loguru import logger
6
- from napari_plugin_engine import napari_hook_implementation
7
6
  from spatialdata import SpatialData
8
7
 
9
8
  from napari_spatialdata import Interactive
@@ -11,7 +10,6 @@ from napari_spatialdata import Interactive
11
10
  readable_extensions = (".zarr",)
12
11
 
13
12
 
14
- @napari_hook_implementation
15
13
  def get_reader(path: str) -> Callable[..., list[tuple[None]]] | None:
16
14
  """Napari hook specification that start the napari-spatialdata plugin with the given path."""
17
15
  # if we know we cannot read the file, we immediately return None.