napari-spatialdata 0.7.1__tar.gz → 0.7.3__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.
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/.github/workflows/benchmarks.yml +2 -2
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/.github/workflows/test.yml +1 -1
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/.pre-commit-config.yaml +3 -3
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/.readthedocs.yaml +1 -1
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/PKG-INFO +6 -6
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/README.md +2 -2
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/asv.conf.json +1 -1
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/setup.cfg +3 -3
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata/_model.py +2 -3
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata/_scatterwidgets.py +24 -28
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata/_sdata_widgets.py +57 -4
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata/_version.py +3 -3
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata/_view.py +5 -13
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata/_viewer.py +57 -14
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata/utils/_utils.py +55 -10
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata.egg-info/PKG-INFO +6 -6
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata.egg-info/SOURCES.txt +2 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata.egg-info/requires.txt +1 -1
- napari_spatialdata-0.7.3/src/napari_spatialdata.egg-info/scm_file_list.json +92 -0
- napari_spatialdata-0.7.3/src/napari_spatialdata.egg-info/scm_version.json +8 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/tests/conftest.py +57 -3
- napari_spatialdata-0.7.3/tests/test_3d_visualization.py +350 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/tests/test_scatterwidgets.py +2 -2
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/tests/test_widgets.py +1 -1
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/tox.ini +3 -3
- napari_spatialdata-0.7.1/tests/test_3d_visualization.py +0 -161
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/.github/BENCHMARK_FAIL_TEMPLATE.md +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/.github/dependabot.yml +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/.github/release.yml +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/.github/workflows/benchmarks_report.yml +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/.github/workflows/release.yml +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/.gitignore +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/.mypy.ini +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/.napari/DESCRIPTION.md +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/LICENSE +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/MANIFEST.in +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/benchmarks/Readme.md +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/benchmarks/__init__.py +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/benchmarks/benchmark.in +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/benchmarks/benchmark.txt +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/benchmarks/benchmark_model.py +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/benchmarks/benchmark_qt_widget.py +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/docs/Makefile +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/docs/_static/.gitkeep +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/docs/_templates/.gitkeep +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/docs/_templates/autosummary/class.rst +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/docs/api.md +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/docs/changelog.md +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/docs/cli.md +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/docs/conf.py +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/docs/contributing.md +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/docs/extensions/typed_returns.py +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/docs/index.md +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/docs/limitations.md +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/docs/make.bat +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/docs/notebooks/mibitof_analysis.ipynb +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/docs/notebooks/nanostring_analysis.ipynb +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/docs/notebooks/scatterwidget.ipynb +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/docs/notebooks/scatterwidget_annotation.ipynb +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/docs/notebooks/spatialdata.ipynb +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/docs/references.bib +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/docs/references.md +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/examples/spatialdata_empty.py +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/examples/spatialdata_visium.py +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/pyproject.toml +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata/__init__.py +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata/__main__.py +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata/_annotationwidgets.py +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata/_interactive.py +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata/_reader.py +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata/_widgets.py +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata/constants/__init__.py +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata/constants/_constants.py +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata/constants/_pkg_constants.py +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata/constants/_utils.py +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata/constants/config.py +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata/napari.yaml +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata/resources/exclamation.png +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata/resources/icons8-home-48.png +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata/resources/icons8-pencil-drawing-50.png +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata/resources/icons8-polygon-80.png +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata/resources/icons8-rectangle-48.png +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata/utils/__init__.py +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata/utils/_categoricals_utils.py +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata/utils/_test_utils.py +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata/utils/_viewer_utils.py +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata.egg-info/dependency_links.txt +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata.egg-info/entry_points.txt +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata.egg-info/top_level.txt +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/tests/__init__.py +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/tests/plots/groundtruth/Images_can_add_element_image.png +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/tests/plots/groundtruth/Images_can_add_element_label.png +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/tests/plots/groundtruth/Images_can_add_element_multiple.png +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/tests/test_cli.py +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/tests/test_interactive.py +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/tests/test_spatialdata.py +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/tests/test_utils.py +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/tests/test_view.py +0 -0
- {napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/tests/test_viewer.py +0 -0
|
@@ -66,7 +66,7 @@ jobs:
|
|
|
66
66
|
- uses: actions/setup-python@v6
|
|
67
67
|
name: Install Python
|
|
68
68
|
with:
|
|
69
|
-
python-version: "3.
|
|
69
|
+
python-version: "3.12"
|
|
70
70
|
cache-dependency-path: pyproject.toml
|
|
71
71
|
|
|
72
72
|
- uses: tlambert03/setup-qt-libs@v1
|
|
@@ -140,7 +140,7 @@ jobs:
|
|
|
140
140
|
env:
|
|
141
141
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
142
142
|
PLATFORM: ${{ matrix.runs-on }}
|
|
143
|
-
PYTHON: "3.
|
|
143
|
+
PYTHON: "3.12"
|
|
144
144
|
BACKEND: ${{ matrix.benchmark-name }}
|
|
145
145
|
RUN_ID: ${{ github.run_id }}
|
|
146
146
|
TITLE: "[test-bot] Benchmark tests failing"
|
|
@@ -7,13 +7,13 @@ 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.
|
|
10
|
+
rev: v1.20.2
|
|
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-pre-commit-mirror
|
|
16
|
-
rev:
|
|
16
|
+
rev: 26.3.1
|
|
17
17
|
hooks:
|
|
18
18
|
- id: black
|
|
19
19
|
additional_dependencies: [toml]
|
|
@@ -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.
|
|
52
|
+
rev: v0.15.12
|
|
53
53
|
hooks:
|
|
54
54
|
- id: ruff
|
|
55
55
|
args: [--fix, --exit-non-zero-on-fix]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: napari-spatialdata
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.3
|
|
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
|
|
@@ -16,12 +16,12 @@ Classifier: Framework :: napari
|
|
|
16
16
|
Classifier: Topic :: Software Development :: Testing
|
|
17
17
|
Classifier: Programming Language :: Python
|
|
18
18
|
Classifier: Programming Language :: Python :: 3
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.12
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
22
|
Classifier: Operating System :: OS Independent
|
|
23
23
|
Classifier: License :: OSI Approved :: BSD License
|
|
24
|
-
Requires-Python: >=3.
|
|
24
|
+
Requires-Python: >=3.12
|
|
25
25
|
Description-Content-Type: text/markdown
|
|
26
26
|
License-File: LICENSE
|
|
27
27
|
Requires-Dist: anndata>=0.12.0
|
|
@@ -78,10 +78,10 @@ Requires-Dist: pydantic>2; extra == "readthedocs"
|
|
|
78
78
|
Provides-Extra: pre
|
|
79
79
|
Requires-Dist: spatialdata>=0.7.3a1; extra == "pre"
|
|
80
80
|
Provides-Extra: all
|
|
81
|
-
Requires-Dist: napari[
|
|
81
|
+
Requires-Dist: napari[pyqt6]; extra == "all"
|
|
82
82
|
Dynamic: license-file
|
|
83
83
|
|
|
84
|
-

|
|
85
85
|
|
|
86
86
|
# napari-spatialdata: interactive exploration and annotation of spatial omics data
|
|
87
87
|
|
|
@@ -108,7 +108,7 @@ You can install `napari-spatialdata` via [pip]:
|
|
|
108
108
|
|
|
109
109
|
pip install napari-spatialdata[all]
|
|
110
110
|
|
|
111
|
-
The `all`
|
|
111
|
+
The `all` extra also installs the Qt bindings `PyQt6` (via `napari[pyqt6]`).
|
|
112
112
|
|
|
113
113
|
Napari now also includes multiple triangulation backends. These improve the speed by which a napari 'Shapes' layer gets
|
|
114
114
|
loaded (this does not improve the speed of editing large numbers of shapes yet!). See also the napari
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-

|
|
2
2
|
|
|
3
3
|
# napari-spatialdata: interactive exploration and annotation of spatial omics data
|
|
4
4
|
|
|
@@ -25,7 +25,7 @@ You can install `napari-spatialdata` via [pip]:
|
|
|
25
25
|
|
|
26
26
|
pip install napari-spatialdata[all]
|
|
27
27
|
|
|
28
|
-
The `all`
|
|
28
|
+
The `all` extra also installs the Qt bindings `PyQt6` (via `napari[pyqt6]`).
|
|
29
29
|
|
|
30
30
|
Napari now also includes multiple triangulation backends. These improve the speed by which a napari 'Shapes' layer gets
|
|
31
31
|
loaded (this does not improve the speed of editing large numbers of shapes yet!). See also the napari
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"show_commit_url": "http://github.com/napari/napari/commit/",
|
|
36
36
|
|
|
37
37
|
// The Pythons you'd like to test against.
|
|
38
|
-
"pythons": ["3.
|
|
38
|
+
"pythons": ["3.12"],
|
|
39
39
|
|
|
40
40
|
// The directory (relative to the current directory) to cache the Python
|
|
41
41
|
// environments in.
|
|
@@ -19,16 +19,16 @@ classifiers =
|
|
|
19
19
|
Topic :: Software Development :: Testing
|
|
20
20
|
Programming Language :: Python
|
|
21
21
|
Programming Language :: Python :: 3
|
|
22
|
-
Programming Language :: Python :: 3.11
|
|
23
22
|
Programming Language :: Python :: 3.12
|
|
24
23
|
Programming Language :: Python :: 3.13
|
|
24
|
+
Programming Language :: Python :: 3.14
|
|
25
25
|
Operating System :: OS Independent
|
|
26
26
|
License :: OSI Approved :: BSD License
|
|
27
27
|
|
|
28
28
|
[options]
|
|
29
29
|
packages = find:
|
|
30
30
|
include_package_data = True
|
|
31
|
-
python_requires = >=3.
|
|
31
|
+
python_requires = >=3.12
|
|
32
32
|
setup_requires = setuptools_scm
|
|
33
33
|
install_requires =
|
|
34
34
|
anndata>=0.12.0
|
|
@@ -89,7 +89,7 @@ readthedocs =
|
|
|
89
89
|
pre =
|
|
90
90
|
spatialdata>=0.7.3a1
|
|
91
91
|
all =
|
|
92
|
-
napari[
|
|
92
|
+
napari[pyqt6]
|
|
93
93
|
|
|
94
94
|
[options.packages.find]
|
|
95
95
|
where = src
|
|
@@ -28,8 +28,6 @@ class DataModel:
|
|
|
28
28
|
_layer: Layer = field(init=False, default=None, repr=True)
|
|
29
29
|
_adata: AnnData | None = field(init=False, default=None, repr=True)
|
|
30
30
|
_adata_layer: str | None = field(init=False, default=None, repr=False)
|
|
31
|
-
_region_key: str | None = field(default=None, repr=True)
|
|
32
|
-
_instance_key: str | None = field(default=None, repr=True)
|
|
33
31
|
_color_by: str = field(default="", repr=True, init=False)
|
|
34
32
|
_system_name: str | None = field(default=None, repr=True)
|
|
35
33
|
|
|
@@ -138,7 +136,8 @@ class DataModel:
|
|
|
138
136
|
except KeyError:
|
|
139
137
|
raise KeyError(f"Key `{name}` not found in `adata.var_names`.") from None
|
|
140
138
|
|
|
141
|
-
|
|
139
|
+
X = self.adata.X if self.adata_layer is None else self.adata.layers[self.adata_layer]
|
|
140
|
+
column = X[ix]
|
|
142
141
|
index = self.adata.obs[[self.instance_key]].set_index(self.instance_key).index
|
|
143
142
|
return column, self._format_key(name, adata_layer=True), index
|
|
144
143
|
|
{napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata/_scatterwidgets.py
RENAMED
|
@@ -251,14 +251,12 @@ class DiscreteColorWidget(GraphicsWidget):
|
|
|
251
251
|
color_button = ColorButton(color=color)
|
|
252
252
|
color_button.setMinimumSize(60, 30)
|
|
253
253
|
color_button.setMaximumSize(60, 30)
|
|
254
|
-
color_button.setStyleSheet(
|
|
255
|
-
"""
|
|
254
|
+
color_button.setStyleSheet("""
|
|
256
255
|
QPushButton {
|
|
257
256
|
background-color: transparent;
|
|
258
257
|
border: none;
|
|
259
258
|
}
|
|
260
|
-
"""
|
|
261
|
-
)
|
|
259
|
+
""")
|
|
262
260
|
|
|
263
261
|
self.color_buttons[obj_category] = color_button
|
|
264
262
|
|
|
@@ -384,8 +382,7 @@ class PlotWidget(GraphicsLayoutWidget):
|
|
|
384
382
|
self.drawing_mode_button = QPushButton(self)
|
|
385
383
|
self.drawing_mode_button.setIcon(QIcon(str(Path(__file__).parent / "resources/icons8-polygon-80.png")))
|
|
386
384
|
self.drawing_mode_button.setIconSize(QSize(24, 24))
|
|
387
|
-
self.drawing_mode_button.setStyleSheet(
|
|
388
|
-
f"""
|
|
385
|
+
self.drawing_mode_button.setStyleSheet(f"""
|
|
389
386
|
QPushButton {{
|
|
390
387
|
background-color: transparent;
|
|
391
388
|
border: none;
|
|
@@ -393,8 +390,7 @@ class PlotWidget(GraphicsLayoutWidget):
|
|
|
393
390
|
QPushButton:checked {{
|
|
394
391
|
border: 1px solid rgb{self.color};
|
|
395
392
|
}}
|
|
396
|
-
"""
|
|
397
|
-
)
|
|
393
|
+
""")
|
|
398
394
|
self.drawing_mode_button.setCheckable(True)
|
|
399
395
|
self.drawing_mode_button.clicked.connect(self.toggle_drawing_mode)
|
|
400
396
|
self.drawing_mode_button.setToolTip("Add freehand ROIs.")
|
|
@@ -405,8 +401,7 @@ class PlotWidget(GraphicsLayoutWidget):
|
|
|
405
401
|
self.rectangle_mode_button = QPushButton(self)
|
|
406
402
|
self.rectangle_mode_button.setIcon(QIcon(str(Path(__file__).parent / "resources/icons8-rectangle-48.png")))
|
|
407
403
|
self.rectangle_mode_button.setIconSize(QSize(24, 24))
|
|
408
|
-
self.rectangle_mode_button.setStyleSheet(
|
|
409
|
-
f"""
|
|
404
|
+
self.rectangle_mode_button.setStyleSheet(f"""
|
|
410
405
|
QPushButton {{
|
|
411
406
|
background-color: transparent;
|
|
412
407
|
border: none;
|
|
@@ -414,8 +409,7 @@ class PlotWidget(GraphicsLayoutWidget):
|
|
|
414
409
|
QPushButton:checked {{
|
|
415
410
|
border: 1px solid rgb{self.color};
|
|
416
411
|
}}
|
|
417
|
-
"""
|
|
418
|
-
)
|
|
412
|
+
""")
|
|
419
413
|
self.rectangle_mode_button.setCheckable(True)
|
|
420
414
|
self.rectangle_mode_button.clicked.connect(self.toggle_rectangle_mode)
|
|
421
415
|
self.rectangle_mode_button.setToolTip("Add rectangular ROIs.")
|
|
@@ -873,14 +867,12 @@ class PlotWidget(GraphicsLayoutWidget):
|
|
|
873
867
|
# view.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn)
|
|
874
868
|
# sets stle of the vertical slider
|
|
875
869
|
view.setStyleSheet(get_current_stylesheet())
|
|
876
|
-
view.setStyleSheet(
|
|
877
|
-
"""
|
|
870
|
+
view.setStyleSheet("""
|
|
878
871
|
QGraphicsView {
|
|
879
872
|
border: none;
|
|
880
873
|
background: rgb(0, 0, 0);
|
|
881
874
|
}
|
|
882
|
-
"""
|
|
883
|
-
)
|
|
875
|
+
""")
|
|
884
876
|
|
|
885
877
|
view.setSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
|
|
886
878
|
|
|
@@ -931,11 +923,11 @@ class PlotWidget(GraphicsLayoutWidget):
|
|
|
931
923
|
logger.debug("Left press detected")
|
|
932
924
|
|
|
933
925
|
# Get the position of the click in the scene
|
|
934
|
-
scene_pos = event.pos()
|
|
926
|
+
scene_pos = self.mapToScene(event.pos())
|
|
935
927
|
|
|
936
928
|
# Check if the click is within the scatter plot view box
|
|
937
929
|
if not self.scatter_plot.vb.sceneBoundingRect().contains(scene_pos):
|
|
938
|
-
logger.debug("Click outside scatter plot view box, scene position: {scene_pos}")
|
|
930
|
+
logger.debug(f"Click outside scatter plot view box, scene position: {scene_pos}")
|
|
939
931
|
return
|
|
940
932
|
|
|
941
933
|
pen = pg.mkPen(color="gray", width=2)
|
|
@@ -946,7 +938,7 @@ class PlotWidget(GraphicsLayoutWidget):
|
|
|
946
938
|
|
|
947
939
|
if self.drawing:
|
|
948
940
|
|
|
949
|
-
plot_pos = self.scatter_plot.vb.mapSceneToView(
|
|
941
|
+
plot_pos = self.scatter_plot.vb.mapSceneToView(scene_pos)
|
|
950
942
|
|
|
951
943
|
self.current_roi = pg.PolyLineROI(
|
|
952
944
|
[], closed=True, removable=True, pen=pen, handlePen=handlePen, hoverPen=hoverPen
|
|
@@ -954,7 +946,7 @@ class PlotWidget(GraphicsLayoutWidget):
|
|
|
954
946
|
self.scatter_plot.addItem(self.current_roi)
|
|
955
947
|
|
|
956
948
|
self.current_points = [(plot_pos.x(), plot_pos.y())]
|
|
957
|
-
self.last_pos = (
|
|
949
|
+
self.last_pos = (scene_pos.x(), scene_pos.y())
|
|
958
950
|
|
|
959
951
|
event.accept()
|
|
960
952
|
|
|
@@ -962,7 +954,7 @@ class PlotWidget(GraphicsLayoutWidget):
|
|
|
962
954
|
|
|
963
955
|
logger.info("Rectangle")
|
|
964
956
|
|
|
965
|
-
plot_pos = self.scatter_plot.vb.mapSceneToView(
|
|
957
|
+
plot_pos = self.scatter_plot.vb.mapSceneToView(scene_pos)
|
|
966
958
|
self.current_roi = pg.RectROI(
|
|
967
959
|
pos=(plot_pos.x(), plot_pos.y()),
|
|
968
960
|
size=(0, 0),
|
|
@@ -987,16 +979,17 @@ class PlotWidget(GraphicsLayoutWidget):
|
|
|
987
979
|
if self.drawing and self.current_roi is not None:
|
|
988
980
|
|
|
989
981
|
if self.last_pos is not None:
|
|
990
|
-
|
|
982
|
+
scene_pos = self.mapToScene(event.pos())
|
|
983
|
+
dist = np.sqrt((scene_pos.x() - self.last_pos[0]) ** 2 + (scene_pos.y() - self.last_pos[1]) ** 2)
|
|
991
984
|
|
|
992
985
|
if dist > self.vertex_add_threshold:
|
|
993
986
|
|
|
994
|
-
plot_pos = self.scatter_plot.vb.mapSceneToView(
|
|
987
|
+
plot_pos = self.scatter_plot.vb.mapSceneToView(scene_pos)
|
|
995
988
|
self.current_points.append((plot_pos.x(), plot_pos.y()))
|
|
996
989
|
|
|
997
990
|
self.current_roi.setPoints(self.current_points)
|
|
998
991
|
|
|
999
|
-
self.last_pos = (
|
|
992
|
+
self.last_pos = (scene_pos.x(), scene_pos.y())
|
|
1000
993
|
|
|
1001
994
|
logger.debug(f"Updated points for PolyLineROI: {self.current_points}")
|
|
1002
995
|
|
|
@@ -1004,7 +997,7 @@ class PlotWidget(GraphicsLayoutWidget):
|
|
|
1004
997
|
|
|
1005
998
|
elif self.rectangle and self.current_roi is not None and self.initial_pos is not None:
|
|
1006
999
|
|
|
1007
|
-
plot_pos = self.scatter_plot.vb.mapSceneToView(event.pos())
|
|
1000
|
+
plot_pos = self.scatter_plot.vb.mapSceneToView(self.mapToScene(event.pos()))
|
|
1008
1001
|
width = plot_pos.x() - self.initial_pos.x() # type: ignore[attr-defined]
|
|
1009
1002
|
height = plot_pos.y() - self.initial_pos.y() # type: ignore[attr-defined]
|
|
1010
1003
|
self.current_roi.setSize([width, height])
|
|
@@ -1018,7 +1011,7 @@ class PlotWidget(GraphicsLayoutWidget):
|
|
|
1018
1011
|
super().mouseMoveEvent(event)
|
|
1019
1012
|
|
|
1020
1013
|
# Get cursor position
|
|
1021
|
-
scene_pos = event.pos()
|
|
1014
|
+
scene_pos = self.mapToScene(event.pos())
|
|
1022
1015
|
plot_pos = self.scatter_plot.vb.mapSceneToView(scene_pos)
|
|
1023
1016
|
|
|
1024
1017
|
# Update the cursor position label
|
|
@@ -1029,7 +1022,10 @@ class PlotWidget(GraphicsLayoutWidget):
|
|
|
1029
1022
|
|
|
1030
1023
|
def mouseReleaseEvent(self, event: Any) -> None:
|
|
1031
1024
|
|
|
1032
|
-
|
|
1025
|
+
# also called by scene().sigMouseClicked with a pyqtgraph MouseClickEvent, whose pos() mapToScene can't take
|
|
1026
|
+
if isinstance(event, QtGui.QMouseEvent):
|
|
1027
|
+
scene_pos = self.mapToScene(event.pos())
|
|
1028
|
+
logger.debug(f"Mouse release event detected at {self.scatter_plot.vb.mapSceneToView(scene_pos)}")
|
|
1033
1029
|
|
|
1034
1030
|
if self.drawing and event.button() == Qt.LeftButton:
|
|
1035
1031
|
|
|
@@ -1057,7 +1053,7 @@ class PlotWidget(GraphicsLayoutWidget):
|
|
|
1057
1053
|
def mouseDoubleClickEvent(self, event: Any) -> None:
|
|
1058
1054
|
if event.button() == Qt.LeftButton:
|
|
1059
1055
|
|
|
1060
|
-
plot_pos = self.scatter_plot.vb.mapSceneToView(event.pos())
|
|
1056
|
+
plot_pos = self.scatter_plot.vb.mapSceneToView(self.mapToScene(event.pos()))
|
|
1061
1057
|
point = Point(plot_pos.x(), plot_pos.y())
|
|
1062
1058
|
|
|
1063
1059
|
self.remove_roi(point=point)
|
{napari_spatialdata-0.7.1 → napari_spatialdata-0.7.3}/src/napari_spatialdata/_sdata_widgets.py
RENAMED
|
@@ -5,7 +5,7 @@ from collections.abc import Iterable
|
|
|
5
5
|
from importlib.metadata import version
|
|
6
6
|
from operator import itemgetter
|
|
7
7
|
from pathlib import Path
|
|
8
|
-
from typing import TYPE_CHECKING
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
9
|
|
|
10
10
|
import numpy as np
|
|
11
11
|
import shapely
|
|
@@ -15,11 +15,20 @@ from napari.utils.notifications import show_info
|
|
|
15
15
|
from packaging.version import parse as parse_version
|
|
16
16
|
from qtpy.QtCore import QThread, Signal
|
|
17
17
|
from qtpy.QtGui import QIcon
|
|
18
|
-
from qtpy.QtWidgets import
|
|
18
|
+
from qtpy.QtWidgets import (
|
|
19
|
+
QCheckBox,
|
|
20
|
+
QLabel,
|
|
21
|
+
QListWidget,
|
|
22
|
+
QListWidgetItem,
|
|
23
|
+
QProgressBar,
|
|
24
|
+
QVBoxLayout,
|
|
25
|
+
QWidget,
|
|
26
|
+
)
|
|
19
27
|
from spatialdata import SpatialData
|
|
20
28
|
from spatialdata.models._utils import DEFAULT_COORDINATE_SYSTEM
|
|
21
29
|
|
|
22
30
|
from napari_spatialdata._viewer import SpatialDataViewer
|
|
31
|
+
from napari_spatialdata.constants import config
|
|
23
32
|
from napari_spatialdata.constants.config import N_CIRCLES_WARNING_THRESHOLD, N_SHAPES_WARNING_THRESHOLD
|
|
24
33
|
from napari_spatialdata.utils._utils import _get_sdata_key, get_duplicate_element_names, get_elements_meta_mapping
|
|
25
34
|
|
|
@@ -174,11 +183,39 @@ class SdataWidget(QWidget):
|
|
|
174
183
|
self.slider.setRange(0, 0)
|
|
175
184
|
self.slider.setVisible(False)
|
|
176
185
|
|
|
186
|
+
self.discard_z_points = QCheckBox("Discard z for 3D points")
|
|
187
|
+
self.discard_z_points.setChecked(config.PROJECT_3D_POINTS_TO_2D)
|
|
188
|
+
self.discard_z_points.setToolTip(
|
|
189
|
+
"When checked, the z coordinate of new points layers is discarded so they are loaded in 2D. "
|
|
190
|
+
"Only applies to new layers; layers already displayed are not affected."
|
|
191
|
+
)
|
|
192
|
+
self.discard_z_points.toggled.connect(self._on_discard_z_points_toggled)
|
|
193
|
+
|
|
194
|
+
self.discard_z_shapes = QCheckBox("Discard z for 2.5D shapes")
|
|
195
|
+
self.discard_z_shapes.setChecked(config.PROJECT_2_5D_SHAPES_TO_2D)
|
|
196
|
+
self.discard_z_shapes.setToolTip(
|
|
197
|
+
"When checked, the z coordinate of new shapes layers is discarded so they are loaded in 2D. "
|
|
198
|
+
"Only applies to new layers; layers already displayed are not affected."
|
|
199
|
+
)
|
|
200
|
+
self.discard_z_shapes.toggled.connect(self._on_discard_z_shapes_toggled)
|
|
201
|
+
|
|
202
|
+
# The 3D toggles only matter when at least one element across the loaded
|
|
203
|
+
# SpatialData objects has a z axis. Otherwise we hide them to save screen
|
|
204
|
+
# real estate for users working with 2D-only data.
|
|
205
|
+
self._has_z_data = self._sdatas_have_z_axis(self._sdata)
|
|
206
|
+
self._three_d_settings_label = QLabel("3D Settings:")
|
|
207
|
+
self._three_d_settings_label.setVisible(self._has_z_data)
|
|
208
|
+
self.discard_z_points.setVisible(self._has_z_data)
|
|
209
|
+
self.discard_z_shapes.setVisible(self._has_z_data)
|
|
210
|
+
|
|
177
211
|
self.layout().addWidget(self.slider)
|
|
178
212
|
self.layout().addWidget(QLabel("Coordinate System:"))
|
|
179
213
|
self.layout().addWidget(self.coordinate_system_widget)
|
|
180
214
|
self.layout().addWidget(QLabel("Elements:"))
|
|
181
215
|
self.layout().addWidget(self.elements_widget)
|
|
216
|
+
self.layout().addWidget(self._three_d_settings_label)
|
|
217
|
+
self.layout().addWidget(self.discard_z_points)
|
|
218
|
+
self.layout().addWidget(self.discard_z_shapes)
|
|
182
219
|
self.elements_widget.itemDoubleClicked.connect(self._on_click_item)
|
|
183
220
|
self.coordinate_system_widget.currentItemChanged.connect(
|
|
184
221
|
lambda item: self.elements_widget._onItemChange(item.text())
|
|
@@ -219,8 +256,6 @@ class SdataWidget(QWidget):
|
|
|
219
256
|
}:
|
|
220
257
|
return
|
|
221
258
|
|
|
222
|
-
type_ = cast(str, type_)
|
|
223
|
-
|
|
224
259
|
self.worker_thread.load_data(type_, text, sdata, selected_cs, multi)
|
|
225
260
|
if not PROBLEMATIC_NUMPY_MACOS:
|
|
226
261
|
self.slider.setVisible(True)
|
|
@@ -258,6 +293,24 @@ class SdataWidget(QWidget):
|
|
|
258
293
|
layer.metadata["_active_in_cs"].add(coordinate_system)
|
|
259
294
|
layer.metadata["_current_cs"] = coordinate_system
|
|
260
295
|
|
|
296
|
+
def _on_discard_z_points_toggled(self, checked: bool) -> None:
|
|
297
|
+
config.PROJECT_3D_POINTS_TO_2D = checked
|
|
298
|
+
|
|
299
|
+
def _on_discard_z_shapes_toggled(self, checked: bool) -> None:
|
|
300
|
+
config.PROJECT_2_5D_SHAPES_TO_2D = checked
|
|
301
|
+
|
|
302
|
+
@staticmethod
|
|
303
|
+
def _sdatas_have_z_axis(sdatas: EventedList) -> bool:
|
|
304
|
+
"""Return ``True`` if any element across the given ``SpatialData`` objects has a z axis.
|
|
305
|
+
|
|
306
|
+
Used to decide whether to expose the 3D / 2.5D projection toggles in the widget.
|
|
307
|
+
"""
|
|
308
|
+
for sdata in sdatas:
|
|
309
|
+
for _, _, element in sdata._gen_elements():
|
|
310
|
+
if SpatialDataViewer._has_z_axis(element):
|
|
311
|
+
return True
|
|
312
|
+
return False
|
|
313
|
+
|
|
261
314
|
def _get_shapes(self, sdata: SpatialData, key: str, selected_cs: str, multi: bool) -> Shapes | Points:
|
|
262
315
|
original_name = key[: key.rfind("_")] if multi else key
|
|
263
316
|
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.7.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 7,
|
|
21
|
+
__version__ = version = '0.7.3'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 7, 3)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g2cb4e2ca7'
|
|
@@ -65,9 +65,7 @@ class QtAdataScatterWidget(QWidget):
|
|
|
65
65
|
self._viewer = napari_viewer
|
|
66
66
|
|
|
67
67
|
self._model = (
|
|
68
|
-
model
|
|
69
|
-
if model is not None
|
|
70
|
-
else self._viewer.window._dock_widgets["SpatialData"].widget().viewer_model._model
|
|
68
|
+
model if model is not None else self._viewer.window.dock_widgets["SpatialData"].viewer_model._model
|
|
71
69
|
)
|
|
72
70
|
|
|
73
71
|
self._select_layer()
|
|
@@ -241,9 +239,8 @@ class QtAdataScatterWidget(QWidget):
|
|
|
241
239
|
|
|
242
240
|
# check that the view widget is present
|
|
243
241
|
# and trigger its update
|
|
244
|
-
if "View (napari-spatialdata)" in
|
|
245
|
-
|
|
246
|
-
qtAdataViewWidget = [x for x in view_widget.children() if isinstance(x, QtAdataViewWidget)][0]
|
|
242
|
+
if "View (napari-spatialdata)" in self._viewer.window.dock_widgets:
|
|
243
|
+
qtAdataViewWidget = self._viewer.window.dock_widgets["View (napari-spatialdata)"]
|
|
247
244
|
qtAdataViewWidget._on_layer_update()
|
|
248
245
|
|
|
249
246
|
# new annotation - update options
|
|
@@ -306,9 +303,7 @@ class QtAdataScatterWidget(QWidget):
|
|
|
306
303
|
selected_table = self.table_name_widget.currentText()
|
|
307
304
|
|
|
308
305
|
# trigger saving of the table
|
|
309
|
-
|
|
310
|
-
# it's used in the save_sdata method
|
|
311
|
-
self._viewer_model = self._viewer.window._dock_widgets["SpatialData"].widget().viewer_model
|
|
306
|
+
self._viewer_model = self._viewer.window.dock_widgets["SpatialData"].viewer_model
|
|
312
307
|
self._viewer_model._write_element_to_disk(
|
|
313
308
|
selected_layer.metadata["sdata"],
|
|
314
309
|
selected_table,
|
|
@@ -611,8 +606,6 @@ class QtAdataViewWidget(QWidget):
|
|
|
611
606
|
if self.model.adata.shape == (0, 0):
|
|
612
607
|
return
|
|
613
608
|
|
|
614
|
-
self.model._region_key = layer.metadata["region_key"] if isinstance(layer, Labels) else None
|
|
615
|
-
self.model._instance_key = layer.metadata["instance_key"] if isinstance(layer, Labels) else None
|
|
616
609
|
self.model.system_name = layer.metadata.get("name", None)
|
|
617
610
|
|
|
618
611
|
if hasattr(
|
|
@@ -695,8 +688,7 @@ class QtAdataAnnotationWidget(QWidget):
|
|
|
695
688
|
def __init__(self, napari_viewer: Viewer):
|
|
696
689
|
super().__init__()
|
|
697
690
|
self._viewer = napari_viewer
|
|
698
|
-
|
|
699
|
-
self._viewer_model = self._viewer.window._dock_widgets["SpatialData"].widget().viewer_model
|
|
691
|
+
self._viewer_model = self._viewer.window.dock_widgets["SpatialData"].viewer_model
|
|
700
692
|
|
|
701
693
|
self.setLayout(QGridLayout())
|
|
702
694
|
self._current_color = "#FFFFFF"
|
|
@@ -189,9 +189,6 @@ class SpatialDataViewer(QObject):
|
|
|
189
189
|
raise ValueError("Cannot export a points element with no points")
|
|
190
190
|
transformed_data = np.array([layer_to_save.data_to_world(xy) for xy in layer_to_save.data])
|
|
191
191
|
swap_data = np.fliplr(transformed_data)
|
|
192
|
-
# ignore z axis if present
|
|
193
|
-
if swap_data.shape[1] == 3:
|
|
194
|
-
swap_data = swap_data[:, :2]
|
|
195
192
|
parsed = PointsModel.parse(swap_data, transformations=transformation)
|
|
196
193
|
|
|
197
194
|
# saving to disk of points temporarily disabled until the interface update that will unify the view widget,
|
|
@@ -261,14 +258,21 @@ class SpatialDataViewer(QObject):
|
|
|
261
258
|
for shape in layer_to_save._data_view.shapes
|
|
262
259
|
]
|
|
263
260
|
|
|
264
|
-
|
|
265
|
-
remove_z = coords.shape[1] == 3
|
|
266
|
-
first_index = 1 if remove_z else 0
|
|
267
|
-
coords = coords[:, first_index::]
|
|
268
|
-
return np.fliplr(coords)
|
|
261
|
+
has_z = coords[0].shape[1] == 3
|
|
269
262
|
|
|
270
|
-
|
|
271
|
-
|
|
263
|
+
def _fix_coords(coords: ArrayLike) -> tuple[ArrayLike, float | None]:
|
|
264
|
+
if coords.shape[1] == 3:
|
|
265
|
+
z_val = float(coords[0, 0])
|
|
266
|
+
yx = coords[:, 1:]
|
|
267
|
+
return np.fliplr(yx), z_val
|
|
268
|
+
return np.fliplr(coords), None
|
|
269
|
+
|
|
270
|
+
fixed = [_fix_coords(p) for p in coords]
|
|
271
|
+
polygons: list[Polygon] = [Polygon(xy) for xy, _ in fixed]
|
|
272
|
+
gdf_dict: dict[str, Any] = {"geometry": polygons}
|
|
273
|
+
if has_z:
|
|
274
|
+
gdf_dict["z"] = [z_val for _, z_val in fixed]
|
|
275
|
+
gdf = GeoDataFrame(gdf_dict)
|
|
272
276
|
|
|
273
277
|
force_2d(gdf)
|
|
274
278
|
parsed = ShapesModel.parse(gdf, transformations=transformation)
|
|
@@ -514,11 +518,15 @@ class SpatialDataViewer(QObject):
|
|
|
514
518
|
original_name = original_name[: original_name.rfind("_")]
|
|
515
519
|
|
|
516
520
|
df = sdata.shapes[original_name]
|
|
517
|
-
|
|
521
|
+
axes = get_axes_names(df)
|
|
522
|
+
include_z = "z" in axes and not config.PROJECT_2_5D_SHAPES_TO_2D
|
|
523
|
+
affine = _get_transform(sdata.shapes[original_name], selected_cs, include_z=include_z)
|
|
518
524
|
|
|
519
|
-
# 2.5D circles not supported yet
|
|
520
525
|
xy = np.array([df.geometry.x, df.geometry.y]).T
|
|
521
526
|
yx = np.fliplr(xy)
|
|
527
|
+
if include_z:
|
|
528
|
+
z_vals = df["z"].to_numpy()
|
|
529
|
+
yx = np.column_stack([z_vals, yx])
|
|
522
530
|
radii = df.radius.to_numpy()
|
|
523
531
|
|
|
524
532
|
adata, table_name, table_names = self._get_table_data(sdata, original_name)
|
|
@@ -561,7 +569,7 @@ class SpatialDataViewer(QObject):
|
|
|
561
569
|
else:
|
|
562
570
|
kwargs |= {"border_color": "white"}
|
|
563
571
|
# useful code to have readily available to debug the correct radius of circles when represented as points
|
|
564
|
-
ellipses = _get_ellipses_from_circles(
|
|
572
|
+
ellipses = _get_ellipses_from_circles(coords=yx, radii=radii)
|
|
565
573
|
layer = Shapes(
|
|
566
574
|
ellipses,
|
|
567
575
|
shape_type="ellipse",
|
|
@@ -804,8 +812,43 @@ class SpatialDataViewer(QObject):
|
|
|
804
812
|
sdata = metadata["sdata"]
|
|
805
813
|
element_name = metadata["name"]
|
|
806
814
|
element_data = sdata[element_name]
|
|
807
|
-
|
|
815
|
+
include_z = self._should_include_z(element_data)
|
|
816
|
+
affine = _get_transform(element_data, coordinate_system, include_z=include_z)
|
|
808
817
|
if affine is not None:
|
|
809
818
|
layer.affine = affine
|
|
810
819
|
if layer._type_string == "points":
|
|
811
820
|
self._adjust_radii_of_points_layer(layer, affine)
|
|
821
|
+
|
|
822
|
+
@staticmethod
|
|
823
|
+
def _has_z_axis(element: Any) -> bool:
|
|
824
|
+
"""Return ``True`` if ``element`` exposes a ``z`` axis.
|
|
825
|
+
|
|
826
|
+
For raster elements (images / labels) the ``z`` axis is reported by
|
|
827
|
+
:func:`spatialdata.models.get_axes_names`. For vector elements (points
|
|
828
|
+
as :class:`~dask.dataframe.DataFrame`, shapes as
|
|
829
|
+
:class:`~geopandas.GeoDataFrame`) the same helper is used.
|
|
830
|
+
"""
|
|
831
|
+
from xarray import DataArray, DataTree
|
|
832
|
+
|
|
833
|
+
if not isinstance(element, DataArray | DataTree | DaskDataFrame | GeoDataFrame):
|
|
834
|
+
return False
|
|
835
|
+
return "z" in get_axes_names(element)
|
|
836
|
+
|
|
837
|
+
@staticmethod
|
|
838
|
+
def _should_include_z(element: DaskDataFrame | GeoDataFrame) -> bool:
|
|
839
|
+
"""Determine whether to include the z axis for a given spatial element.
|
|
840
|
+
|
|
841
|
+
For raster data (images, labels) z is always included when present.
|
|
842
|
+
For vector data (points, shapes) z inclusion depends on the user-facing
|
|
843
|
+
projection config flags.
|
|
844
|
+
"""
|
|
845
|
+
from xarray import DataArray, DataTree
|
|
846
|
+
|
|
847
|
+
if isinstance(element, DataArray | DataTree):
|
|
848
|
+
return True
|
|
849
|
+
axes = get_axes_names(element)
|
|
850
|
+
if "z" not in axes:
|
|
851
|
+
return False
|
|
852
|
+
if isinstance(element, DaskDataFrame):
|
|
853
|
+
return not config.PROJECT_3D_POINTS_TO_2D
|
|
854
|
+
return not config.PROJECT_2_5D_SHAPES_TO_2D
|