Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions docs/examples/PhasePlotTempExample.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"id": "631b90ba-e543-4078-b234-4fc7b18c8fbf",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"[<Image layer 'nuclei' at 0x76b12a76d0f0>,\n",
" <Image layer 'WGA' at 0x76b12a217c70>,\n",
" <Image layer 'actin' at 0x76b12a28a9e0>]"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"yt : [INFO ] 2024-11-21 14:42:35,829 Parameters: current_time = 0.0\n",
"yt : [INFO ] 2024-11-21 14:42:35,830 Parameters: domain_dimensions = [ 16 512 512]\n",
"yt : [INFO ] 2024-11-21 14:42:35,831 Parameters: domain_left_edge = [0. 0. 0.]\n",
"yt : [INFO ] 2024-11-21 14:42:35,832 Parameters: domain_right_edge = [1. 1. 1.]\n",
"yt : [INFO ] 2024-11-21 14:42:35,832 Parameters: cosmological_simulation = 0\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"x\n",
"y\n",
"nuclei\n",
"nuclei\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"yt : [INFO ] 2024-11-21 14:43:09,699 Parameters: current_time = 0.0\n",
"yt : [INFO ] 2024-11-21 14:43:09,700 Parameters: domain_dimensions = [ 16 512 512]\n",
"yt : [INFO ] 2024-11-21 14:43:09,700 Parameters: domain_left_edge = [0. 0. 0.]\n",
"yt : [INFO ] 2024-11-21 14:43:09,701 Parameters: domain_right_edge = [1. 1. 1.]\n",
"yt : [INFO ] 2024-11-21 14:43:09,702 Parameters: cosmological_simulation = 0\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"nuclei\n",
"WGA\n",
"actin\n",
"x\n"
]
}
],
"source": [
"import napari \n",
"import yt \n",
"\n",
"v = napari.Viewer()\n",
"v.open_sample(\n",
" plugin = 'napari', \n",
" sample = 'kidney',\n",
")"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2e92b80f-7ee3-4e72-9dd7-aff1a0342689",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
333 changes: 333 additions & 0 deletions docs/examples/ytnapari_to_yt.ipynb

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,8 @@ validate_release = { cmd = "python repo_utilities/validate.py", help = "validate
update_schema_docs = { cmd = "python repo_utilities/update_schema_docs.py", help = "updates the schema related documentation" }
update_sample_data = { cmd = "python repo_utilities/update_sample_data.py", help = "updates sample data code" }
test = "pytest -v --color=yes --cov=yt_napari --cov-report=html"

[tool.pytest.ini_options]
filterwarnings = [
'ignore:\s*Pyarrow will become a required dependency of pandas:DeprecationWarning',
]
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ install_requires =
qtpy
unyt
yt>=4.0.1
superqt
python_requires = >=3.10
package_dir =
=src
Expand Down
9 changes: 9 additions & 0 deletions src/yt_napari/_gui_utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import pydantic
from magicgui import type_map, widgets
from pydantic_core import PydanticUndefinedType
from qtpy.QtWidgets import QLayout

from yt_napari import _data_model
from yt_napari.logging import ytnapari_log
Expand Down Expand Up @@ -380,3 +381,11 @@ def get_yt_metadata_container():
def _is_base_model_or_yt_obj(field_info: pydantic.fields.FieldInfo):
ftype = field_info.annotation
return ftype in _data_model._data_model_list


def clearLayout(layout: QLayout):
# remove all widgets from a layout
while layout.count():
child = layout.takeAt(0)
if child.widget():
child.widget().deleteLater()
81 changes: 81 additions & 0 deletions src/yt_napari/_tests/test_layers_to_yt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import napari
import numpy as np
import pytest
from yt.data_objects.static_output import Dataset

from yt_napari.viewer import layers_to_yt


@pytest.fixture
def some_images():

sh = (8, 16, 32)
n_images = 4
images = []

for im in range(n_images):
images.append(
{
"value": np.random.random(sh),
"name": f"image_{im}",
}
)

return images


def test_layers_to_yt(
make_napari_viewer,
some_images,
):

viewer: napari.Viewer = make_napari_viewer()

for im in some_images:
viewer.add_image(im["value"], name=im["name"])

bbox = np.array([[-1.0, 1.0], [-1.0, 1.0], [-1.5, 2.0]])
ds = layers_to_yt(viewer, layers=["image_0", "image_1"], bbox=bbox)

assert isinstance(ds, Dataset)
assert len(ds.field_list) == 2


def test_layers_bad_names(make_napari_viewer, some_images):
viewer: napari.Viewer = make_napari_viewer()
for im in some_images:
viewer.add_image(im["value"], name=im["name"])

with pytest.raises(RuntimeError, match="Layer notalayer not found"):
_ = layers_to_yt(viewer, layers=["image_0", "notalayer"])

with pytest.raises(RuntimeError, match="Layer 100 not found"):
_ = layers_to_yt(viewer, layers=[0, 100])


def test_layers_bad_shape(make_napari_viewer, some_images):
viewer: napari.Viewer = make_napari_viewer()
for im in some_images:
viewer.add_image(im["value"], name=im["name"])

viewer.add_image(np.random.random((10, 10, 20)), name="bad_shape")

with pytest.raises(RuntimeError, match="Layer notalayer not found"):
_ = layers_to_yt(viewer, layers=["image_0", "notalayer"])

with pytest.raises(RuntimeError, match="Can only export layers as a yt dataset"):
_ = layers_to_yt(viewer)


def test_additional_kwargs(
make_napari_viewer,
some_images,
):
viewer: napari.Viewer = make_napari_viewer()

for im in some_images:
viewer.add_image(im["value"], name=im["name"])

ds = layers_to_yt(viewer, length_unit="km")
assert len(ds.field_list) == len(some_images)
assert ds.domain_right_edge.to("km")[2].d == 1
170 changes: 170 additions & 0 deletions src/yt_napari/_tests/test_phaseplot_widget.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import os

import numpy as np
import pytest
from napari import Viewer
from qtpy.QtWidgets import QComboBox, QVBoxLayout
from yt.visualization.profile_plotter import PhasePlot

from yt_napari._widget_2d_plots import YTPhasePlot, _validate_cmyt_name


@pytest.fixture
def some_images():

sh = (8, 16, 32)
n_images = 3
images = []

for im in range(n_images):
images.append(
{
"value": np.random.random(sh),
"name": f"image_{im}",
}
)

return images


def test_phaseplot_widget(
make_napari_viewer,
some_images,
tmp_path,
):

viewer: Viewer = make_napari_viewer()
r = YTPhasePlot(napari_viewer=viewer)

for im in some_images:
viewer.add_image(im["value"], name=im["name"])

# check that layers are populated
r.update_layers.native.click()
for layer in viewer.layers:
assert layer.name in r.available_layer_list

r.render_button.native.click()
assert isinstance(r.phase_plot, PhasePlot)

r.callback_container.apply_logx.value = True
r.callback_container.apply_logy.value = True
r.callback_container.apply_logz.value = True
r.callback_container.reverse_cmap.value = True
r.callback_container.save.value = True

svfig = str(tmp_path / "pp_fig.png")
r.callback_container.savename.value = svfig
r.run_callback_button.native.click()
assert os.path.isfile(svfig)

r.deleteLater()


def test_phaseplot_fields(
make_napari_viewer,
some_images,
):
viewer: Viewer = make_napari_viewer()
r = YTPhasePlot(napari_viewer=viewer)

for im in some_images:
viewer.add_image(im["value"], name=im["name"])

r.update_layers.native.click()

r.layer_1.setCurrentIndex(len(some_images))
assert r.layer_1.currentText() == r._default_index_fields[0]
assert r.layer_1.currentIndex() == len(some_images)

r.render_button.native.click()

r.deleteLater()


def test_weight_field(
make_napari_viewer,
some_images,
):
viewer: Viewer = make_napari_viewer()
r = YTPhasePlot(napari_viewer=viewer)

for im in some_images:
viewer.add_image(im["value"], name=im["name"])

r.update_layers.native.click()

r.layer_4_weight.setCurrentIndex(1)
assert r.layer_4_weight.currentText() == some_images[0]["name"]

r.render_button.native.click()
r.deleteLater()


def test_apply_callbacks_without_render(
make_napari_viewer,
some_images,
):
viewer: Viewer = make_napari_viewer()
r = YTPhasePlot(napari_viewer=viewer)

for im in some_images:
viewer.add_image(im["value"], name=im["name"])

r.update_layers.native.click()

assert r.phase_plot is None
r.run_callback_button.native.click()
assert isinstance(r.phase_plot, PhasePlot)
r.deleteLater()


@pytest.mark.parametrize(
"input_name,expected",
[("cmapname.cmyt", "cmyt.cmapname"), ("notacmytcmap", "notacmytcmap")],
)
def test_validate_cmyt_name(input_name: str, expected: str):
result = _validate_cmyt_name(input_name)
assert result == expected


def test_add_layer_dropdown(
make_napari_viewer,
some_images,
):
viewer: Viewer = make_napari_viewer()
r = YTPhasePlot(napari_viewer=viewer)

for im in some_images:
viewer.add_image(im["value"], name=im["name"])

r.update_layers.native.click()

sub_QVbox = QVBoxLayout()

r.add_layer_dropdown(
"teststr",
"teststr2",
sub_QVbox,
value=None,
)
n_items = sub_QVbox.count()
index = n_items - 1
# dropdown gets added to an inner layout
layer_hbox = sub_QVbox.itemAt(index).layout()
# should be 2 widgets here
n_items = layer_hbox.count()
assert n_items == 2
assert isinstance(layer_hbox.itemAt(1).widget(), QComboBox)

r.add_layer_dropdown(
"teststr",
"teststr2",
sub_QVbox,
value=None,
value_index=100,
)

sub_QVbox.deleteLater()

r.deleteLater()
Loading