Skip to content

Commit

Permalink
Merge pull request #62 from chrishavlin/slices_and_vols
Browse files Browse the repository at this point in the history
handling mixed slices and volumes
  • Loading branch information
chrishavlin authored Jul 27, 2023
2 parents cbc8af9 + 8ec7dde commit e965a86
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 7 deletions.
10 changes: 9 additions & 1 deletion .github/workflows/test_and_deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,17 @@ jobs:
# setup-miniconda: https://github.com/conda-incubator/setup-miniconda
# and
# tox-conda: https://github.com/tox-dev/tox-conda
- name: Install dependencies
- name: Upgrade pip
run: |
python -m pip install --upgrade pip
- name: Limit pip for windows
if: runner.os == 'Windows'
run: |
python -m pip install --upgrade "pip<22"
- name: Install dependencies
run: |
python -m pip install setuptools tox tox-gh-actions
# this runs the platform-specific tests declared in tox.ini
Expand Down
65 changes: 61 additions & 4 deletions src/yt_napari/_model_ingestor.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,22 @@ def _le_re_to_cen_wid(

class LayerDomain:
# container for domain info for a single layer
# left_edge, right_edge, resolution, n_d are all self explanatory.
# other parameters:
#
# new_dim_value: optional unyt_quantity.
# If n_d == 2, and upgrade_to_3D is subsequently called, then this value
# will be used for the new
# new_dim_axis: optional int.
# the index position to add the new_dim_position, default is last
def __init__(
self,
left_edge: unyt_array,
right_edge: unyt_array,
resolution: tuple,
n_d: int = 3,
n_d: Optional[int] = 3,
new_dim_value: Optional[unyt_quantity] = None,
new_dim_axis: Optional[int] = 2,
):

if len(left_edge) != len(right_edge):
Expand All @@ -33,7 +43,10 @@ def __init__(
if len(resolution) == 1:
resolution = resolution * n_d # assume same in every dim
else:
raise ValueError("length of resolution does not match edge arrays")
msg = f"{len(resolution)}:{len(left_edge)}"
raise ValueError(
f"length of resolution does not match edge arrays {msg}"
)

self.left_edge = left_edge
self.right_edge = right_edge
Expand All @@ -43,6 +56,36 @@ def __init__(
self.aspect_ratio = self.width / self.width[0]
self.requires_scale = np.any(self.aspect_ratio != unyt_array(1.0, ""))
self.n_d = n_d
if new_dim_value is None:
new_dim_value = unyt_quantity(0.0, left_edge.units)
self.new_dim_value = new_dim_value
self.new_dim_axis = new_dim_axis

def upgrade_to_3D(self):
# note: this is not (yet) used when loading planes in 3d scenes.
if self.n_d == 3:
return # already 3D, nothing to do

if self.n_d == 2:
new_l_r = self.new_dim_value
axid = self.new_dim_axis
self.left_edge = _insert_to_unyt_array(self.left_edge, new_l_r, axid)
self.right_edge = _insert_to_unyt_array(self.right_edge, new_l_r, axid)
self.resolution = _insert_to_unyt_array(self.right_edge, 1, axid)
self.grid_width = _insert_to_unyt_array(self.grid_width, 0, axid)
self.aspect_ratio = _insert_to_unyt_array(self.aspect_ratio, 1.0, axid)
self.n_d = 3


def _insert_to_unyt_array(
x: unyt_array, new_value: Union[float, unyt_array], position: int
) -> unyt_array:
# just for scalars
if isinstance(new_value, unyt_array):
# reminder: unyt_quantity is instance of unyt_array
new_value = new_value.to(x.units).d

return unyt_array(np.insert(x.d, position, new_value), x.units)


# define types for the napari layer tuples
Expand All @@ -65,6 +108,9 @@ def __init__(self, ref_layer_domain: LayerDomain):
self.grid_width = ref_layer_domain.grid_width
self.aspect_ratio = ref_layer_domain.aspect_ratio

# and store the full domain
self.layer_domain = ref_layer_domain

def calculate_scale(self, other_layer: LayerDomain) -> unyt_array:
# calculate the pixel scale for a layer relative to the reference

Expand All @@ -73,6 +119,7 @@ def calculate_scale(self, other_layer: LayerDomain) -> unyt_array:
# layers. scale > 1 will take a small number of pixels and stretch them
# to cover more pixels. scale < 1 will shrink them.
sc = other_layer.grid_width / self.grid_width
sc[sc == 0] = 1.0

# we also need to multiply by the initial reference layer aspect ratio
# to account for any initial distortion.
Expand All @@ -96,6 +143,12 @@ def align_sanitize_layer(self, layer: SpatialLayer) -> Layer:
# pull out the elements of the SpatialLayer tuple
im_arr, im_kwargs, layer_type, domain = layer

# bypass if adding a 2d layer
if domain.n_d == 2 and self.layer_domain.n_d == 3:
# when mixing 2d and 3d selections, cannot guarantee alignment
# or scaling, simply return with no adjustment
return (im_arr, im_kwargs, layer_type)

# calculate scale and translation
scale = self.calculate_scale(domain)
translate = self.calculate_translation(domain)
Expand Down Expand Up @@ -371,7 +424,12 @@ def _process_slice(
)

layer_domain = LayerDomain(
left_edge=LE, right_edge=RE, resolution=resolution, n_d=2
left_edge=LE,
right_edge=RE,
resolution=resolution,
n_d=2,
new_dim_axis=2,
new_dim_value=0.0,
)

return frb, layer_domain
Expand Down Expand Up @@ -428,7 +486,6 @@ def _process_validated_model(model: InputModel) -> List[SpatialLayer]:
# return a list of layer tuples with domain information

layer_list = []

# our model is already validated, so we can assume the field exist with
# their correct types. This is all the yt-specific code required to load a
# dataset and return a plain numpy array
Expand Down
80 changes: 78 additions & 2 deletions src/yt_napari/_tests/test_model_ingestor.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,72 @@ def test_layer_domain(domains_to_test):
assert np.all(layer_domain.width == d.width)

# check some instantiation things
with pytest.raises(ValueError):
with pytest.raises(ValueError, match="length of edge arrays must match"):
_ = _mi.LayerDomain(d.left_edge, unyt.unyt_array([1, 2], "m"), d.resolution)

with pytest.raises(ValueError):
with pytest.raises(ValueError, match="length of resolution does not"):
_ = _mi.LayerDomain(d.left_edge, d.right_edge, (10, 12))

ld = _mi.LayerDomain(d.left_edge, d.right_edge, (10,))
assert len(ld.resolution) == 3


def test_layer_domain_dimensionality():
# note: the code being tested here could be used to help orient the slices
# in 3D but is not currently used.
# sets of left_edge, right_edge, center, width, res
le = unyt.unyt_array([1.0, 1.0], "km")
re = unyt.unyt_array([2000.0, 2000.0], "m")
res = (10, 20)
ld = _mi.LayerDomain(le, re, res, n_d=2)
assert ld.n_d == 2

ld.upgrade_to_3D()
assert ld.n_d == 3
assert len(ld.left_edge) == 3
assert ld.left_edge[-1] == 0.0
ld.upgrade_to_3D() # nothing should happen

ld = _mi.LayerDomain(le, re, res, n_d=2, new_dim_value=0.5)
ld.upgrade_to_3D()
assert ld.left_edge[2] == unyt.unyt_quantity(0.5, le.units)

new_val = unyt.unyt_quantity(0.5, "km")
ld = _mi.LayerDomain(le, re, res, n_d=2, new_dim_value=new_val)
ld.upgrade_to_3D()
assert ld.left_edge[2].to("km") == new_val

ld = _mi.LayerDomain(le, re, res, n_d=2, new_dim_value=new_val, new_dim_axis=0)
ld.upgrade_to_3D()
assert ld.left_edge[0].to("km") == new_val


_test_cases_insert = [
(
unyt.unyt_array([1.0, 1.0], "km"),
unyt.unyt_array(
[
1000.0,
],
"m",
),
unyt.unyt_array([1.0, 1.0, 1.0], "km"),
),
(
unyt.unyt_array([1.0, 1.0], "km"),
unyt.unyt_quantity(1000.0, "m"),
unyt.unyt_array([1.0, 1.0, 1.0], "km"),
),
(unyt.unyt_array([1.0, 1.0], "km"), 0.5, unyt.unyt_array([1.0, 1.0, 0.5], "km")),
]


@pytest.mark.parametrize("x,x2,expected", _test_cases_insert)
def test_insert_to_unyt_array(x, x2, expected):
result = _mi._insert_to_unyt_array(x, x2, 2)
assert np.all(result == expected)


def test_domain_tracking(domains_to_test):

full_domain = _mi.PhysicalDomainTracker()
Expand Down Expand Up @@ -239,3 +295,23 @@ def test_ref_layer_selection(domains_to_test):

with pytest.raises(ValueError, match="method must be one of"):
_ = _mi._choose_ref_layer(spatial_layer_list, method="not_a_method")


def test_2d_3d_mix():

le = unyt.unyt_array([1.0, 1.0, 1.0], "km")
re = unyt.unyt_array([2000.0, 2000.0, 2000.0], "m")
res = (10, 20, 15)
layer_3d = _mi.LayerDomain(le, re, res)
ref = _mi.ReferenceLayer(layer_3d)

le = unyt.unyt_array([1, 1], "km")
re = unyt.unyt_array([2000.0, 2000.0], "m")
res = (10, 20)
layer_2d = _mi.LayerDomain(
le, re, res, n_d=2, new_dim_value=unyt.unyt_quantity(1, "km")
)

sp_layer = (np.random.random(res), {}, "testname", layer_2d)
new_layer_2d = ref.align_sanitize_layer(sp_layer)
assert "scale" not in new_layer_2d[1] # no scale when it is all 1

0 comments on commit e965a86

Please sign in to comment.