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
# %%=================FUNCTION================================================
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import codecs
import csv
import os
import nptdms
import matplotlib.pyplot as plt
from scipy import stats
from scipy.signal import savgol_filter
from scipy import optimize
import time
import sys
# %%==============CONVERSION===================================================
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
file_convert = {'.asc': _readasc,
'.dat': _readdat,
......@@ -45,50 +47,294 @@ def convert(path, file, var):
# %%==================EVALUATION SHEAR CURVE DATA==============================
def eval_shearstress(cur_dat, var):
velnoise, stressnoise = var['velnoise'], var['stressnoise']
# Searching for velocity changes to define data range
vel_above = np.nonzero(cur_dat['velocity'] > var['velnoise'])
switch = np.nonzero(np.diff(vel_above) > 10)
start_1 = vel_above[0][0]
end_1 = start_1+switch[1][0]
start_2 = end_1+np.max(np.diff(vel_above))
end_2 = vel_above[0][-1]
# smoothing function: 1st#: window size, 2nd#: polynomial order
shearstress_smooth = savgol_filter(
def eval_shearstress(cur_dat, var, review=None):
"""
Evaluates the shear stress data and picks the needed data points.
If the review option is set, one may revise individual points that have
been chosen by the automatic picking algorithm, or skip autopicking.
Possible review options
-----------------------
None (default):
automatic picking without revision
'auto':
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'],
var['smoothwindow'],
int(float(var['smoothwindow'])),
3)
# Peak friction
i_peak = np.argmax(shearstress_smooth[start_1:end_1])+start_1
tau_peak = shearstress_smooth[i_peak]
# Dynamic friction
i_dyn = np.argmin(shearstress_smooth[i_peak:end_1])+i_peak
tau_dyn = shearstress_smooth[i_dyn]
# Static friction (reactivation)
i_stat = np.argmax(shearstress_smooth[start_2:end_2])+start_2
tau_stat = shearstress_smooth[i_stat]
peak = [cur_dat['displacement'][i_peak], tau_peak]
dyn = [cur_dat['displacement'][i_dyn], tau_dyn]
stat = [cur_dat['displacement'][i_stat], tau_stat]
# Processing
velnoise, stressnoise = var['velnoise'], var['stressnoise']
# Searching for velocity changes to define data range
vel_above = np.nonzero(cur_dat['velocity'] > var['velnoise'])
switch = np.nonzero(np.diff(vel_above) > 10)
start_1 = vel_above[0][0]
end_1 = start_1+switch[1][0]-10
start_2 = end_1+np.max(np.diff(vel_above))+10
end_2 = vel_above[0][-1]
# Peak friction
i_peak = np.argmax(shear_smth[start_1:end_1])+start_1
tau_peak = shear_smth[i_peak]
# Dynamic friction
i_dyn = np.argmin(shear_smth[i_peak:end_1])+i_peak
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']
# Calculate relative weakening (after Ritter et al., 2016)
weak = 1-(dyn[1]/peak[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)
......@@ -138,14 +384,16 @@ def rst_analstd(x, y):
# %%======================BOOTSTRAP LINEAR REGRESSION==========================
def fit_bootstrap(p0, datax, datay, function=None,
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
# You can choose the confidence interval that you want for your
# parameter estimates:
# 1sigma corresponds to 68.3% confidence interval
# 2sigma corresponds to 95.44% confidence interval
# ---- Helper function which gives a linear function ----
"""
Does a bootstrap fit of datax and datay with the initial parameters p0.
As a standard the function used is a linear function
You can choose the confidence interval that you want for your
parameter estimates:
1sigma corresponds to 68.3% confidence interval
2sigma corresponds to 95.44% confidence interval
"""
def _poly1(x, a, b):
"""Linear Function"""
return (a*x+b)
if ~function:
......@@ -348,7 +596,7 @@ def plotts(path, name, exp_data, normal_stress):
label=cur_norm)
# Extract labels, sort them and only display once per repetition
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)
handles = np.take_along_axis(np.array(handles), sort_ind, axis=0)
labels = np.take_along_axis(np.array(labels), sort_ind, axis=0)
......
......@@ -91,7 +91,7 @@ if rst == 1:
rfnc.plotstd(path_out, projectname, strength, fric_std)
rfnc.plothist(path_out, projectname, strength, data_mut)
print('==================================================================')
print('>>> Fiction data plotted')
print('>>> Friction data plotted')
print('==================================================================')
if plot_ts == 1:
rfnc.plotts(path_out, projectname, exp_data, normal_stress)
......
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
"""
RST_pick_GUI.py
GUI based approach for RSTpicking
......@@ -9,7 +9,7 @@ GUI based approach for RSTpicking
__AUTHOR__: Michael Rudolf
__DATE__: 20-Feb-2019
'''
"""
import configparser
import tkinter as tk
......@@ -18,6 +18,11 @@ from tkinter import messagebox
import re
import os
import logging
import RST_Func as rfnc
import warnings
import sys
warnings.filterwarnings("ignore")
log = logging.getLogger()
log.setLevel(logging.INFO)
......@@ -33,12 +38,17 @@ class RST_pick_GUI(tk.Tk):
self.path_in = tk.StringVar()
self.path_out = tk.StringVar()
self.path_cfg = tk.StringVar()
self.proc = tk.StringVar()
self.plot_ts = tk.IntVar()
self.save_ts = tk.IntVar()
self.rev_pick = tk.IntVar()
self.quit_onprc = tk.IntVar()
self.rst = tk.IntVar()
self.cfg = configparser.ConfigParser()
self.cfg.optionxform = str
self.path_cfg.set('default.ini')
self.params = dict()
self.protocol('WM_DELETE_WINDOW', self.closing_menu)
# Initialize Object
self.initalize()
......@@ -91,25 +101,36 @@ class RST_pick_GUI(tk.Tk):
anchor='w',
font=('Helvetica', 12, 'bold'))
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
self.opt_pick = tk.Checkbutton(self,
text='Automatically pick data',
text='Automatic Picking',
anchor='w',
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
self.opt_save = tk.Checkbutton(self,
text='Save picked data',
text='Save Data',
anchor='w',
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
self.button_options = tk.Button(self,
text='More options...')
......@@ -126,8 +147,8 @@ class RST_pick_GUI(tk.Tk):
self.make_dpi_aware()
def get_HWND_dpi(self):
# Detects high dpi displays and rescales gui in Windows
# Adapted from the user 'dingles at stack-overflow'
"""Detects high dpi displays and rescales gui in Windows
Adapted from the user 'dingles at stack-overflow"""
if os.name == "nt":
from ctypes import windll, pointer, wintypes
try:
......@@ -158,7 +179,7 @@ class RST_pick_GUI(tk.Tk):
return None, None, 1 # What to do for other OSs?
def TkGeometryScale(s, cvtfunc):
# Adapted from the user 'dingles at stack-overflow'
"""Adapted from the user 'dingles at stack-overflow"""
# format "WxH+X+Y"
patt = r"(?P<W>\d+)x(?P<H>\d+)\+(?P<X>\d+)\+(?P<Y>\d+)"
R = re.compile(patt).search(s)
......@@ -169,18 +190,28 @@ class RST_pick_GUI(tk.Tk):
return G
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.TkScale = lambda v: int(float(v) * self.DPI_scaling)
self.TkGeometryScale = lambda s: self.TkGeometryScale(s, self.TkScale)
def resize_all_elements(self):
# Resizes all elements according to the DPI scaling of the window
"""
Resizes all elements according to the DPI scaling of the window
(WIP)
-----
"""
pass
def plot_window(self, event):
"""Shows a plot window"""
plot_win = tk.Toplevel(self)
plot_win.wm_title('Plot Window')
return
# == CALLBACKS ==
def folder_browser(self, event):
"""Callback for folderbrowser."""
f_path = filedialog.askdirectory()
if event.widget._name == '!button': # Input path
self.entry_input.delete(0, 'end')
......@@ -192,12 +223,100 @@ class RST_pick_GUI(tk.Tk):
print(event.widget._name)
def start_processing(self, event):
print('Start Processing')
for item in self.params.items():
print(item)
"""Initiates Processing of data"""
# Create Var dictionary:
Vars = dict()
for item in self.params:
Vars[item] = float(self.params[item].get())
# Other Variables
path_in = self.path_in.get()
path_out = self.path_out.get()
projectname = self.projectname.get()
# Generate a list containing all files
supported_filetypes = ['.asc', '.dat', '.tdms']
file_list = [
f for f in os.listdir(path_in)
if os.path.splitext(f)[-1] in supported_filetypes
]
# Data is stored as a list of dictionaries containing the data
exp_data = [rfnc.convert(path_in, f, Vars) for f in file_list]
if self.rst.get() == 1:
# Evaluate all data sets and store results in a list
if self.rev_pick.get() == 1:
eval_data = [rfnc.eval_shearstress(cur_dat, Vars,
review='auto')
for cur_dat in exp_data]
else:
eval_data = [rfnc.eval_shearstress(cur_dat, Vars)
for cur_dat in exp_data]
print('Automatic analysis successful')
else:
print('Automatic analysis skipped, initiating manual picking')
eval_data = [rfnc.eval_shearstress(cur_dat, Vars, review='manual')
for cur_dat in exp_data]
# Create lists of parameters for analysis
normal_stress = [ev[0] for ev in eval_data]
peak_stress = [ev[1][1] for ev in eval_data]
dyn_stress = [ev[2][1] for ev in eval_data]
stat_stress = [ev[3][1] for ev in eval_data]
dilation = [ev[4] for ev in eval_data]
weak = [ev[5] for ev in eval_data]
weak_p = [ev[6] for ev in eval_data]
# ==========ANALYSIS OF FRICTION COEFFICIENTS AND COHESIONS=======
# ===========Analysis 1: Mutual linear regression=================
peakfric_mut, peakdata_mut = rfnc.rst_analmut(normal_stress,
peak_stress)
dynfric_mut, dyndata_mut = rfnc.rst_analmut(normal_stress,
dyn_stress)
reactfric_mut, reactdata_mut = rfnc.rst_analmut(normal_stress,
stat_stress)
# ===========ANALYSIS 2: Standard regression of all data pairs ==