"""Additional methods which did not earn its own space in the main methods.
Maybe because they are more general and made for higher purposes.
"""
import warnings
from math import atan2
import numpy as np
import pandas as pd
import tensorflow.keras.backend as K
from scipy import ndimage
from sklearn.metrics import r2_score
from tensorflow.keras.callbacks import LearningRateScheduler
[docs]
def mish(x):
return x * K.tanh(K.softplus(x))
[docs]
def step_decay_schedule(initial_lr=1e-4, decay_factor=0.9, num_epochs=50):
def schedule(epoch):
return initial_lr * (1 - epoch / num_epochs) ** decay_factor
return LearningRateScheduler(schedule)
[docs]
def euclidean_distance(y_true, y_pred):
return np.sqrt(np.sum(np.square(y_true - y_pred), axis=-1))
[docs]
def angle_between_points(y_true, y_pred):
dx = y_pred[:, 0] - y_true[:, 0]
dy = y_pred[:, 1] - y_true[:, 1]
rads = np.array([atan2(dyy, dxx) for dyy, dxx in zip(dy, dx)])
rads += np.pi
return rads
[docs]
def get_model_scores(real_y, pred_y, euc_pred, **args):
try:
(agg_scores, subtr_scores) = quantify_predictions(real_y, pred_y, euc_pred, percentile_cut=None)
(agg_scores_pct, subtr_scores_pct) = quantify_predictions(real_y, pred_y, euc_pred, **args)
except ValueError:
# Participant has only NaNs or no data, return empty dataframes
agg_scores, subtr_scores, agg_scores_pct, subtr_scores_pct = (
np.random.rand(9) * np.NaN,
np.random.rand(9) * np.NaN,
np.random.rand(9) * np.NaN,
np.random.rand(9) * np.NaN,
) # 9 return parameters
df_scores = pd.DataFrame(
[agg_scores, subtr_scores, agg_scores_pct, subtr_scores_pct],
index=["Default", "Default subTR", "Refined", "Refined subTR"],
)
df_scores.columns = pd.MultiIndex.from_tuples(
(
("Pearson", "X"),
("Pearson", "Y"),
("Pearson", "Mean"),
("R^2-Score", "X"),
("R^2-Score", "Y"),
("R^2-Score", "Mean"),
("Eucl. Error", "Mean"),
("Eucl. Error", "Median"),
("Eucl. Error", "Std"),
)
)
return df_scores
[docs]
def quantify_predictions(y_true, y_pred, euc_pred, subtr_functor=np.median, percentile_cut=None):
# Take care of NaN in XYs
nan_indices = np.any(np.isnan(y_true), axis=(1, 2))
y_true = y_true[~nan_indices, ...]
y_pred = y_pred[~nan_indices, ...]
euc_pred = euc_pred[~nan_indices, ...]
if y_true.size < 1:
raise ValueError("Participant has no ground truth data, nothing to quantify")
# Aggregate across subTR values
y_true_agg = subtr_functor(y_true, axis=1)
y_pred_agg = subtr_functor(y_pred, axis=1)
euc_pred_agg = subtr_functor(euc_pred, axis=1)
# Use flattened array for subTR comparison
y_true_flat = np.reshape(y_true, (y_true.shape[0] * y_true.shape[1], -1))
y_pred_flat = np.reshape(y_pred, (y_pred.shape[0] * y_pred.shape[1], -1))
euc_pred_flat = np.reshape(euc_pred, (euc_pred.shape[0] * euc_pred.shape[1], -1))
agg_scores = calculate_scores(y_true_agg, y_pred_agg, euc_pred_agg, percentile_cut=percentile_cut)
flat_scores = calculate_scores(y_true_flat, y_pred_flat, euc_pred_flat[..., 0], percentile_cut=percentile_cut)
return (agg_scores, flat_scores)
[docs]
def calculate_scores(y_true, y_pred, euc_pred, percentile_cut=None):
if percentile_cut is not None:
bad_indices = euc_pred > np.percentile(euc_pred, percentile_cut)
y_true = y_true[~bad_indices, ...]
y_pred = y_pred[~bad_indices, ...]
euc_pred = euc_pred[~bad_indices, ...]
pearson_x = np.corrcoef(y_true[:, 0], y_pred[:, 0])[0, 1]
pearson_y = np.corrcoef(y_true[:, 1], y_pred[:, 1])[0, 1]
pearson_mean = (pearson_x + pearson_y) / 2
r2_x = r2_score(y_true[:, 0], y_pred[:, 0])
r2_y = r2_score(y_true[:, 1], y_pred[:, 1])
r2_mean = (r2_x + r2_y) / 2
euc = euclidean_distance(y_true, y_pred)
return (pearson_x, pearson_y, pearson_mean, r2_x, r2_y, r2_mean, np.mean(euc), np.median(euc), np.std(euc))
[docs]
def smooth_signal(signal, N):
"""
Smooth by convolving a filter with 1/N.
Parameters
----------
signal : array_like
Signal to be smoothed
N : int
smoothing_factor
Returns
-------
signal : array_like
Smoothed signal
"""
# Preprocess edges
signal = np.concatenate([signal[0:N], signal, signal[-N:]])
# Convolve
signal = np.convolve(signal, np.ones((N,)) / N, mode="same")
# Postprocess edges
signal = signal[N:-N]
return signal
[docs]
class color:
# From https://stackoverflow.com/questions/8924173/how-do-i-print-bold-text-in-python
PURPLE = "\033[95m"
CYAN = "\033[96m"
DARKCYAN = "\033[36m"
BLUE = "\033[94m"
GREEN = "\033[92m"
YELLOW = "\033[93m"
RED = "\033[91m"
BOLD = "\033[1m"
UNDERLINE = "\033[4m"
END = "\033[0m"
# --------------------------------------------------------------------------------
# ------------------------Command Line Options------------------------------------
# --------------------------------------------------------------------------------
[docs]
class Arg:
def __init__(self, *args, **kwargs):
self.cli = args
self.kwargs = kwargs
CLI_OPTIONS = {
"verbosity": Arg(
"-v",
"--verbose",
help="Verbosity level",
default=0,
),
"gpu_id": Arg("--gpu_id", help="Which GPU to use for training", metavar="gpu_id", default=""),
"dataset_path": Arg(
"--dataset_path",
help="Path to dataset. Base folder is also dataset name.",
metavar="dataset_path",
default="./",
),
"weights_path": Arg(
"--weights_path", help="Path to where weights should be stored.", metavar="weights_path", default="./weights/"
),
"datasets": Arg("--datasets", help="If given only train subset of all datasets.", metavar="datasets", default=None),
}