Skip to content

Conversation

@Stefan14100
Copy link

Description

An InView widget was added to enable real-time comparison of the model with experimental data as selected parameter values are adjusted with sliders. Once the model is satisfactory, the manually tuned parameters can be sent back to the fitting page for further refinement.

Added

  • src\sas\qtgui\Perspectives\Fitting\media\inview.rst
  • src\sas\qtgui\Perspectives\Fitting\UI\InViewWidgetUI.ui
  • src\sas\qtgui\Perspectives\Fitting\InViewWidget.py
  • src\sas\qtgui\Perspectives\Fitting\UnitTesting\InViewWidgetTest.py

Modified

  • src\sas\qtgui\Perspectives\Fitting\UI\FittingWidgetUI.ui
  • src\sas\qtgui\Perspectives\Fitting\FittingWidget.py

Fixes #2091

How Has This Been Tested?

Installed on Windows 10 using the generated installer. Dummy data imported from the ‘example data’ were used with the sphere and sphere–multishell models. The provided parameters were selected, and their ranges were constrained to finite bounds. No issues were observed during these tests, and the InView widget performed as expected.

Review Checklist:

Documentation

  • There is nothing that needs documenting
  • Documentation changes are in this PR
  • There is an issue open for the documentation (link?)

Installers

  • There is a chance this will affect the installers, if so
    • Windows installer (GH artifact) has been tested (installed and worked) -
    • MacOSX installer (GH artifact) has been tested (installed and worked)
    • Wheels installer (GH artifact) has been tested (installed and worked)

Licensing

  • The introduced changes comply with SasView license (BSD 3-Clause)

Copy link
Contributor

@krzywon krzywon left a comment

Choose a reason for hiding this comment

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

Overall, this is a really nice new feature! The ability to send the values to the fit will really help users find their initial parameters.

A few general comments, all of which could be future improvements:

  • Not allowing inf/-inf for the parameter limits will force users to modify the limits for a large number of models and parameters. Maybe set new limits based on the current value of the parameter, instead?
  • The value inputs in the InView window can probably be smaller, and with fewer significant digits (you currently use 8 decimals, likely only need 3 at most).
  • The slider step size depends on the range, not the current parameter value. For example, sliding the bar for the scale property, currently set to 1, with a range of 0 to 10000 will step up to 100 and then down to 0. Adding a step size control input could be helpful for this as well.
  • If possible, a plot of the residuals (as a sub plot of the main plot) would be useful, but that might be beyond the scope of this PR.
  • I understand why 2D fits are not allowed for now, but would be nice to have.

A few general code comments that will help future developers maintain this work:

