Lot of refactoring, memory and speed optimizations for all the graphs scripts
This commit is contained in:
@@ -12,18 +12,17 @@
|
||||
#####################################################################
|
||||
|
||||
import optparse, matplotlib, sys, importlib, os
|
||||
from datetime import datetime
|
||||
from collections import namedtuple
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
import matplotlib.font_manager, matplotlib.ticker, matplotlib.colors
|
||||
from scipy.interpolate import griddata
|
||||
import matplotlib.pyplot, matplotlib.dates, matplotlib.font_manager
|
||||
import matplotlib.ticker, matplotlib.gridspec, matplotlib.colors
|
||||
import matplotlib.patches
|
||||
from datetime import datetime
|
||||
|
||||
matplotlib.use('Agg')
|
||||
|
||||
from locale_utils import set_locale, print_with_c_locale
|
||||
from common_func import compute_spectrogram, detect_peaks
|
||||
from common_func import compute_spectrogram, detect_peaks, get_git_version, parse_log, setup_klipper_import
|
||||
|
||||
|
||||
ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" # For paired peaks names
|
||||
@@ -141,14 +140,14 @@ def interpolate_2d(target_x, target_y, source_x, source_y, source_data):
|
||||
# Main logic function to combine two similar spectrogram - ie. from both belts paths - by substracting signals in order to create
|
||||
# a new composite spectrogram. This result of a divergent but mostly centered new spectrogram (center will be white) with some colored zones
|
||||
# highlighting differences in the belts paths. The summative spectrogram is used for the MHI calculation.
|
||||
def combined_spectrogram(data1, data2):
|
||||
def compute_combined_spectrogram(data1, data2):
|
||||
pdata1, bins1, t1 = compute_spectrogram(data1)
|
||||
pdata2, bins2, t2 = compute_spectrogram(data2)
|
||||
|
||||
# Interpolate the spectrograms
|
||||
pdata2_interpolated = interpolate_2d(bins1, t1, bins2, t2, pdata2)
|
||||
|
||||
# Cobine them in two form: a summed diff for the MHI computation and a diverging diff for the spectrogram colors
|
||||
# Combine them in two form: a summed diff for the MHI computation and a diverging diff for the spectrogram colors
|
||||
combined_sum = np.abs(pdata1 - pdata2_interpolated)
|
||||
combined_divergent = pdata1 - pdata2_interpolated
|
||||
|
||||
@@ -184,26 +183,27 @@ def compute_mhi(combined_data, similarity_coefficient, num_unpaired_peaks):
|
||||
|
||||
|
||||
# LUT to transform the MHI into a textual value easy to understand for the users of the script
|
||||
def mhi_lut(mhi):
|
||||
if 0 <= mhi <= 30:
|
||||
return "Excellent mechanical health"
|
||||
elif 30 < mhi <= 45:
|
||||
return "Good mechanical health"
|
||||
elif 45 < mhi <= 55:
|
||||
return "Acceptable mechanical health"
|
||||
elif 55 < mhi <= 70:
|
||||
return "Potential signs of a mechanical issue"
|
||||
elif 70 < mhi <= 85:
|
||||
return "Likely a mechanical issue"
|
||||
elif 85 < mhi <= 100:
|
||||
return "Mechanical issue detected"
|
||||
def mhi_lut(mhi):
|
||||
ranges = [
|
||||
(0, 30, "Excellent mechanical health"),
|
||||
(30, 45, "Good mechanical health"),
|
||||
(45, 55, "Acceptable mechanical health"),
|
||||
(55, 70, "Potential signs of a mechanical issue"),
|
||||
(70, 85, "Likely a mechanical issue"),
|
||||
(85, 100, "Mechanical issue detected")
|
||||
]
|
||||
for lower, upper, message in ranges:
|
||||
if lower < mhi <= upper:
|
||||
return message
|
||||
|
||||
return "Error computing MHI value"
|
||||
|
||||
|
||||
######################################################################
|
||||
# Graphing
|
||||
######################################################################
|
||||
|
||||
def plot_compare_frequency(ax, lognames, signal1, signal2, max_freq):
|
||||
def plot_compare_frequency(ax, lognames, signal1, signal2, similarity_factor, max_freq):
|
||||
# Get the belt name for the legend to avoid putting the full file name
|
||||
signal1_belt = (lognames[0].split('/')[-1]).split('_')[-1][0]
|
||||
signal2_belt = (lognames[1].split('/')[-1]).split('_')[-1][0]
|
||||
@@ -264,13 +264,11 @@ def plot_compare_frequency(ax, lognames, signal1, signal2, max_freq):
|
||||
ha='left', fontsize=13, color='red', weight='bold')
|
||||
unpaired_peak_count += 1
|
||||
|
||||
# Compute the similarity (using cross-correlation of the PSD signals)
|
||||
# Add estimated similarity to the graph
|
||||
ax2 = ax.twinx() # To split the legends in two box
|
||||
ax2.yaxis.set_visible(False)
|
||||
similarity_factor = compute_curve_similarity_factor(signal1, signal2)
|
||||
ax2.plot([], [], ' ', label=f'Estimated similarity: {similarity_factor:.1f}%')
|
||||
ax2.plot([], [], ' ', label=f'Number of unpaired peaks: {unpaired_peak_count}')
|
||||
print_with_c_locale(f"Belts estimated similarity: {similarity_factor:.1f}%")
|
||||
|
||||
# Setting axis parameters, grid and graph title
|
||||
ax.set_xlabel('Frequency (Hz)')
|
||||
@@ -304,25 +302,20 @@ def plot_compare_frequency(ax, lognames, signal1, signal2, max_freq):
|
||||
ax.legend(loc='upper left', prop=fontP)
|
||||
ax2.legend(loc='upper right', prop=fontP)
|
||||
|
||||
return similarity_factor, unpaired_peak_count
|
||||
return
|
||||
|
||||
|
||||
def plot_difference_spectrogram(ax, data1, data2, signal1, signal2, similarity_factor, max_freq):
|
||||
combined_sum, combined_divergent, bins, t = combined_spectrogram(data1, data2)
|
||||
|
||||
# Compute the MHI value from the differential spectrogram sum of gradient, salted with
|
||||
# the similarity factor and the number or unpaired peaks from the belts frequency profile
|
||||
# Be careful, this value is highly opinionated and is pretty experimental!
|
||||
mhi, textual_mhi = compute_mhi(combined_sum, similarity_factor, len(signal1.unpaired_peaks) + len(signal2.unpaired_peaks))
|
||||
print_with_c_locale(f"[experimental] Mechanical Health Indicator: {textual_mhi.lower()} ({mhi:.1f}%)")
|
||||
def plot_difference_spectrogram(ax, signal1, signal2, t, bins, combined_divergent, textual_mhi, max_freq):
|
||||
ax.set_title(f"Differential Spectrogram", fontsize=14, color=KLIPPAIN_COLORS['dark_orange'], weight='bold')
|
||||
ax.plot([], [], ' ', label=f'{textual_mhi} (experimental)')
|
||||
|
||||
# Draw the differential spectrogram with a specific custom norm to get orange or purple values where there is signal or white near zeros
|
||||
# imgshow is better suited here than pcolormesh since its result is already rasterized and we doesn't need to keep vector graphics
|
||||
# when saving to a final .png file. Using it also allow to save ~150-200MB of RAM during the "fig.savefig" operation.
|
||||
colors = [KLIPPAIN_COLORS['dark_orange'], KLIPPAIN_COLORS['orange'], 'white', KLIPPAIN_COLORS['purple'], KLIPPAIN_COLORS['dark_purple']]
|
||||
cm = matplotlib.colors.LinearSegmentedColormap.from_list('klippain_divergent', list(zip([0, 0.25, 0.5, 0.75, 1], colors)))
|
||||
norm = matplotlib.colors.TwoSlopeNorm(vmin=np.min(combined_divergent), vcenter=0, vmax=np.max(combined_divergent))
|
||||
ax.pcolormesh(t, bins, combined_divergent.T, cmap=cm, norm=norm, shading='gouraud')
|
||||
ax.imshow(combined_divergent.T, cmap=cm, norm=norm, aspect='auto', extent=[t[0], t[-1], bins[0], bins[-1]], interpolation='bilinear', origin='lower')
|
||||
|
||||
ax.set_xlabel('Frequency (hz)')
|
||||
ax.set_xlim([0., max_freq])
|
||||
@@ -389,50 +382,47 @@ def compute_signal_data(data, max_freq):
|
||||
# Startup and main routines
|
||||
######################################################################
|
||||
|
||||
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 this script. Please use the official Klipper "
|
||||
"graph_accelerometer.py script to process it instead." % (logname,))
|
||||
|
||||
|
||||
def setup_klipper_import(kdir):
|
||||
global shaper_calibrate
|
||||
kdir = os.path.expanduser(kdir)
|
||||
sys.path.append(os.path.join(kdir, 'klippy'))
|
||||
shaper_calibrate = importlib.import_module('.shaper_calibrate', 'extras')
|
||||
|
||||
|
||||
def belts_calibration(lognames, klipperdir="~/klipper", max_freq=200.):
|
||||
set_locale()
|
||||
setup_klipper_import(klipperdir)
|
||||
global shaper_calibrate
|
||||
shaper_calibrate = setup_klipper_import(klipperdir)
|
||||
|
||||
# Parse data
|
||||
datas = [parse_log(fn) for fn in lognames]
|
||||
if len(datas) > 2:
|
||||
raise ValueError("Incorrect number of .csv files used (this function needs two files to compare them)")
|
||||
raise ValueError("Incorrect number of .csv files used (this function needs exactly two files to compare them)!")
|
||||
|
||||
# Compute calibration data for the two datasets with automatic peaks detection
|
||||
signal1 = compute_signal_data(datas[0], max_freq)
|
||||
signal2 = compute_signal_data(datas[1], max_freq)
|
||||
combined_sum, combined_divergent, bins, t = compute_combined_spectrogram(datas[0], datas[1])
|
||||
del datas
|
||||
|
||||
# Pair the peaks across the two datasets
|
||||
paired_peaks, unpaired_peaks1, unpaired_peaks2 = pair_peaks(signal1.peaks, signal1.freqs, signal1.psd,
|
||||
signal2.peaks, signal2.freqs, signal2.psd)
|
||||
signal1 = signal1._replace(paired_peaks=paired_peaks, unpaired_peaks=unpaired_peaks1)
|
||||
signal2 = signal2._replace(paired_peaks=paired_peaks, unpaired_peaks=unpaired_peaks2)
|
||||
signal1 = signal1._replace(paired_peaks = paired_peaks, unpaired_peaks = unpaired_peaks1)
|
||||
signal2 = signal2._replace(paired_peaks = paired_peaks, unpaired_peaks = unpaired_peaks2)
|
||||
|
||||
fig = matplotlib.pyplot.figure()
|
||||
gs = matplotlib.gridspec.GridSpec(2, 1, height_ratios=[4, 3])
|
||||
ax1 = fig.add_subplot(gs[0])
|
||||
ax2 = fig.add_subplot(gs[1])
|
||||
# Compute the similarity (using cross-correlation of the PSD signals)
|
||||
similarity_factor = compute_curve_similarity_factor(signal1, signal2)
|
||||
print_with_c_locale(f"Belts estimated similarity: {similarity_factor:.1f}%")
|
||||
# Compute the MHI value from the differential spectrogram sum of gradient, salted with the similarity factor and the number of
|
||||
# unpaired peaks from the belts frequency profile. Be careful, this value is highly opinionated and is pretty experimental!
|
||||
mhi, textual_mhi = compute_mhi(combined_sum, similarity_factor, len(signal1.unpaired_peaks) + len(signal2.unpaired_peaks))
|
||||
print_with_c_locale(f"[experimental] Mechanical Health Indicator: {textual_mhi.lower()} ({mhi:.1f}%)")
|
||||
|
||||
# Create graph layout
|
||||
fig, (ax1, ax2) = plt.subplots(2, 1, gridspec_kw={
|
||||
'height_ratios':[4, 3],
|
||||
'bottom':0.050,
|
||||
'top':0.890,
|
||||
'left':0.085,
|
||||
'right':0.966,
|
||||
'hspace':0.169,
|
||||
'wspace':0.200
|
||||
})
|
||||
fig.set_size_inches(8.3, 11.6)
|
||||
|
||||
# Add title
|
||||
title_line1 = "RELATIVE BELT CALIBRATION TOOL"
|
||||
@@ -447,18 +437,19 @@ def belts_calibration(lognames, klipperdir="~/klipper", max_freq=200.):
|
||||
fig.text(0.12, 0.957, title_line2, ha='left', va='top', fontsize=16, color=KLIPPAIN_COLORS['dark_purple'])
|
||||
|
||||
# Plot the graphs
|
||||
similarity_factor, _ = plot_compare_frequency(ax1, lognames, signal1, signal2, max_freq)
|
||||
plot_difference_spectrogram(ax2, datas[0], datas[1], signal1, signal2, similarity_factor, max_freq)
|
||||
|
||||
fig.set_size_inches(8.3, 11.6)
|
||||
fig.tight_layout()
|
||||
fig.subplots_adjust(top=0.89)
|
||||
plot_compare_frequency(ax1, lognames, signal1, signal2, similarity_factor, max_freq)
|
||||
plot_difference_spectrogram(ax2, signal1, signal2, t, bins, combined_divergent, textual_mhi, max_freq)
|
||||
|
||||
# Adding a small Klippain logo to the top left corner of the figure
|
||||
ax_logo = fig.add_axes([0.001, 0.899, 0.1, 0.1], anchor='NW', zorder=-1)
|
||||
ax_logo.imshow(matplotlib.pyplot.imread(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'klippain.png')))
|
||||
ax_logo = fig.add_axes([0.001, 0.8995, 0.1, 0.1], anchor='NW')
|
||||
ax_logo.imshow(plt.imread(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'klippain.png')))
|
||||
ax_logo.axis('off')
|
||||
|
||||
|
||||
# Adding Shake&Tune version in the top right corner
|
||||
st_version = get_git_version()
|
||||
if st_version is not None:
|
||||
fig.text(0.995, 0.985, st_version, ha='right', va='bottom', fontsize=8, color=KLIPPAIN_COLORS['purple'])
|
||||
|
||||
return fig
|
||||
|
||||
|
||||
@@ -479,7 +470,7 @@ def main():
|
||||
opts.error("You must specify an output file.png to use the script (option -o)")
|
||||
|
||||
fig = belts_calibration(args, options.klipperdir, options.max_freq)
|
||||
fig.savefig(options.output)
|
||||
fig.savefig(options.output, dpi=150)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
Reference in New Issue
Block a user