Skip to content

Commit

Permalink
enh: visualize POC methods when in preprocessing tab (close #15)
Browse files Browse the repository at this point in the history
  • Loading branch information
paulmueller committed Aug 16, 2021
1 parent eb46a6c commit 3e1347d
Show file tree
Hide file tree
Showing 11 changed files with 170 additions and 48 deletions.
8 changes: 5 additions & 3 deletions CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
- BREAKING CHANGE: The default contact point estimation method
changed. This means that your fitted contact points and any
E(delta)-related results will change!
- feat: allow to select point of contact method
- feat: allow to select point of contact method (#15)
- enh: restructure preprocessing tab and avoid situations where
preprocessing dependencies are not met
- setup: bump nanite from 2.0.0 to 3.0.0 (improved preprocessing)
preprocessing dependencies are not met (#15)
- enh: visualize contact point estimation algorithms in
preprocessing tab
- setup: bump nanite from 2.0.0 to 3.1.0 (improved preprocessing)
0.9.4
- enh: only show exact sneddon model "sneddon_spher" in developer
mode to avoid confusion
Expand Down
38 changes: 24 additions & 14 deletions pyjibe/fd/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,9 +232,8 @@ def callback(partial):

def autosave(self, fdist):
"""Performs autosaving for all files"""
if (self.cb_autosave.checkState() == QtCore.Qt.Checked and
fdist.fit_properties and
fdist.fit_properties["success"]):
if (self.cb_autosave.checkState() == QtCore.Qt.Checked
and fdist.fit_properties.get("success", False)):
# Determine the directory of the current curve
adir = os.path.dirname(fdist.path)
model_key = fdist.fit_properties["model_key"]
Expand All @@ -248,7 +247,7 @@ def autosave(self, fdist):
# fdist was fitted
ar.fit_properties and
# fit was successful
ar.fit_properties["success"] and
ar.fit_properties.get("success", False) and
# fdist was fitted with same model
ar.fit_properties["model_key"] == model_key and
# user selected curve for export ("use")
Expand Down Expand Up @@ -389,13 +388,13 @@ def on_curve_list(self):
fdist = self.current_curve
idx = self.current_index
# perform preprocessing
self.tab_preprocess.fit_apply_preprocessing(fdist)
self.tab_preprocess.apply_preprocessing(fdist)
# update user interface with initial parameters
self.tab_fit.fit_update_parameters(fdist)
# fit data
self.tab_fit.fit_approach_retract(fdist)
# set plot data (time consuming)
self.widget_fdist.mpl_curve_update(fdist)
self.widget_plot_fd.mpl_curve_update(fdist)
# update info
self.info_update(fdist)
# Display new rating
Expand Down Expand Up @@ -496,7 +495,7 @@ def on_fit_all(self):
if bar.wasCanceled():
break
try:
self.tab_preprocess.fit_apply_preprocessing(fdist)
self.tab_preprocess.apply_preprocessing(fdist)
self.tab_fit.fit_approach_retract(fdist, update_ui=False)
self.curve_list_update(item=ii)
except BaseException as e:
Expand All @@ -522,27 +521,27 @@ def on_model(self):
# have to `fit_update_parameters` in order to display
# potential new parameter names of the new model.
fdist = self.current_curve
self.tab_preprocess.fit_apply_preprocessing(fdist)
self.tab_preprocess.apply_preprocessing(fdist)
self.tab_fit.fit_update_parameters(fdist)
self.tab_fit.fit_approach_retract(fdist)
self.widget_fdist.mpl_curve_update(fdist)
self.widget_plot_fd.mpl_curve_update(fdist)
self.curve_list_update()
self.tab_qmap.mpl_qmap_update()

@QtCore.pyqtSlot()
def on_mpl_curve_update(self):
fdist = self.current_curve
self.widget_fdist.mpl_curve_update(fdist)
self.widget_plot_fd.mpl_curve_update(fdist)

@QtCore.pyqtSlot()
def on_params_init(self):
"""Called when the initial parameters are changed"""
fdist = self.current_curve
idx = self.current_index
self.tab_preprocess.fit_apply_preprocessing(fdist)
self.tab_preprocess.apply_preprocessing(fdist)
self.tab_fit.anc_update_parameters(fdist)
self.tab_fit.fit_approach_retract(fdist)
self.widget_fdist.mpl_curve_update(fdist)
self.widget_plot_fd.mpl_curve_update(fdist)
self.curve_list_update(item=idx)
self.tab_qmap.mpl_qmap_update()

Expand Down Expand Up @@ -577,9 +576,20 @@ def on_tab_changed(self):
prevtab = self.tabs.currentWidget()

curtab = self.tabs.currentWidget()
if curtab == self.tab_fit and prevtab == self.tab_preprocess:

# stacked plot widget
if curtab == self.tab_preprocess:
self.stackedWidget.setCurrentWidget(self.widget_plot_preproc)
self.tab_preprocess.apply_preprocessing(self.current_curve)
else:
self.stackedWidget.setCurrentWidget(self.widget_plot_fd)

# preprocessing probably changed; This means the plot has to
# be updated.
if curtab != self.tab_preprocess and prevtab == self.tab_preprocess:
self.on_params_init()
elif curtab == self.tab_qmap:

if curtab == self.tab_qmap:
# Redraw the current map
self.tab_qmap.mpl_qmap_update()
elif curtab == self.tab_edelta:
Expand Down
21 changes: 13 additions & 8 deletions pyjibe/fd/main.ui
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,9 @@
<number>3</number>
</property>
<item>
<widget class="WidgetFDist" name="widget_fdist" native="true">
<property name="enabled">
<bool>true</bool>
</property>
<widget class="QStackedWidget" name="stackedWidget">
<widget class="WidgetPlotFD" name="widget_plot_fd"/>
<widget class="WidgetPlotPreproc" name="widget_plot_preproc"/>
</widget>
</item>
<item>
Expand Down Expand Up @@ -563,15 +562,21 @@
<container>1</container>
</customwidget>
<customwidget>
<class>WidgetFDist</class>
<class>TabInfo</class>
<extends>QWidget</extends>
<header>pyjibe.fd.widget_fdist</header>
<header>pyjibe.fd.tab_info</header>
<container>1</container>
</customwidget>
<customwidget>
<class>TabInfo</class>
<class>WidgetPlotFD</class>
<extends>QWidget</extends>
<header>pyjibe.fd.tab_info</header>
<header>pyjibe.fd.widget_plot_fd</header>
<container>1</container>
</customwidget>
<customwidget>
<class>WidgetPlotPreproc</class>
<extends>QWidget</extends>
<header>pyjibe.fd.widget_plot_preproc</header>
<container>1</container>
</customwidget>
</customwidgets>
Expand Down
29 changes: 29 additions & 0 deletions pyjibe/fd/mpl_preproc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import (
FigureCanvasQTAgg as FigureCanvas)

from ..head import custom_widgets


class MPLPreproc:
def __init__(self):
"""Matplotlib plot for preprocessing data"""
# Add matplotlib figure
self.figure = Figure(facecolor="none", tight_layout=True,
frameon=True)
self.axis = self.figure.add_subplot(111)
self.axis.set_facecolor('#FFFFFF')
self.canvas = FigureCanvas(self.figure)
self.canvas.draw()

def add_toolbar(self, widget):
"""Add toolbar to PyQT widget"""
self.toolbar = custom_widgets.NavigationToolbarPreproc(
self.canvas,
widget,
coordinates=True
)

def clear(self):
self.axis.clear()
self.axis.grid()
2 changes: 1 addition & 1 deletion pyjibe/fd/tab_fit.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ def fit_approach_retract(self, fdist, update_ui=True):
optimal_fit_num_samples=optimal_fit_num_samples,
)
ftab = self.table_parameters_fitted
if fdist.fit_properties["success"]:
if fdist.fit_properties.get("success", False):
# Perform automatic saving of results
self.fd.autosave(fdist)
# Display automatically detected optimal indentation depth
Expand Down
51 changes: 38 additions & 13 deletions pyjibe/fd/tab_preprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ def __init__(self, *args, **kwargs):
pwidget = WidgetPreprocessItem(identifier=pid, parent=self)
self._map_widgets_to_preproc_ids[pwidget] = pid
self.layout_preproc_area.addWidget(pwidget)
pwidget.stateChanged.connect(self.check_selection)
if pid == "correct_tip_offset":
idx = pwidget.comboBox.findData("deviation_from_baseline")
pwidget.comboBox.setCurrentIndex(idx)
pwidget.preproc_step_changed.connect(self.on_preproc_step_changed)
spacer_item = QtWidgets.QSpacerItem(20, 0,
QtWidgets.QSizePolicy.Minimum,
QtWidgets.QSizePolicy.Expanding)
Expand All @@ -39,24 +39,52 @@ def __init__(self, *args, **kwargs):
# Apply recommended defaults
self.cb_preproc_presel.setCurrentIndex(1)

@QtCore.pyqtSlot(int)
def check_selection(self, state):
@property
def fd(self):
return self.parent().parent().parent().parent()

def apply_preprocessing(self, fdist=None):
"""Apply the preprocessing steps if required"""
if fdist is None:
if hasattr(self, "fd"):
fdist = self.fd.current_curve
else:
# initialization not finished
return
identifiers, options = self.current_preprocessing()
# Perform preprocessing
preproc_visible = self.fd.stackedWidget.currentWidget() == \
self.fd.widget_plot_preproc
details = fdist.apply_preprocessing(identifiers,
options=options,
ret_details=preproc_visible)
if preproc_visible:
self.fd.widget_plot_preproc.update_details(details)

@QtCore.pyqtSlot()
def on_preproc_step_changed(self):
self.check_selection()
self.apply_preprocessing()

@QtCore.pyqtSlot()
def check_selection(self):
"""If the user selects an item, make sure requirements are checked"""
sender = self.sender()
state = sender.isChecked()
if sender in self._map_widgets_to_preproc_ids:
pid = self._map_widgets_to_preproc_ids[sender]
if state == 2:
if state:
# Enable all steps that this step here requires
req_stps = IndentationPreprocessor.get_require_steps(pid)
req_stps = IndentationPreprocessor.get_steps_required(pid)
if req_stps:
for pwid in self._map_widgets_to_preproc_ids:
if self._map_widgets_to_preproc_ids[pwid] in req_stps:
pwid.setChecked(True)
if state == 0:
else:
# Disable all steps that depend on this one
for dwid in self._map_widgets_to_preproc_ids:
did = self._map_widgets_to_preproc_ids[dwid]
req_stps = IndentationPreprocessor.get_require_steps(did)
req_stps = IndentationPreprocessor.get_steps_required(did)
if req_stps and pid in req_stps:
dwid.setChecked(False)

Expand All @@ -77,12 +105,6 @@ def current_preprocessing(self):
identifiers = IndentationPreprocessor.autosort(identifiers)
return identifiers, options

def fit_apply_preprocessing(self, fdist):
"""Apply the preprocessing steps if required"""
identifiers, options = self.current_preprocessing()
# Perform preprocessing
fdist.apply_preprocessing(identifiers, options=options)

@QtCore.pyqtSlot()
def on_preset_changed(self):
"""Update preselection"""
Expand All @@ -98,8 +120,11 @@ def on_preset_changed(self):
raise ValueError(f"Unknown text '{text}'!")

for pwidget in self._map_widgets_to_preproc_ids:
pwidget.blockSignals(True)
pid = self._map_widgets_to_preproc_ids[pwidget]
pwidget.setChecked(pid in used_methods)
pwidget.blockSignals(False)
self.apply_preprocessing()

def set_preprocessing(self, preprocessing, options=None):
"""Set preprocessing (mostly used for testing)"""
Expand Down
6 changes: 3 additions & 3 deletions pyjibe/fd/widget_fdist.py → pyjibe/fd/widget_plot_fd.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
from .mpl_indent import MPLIndentation


class WidgetFDist(QtWidgets.QWidget):
class WidgetPlotFD(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
"""Display force-indentation graph with navigation"""
super(WidgetFDist, self).__init__(*args, **kwargs)
super(WidgetPlotFD, self).__init__(*args, **kwargs)

self.mplvl = QtWidgets.QVBoxLayout(self)
# Setup the matplotlib interface for approach retract plotting
Expand All @@ -18,7 +18,7 @@ def __init__(self, *args, **kwargs):

@property
def fd(self):
return self.parent().parent()
return self.parent().parent().parent()

def mpl_curve_update(self, fdist):
"""Update the force-indentation curve"""
Expand Down
36 changes: 36 additions & 0 deletions pyjibe/fd/widget_plot_preproc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""Widget containing preprocessing plot"""
from PyQt5 import QtWidgets

from .mpl_preproc import MPLPreproc


class WidgetPlotPreproc(QtWidgets.QWidget):
def __init__(self, *args, **kwargs):
"""Display preprocessing graph with navigation"""
super(WidgetPlotPreproc, self).__init__(*args, **kwargs)

self.mplvl = QtWidgets.QVBoxLayout(self)
# Setup the matplotlib interface for approach retract plotting
self.mpl_curve = MPLPreproc()
self.mpl_curve.add_toolbar(self)
self.mplvl.addWidget(self.mpl_curve.canvas)
self.mplvl.addWidget(self.mpl_curve.toolbar)

@property
def fd(self):
return self.parent().parent().parent()

def update_details(self, details):
"""Update UI with details"""
self.mpl_curve.clear()
if details is None:
return
elif "correct_tip_offset" in details:
meth = details["correct_tip_offset"]
self.mpl_curve.clear()
self.mpl_curve.axis.set_title(meth["method"])
for key in meth:
if key.startswith("plot "):
self.mpl_curve.axis.plot(*meth[key], label=key[5:])
self.mpl_curve.axis.legend()
self.mpl_curve.canvas.draw()
14 changes: 9 additions & 5 deletions pyjibe/fd/widget_preprocess_item.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@


class WidgetPreprocessItem(QtWidgets.QWidget):
#: Pass-through for stateChanged of self.CheckBox
stateChanged = QtCore.pyqtSignal(int)
#: Triggered whenever the state changes
preproc_step_changed = QtCore.pyqtSignal()

def __init__(self, identifier, *args, **kwargs):
"""Special widget for preprocessing options"""
Expand Down Expand Up @@ -40,9 +40,8 @@ def __init__(self, identifier, *args, **kwargs):
self.update_enabled()

# signal: passthrough stateChanged
self.checkBox.stateChanged.connect(self.stateChanged)
# signal: enable/disable widget area
self.checkBox.stateChanged.connect(self.update_enabled)
self.checkBox.stateChanged.connect(self.on_state_changed)
self.comboBox.currentIndexChanged.connect(self.on_state_changed)

def get_options(self):
"""Return preprocessing options"""
Expand All @@ -58,6 +57,11 @@ def get_options(self):
def isChecked(self):
return self.checkBox.isChecked()

@QtCore.pyqtSlot()
def on_state_changed(self):
self.update_enabled()
self.preproc_step_changed.emit()

def setChecked(self, *args, **kwargs):
self.checkBox.setChecked(*args, **kwargs)

Expand Down
3 changes: 2 additions & 1 deletion pyjibe/head/custom_widgets/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from .mpl_navigation_toolbar_icons import (
NavigationToolbarIndent,
NavigationToolbarEDelta,
NavigationToolbarQMap
NavigationToolbarQMap,
NavigationToolbarPreproc
)
from .wait_cursor import ShowWaitCursor, show_wait_cursor

0 comments on commit 3e1347d

Please sign in to comment.