Skip to content
1 change: 1 addition & 0 deletions dcscope/gui/analysis/ana_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ def __init__(self, *args, **kwargs):
self.comboBox_division.addItem("One plot per dataset", "each")
self.comboBox_division.addItem("Scatter plots and joint contour plot",
"multiscatter+contour")
self.comboBox_division.addItem("Only contour plots", "onlycontours")
self.comboBox_division.setCurrentIndex(2)

# signals
Expand Down
13 changes: 13 additions & 0 deletions dcscope/gui/pipeline_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,19 @@ def update_content_plot(self, plot_state, slot_states, dslist):
colspan=1)
pp.redraw(dslist, slot_states, plot_state_contour)

elif lay["division"] == "onlycontours":
# contour plots
plot_state_contour = copy.deepcopy(plot_state)
plot_state_contour["scatter"]["enabled"] = False
pp = PipelinePlotItem(parent=linner)
self.plot_items.append(pp)
linner.addItem(item=pp,
row=None,
col=None,
rowspan=1,
colspan=1)
pp.redraw(dslist, slot_states, plot_state_contour)

# colorbar
colorbar_kwds = {}

Expand Down
2 changes: 2 additions & 0 deletions dcscope/pipeline/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,8 @@ def get_plot_col_row_count(self, plot_id, pipeline_state=None):
num_plots = 1
elif div == "multiscatter+contour":
num_plots = num_scat + 1
elif div == "onlycontours":
num_plots = 1
else:
raise ValueError(f"Unrecognized division: '{div}'")

