Skip to content

Clipping plane slider layer controls #29

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 47 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
67e4023
implement the clipping plane sliders directly in the image layer cont…
AnniekStok Mar 19, 2025
473fb50
move (clipping) plane slider business to its own depiction class for …
AnniekStok Mar 20, 2025
2e9b42c
clean up
AnniekStok Mar 20, 2025
348ed54
add the depiction controls to the labels layer controls
AnniekStok Mar 20, 2025
8e1594a
add experimental_clipping_planes event so that linked image and label…
AnniekStok Mar 21, 2025
98faba9
also add experimental_clipping_planes events to the points layer, so …
AnniekStok Mar 21, 2025
8325dcc
also add experimental_clipping_planes event to the tracks layer, so t…
AnniekStok Mar 21, 2025
8268d41
add link/unlink clipping plane functions to the tracks_layer_group
AnniekStok Mar 21, 2025
9290971
emit clipping_planes event when activating/deactivating the clipping …
AnniekStok Mar 21, 2025
8994c20
attempt to do properly parenting and cleanup of widgets
AnniekStok Mar 21, 2025
be1b518
early return of set_orientation if image ndim <3
AnniekStok Mar 21, 2025
d52586f
fix depiction combobox test
AnniekStok Mar 21, 2025
a9fb959
fix depictionControls tests
AnniekStok Mar 21, 2025
487b121
do not use the viewer step, but the layer scale for computing plane p…
AnniekStok Mar 25, 2025
ed3283b
call the on_display_changed function to ensure that interpolation_com…
AnniekStok Mar 25, 2025
ca79a36
update test for checking if sliders and labels are hidden/not hidden …
AnniekStok Mar 25, 2025
f4d3b5d
ensure changeClippingPlaneRange exits early if data.ndim <3 and updat…
AnniekStok Mar 25, 2025
4d27f9f
attempt to properly clean up all layer event connections created by Q…
AnniekStok Mar 26, 2025
43a466f
early return in oreitn_clipping_plane_normals if ndim not >= 3
AnniekStok Mar 26, 2025
a180402
update viewer title
AnniekStok Mar 26, 2025
2ab0a32
make sure only one depictionControls instance is generated and get ri…
AnniekStok Mar 27, 2025
f680159
break circular references in depictionControls when closing
AnniekStok Mar 27, 2025
37233f9
Merge branch 'main' into clipping_plane_slider_layer_controls
AnniekStok Mar 27, 2025
1b8cf1a
add class docstring for the QtLayerDepiction class
AnniekStok Mar 27, 2025
cad2b7d
check if self.layer still exists before attempting to disconnect
AnniekStok Mar 27, 2025
247dd1d
remove redundant (?) clipping plane event call from base.py
AnniekStok Mar 28, 2025
07c0bc6
cleanup and documentation of depiction controls
AnniekStok Mar 28, 2025
687a70b
add tests for plane and clipping plane changes
AnniekStok Mar 28, 2025
364faf8
add tests for computing the clipping plane slider span and activation…
AnniekStok Mar 28, 2025
8d51509
add test for linking clipping planes in the trackslayersgroup
AnniekStok Mar 28, 2025
e9b98fe
delete all plane depiction related code from the depiction_controls
AnniekStok Apr 4, 2025
ff6a74e
replace QRangeSlider with two separate sliders controlling width and …
AnniekStok Apr 4, 2025
2d86ef8
rename depiction controls to clipping plane controls
AnniekStok Apr 7, 2025
40f0b7a
update tests
AnniekStok Apr 7, 2025
7e188f9
early return in set_clipping_plane_sliders if ndim < 3
AnniekStok Apr 7, 2025
11a8125
fix typo
AnniekStok Apr 14, 2025
06e45d1
fix mistake in dims checking
AnniekStok Apr 14, 2025
f1a0a1c
fix issue with oblique orientation having a negative clipping range a…
AnniekStok Apr 14, 2025
3aae041
remove plane related code
AnniekStok Apr 14, 2025
8dbd7c6
remove remaining plane code
AnniekStok Apr 14, 2025
599425e
fix/remove tests using plane
AnniekStok Apr 15, 2025
8717492
ensure the value of the clipping plane width slider is always odd
AnniekStok Apr 22, 2025
60367fc
rename experimental_clipping_planes to clipping_planes
AnniekStok Apr 22, 2025
7de401d
remove trailing comma
AnniekStok Apr 22, 2025
01984c1
minor docstring fixes
AnniekStok Apr 22, 2025
d756e87
remove depiction code since we are only using volume now
AnniekStok Apr 22, 2025
06539eb
Merge branch 'main' into clipping_plane_slider_layer_controls
cmalinmayor Apr 24, 2025
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
162 changes: 112 additions & 50 deletions finn/_qt/layer_controls/_tests/test_qt_image_layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from finn._qt.layer_controls.qt_image_controls import QtImageControls
from finn.components.dims import Dims
from finn.layers import Image
from finn.layers.utils.plane import ClippingPlane


