mirror of
				https://github.com/Klipper3d/klipper.git
				synced 2025-10-31 18:36:09 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			150 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			150 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env python
 | |
| # Script to perform motion analysis and graphing
 | |
| #
 | |
| # Copyright (C) 2019-2021  Kevin O'Connor <kevin@koconnor.net>
 | |
| #
 | |
| # This file may be distributed under the terms of the GNU GPLv3 license.
 | |
| import sys, optparse, ast
 | |
| import matplotlib
 | |
| import readlog, analyzers
 | |
| try:
 | |
|     import urlparse
 | |
| except:
 | |
|     import urllib.parse as urlparse
 | |
| 
 | |
| 
 | |
| ######################################################################
 | |
| # Graphing
 | |
| ######################################################################
 | |
| 
 | |
| def plot_motion(amanager, graphs, log_prefix):
 | |
|     # Generate data
 | |
|     for graph in graphs:
 | |
|         for dataset, plot_params in graph:
 | |
|             amanager.setup_dataset(dataset)
 | |
|     amanager.generate_datasets()
 | |
|     datasets = amanager.get_datasets()
 | |
|     times = amanager.get_dataset_times()
 | |
|     # Build plot
 | |
|     fontP = matplotlib.font_manager.FontProperties()
 | |
|     fontP.set_size('x-small')
 | |
|     fig, rows = matplotlib.pyplot.subplots(nrows=len(graphs), sharex=True)
 | |
|     if len(graphs) == 1:
 | |
|         rows = [rows]
 | |
|     rows[0].set_title("Motion Analysis (%s)" % (log_prefix,))
 | |
|     for graph, graph_ax in zip(graphs, rows):
 | |
|         graph_units = graph_twin_units = twin_ax = None
 | |
|         for dataset, plot_params in graph:
 | |
|             label = amanager.get_label(dataset)
 | |
|             ax = graph_ax
 | |
|             if graph_units is None:
 | |
|                 graph_units = label['units']
 | |
|                 ax.set_ylabel(graph_units)
 | |
|             elif label['units'] != graph_units:
 | |
|                 if graph_twin_units is None:
 | |
|                     ax = twin_ax = graph_ax.twinx()
 | |
|                     graph_twin_units = label['units']
 | |
|                     ax.set_ylabel(graph_twin_units)
 | |
|                 elif label['units'] == graph_twin_units:
 | |
|                     ax = twin_ax
 | |
|                 else:
 | |
|                     graph_units = "Unknown"
 | |
|                     ax.set_ylabel(graph_units)
 | |
|             pparams = {'label': label['label'], 'alpha': 0.8}
 | |
|             pparams.update(plot_params)
 | |
|             ax.plot(times, datasets[dataset], **pparams)
 | |
|         if twin_ax is not None:
 | |
|             li1, la1 = graph_ax.get_legend_handles_labels()
 | |
|             li2, la2 = twin_ax.get_legend_handles_labels()
 | |
|             twin_ax.legend(li1 + li2, la1 + la2, loc='best', prop=fontP)
 | |
|         else:
 | |
|             graph_ax.legend(loc='best', prop=fontP)
 | |
|         graph_ax.grid(True)
 | |
|     rows[-1].set_xlabel('Time (s)')
 | |
|     return fig
 | |
| 
 | |
| 
 | |
| ######################################################################
 | |
| # Startup
 | |
| ######################################################################
 | |
| 
 | |
| def setup_matplotlib(output_to_file):
 | |
|     global matplotlib
 | |
|     if output_to_file:
 | |
|         matplotlib.use('Agg')
 | |
|     import matplotlib.pyplot, matplotlib.dates, matplotlib.font_manager
 | |
|     import matplotlib.ticker
 | |
| 
 | |
| def parse_graph_description(desc):
 | |
|     if '?' not in desc:
 | |
|         return (desc, {})
 | |
|     dataset, params = desc.split('?', 1)
 | |
|     params = {k: v for k, v in urlparse.parse_qsl(params)}
 | |
|     for fkey in ['alpha']:
 | |
|         if fkey in params:
 | |
|             params[fkey] = float(params[fkey])
 | |
|     return (dataset, params)
 | |
| 
 | |
| def list_datasets():
 | |
|     datasets = readlog.list_datasets() + analyzers.list_datasets()
 | |
|     out = ["\nAvailable datasets:\n"]
 | |
|     for dataset, desc in datasets:
 | |
|         out.append("%-24s: %s\n" % (dataset, desc))
 | |
|     out.append("\n")
 | |
|     sys.stdout.write("".join(out))
 | |
|     sys.exit(0)
 | |
| 
 | |
| def main():
 | |
|     # Parse command-line arguments
 | |
|     usage = "%prog [options] <logname>"
 | |
|     opts = optparse.OptionParser(usage)
 | |
|     opts.add_option("-o", "--output", type="string", dest="output",
 | |
|                     default=None, help="filename of output graph")
 | |
|     opts.add_option("-s", "--skip", type="float", default=0.,
 | |
|                     help="Set the start time to graph")
 | |
|     opts.add_option("-d", "--duration", type="float", default=5.,
 | |
|                     help="Number of seconds to graph")
 | |
|     opts.add_option("--segment-time", type="float", default=0.000100,
 | |
|                     help="Analysis segment time (default 0.000100 seconds)")
 | |
|     opts.add_option("-g", "--graph", help="Graph to generate (python literal)")
 | |
|     opts.add_option("-l", "--list-datasets", action="store_true",
 | |
|                     help="List available datasets")
 | |
|     options, args = opts.parse_args()
 | |
|     if options.list_datasets:
 | |
|         list_datasets()
 | |
|     if len(args) != 1:
 | |
|         opts.error("Incorrect number of arguments")
 | |
|     log_prefix = args[0]
 | |
| 
 | |
|     # Open data files
 | |
|     lmanager = readlog.LogManager(log_prefix)
 | |
|     lmanager.setup_index()
 | |
|     lmanager.seek_time(options.skip)
 | |
|     amanager = analyzers.AnalyzerManager(lmanager, options.segment_time)
 | |
|     amanager.set_duration(options.duration)
 | |
| 
 | |
|     # Default graphs to draw
 | |
|     graph_descs = [
 | |
|         ["trapq(toolhead,velocity)?color=green"],
 | |
|         ["trapq(toolhead,accel)?color=green"],
 | |
|         ["deviation(stepq(stepper_x),kin(stepper_x))?color=blue"],
 | |
|     ]
 | |
|     if options.graph is not None:
 | |
|         graph_descs = ast.literal_eval(options.graph)
 | |
|     graphs = [[parse_graph_description(g) for g in graph_row]
 | |
|               for graph_row in graph_descs]
 | |
| 
 | |
|     # Draw graph
 | |
|     setup_matplotlib(options.output is not None)
 | |
|     fig = plot_motion(amanager, graphs, log_prefix)
 | |
| 
 | |
|     # Show graph
 | |
|     if options.output is None:
 | |
|         matplotlib.pyplot.show()
 | |
|     else:
 | |
|         fig.set_size_inches(8, 6)
 | |
|         fig.savefig(options.output)
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     main()
 |