Commit 89748dc8 authored by Michael Rudolf's avatar Michael Rudolf
Browse files

# GUI-Update

The GUI Version is now fully functional and integrated into RST_Func.
To start the GUI-based picking use `python3 RST_pick_GUI.py`.
When using the default options it is equivalent to RST_main.py.

- Added more default options to the config.
-  Extended the `eval_shearstress` function by the option to review picks
- Changed the convention of DocStrings from single to double quotes.
- Added DocStrings to some functions.
parent 06aef3e9
...@@ -7,21 +7,23 @@ Created on Mon Jul 23 11:41:01 2018 ...@@ -7,21 +7,23 @@ Created on Mon Jul 23 11:41:01 2018
# %%=================FUNCTION================================================ # %%=================FUNCTION================================================
import numpy as np import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec import matplotlib.gridspec as gridspec
import codecs import codecs
import csv import csv
import os import os
import nptdms import nptdms
import matplotlib.pyplot as plt
from scipy import stats from scipy import stats
from scipy.signal import savgol_filter from scipy.signal import savgol_filter
from scipy import optimize from scipy import optimize
import time
import sys
# %%==============CONVERSION=================================================== # %%==============CONVERSION===================================================
def convert(path, file, var): def convert(path, file, var):
'''Reads data from source files and returns a dictionary with data''' """Reads data from source files and returns a dictionary with data"""
# possible inputs and linked helper functions # possible inputs and linked helper functions
file_convert = {'.asc': _readasc, file_convert = {'.asc': _readasc,
'.dat': _readdat, '.dat': _readdat,
...@@ -45,50 +47,294 @@ def convert(path, file, var): ...@@ -45,50 +47,294 @@ def convert(path, file, var):
# %%==================EVALUATION SHEAR CURVE DATA============================== # %%==================EVALUATION SHEAR CURVE DATA==============================
def eval_shearstress(cur_dat, var): def eval_shearstress(cur_dat, var, review=None):
velnoise, stressnoise = var['velnoise'], var['stressnoise'] """
# Searching for velocity changes to define data range Evaluates the shear stress data and picks the needed data points.
vel_above = np.nonzero(cur_dat['velocity'] > var['velnoise']) If the review option is set, one may revise individual points that have
switch = np.nonzero(np.diff(vel_above) > 10) been chosen by the automatic picking algorithm, or skip autopicking.
start_1 = vel_above[0][0]
end_1 = start_1+switch[1][0] Possible review options
start_2 = end_1+np.max(np.diff(vel_above)) -----------------------
end_2 = vel_above[0][-1] None (default):
automatic picking without revision
# smoothing function: 1st#: window size, 2nd#: polynomial order 'auto':
shearstress_smooth = savgol_filter( automatic picking with revision of each set separately
'manual':
skips automatic picking and opens a manual picking dialog
"""
global picks
# Internal functions
def _auto_pick(cur_dat, var):
"""Helper function to automatically pick the points"""
# smoothing function: 1st#: window size, 2nd#: polynomial order
shear_smth = savgol_filter(
cur_dat['shearstress'], cur_dat['shearstress'],
var['smoothwindow'], int(float(var['smoothwindow'])),
3) 3)
# Processing
# Peak friction velnoise, stressnoise = var['velnoise'], var['stressnoise']
i_peak = np.argmax(shearstress_smooth[start_1:end_1])+start_1 # Searching for velocity changes to define data range
tau_peak = shearstress_smooth[i_peak] vel_above = np.nonzero(cur_dat['velocity'] > var['velnoise'])
switch = np.nonzero(np.diff(vel_above) > 10)
# Dynamic friction start_1 = vel_above[0][0]
i_dyn = np.argmin(shearstress_smooth[i_peak:end_1])+i_peak end_1 = start_1+switch[1][0]-10
tau_dyn = shearstress_smooth[i_dyn] start_2 = end_1+np.max(np.diff(vel_above))+10
end_2 = vel_above[0][-1]
# Static friction (reactivation) # Peak friction
i_stat = np.argmax(shearstress_smooth[start_2:end_2])+start_2 i_peak = np.argmax(shear_smth[start_1:end_1])+start_1
tau_stat = shearstress_smooth[i_stat] tau_peak = shear_smth[i_peak]
peak = [cur_dat['displacement'][i_peak], tau_peak] # Dynamic friction
dyn = [cur_dat['displacement'][i_dyn], tau_dyn] i_dyn = np.argmin(shear_smth[i_peak:end_1])+i_peak
stat = [cur_dat['displacement'][i_stat], tau_stat] tau_dyn = shear_smth[i_dyn]
# Static friction (reactivation)
i_stat = np.argmax(shear_smth[start_2:end_2])+start_2
tau_stat = shear_smth[i_stat]
# Store picks in dictionary
picks = dict()
picks['peak'] = [tau_peak, i_peak]
picks['dynamic'] = [tau_dyn, i_dyn]
picks['static'] = [tau_stat, i_stat]
return picks
def _manual_pick(cur_dat, var,
pick_list=['peak', 'dynamic', 'static'],
picks=dict()):
"""
Creates an interactive plot to pick the three important points for the
RST analysis. By manually specifying pick_list, one can only select a
specific peak. This is helpful if you need to update a specific
automatic pick, without needing to repick everything.
"""
pick_string = {
'peak': 'Select first peak',
'dynamic': 'Select dynamic (steady state) peak',
'static': 'Select static (reactivation) peak'
}
pick_func = {
'peak': np.max,
'dynamic': np.median,
'static': np.max
}
pick_col = {
'peak': 'r',
'dynamic': 'y',
'static': 'b'
}
(fig, ax) = _pick_base_plot(cur_dat, var)
fig.canvas.set_window_title('Manual Peak Selection')
plt.pause(0.001)
# Loop which runs over all peak types
for pick in pick_list:
ax.set_title(pick_string[pick])
plt.pause(0.001)
# Loop preventing an error when the user clicks outside the plot
goodPick = False
while not goodPick:
try:
(sel_dat, sel) = _select_data(cur_dat)
pdata = pick_func[pick](cur_dat['shearstress'][sel_dat])
goodPick = True
except ValueError as e:
pass
ax.plot(cur_dat['displacement'][sel_dat],
cur_dat['shearstress'][sel_dat],
color=pick_col[pick])
plt.pause(0.001)
ax.hlines(pdata,
np.min(cur_dat['displacement']),
np.max(cur_dat['displacement']),
color=pick_col[pick])
picks[pick] = [pdata, sel]
plt.pause(0.001)
plt.pause(1)
plt.close()
return picks
def _evaluate_picks(cur_dat, picks):
# Calculate values
peak = [cur_dat['displacement'][picks['peak'][1]],
picks['peak'][0]]
dyn = [cur_dat['displacement'][picks['dynamic'][1]],
picks['dynamic'][0]]
stat = [cur_dat['displacement'][picks['static'][1]],
picks['static'][0]]
# Calculate dilation for peak
i_d1 = np.argmin(cur_dat['liddispl'][:picks['peak'][1]])
d1 = cur_dat['liddispl'][i_d1]
i_d2 = np.argmax(
cur_dat['liddispl'][picks['peak'][1]:picks['dynamic'][1]])
d2 = cur_dat['liddispl'][i_d2]
deltad = d2-d1
return (peak, dyn, stat, deltad)
def _select_data(cur_dat):
"""Creates a slice of +-20 points around a selected point"""
plt.show(block=False)
selection = plt.ginput(1)
i = np.searchsorted(cur_dat['displacement'],
selection[0],
side='right')
set_slice = slice(i[0]-20, i[0]+20)
return (set_slice, i[0])
def _pick_base_plot(cur_dat, var):
"""Shows the current set, serving as a base plot for all plots"""
normstress = (
int(np.mean(cur_dat['normalstress'])/var['prec'])*var['prec']
)
# Create plot
fig, ax = plt.subplots()
dpi = fig.get_dpi()
fig.set_size_inches((1920/2)/float(dpi), (1080/2)/float(dpi))
plt.subplots_adjust(bottom=0.2)
ax.plot(cur_dat['displacement'],
cur_dat['shearstress'],
label='original data')
shear_smth = savgol_filter(
cur_dat['shearstress'],
int(float(var['smoothwindow'])),
3)
ax.plot(cur_dat['displacement'],
shear_smth,
':',
label='filtered data')
ax.set_ylim(0)
ax.set_xlim(np.min(cur_dat['displacement']),
np.max(cur_dat['displacement']))
ax.set_title('Normal stress: %i' % normstress)
return (fig, ax)
def _review_picks(fig, ax, picks):
"""Adds picks to the pick_base_plot"""
ALL_OK = False
def _switch(event):
nonlocal ALL_OK
ALL_OK = not ALL_OK
base_col_list = ['r', 'y', 'b']
col_list = [base_col_list[i] for i in range(len(picks))]
(peak, dyn, stat, deltad) = _evaluate_picks(cur_dat, picks)
# fig.show()
pnt_peak = ax.plot(peak[0], peak[1], 's',
color='r',
label='peak')
pnt_dyn = ax.plot(dyn[0], dyn[1], 's',
color='y',
label='dyn')
pnt_stat = ax.plot(stat[0], stat[1], 's',
color='b',
label='stat')
ax.legend()
# Button callbacks
btn = dict()
btn['peak'] = (
lambda
arg1=cur_dat,
arg2=var,
kw1=['peak'],
kw2=picks:
_manual_pick(arg1, arg2, pick_list=kw1, picks=kw2)
)
btn['dyn'] = (
lambda
arg1=cur_dat,
arg2=var,
kw1=['dynamic'],
kw2=picks:
_manual_pick(arg1, arg2, pick_list=kw1, picks=kw2)
)
btn['stat'] = (
lambda
arg1=cur_dat,
arg2=var,
kw1=['static'],
kw2=picks:
_manual_pick(arg1, arg2, pick_list=kw1, picks=kw2)
)
def _cb_peak(event):
nonlocal pnt_peak
btn['peak']()
(peak, dyn, stat, deltad) = _evaluate_picks(cur_dat, picks)
for p in pnt_peak:
p.set_xdata(peak[0])
p.set_ydata(peak[1])
def _cb_dyn(event):
nonlocal pnt_dyn
btn['dyn']()
(peak, dyn, stat, deltad) = _evaluate_picks(cur_dat, picks)
for p in pnt_dyn:
p.set_xdata(dyn[0])
p.set_ydata(dyn[1])
def _cb_stat(event):
nonlocal pnt_stat
btn['stat']()
(peak, dyn, stat, deltad) = _evaluate_picks(cur_dat, picks)
for p in pnt_stat:
p.set_xdata(stat[0])
p.set_ydata(stat[1])
# Add Buttons
axpeak = plt.axes([0.5, 0.05, 0.1, 0.075])
axdyn = plt.axes([0.6, 0.05, 0.1, 0.075])
axstat = plt.axes([0.7, 0.05, 0.1, 0.075])
axok = plt.axes([0.81, 0.05, 0.1, 0.075])
bpeak = mpl.widgets.Button(axpeak, 'Peak',
color='xkcd:red',
hovercolor='xkcd:rose')
bdyn = mpl.widgets.Button(axdyn, 'Dynamic',
color='xkcd:yellow',
hovercolor='xkcd:mustard')
bstat = mpl.widgets.Button(axstat, 'Static',
color='xkcd:blue',
hovercolor='xkcd:teal')
bok = mpl.widgets.Button(axok, 'All ok!')
bpeak.on_clicked(_cb_peak)
bdyn.on_clicked(_cb_dyn)
bstat.on_clicked(_cb_stat)
bok.on_clicked(_switch)
# Keeps plot alive and stops further execution
while not ALL_OK:
if plt.get_fignums():
plt.draw()
plt.pause(.01)
else:
sys.exit(0)
return (fig, ax)
if not review:
picks = _auto_pick(cur_dat, var)
elif review == 'auto':
picks = _auto_pick(cur_dat, var)
(fig, ax) = _pick_base_plot(cur_dat, var)
(fig, ax) = _review_picks(fig, ax, picks)
plt.close()
elif review == 'manual':
picks = _manual_pick(cur_dat, var)
else:
print('No revision set.')
pass
(peak, dyn, stat, deltad) = _evaluate_picks(cur_dat, picks)
# Further calculations for output
normal = int(np.mean(cur_dat['normalstress'])/var['prec'])*var['prec'] normal = int(np.mean(cur_dat['normalstress'])/var['prec'])*var['prec']
# Calculate relative weakening (after Ritter et al., 2016) # Calculate relative weakening (after Ritter et al., 2016)
weak = 1-(dyn[1]/peak[1]) weak = 1-(dyn[1]/peak[1])
weak_p = (peak[1]/dyn[1])-1 weak_p = (peak[1]/dyn[1])-1
# Calculate dilation for peak
i_d1 = np.argmin(cur_dat['liddispl'][:i_peak])
d1 = cur_dat['liddispl'][i_d1]
i_d2 = np.argmax(cur_dat['liddispl'][i_peak:end_1])
d2 = cur_dat['liddispl'][i_d2]
deltad = d2-d1
return (normal, peak, dyn, stat, deltad, weak, weak_p) return (normal, peak, dyn, stat, deltad, weak, weak_p)
...@@ -138,14 +384,16 @@ def rst_analstd(x, y): ...@@ -138,14 +384,16 @@ def rst_analstd(x, y):
# %%======================BOOTSTRAP LINEAR REGRESSION========================== # %%======================BOOTSTRAP LINEAR REGRESSION==========================
def fit_bootstrap(p0, datax, datay, function=None, def fit_bootstrap(p0, datax, datay, function=None,
yerr_systematic=0.0, nsigma=2, nsets=100): yerr_systematic=0.0, nsigma=2, nsets=100):
# Does a bootstrap fit of datax and datay with the initial parameters p0. """
# As a standard the function used is a linear function Does a bootstrap fit of datax and datay with the initial parameters p0.
# You can choose the confidence interval that you want for your As a standard the function used is a linear function
# parameter estimates: You can choose the confidence interval that you want for your
# 1sigma corresponds to 68.3% confidence interval parameter estimates:
# 2sigma corresponds to 95.44% confidence interval 1sigma corresponds to 68.3% confidence interval
# ---- Helper function which gives a linear function ---- 2sigma corresponds to 95.44% confidence interval
"""
def _poly1(x, a, b): def _poly1(x, a, b):
"""Linear Function"""
return (a*x+b) return (a*x+b)
if ~function: if ~function:
...@@ -348,7 +596,7 @@ def plotts(path, name, exp_data, normal_stress): ...@@ -348,7 +596,7 @@ def plotts(path, name, exp_data, normal_stress):
label=cur_norm) label=cur_norm)
# Extract labels, sort them and only display once per repetition # Extract labels, sort them and only display once per repetition
handles, labels = ax3.get_legend_handles_labels() handles, labels = ax3.get_legend_handles_labels()
labels_num = [int(l) for l in labels] labels_num = [int(float(l)) for l in labels]
sort_ind = np.argsort(labels_num) sort_ind = np.argsort(labels_num)
handles = np.take_along_axis(np.array(handles), sort_ind, axis=0) handles = np.take_along_axis(np.array(handles), sort_ind, axis=0)
labels = np.take_along_axis(np.array(labels), sort_ind, axis=0) labels = np.take_along_axis(np.array(labels), sort_ind, axis=0)
......
...@@ -91,7 +91,7 @@ if rst == 1: ...@@ -91,7 +91,7 @@ if rst == 1:
rfnc.plotstd(path_out, projectname, strength, fric_std) rfnc.plotstd(path_out, projectname, strength, fric_std)
rfnc.plothist(path_out, projectname, strength, data_mut) rfnc.plothist(path_out, projectname, strength, data_mut)
print('==================================================================') print('==================================================================')
print('>>> Fiction data plotted') print('>>> Friction data plotted')
print('==================================================================') print('==================================================================')
if plot_ts == 1: if plot_ts == 1:
rfnc.plotts(path_out, projectname, exp_data, normal_stress) rfnc.plotts(path_out, projectname, exp_data, normal_stress)
......
#!/usr/bin/env python3 #!/usr/bin/env python3
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
''' """
RST_pick_GUI.py RST_pick_GUI.py
GUI based approach for RSTpicking GUI based approach for RSTpicking
...@@ -9,7 +9,7 @@ GUI based approach for RSTpicking ...@@ -9,7 +9,7 @@ GUI based approach for RSTpicking
__AUTHOR__: Michael Rudolf __AUTHOR__: Michael Rudolf
__DATE__: 20-Feb-2019 __DATE__: 20-Feb-2019
''' """
import configparser import configparser
import tkinter as tk import tkinter as tk
...@@ -18,6 +18,11 @@ from tkinter import messagebox ...@@ -18,6 +18,11 @@ from tkinter import messagebox
import re import re
import os import os
import logging import logging
import RST_Func as rfnc
import warnings
import sys
warnings.filterwarnings("ignore")
log = logging.getLogger() log = logging.getLogger()
log.setLevel(logging.INFO) log.setLevel(logging.INFO)
...@@ -33,12 +38,17 @@ class RST_pick_GUI(tk.Tk): ...@@ -33,12 +38,17 @@ class RST_pick_GUI(tk.Tk):
self.path_in = tk.StringVar() self.path_in = tk.StringVar()
self.path_out = tk.StringVar() self.path_out = tk.StringVar()
self.path_cfg = tk.StringVar() self.path_cfg = tk.StringVar()
self.proc = tk.StringVar()
self.plot_ts = tk.IntVar() self.plot_ts = tk.IntVar()
self.save_ts = tk.IntVar() self.save_ts = tk.IntVar()
self.rev_pick = tk.IntVar()
self.quit_onprc = tk.IntVar()
self.rst = tk.IntVar() self.rst = tk.IntVar()
self.cfg = configparser.ConfigParser() self.cfg = configparser.ConfigParser()
self.cfg.optionxform = str
self.path_cfg.set('default.ini') self.path_cfg.set('default.ini')
self.params = dict() self.params = dict()
self.protocol('WM_DELETE_WINDOW', self.closing_menu)
# Initialize Object # Initialize Object
self.initalize() self.initalize()
...@@ -91,25 +101,36 @@ class RST_pick_GUI(tk.Tk): ...@@ -91,25 +101,36 @@ class RST_pick_GUI(tk.Tk):
anchor='w', anchor='w',
font=('Helvetica', 12, 'bold')) font=('Helvetica', 12, 'bold'))
self.label_options.grid(column=0, row=3, columnspan=3, sticky='EW') self.label_options.grid(column=0, row=3, columnspan=3, sticky='EW')
# Plot Data
self.opt_plot = tk.Checkbutton(self,
text='Plot time series',
anchor='w',
variable=self.plot_ts)
self.opt_plot.grid(column=0, row=4, sticky='EW')
# Pick Data # Pick Data
self.opt_pick = tk.Checkbutton(self, self.opt_pick = tk.Checkbutton(self,
text='Automatically pick data', text='Automatic Picking',
anchor='w', anchor='w',
variable=self.rst) variable=self.rst)
self.opt_pick.grid(column=0, row=5, sticky='EW') self.opt_pick.grid(column=0, row=4, sticky='EW')
# Save Data # Save Data
self.opt_save = tk.Checkbutton(self, self.opt_save = tk.Checkbutton(self,
text='Save picked data', text='Save Data',
anchor='w', anchor='w',
variable=self.save_ts) variable=self.save_ts)
self.opt_save.grid(column=0, row=6, sticky='EW') self.opt_save.grid(column=0, row=5, sticky='EW')
# Plot Data
self.opt_plot = tk.Checkbutton(self,
text='Plot time series',
anchor='w',
variable=self.plot_ts)
self.opt_plot.grid(column=0, row=6, sticky='EW')
# Review Picking
self.opt_revpick = tk.Checkbutton(self,
text='Review picking during run',
anchor='w',
variable=self.rev_pick)
self.opt_revpick.grid(column=1, row=4, sticky='EW')
# Review Picking
self.opt_quit = tk.Checkbutton(self,
text='Quit after processing',
anchor='w',
variable=self.quit_onprc)
self.opt_quit.grid(column=1, row=5, sticky='EW')
# More Options # More Options
self.button_options = tk.Button(self, self.button_options = tk.Button(self,
text='More options...') text='More options...')
...@@ -126,8 +147,8 @@ class RST_pick_GUI(tk.Tk): ...@@ -126,8 +147,8 @@ class RST_pick_GUI(tk.Tk):
self.make_dpi_aware() self.make_dpi_aware()
def get_HWND_dpi(self): def get_HWND_dpi(self):
# Detects high dpi displays and rescales gui in Windows """Detects high dpi displays and rescales gui in Windows
# Adapted from the user 'dingles at stack-overflow' Adapted from the user 'dingles at stack-overflow"""
if os.name == "nt": if os.name == "nt":
from ctypes import windll, pointer, wintypes from ctypes import windll, pointer, wintypes
try: try:
...@@ -158,7 +179,7 @@ class RST_pick_GUI(tk.Tk): ...@@ -158,7 +179,7 @@ class RST_pick_GUI(tk.Tk):
return None, None, 1 # What to do for other OSs? return None, None, 1 # What to do for other OSs?
def TkGeometryScale(s, cvtfunc): def TkGeometryScale(s, cvtfunc):
# Adapted from the user 'dingles at stack-overflow' """Adapted from the user 'dingles at stack-overflow"""
# format "WxH+X+Y" # format "WxH+X+Y"
patt = r"(?P<W>\d+)x(?P<H>\d+)\+(?P<X>\d+)\+(?P<Y>\d+)" patt = r"(?P<W>\d+)x(?P<H>\d+)\+(?P<X>\d+)\+(?P<Y>\d+)"
R = re.compile(patt).search(s) R = re.compile(patt).search(s)
...@@ -169,18 +190,28 @@ class RST_pick_GUI(tk.Tk): ...@@ -169,18 +190,28 @@ class RST_pick_GUI(tk.Tk):
return G return G
def make_dpi_aware(self): def make_dpi_aware(self):
# Adapted from the user 'dingles' at stack-overflow.com """Adapted from the user 'dingles' at stack-overflow.com"""
self.DPI_X, self.DPI_Y, self.DPI_scaling = self.get_HWND_dpi() self.DPI_X, self.DPI_Y, self.DPI_scaling = self.get_HWND_dpi()
self.TkScale = lambda v: int(float(v) * self.DPI_scaling) self.TkScale = lambda v: int(float(v) * self.DPI_scaling)
self.TkGeometryScale = lambda s: self.TkGeometryScale(s, self.TkScale) self.TkGeometryScale = lambda s: self.TkGeometryScale(s, self.TkScale)
def resize_all_elements(self): def resize_all_elements(self):
# Resizes all elements according to the DPI scaling of the window