-
Notifications
You must be signed in to change notification settings - Fork 47
2091 slider option for parameters #3790
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
base: main
Are you sure you want to change the base?
Conversation
krzywon
left a comment
There was a problem hiding this 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/-inffor 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
InViewwindow 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:
- Please add docstrings. https://peps.python.org/pep-0257/
- Please add type hints. https://docs.python.org/3/library/typing.html
| """ | ||
| 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)) |
There was a problem hiding this comment.
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.
| 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) |
There was a problem hiding this comment.
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)
| 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Simplification:
| 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 |
| self._in_view_widget.setData(self.logic.data) | ||
|
|
||
| # Construct sliders from the Fit Page | ||
| self._in_view_widget.initFromFitPage(self) |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
| 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() |
There was a problem hiding this comment.
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] |
There was a problem hiding this comment.
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).
| except Exception: | ||
| pass |
There was a problem hiding this comment.
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: | |||
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
|
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. |
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
Modified
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
Installers
Licensing