def test_interpolation_combobox(qtbot):
Expand Down Expand Up @@ -40,72 +41,133 @@ def test_rendering_combobox(qtbot):
assert combo.findText("iso") == combo.currentIndex()


def test_depiction_combobox_changes(qtbot):
"""Changing the model attribute should update the view."""
def test_plane_controls_show_hide_on_ndisplay_change(qtbot):
"""Changing ndisplay should show/hide plane controls in 3D."""
layer = Image(np.random.rand(10, 15, 20))
qtctrl = QtImageControls(layer)
qtctrl.ndisplay = 3
qtbot.addWidget(qtctrl)
combo_box = qtctrl.depictionComboBox
opts = {combo_box.itemText(i) for i in range(combo_box.count())}
depiction_options = {
"volume",
"plane",
}
assert opts == depiction_options
layer.depiction = "plane"
assert combo_box.findText("plane") == combo_box.currentIndex()
layer.depiction = "volume"
assert combo_box.findText("volume") == combo_box.currentIndex()
qtctrl.ndisplay = 3

assert not qtctrl.clippingPlaneControls.planeNormalLabel.isHidden()
assert not qtctrl.clippingPlaneControls.planeNormalButtons.isHidden()

def test_plane_controls_show_hide_on_depiction_change(qtbot):
"""Changing depiction mode should show/hide plane controls in 3D."""
layer = Image(np.random.rand(10, 15, 20))
qtctrl = QtImageControls(layer)
qtbot.addWidget(qtctrl)
qtctrl.ndisplay = 3
assert not qtctrl.clippingPlaneControls.clippingPlaneLabel.isHidden()
assert not qtctrl.clippingPlaneControls.clippingPlaneCheckbox.isHidden()

layer.depiction = "volume"
assert qtctrl.planeThicknessSlider.isHidden()
assert qtctrl.planeThicknessLabel.isHidden()
assert qtctrl.planeNormalButtons.isHidden()
assert qtctrl.planeNormalLabel.isHidden()
assert not qtctrl.clippingPlaneControls.clippingPlaneWidthLabel.isHidden()
assert not qtctrl.clippingPlaneControls.clippingPlaneWidthSlider.isHidden()

layer.depiction = "plane"
assert not qtctrl.planeThicknessSlider.isHidden()
assert not qtctrl.planeThicknessLabel.isHidden()
assert not qtctrl.planeNormalButtons.isHidden()
assert not qtctrl.planeNormalLabel.isHidden()
assert not qtctrl.clippingPlaneControls.clippingPlaneCenterLabel.isHidden()
assert not qtctrl.clippingPlaneControls.clippingPlaneCenterSlider.isHidden()

qtctrl.ndisplay = 2
assert qtctrl.clippingPlaneControls.planeNormalLabel.isHidden()
assert qtctrl.clippingPlaneControls.planeNormalButtons.isHidden()

def test_plane_controls_show_hide_on_ndisplay_change(qtbot):
"""Changing ndisplay should show/hide plane controls if depicting a plane."""
layer = Image(np.random.rand(10, 15, 20))
layer.depiction = "plane"
qtctrl = QtImageControls(layer)
qtbot.addWidget(qtctrl)
assert qtctrl.clippingPlaneControls.clippingPlaneLabel.isHidden()
assert qtctrl.clippingPlaneControls.clippingPlaneCheckbox.isHidden()

assert qtctrl.ndisplay == 2
assert qtctrl.planeThicknessSlider.isHidden()
assert qtctrl.planeThicknessLabel.isHidden()
assert qtctrl.planeNormalButtons.isHidden()
assert qtctrl.planeNormalLabel.isHidden()
assert qtctrl.clippingPlaneControls.clippingPlaneWidthLabel.isHidden()
assert qtctrl.clippingPlaneControls.clippingPlaneWidthSlider.isHidden()

qtctrl.ndisplay = 3
assert not qtctrl.planeThicknessSlider.isHidden()
assert not qtctrl.planeThicknessLabel.isHidden()
assert not qtctrl.planeNormalButtons.isHidden()
assert not qtctrl.planeNormalLabel.isHidden()
assert qtctrl.clippingPlaneControls.clippingPlaneCenterLabel.isHidden()
assert qtctrl.clippingPlaneControls.clippingPlaneCenterSlider.isHidden()