"""
if self._in_view_widget is None:
self._in_view_widget = InViewWidget(parent=self)
self._in_view_widget.destroyed.connect(lambda: setattr(self, '_in_view_widget', None))
Copy link
Contributor

Choose a reason for hiding this comment

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

The connection on line 1694 already does this, but in a cleaner way.

Comment on lines +1685 to +1691
def _on_inview_closed():
self._in_view_widget = None
# Enabling paramters edits
self.lstParams.setEnabled(True)
self.cbCategory.setEnabled(True)
self.cmdFit.setEnabled(True)
self.cmdPlot.setEnabled(True)
Copy link
Contributor

Choose a reason for hiding this comment

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

Create the following class-level method and you can replace the last four lines of this function here and in at least 2 other places.

        def toggle_inview_window():
            toggle = self._in_view_widget == None
            # Enabling paramters edits
            self.lstParams.setEnabled(toggle)
            self.cbCategory.setEnabled(toggle)
            self.cmdFit.setEnabled(toggle)
            self.cmdPlot.setEnabled(toggle)

Comment on lines +1714 to +1729
if not np.isfinite(pmin) or not np.isfinite(pmax):
has_infinite_bounds = True
break
except Exception:
# If anything odd, be conservative and flag
has_infinite_bounds = True
break
if has_infinite_bounds:
QtWidgets.QMessageBox.warning(self, "InView",
"Some parameters have infinite bounds. Please set appropriate min/max ranges.")
# Re-enable controls since we are not opening InView
self.lstParams.setEnabled(True)
self.cbCategory.setEnabled(True)
self.cmdFit.setEnabled(True)
self.cmdPlot.setEnabled(True)
return
Copy link
Contributor

Choose a reason for hiding this comment

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

Simplification:

Suggested change
if not np.isfinite(pmin) or not np.isfinite(pmax):
has_infinite_bounds = True
break
except Exception:
# If anything odd, be conservative and flag
has_infinite_bounds = True
break
if has_infinite_bounds:
QtWidgets.QMessageBox.warning(self, "InView",
"Some parameters have infinite bounds. Please set appropriate min/max ranges.")
# Re-enable controls since we are not opening InView
self.lstParams.setEnabled(True)
self.cbCategory.setEnabled(True)
self.cmdFit.setEnabled(True)
self.cmdPlot.setEnabled(True)
return
if not np.isfinite(pmin) or not np.isfinite(pmax):
# Fall through to exception handling, which mimics behavior required here.
raise Exception()
except Exception:
# If anything odd, be conservative and flag
QtWidgets.QMessageBox.warning(self, "InView",
"Some parameters have infinite bounds. Please set appropriate min/max ranges.")
# Re-enable controls since we are not opening InView
self._on_inview_closed()
return

Comment on lines +1733 to +1736
self._in_view_widget.setData(self.logic.data)

# Construct sliders from the Fit Page
self._in_view_widget.initFromFitPage(self)
Copy link
Contributor

Choose a reason for hiding this comment

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

You are creating a new window in this method and the fitpage is already passed to the InViewWidget. Instead, pass the data to __init__() and call both setData() and initFromFitPage() as part of the instantiation, which allows you to remove the arguments from both methods.

def setData(self, data):
if not isinstance(data, Data1D):
return
self._has_data = True
Copy link
Contributor

Choose a reason for hiding this comment

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

This won't be true if an exception is thrown later. Maybe move this to after the try/except block?

Comment on lines +115 to +128
while layout.count():
item = layout.takeAt(0)
w = item.widget()
if w is not None:
w.deleteLater()
else:
sub = item.layout()
if sub is not None:
while sub.count():
child = sub.takeAt(0)
cw = child.widget()
if cw is not None:
cw.deleteLater()
sub.deleteLater()
Copy link
Contributor

Choose a reason for hiding this comment

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

Your solution will only delete 2 levels of children. In case you need a multi-level approach:

def clear_all_inputs(self):
    layout = self.sliderBox.layout()
    self._clear_recursion(layout)

def _clear_recursion(self, layout):
    while layout.count():
        item = layout.takeAt(0)
        w = item.widget()
        if w is not None:
            w.deleteLater()
        else:
            if item.layout() is not None:
                self._clear_recursion(item.layout())

def _build_sliders(self, params):
    self.clear_all_inputs()

self._slider_meta = {}
steps = 1000
for name in params:
info = self._param_info[name]
Copy link
Contributor

Choose a reason for hiding this comment

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

Putting this all into a separate method (called _build_param_slider or similar) would be ideal for a number of reasons (easier to find, more modular, easier to refactor).

Comment on lines +185 to +186
except Exception:
pass
Copy link
Contributor

Choose a reason for hiding this comment

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

You have a number of bare exceptions that are swallowed. What could cause these errors you're capturing and what should the user and/or developer know if this happens?

@@ -0,0 +1,27 @@
.. _InView_Documentation:
Copy link
Contributor

Choose a reason for hiding this comment

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

A link to this document from the fitting documentation could be helpful.

Next to the interactive plot, one would see sliders for each parameter that has been previously selected. Once the model is satisfactory, it can be send back to Fitting
via 'Update fitted parameters'.

.. image:: InViewWindow.png No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you forgot to include the image files here.

@bmaranville
Copy link
Contributor

You could filter out parameters that have infinite limits and not show the sliders for those, and provide an easy way to edit the limits if they want to enable slider controls for a parameter.

@butlerpd butlerpd self-requested a review December 16, 2025 14:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants