Lot of refactoring, memory and speed optimizations for all the graphs scripts

This commit is contained in:
Félix Boisselier
2024-01-03 00:00:30 +01:00
parent 0170e34cab
commit e056ec2249
7 changed files with 359 additions and 333 deletions

View File

@@ -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__':