Expand Down
2 changes: 1 addition & 1 deletion dcscope/pipeline/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
"identifier": str,
"layout": {
"column count": int,
"division": ["each", "merge", "multiscatter+contour"],
"division": ["each", "merge", "multiscatter+contour", "onlycontours"],
"label plots": bool,
"name": str,
"size x": float,
Expand Down
156 changes: 156 additions & 0 deletions tests/test_gui_plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,3 +314,159 @@ def test_changing_lut_identifier_in_analysis_view_plots(qtbot):
qtbot.mouseClick(pv.pushButton_apply, QtCore.Qt.MouseButton.LeftButton)

assert pv.comboBox_lut.currentData() == "HE-3D-FEM-22"


def test_zoomin_contours(qtbot):
"""Test that zooming in on contours works correctly"""
mw = DCscope()
qtbot.addWidget(mw)

# Add test datasets
paths = [
datapath / "artificial_with_image_bg.rtdc",
datapath / "blood_rbc_leukocytes.rtdc",
datapath / "calibration_beads_47.rtdc"
]

mw.add_dataslot(paths=paths)

# Add a plot
plot_id = mw.add_plot()

# Activate analysis view
pe = mw.block_matrix.get_widget(filt_plot_id=plot_id)
qtbot.mouseClick(pe.toolButton_modify, QtCore.Qt.MouseButton.LeftButton)

# Switch to plot tab
mw.widget_ana_view.tabWidget.setCurrentWidget(mw.widget_ana_view.tab_plot)
pv = mw.widget_ana_view.widget_plot

# Enable contours and zoom-in
# pv.checkBox_contour.setChecked(True)
pv.checkBox_zoomin.setChecked(True)

# Apply changes
qtbot.mouseClick(pv.pushButton_apply, QtCore.Qt.MouseButton.LeftButton)

# Open the plot window
mw.add_plot_window(plot_id)

# Get the plot widget
plot_widget = mw.subwindows_plots[plot_id].widget()

# Get the plot items from the pipeline plot
if plot_widget.plot_items:
# Get the first plot item (in case there are multiple due to division)
plot_item = plot_widget.plot_items[0]

# Get the current view range
x_range = plot_item.getViewBox().viewRange()[0]
y_range = plot_item.getViewBox().viewRange()[1]

# Verify that zoom was applied
assert np.all(np.isfinite(x_range))
assert np.all(np.isfinite(y_range))
assert x_range[0] != 0 or x_range[1] != 1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please test for actual values of the range (the zoomed-in values). A future regression might pass this test even though it breaks the functionality.

assert y_range[0] != 0 or y_range[1] != 1


def test_only_contours_division(qtbot):
"""Test that 'onlycontours' division mode works correctly"""
mw = DCscope()
qtbot.addWidget(mw)

# Add multiple datasets
path = datapath / "calibration_beads_47.rtdc"
mw.add_dataslot(paths=[path, path]) # Add same dataset twice for testing

# Add a plot
plot_id = mw.add_plot()

# Activate analysis view
pe = mw.block_matrix.get_widget(filt_plot_id=plot_id)
qtbot.mouseClick(pe.toolButton_modify, QtCore.Qt.MouseButton.LeftButton)

# Switch to plot tab
mw.widget_ana_view.tabWidget.setCurrentWidget(mw.widget_ana_view.tab_plot)
pv = mw.widget_ana_view.widget_plot

# Get the initial plot state
plot_state = mw.pipeline.get_plot(plot_id).__getstate__()

# Verify initial division mode
assert plot_state["layout"]["division"] == "multiscatter+contour"

# Set division to "onlycontours"
idx = pv.comboBox_division.findData("onlycontours")
pv.comboBox_division.setCurrentIndex(idx)

# Apply changes
qtbot.mouseClick(pv.pushButton_apply, QtCore.Qt.MouseButton.LeftButton)

# Get the plot widget
pw = mw.block_matrix.get_widget(filt_plot_id=plot_id)

# Activate plots for contour view
qtbot.mouseClick(pw.toolButton_toggle, QtCore.Qt.MouseButton.LeftButton)

# Get the plot state
plot_state = mw.pipeline.get_plot(plot_id).__getstate__()

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add an assert that makes sure there is only one plot.

# Verify division mode
assert plot_state["layout"]["division"] == "onlycontours"


def test_contour_plot_with_invalid_percentiles(qtbot):
"""Test contour plot with edge case percentiles (e.g., 100% KDE)"""
mw = DCscope()
qtbot.addWidget(mw)

# Add a dataset
path = datapath / "calibration_beads_47.rtdc"
slot_id = mw.add_dataslot(paths=[path])[0]

# Add a plot
plot_id = mw.add_plot()

# Activate the slot-plot pair to show data
pe = mw.block_matrix.get_widget(slot_id, plot_id)
qtbot.mouseClick(pe, QtCore.Qt.MouseButton.LeftButton)

# Activate analysis view
pe = mw.block_matrix.get_widget(filt_plot_id=plot_id)
qtbot.mouseClick(pe.toolButton_modify, QtCore.Qt.MouseButton.LeftButton)

# Switch to plot tab
mw.widget_ana_view.tabWidget.setCurrentWidget(mw.widget_ana_view.tab_plot)
pv = mw.widget_ana_view.widget_plot

# Enable contours
pv.groupBox_contour.setChecked(True)

# Set contour percentiles to extreme values (edge cases)
# 100% percentile is at the maximum KDE value
pv.doubleSpinBox_perc_1.setValue(100.0) # Maximum percentile
pv.doubleSpinBox_perc_2.setValue(99.0) # Near maximum
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please set the second level to 100% as well. The test is about catching error messages when nonsense is plotted.


# Apply changes
qtbot.mouseClick(pv.pushButton_apply, QtCore.Qt.MouseButton.LeftButton)

# Verify the plot state was updated with the new percentiles
plot_state = mw.pipeline.get_plot(plot_id).__getstate__()
con = plot_state["contour"]

# Check that percentiles were set
assert con["percentiles"][0] == 100.0
assert con["percentiles"][1] == 99.0
assert con["enabled"] is True

# Open the plot window to verify rendering works
mw.add_plot_window(plot_id)

# Get the plot widget
plot_widget = mw.subwindows_plots[plot_id].widget()

# Check that plot items were created
assert plot_widget is not None
if plot_widget.plot_items:
assert len(plot_widget.plot_items) > 0
Loading