-
-
Notifications
You must be signed in to change notification settings - Fork 96
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #39 from Frix-x/develop
v.2.5.0
- Loading branch information
Showing
19 changed files
with
645 additions
and
565 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
#!/usr/bin/env python3 | ||
|
||
# Common functions for the Shake&Tune package | ||
# Written by Frix_x#0161 # | ||
|
||
import math | ||
import os, sys | ||
from importlib import import_module | ||
from pathlib import Path | ||
import numpy as np | ||
from scipy.signal import spectrogram | ||
from git import GitCommandError, Repo | ||
|
||
|
||
def parse_log(logname): | ||
with open(logname) as f: | ||
for header in f: | ||
if not header.startswith('#'): | ||
break | ||
if not header.startswith('freq,psd_x,psd_y,psd_z,psd_xyz'): | ||
# Raw accelerometer data | ||
return np.loadtxt(logname, comments='#', delimiter=',') | ||
# Power spectral density data or shaper calibration data | ||
raise ValueError("File %s does not contain raw accelerometer data and therefore " | ||
"is not supported by Shake&Tune. Please use the official Klipper " | ||
"script to process it instead." % (logname,)) | ||
|
||
|
||
def setup_klipper_import(kdir): | ||
kdir = os.path.expanduser(kdir) | ||
sys.path.append(os.path.join(kdir, 'klippy')) | ||
return import_module('.shaper_calibrate', 'extras') | ||
|
||
|
||
# This is used to print the current S&T version on top of the png graph file | ||
def get_git_version(): | ||
try: | ||
# Get the absolute path of the script, resolving any symlinks | ||
# Then get 2 times to parent dir to be at the git root folder | ||
script_path = Path(__file__).resolve() | ||
repo_path = script_path.parents[2] | ||
repo = Repo(repo_path) | ||
|
||
try: | ||
version = repo.git.describe('--tags') | ||
except GitCommandError: | ||
# If no tag is found, use the simplified commit SHA instead | ||
version = repo.head.commit.hexsha[:7] | ||
return version | ||
|
||
except Exception as e: | ||
return None | ||
|
||
|
||
# This is Klipper's spectrogram generation function adapted to use Scipy | ||
def compute_spectrogram(data): | ||
N = data.shape[0] | ||
Fs = N / (data[-1, 0] - data[0, 0]) | ||
# Round up to a power of 2 for faster FFT | ||
M = 1 << int(.5 * Fs - 1).bit_length() | ||
window = np.kaiser(M, 6.) | ||
|
||
def _specgram(x): | ||
return spectrogram(x, fs=Fs, window=window, nperseg=M, noverlap=M//2, | ||
detrend='constant', scaling='density', mode='psd') | ||
|
||
d = {'x': data[:, 1], 'y': data[:, 2], 'z': data[:, 3]} | ||
f, t, pdata = _specgram(d['x']) | ||
for axis in 'yz': | ||
pdata += _specgram(d[axis])[2] | ||
return pdata, t, f | ||
|
||
|
||
# Compute natural resonant frequency and damping ratio by using the half power bandwidth method with interpolated frequencies | ||
def compute_mechanical_parameters(psd, freqs): | ||
max_power_index = np.argmax(psd) | ||
fr = freqs[max_power_index] | ||
max_power = psd[max_power_index] | ||
|
||
half_power = max_power / math.sqrt(2) | ||
idx_below = np.where(psd[:max_power_index] <= half_power)[0][-1] | ||
idx_above = np.where(psd[max_power_index:] <= half_power)[0][0] + max_power_index | ||
freq_below_half_power = freqs[idx_below] + (half_power - psd[idx_below]) * (freqs[idx_below + 1] - freqs[idx_below]) / (psd[idx_below + 1] - psd[idx_below]) | ||
freq_above_half_power = freqs[idx_above - 1] + (half_power - psd[idx_above - 1]) * (freqs[idx_above] - freqs[idx_above - 1]) / (psd[idx_above] - psd[idx_above - 1]) | ||
|
||
bandwidth = freq_above_half_power - freq_below_half_power | ||
zeta = bandwidth / (2 * fr) | ||
|
||
return fr, zeta, max_power_index | ||
|
||
# This find all the peaks in a curve by looking at when the derivative term goes from positive to negative | ||
# Then only the peaks found above a threshold are kept to avoid capturing peaks in the low amplitude noise of a signal | ||
def detect_peaks(data, indices, detection_threshold, relative_height_threshold=None, window_size=5, vicinity=3): | ||
# Smooth the curve using a moving average to avoid catching peaks everywhere in noisy signals | ||
kernel = np.ones(window_size) / window_size | ||
smoothed_data = np.convolve(data, kernel, mode='valid') | ||
mean_pad = [np.mean(data[:window_size])] * (window_size // 2) | ||
smoothed_data = np.concatenate((mean_pad, smoothed_data)) | ||
|
||
# Find peaks on the smoothed curve | ||
smoothed_peaks = np.where((smoothed_data[:-2] < smoothed_data[1:-1]) & (smoothed_data[1:-1] > smoothed_data[2:]))[0] + 1 | ||
smoothed_peaks = smoothed_peaks[smoothed_data[smoothed_peaks] > detection_threshold] | ||
|
||
# Additional validation for peaks based on relative height | ||
valid_peaks = smoothed_peaks | ||
if relative_height_threshold is not None: | ||
valid_peaks = [] | ||
for peak in smoothed_peaks: | ||
peak_height = smoothed_data[peak] - np.min(smoothed_data[max(0, peak-vicinity):min(len(smoothed_data), peak+vicinity+1)]) | ||
if peak_height > relative_height_threshold * smoothed_data[peak]: | ||
valid_peaks.append(peak) | ||
|
||
# Refine peak positions on the original curve | ||
refined_peaks = [] | ||
for peak in valid_peaks: | ||
local_max = peak + np.argmax(data[max(0, peak-vicinity):min(len(data), peak+vicinity+1)]) - vicinity | ||
refined_peaks.append(local_max) | ||
|
||
num_peaks = len(refined_peaks) | ||
|
||
return num_peaks, np.array(refined_peaks), indices[refined_peaks] |
Oops, something went wrong.