diff options
Diffstat (limited to 'scripts/calibrate_shaper.py')
-rwxr-xr-x | scripts/calibrate_shaper.py | 281 |
1 files changed, 184 insertions, 97 deletions
diff --git a/scripts/calibrate_shaper.py b/scripts/calibrate_shaper.py index b56ce5da..6109d843 100755 --- a/scripts/calibrate_shaper.py +++ b/scripts/calibrate_shaper.py @@ -9,40 +9,58 @@ from __future__ import print_function import importlib, optparse, os, sys from textwrap import wrap import numpy as np, matplotlib -sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), - '..', 'klippy')) -shaper_calibrate = importlib.import_module('.shaper_calibrate', 'extras') -MAX_TITLE_LENGTH=65 +sys.path.append( + os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "klippy") +) +shaper_calibrate = importlib.import_module(".shaper_calibrate", "extras") + +MAX_TITLE_LENGTH = 65 + def parse_log(logname): with open(logname) as f: for header in f: - if not header.startswith('#'): + if not header.startswith("#"): break - if not header.startswith('freq,psd_x,psd_y,psd_z,psd_xyz'): + if not header.startswith("freq,psd_x,psd_y,psd_z,psd_xyz"): # Raw accelerometer data - return np.loadtxt(logname, comments='#', delimiter=',') + return np.loadtxt(logname, comments="#", delimiter=",") # Parse power spectral density data - data = np.loadtxt(logname, skiprows=1, comments='#', delimiter=',') + data = np.loadtxt(logname, skiprows=1, comments="#", delimiter=",") calibration_data = shaper_calibrate.CalibrationData( - freq_bins=data[:,0], psd_sum=data[:,4], - psd_x=data[:,1], psd_y=data[:,2], psd_z=data[:,3]) + freq_bins=data[:, 0], + psd_sum=data[:, 4], + psd_x=data[:, 1], + psd_y=data[:, 2], + psd_z=data[:, 3], + ) calibration_data.set_numpy(np) # If input shapers are present in the CSV file, the frequency # response is already normalized to input frequencies - if 'mzv' not in header: + if "mzv" not in header: calibration_data.normalize_to_frequencies() return calibration_data + ###################################################################### # Shaper calibration ###################################################################### + # Find the best shaper parameters -def calibrate_shaper(datas, csv_output, *, shapers, damping_ratio, scv, - shaper_freqs, max_smoothing, test_damping_ratios, - max_freq): +def calibrate_shaper( + datas, + csv_output, + *, + shapers, + damping_ratio, + scv, + shaper_freqs, + max_smoothing, + test_damping_ratios, + max_freq +): helper = shaper_calibrate.ShaperCalibrate(printer=None) if isinstance(datas[0], shaper_calibrate.CalibrationData): calibration_data = datas[0] @@ -55,28 +73,35 @@ def calibrate_shaper(datas, csv_output, *, shapers, damping_ratio, scv, calibration_data.add_data(helper.process_accelerometer_data(data)) calibration_data.normalize_to_frequencies() - shaper, all_shapers = helper.find_best_shaper( - calibration_data, shapers=shapers, damping_ratio=damping_ratio, - scv=scv, shaper_freqs=shaper_freqs, max_smoothing=max_smoothing, - test_damping_ratios=test_damping_ratios, max_freq=max_freq, - logger=print) + calibration_data, + shapers=shapers, + damping_ratio=damping_ratio, + scv=scv, + shaper_freqs=shaper_freqs, + max_smoothing=max_smoothing, + test_damping_ratios=test_damping_ratios, + max_freq=max_freq, + logger=print, + ) if not shaper: - print("No recommended shaper, possibly invalid value for --shapers=%s" % - (','.join(shapers))) + print( + "No recommended shaper, possibly invalid value for --shapers=%s" + % (",".join(shapers)) + ) return None, None, None print("Recommended shaper is %s @ %.1f Hz" % (shaper.name, shaper.freq)) if csv_output is not None: - helper.save_calibration_data( - csv_output, calibration_data, all_shapers) + helper.save_calibration_data(csv_output, calibration_data, all_shapers) return shaper.name, all_shapers, calibration_data + ###################################################################### # Plot frequency response and suggested input shapers ###################################################################### -def plot_freq_response(lognames, calibration_data, shapers, - selected_shaper, max_freq): + +def plot_freq_response(lognames, calibration_data, shapers, selected_shaper, max_freq): freqs = calibration_data.freq_bins psd = calibration_data.psd_sum[freqs <= max_freq] px = calibration_data.psd_x[freqs <= max_freq] @@ -85,89 +110,140 @@ def plot_freq_response(lognames, calibration_data, shapers, freqs = freqs[freqs <= max_freq] fontP = matplotlib.font_manager.FontProperties() - fontP.set_size('x-small') + fontP.set_size("x-small") fig, ax = matplotlib.pyplot.subplots() - ax.set_xlabel('Frequency, Hz') + ax.set_xlabel("Frequency, Hz") ax.set_xlim([0, max_freq]) - ax.set_ylabel('Power spectral density') + ax.set_ylabel("Power spectral density") - ax.plot(freqs, psd, label='X+Y+Z', color='purple') - ax.plot(freqs, px, label='X', color='red') - ax.plot(freqs, py, label='Y', color='green') - ax.plot(freqs, pz, label='Z', color='blue') + ax.plot(freqs, psd, label="X+Y+Z", color="purple") + ax.plot(freqs, px, label="X", color="red") + ax.plot(freqs, py, label="Y", color="green") + ax.plot(freqs, pz, label="Z", color="blue") - title = "Frequency response and shapers (%s)" % (', '.join(lognames)) + title = "Frequency response and shapers (%s)" % (", ".join(lognames)) ax.set_title("\n".join(wrap(title, MAX_TITLE_LENGTH))) ax.xaxis.set_minor_locator(matplotlib.ticker.MultipleLocator(5)) ax.yaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator()) - ax.ticklabel_format(axis='y', style='scientific', scilimits=(0,0)) - ax.grid(which='major', color='grey') - ax.grid(which='minor', color='lightgrey') + ax.ticklabel_format(axis="y", style="scientific", scilimits=(0, 0)) + ax.grid(which="major", color="grey") + ax.grid(which="minor", color="lightgrey") ax2 = ax.twinx() - ax2.set_ylabel('Shaper vibration reduction (ratio)') + ax2.set_ylabel("Shaper vibration reduction (ratio)") best_shaper_vals = None for shaper in shapers: label = "%s (%.1f Hz, vibr=%.1f%%, sm~=%.2f, accel<=%.f)" % ( - shaper.name.upper(), shaper.freq, - shaper.vibrs * 100., shaper.smoothing, - round(shaper.max_accel / 100.) * 100.) - linestyle = 'dotted' + shaper.name.upper(), + shaper.freq, + shaper.vibrs * 100.0, + shaper.smoothing, + round(shaper.max_accel / 100.0) * 100.0, + ) + linestyle = "dotted" if shaper.name == selected_shaper: - linestyle = 'dashdot' + linestyle = "dashdot" best_shaper_vals = shaper.vals ax2.plot(freqs, shaper.vals, label=label, linestyle=linestyle) - ax.plot(freqs, psd * best_shaper_vals, - label='After\nshaper', color='cyan') + ax.plot(freqs, psd * best_shaper_vals, label="After\nshaper", color="cyan") # A hack to add a human-readable shaper recommendation to legend - ax2.plot([], [], ' ', - label="Recommended shaper: %s" % (selected_shaper.upper())) + ax2.plot([], [], " ", label="Recommended shaper: %s" % (selected_shaper.upper())) - ax.legend(loc='upper left', prop=fontP) - ax2.legend(loc='upper right', prop=fontP) + ax.legend(loc="upper left", prop=fontP) + ax2.legend(loc="upper right", prop=fontP) fig.tight_layout() return fig + ###################################################################### # Startup ###################################################################### + def setup_matplotlib(output_to_file): global matplotlib if output_to_file: - matplotlib.rcParams.update({'figure.autolayout': True}) - matplotlib.use('Agg') + matplotlib.rcParams.update({"figure.autolayout": True}) + matplotlib.use("Agg") import matplotlib.pyplot, matplotlib.dates, matplotlib.font_manager import matplotlib.ticker + def main(): # Parse command-line arguments usage = "%prog [options] <logs>" opts = optparse.OptionParser(usage) - opts.add_option("-o", "--output", type="string", dest="output", - default=None, help="filename of output graph") - opts.add_option("-c", "--csv", type="string", dest="csv", - default=None, help="filename of output csv file") - opts.add_option("-f", "--max_freq", type="float", default=200., - help="maximum frequency to plot") - opts.add_option("-s", "--max_smoothing", type="float", dest="max_smoothing", - default=None, help="maximum shaper smoothing to allow") - opts.add_option("--scv", "--square_corner_velocity", type="float", - dest="scv", default=5., help="square corner velocity") - opts.add_option("--shaper_freq", type="string", dest="shaper_freq", - default=None, help="shaper frequency(-ies) to test, " + - "either a comma-separated list of floats, or a range in " + - "the format [start]:end[:step]") - opts.add_option("--shapers", type="string", dest="shapers", default=None, - help="a comma-separated list of shapers to test") - opts.add_option("--damping_ratio", type="float", dest="damping_ratio", - default=None, help="shaper damping_ratio parameter") - opts.add_option("--test_damping_ratios", type="string", - dest="test_damping_ratios", default=None, - help="a comma-separated liat of damping ratios to test " + - "input shaper for") + opts.add_option( + "-o", + "--output", + type="string", + dest="output", + default=None, + help="filename of output graph", + ) + opts.add_option( + "-c", + "--csv", + type="string", + dest="csv", + default=None, + help="filename of output csv file", + ) + opts.add_option( + "-f", + "--max_freq", + type="float", + default=200.0, + help="maximum frequency to plot", + ) + opts.add_option( + "-s", + "--max_smoothing", + type="float", + dest="max_smoothing", + default=None, + help="maximum shaper smoothing to allow", + ) + opts.add_option( + "--scv", + "--square_corner_velocity", + type="float", + dest="scv", + default=5.0, + help="square corner velocity", + ) + opts.add_option( + "--shaper_freq", + type="string", + dest="shaper_freq", + default=None, + help="shaper frequency(-ies) to test, " + + "either a comma-separated list of floats, or a range in " + + "the format [start]:end[:step]", + ) + opts.add_option( + "--shapers", + type="string", + dest="shapers", + default=None, + help="a comma-separated list of shapers to test", + ) + opts.add_option( + "--damping_ratio", + type="float", + dest="damping_ratio", + default=None, + help="shaper damping_ratio parameter", + ) + opts.add_option( + "--test_damping_ratios", + type="string", + dest="test_damping_ratios", + default=None, + help="a comma-separated liat of damping ratios to test " + "input shaper for", + ) options, args = opts.parse_args() if len(args) < 1: opts.error("Incorrect number of arguments") @@ -177,59 +253,68 @@ def main(): max_freq = options.max_freq if options.shaper_freq is None: shaper_freqs = [] - elif options.shaper_freq.find(':') >= 0: + elif options.shaper_freq.find(":") >= 0: freq_start = None freq_end = None freq_step = None try: - freqs_parsed = options.shaper_freq.partition(':') + freqs_parsed = options.shaper_freq.partition(":") if freqs_parsed[0]: freq_start = float(freqs_parsed[0]) - freqs_parsed = freqs_parsed[-1].partition(':') + freqs_parsed = freqs_parsed[-1].partition(":") freq_end = float(freqs_parsed[0]) if freq_start and freq_start > freq_end: - opts.error("Invalid --shaper_freq param: start range larger " + - "than its end") - if freqs_parsed[-1].find(':') >= 0: + opts.error( + "Invalid --shaper_freq param: start range larger " + "than its end" + ) + if freqs_parsed[-1].find(":") >= 0: opts.error("Invalid --shaper_freq param format") if freqs_parsed[-1]: freq_step = float(freqs_parsed[-1]) except ValueError: - opts.error("--shaper_freq param does not specify correct range " + - "in the format [start]:end[:step]") + opts.error( + "--shaper_freq param does not specify correct range " + + "in the format [start]:end[:step]" + ) shaper_freqs = (freq_start, freq_end, freq_step) - max_freq = max(max_freq, freq_end * 4./3.) + max_freq = max(max_freq, freq_end * 4.0 / 3.0) else: try: - shaper_freqs = [float(s) for s in options.shaper_freq.split(',')] + shaper_freqs = [float(s) for s in options.shaper_freq.split(",")] except ValueError: opts.error("invalid floating point value in --shaper_freq param") - max_freq = max(max_freq, max(shaper_freqs) * 4./3.) + max_freq = max(max_freq, max(shaper_freqs) * 4.0 / 3.0) if options.test_damping_ratios: try: - test_damping_ratios = [float(s) for s in - options.test_damping_ratios.split(',')] + test_damping_ratios = [ + float(s) for s in options.test_damping_ratios.split(",") + ] except ValueError: - opts.error("invalid floating point value in " + - "--test_damping_ratios param") + opts.error( + "invalid floating point value in " + "--test_damping_ratios param" + ) else: test_damping_ratios = None if options.shapers is None: shapers = None else: - shapers = options.shapers.lower().split(',') + shapers = options.shapers.lower().split(",") # Parse data datas = [parse_log(fn) for fn in args] # Calibrate shaper and generate outputs selected_shaper, shapers, calibration_data = calibrate_shaper( - datas, options.csv, shapers=shapers, - damping_ratio=options.damping_ratio, - scv=options.scv, shaper_freqs=shaper_freqs, - max_smoothing=options.max_smoothing, - test_damping_ratios=test_damping_ratios, - max_freq=max_freq) + datas, + options.csv, + shapers=shapers, + damping_ratio=options.damping_ratio, + scv=options.scv, + shaper_freqs=shaper_freqs, + max_smoothing=options.max_smoothing, + test_damping_ratios=test_damping_ratios, + max_freq=max_freq, + ) if selected_shaper is None: return @@ -237,8 +322,9 @@ def main(): # Draw graph setup_matplotlib(options.output is not None) - fig = plot_freq_response(args, calibration_data, shapers, - selected_shaper, max_freq) + fig = plot_freq_response( + args, calibration_data, shapers, selected_shaper, max_freq + ) # Show graph if options.output is None: @@ -247,5 +333,6 @@ def main(): fig.set_size_inches(8, 6) fig.savefig(options.output) -if __name__ == '__main__': + +if __name__ == "__main__": main() |