def test_plane_slider_value_change(qtbot):
"""Changing the model should update the view."""
def test_set_clipping_plane_position(qtbot):
"""Test if updating the clipping plane slider updates the clipping plane positions"""
layer = Image(np.random.rand(10, 15, 20))
qtctrl = QtImageControls(layer)
qtbot.addWidget(qtctrl)
layer.plane.thickness *= 2
assert qtctrl.planeThicknessSlider.value() == layer.plane.thickness
width = 3
center = 4
qtctrl.clippingPlaneControls.set_clipping_plane_positions(width, center)

position1 = center - width // 2
position2 = (center + width // 2) + 1

plane_normal = np.array(layer.clipping_planes[0].normal)
new_position1 = np.array([0, 0, 0]) + position1 * plane_normal
new_position1 = (
int(new_position1[0] * layer.scale[-3]),
int(new_position1[1] * layer.scale[-2]),
int(new_position1[2] * layer.scale[-1]),
)
new_position2 = np.array([0, 0, 0]) + position2 * plane_normal
new_position2 = (
int(new_position2[0] * layer.scale[-3]),
int(new_position2[1] * layer.scale[-2]),
int(new_position2[2] * layer.scale[-1]),
)

assert layer.clipping_planes[0].position == new_position1
assert layer.clipping_planes[1].position == new_position2


def test_compute_plane_range(qtbot):
"""Test the _compute_plane_range function."""
layer_data = np.random.rand(10, 15, 20)
layer = Image(layer_data)
qtctrl = QtImageControls(layer)
qtbot.addWidget(qtctrl)

# Set the plane normal
layer.clipping_planes[0].normal = [1, 0, 0] # Normal along the x-axis
expected_range = (0, layer_data.shape[-3]) # Range along the x-axis

# Call _compute_plane_range
computed_range = qtctrl.clippingPlaneControls._compute_plane_range()
assert computed_range == expected_range, (
f"Expected {expected_range}, got {computed_range}"
)

# Test with a different plane normal
layer.clipping_planes[0].normal = [0, 1, 0] # Normal along the x-axis
expected_range = (0, layer_data.shape[-2]) # Range along the y-axis
computed_range = qtctrl.clippingPlaneControls._compute_plane_range()
assert computed_range == expected_range, (
f"Expected {expected_range}, got {computed_range}"
)

# Test with an oblique plane normal
layer.clipping_planes[0].normal = [1, 1, 1] # Normal along the x-axis
computed_range = qtctrl.clippingPlaneControls._compute_plane_range()
expected_range = (np.float64(0.0), np.float64(25.98))
np.testing.assert_almost_equal(computed_range[0], expected_range[0], decimal=1)


def test_activate_clipping_plane(qtbot):
"""Test the _activateClippingPlane function."""
layer_data = np.random.rand(10, 15, 20)
layer = Image(layer_data)
qtctrl = QtImageControls(layer)
qtbot.addWidget(qtctrl)

# Ensure the clipping_planes are initialized
layer.clipping_planes = [
ClippingPlane(normal=[1, 0, 0], position=[0, 0, 0], enabled=False),
ClippingPlane(normal=[-1, 0, 0], position=[0, 0, 0], enabled=False),
]

# Activate the clipping plane
qtctrl.clippingPlaneControls._activateClippingPlane(True)
assert layer.clipping_planes[0].enabled is True
assert layer.clipping_planes[1].enabled is True

assert qtctrl.clippingPlaneControls.clippingPlaneWidthSlider.isEnabled() is True
assert qtctrl.clippingPlaneControls.clippingPlaneCenterSlider.isEnabled() is True
assert qtctrl.clippingPlaneControls.clippingPlaneWidthLabel.isEnabled() is True
assert qtctrl.clippingPlaneControls.clippingPlaneCenterLabel.isEnabled() is True

# Deactivate the clipping plane
qtctrl.clippingPlaneControls._activateClippingPlane(False)
assert layer.clipping_planes[0].enabled is False
assert layer.clipping_planes[1].enabled is False

assert qtctrl.clippingPlaneControls.clippingPlaneWidthSlider.isEnabled() is False
assert qtctrl.clippingPlaneControls.clippingPlaneCenterSlider.isEnabled() is False
assert qtctrl.clippingPlaneControls.clippingPlaneWidthLabel.isEnabled() is False
assert qtctrl.clippingPlaneControls.clippingPlaneCenterLabel.isEnabled() is False


def test_auto_contrast_buttons(qtbot):
Expand Down
2 changes: 1 addition & 1 deletion finn/_qt/layer_controls/_tests/test_qt_layer_controls.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ def test_create_layer_controls_qslider(
max_value = [qslider.maximum()] * num_values
min_value = [qslider.minimum()] * num_values
value_range_to_max = list(zip(base_value_range, max_value, strict=False))
base_value_range_copy = base_value_range.copy()
base_value_range_copy = list(base_value_range).copy()
base_value_range_copy.reverse()
value_range_to_min = list(
zip(min_value, base_value_range_copy, strict=False)
Expand Down
Loading
Loading