| 
									
										
										
										
											2021-12-08 21:44:07 +01:00
										 |  |  | #!/usr/bin/env python3 | 
					
						
							| 
									
										
										
										
											2020-07-31 00:33:56 -04:00
										 |  |  | # Generate adxl345 accelerometer graphs | 
					
						
							|  |  |  | # | 
					
						
							|  |  |  | # Copyright (C) 2020  Kevin O'Connor <kevin@koconnor.net> | 
					
						
							| 
									
										
										
										
											2020-10-15 02:08:10 +02:00
										 |  |  | # Copyright (C) 2020  Dmitry Butyugin <dmbutyugin@google.com> | 
					
						
							| 
									
										
										
										
											2020-07-31 00:33:56 -04:00
										 |  |  | # | 
					
						
							|  |  |  | # This file may be distributed under the terms of the GNU GPLv3 license. | 
					
						
							| 
									
										
										
										
											2021-10-22 20:46:20 +02:00
										 |  |  | import importlib, optparse, os, sys | 
					
						
							| 
									
										
										
										
											2020-10-15 02:08:10 +02:00
										 |  |  | from textwrap import wrap | 
					
						
							|  |  |  | import numpy as np, matplotlib | 
					
						
							|  |  |  | sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), | 
					
						
							| 
									
										
										
										
											2021-10-22 20:46:20 +02:00
										 |  |  |                              '..', 'klippy')) | 
					
						
							|  |  |  | shaper_calibrate = importlib.import_module('.shaper_calibrate', 'extras') | 
					
						
							| 
									
										
										
										
											2020-10-15 02:08:10 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-05 23:48:03 +01:00
										 |  |  | MAX_TITLE_LENGTH=65 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def parse_log(logname, opts): | 
					
						
							|  |  |  |     with open(logname) as f: | 
					
						
							|  |  |  |         for header in f: | 
					
						
							| 
									
										
										
										
											2023-02-22 19:55:32 +01:00
										 |  |  |             if header.startswith('#'): | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             if header.startswith('freq,psd_x,psd_y,psd_z,psd_xyz'): | 
					
						
							|  |  |  |                 # Processed power spectral density file | 
					
						
							| 
									
										
										
										
											2020-12-05 23:48:03 +01:00
										 |  |  |                 break | 
					
						
							|  |  |  |             # Raw accelerometer data | 
					
						
							|  |  |  |             return np.loadtxt(logname, comments='#', delimiter=',') | 
					
						
							| 
									
										
										
										
											2021-08-05 23:53:55 +02:00
										 |  |  |     # Parse power spectral density data | 
					
						
							|  |  |  |     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]) | 
					
						
							|  |  |  |     calibration_data.set_numpy(np) | 
					
						
							|  |  |  |     return calibration_data | 
					
						
							| 
									
										
										
										
											2020-10-15 02:08:10 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | # Raw accelerometer graphing | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							| 
									
										
										
										
											2020-07-31 00:33:56 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-09-29 22:50:17 +02:00
										 |  |  | def plot_accel(datas, lognames): | 
					
						
							| 
									
										
										
										
											2020-07-31 00:33:56 -04:00
										 |  |  |     fig, axes = matplotlib.pyplot.subplots(nrows=3, sharex=True) | 
					
						
							| 
									
										
										
										
											2021-09-29 22:50:17 +02:00
										 |  |  |     axes[0].set_title("\n".join(wrap( | 
					
						
							|  |  |  |         "Accelerometer data (%s)" % (', '.join(lognames)), MAX_TITLE_LENGTH))) | 
					
						
							| 
									
										
										
										
											2020-07-31 00:33:56 -04:00
										 |  |  |     axis_names = ['x', 'y', 'z'] | 
					
						
							| 
									
										
										
										
											2021-09-29 22:50:17 +02:00
										 |  |  |     for data, logname in zip(datas, lognames): | 
					
						
							|  |  |  |         if isinstance(data, shaper_calibrate.CalibrationData): | 
					
						
							|  |  |  |             raise error("Cannot plot raw accelerometer data using the processed" | 
					
						
							|  |  |  |                         " resonances, raw_data input is required") | 
					
						
							|  |  |  |         first_time = data[0, 0] | 
					
						
							|  |  |  |         times = data[:,0] - first_time | 
					
						
							|  |  |  |         for i in range(len(axis_names)): | 
					
						
							|  |  |  |             avg = data[:,i+1].mean() | 
					
						
							|  |  |  |             adata = data[:,i+1] - data[:,i+1].mean() | 
					
						
							|  |  |  |             ax = axes[i] | 
					
						
							|  |  |  |             label = '\n'.join(wrap(logname, 60)) + ' (%+.3f mm/s^2)' % (-avg,) | 
					
						
							|  |  |  |             ax.plot(times, adata, alpha=0.8, label=label) | 
					
						
							|  |  |  |     axes[-1].set_xlabel('Time (s)') | 
					
						
							|  |  |  |     fontP = matplotlib.font_manager.FontProperties() | 
					
						
							|  |  |  |     fontP.set_size('x-small') | 
					
						
							| 
									
										
										
										
											2020-07-31 00:33:56 -04:00
										 |  |  |     for i in range(len(axis_names)): | 
					
						
							|  |  |  |         ax = axes[i] | 
					
						
							|  |  |  |         ax.grid(True) | 
					
						
							| 
									
										
										
										
											2021-09-29 22:50:17 +02:00
										 |  |  |         ax.legend(loc='best', prop=fontP) | 
					
						
							|  |  |  |         ax.set_ylabel('%s accel' % (axis_names[i],)) | 
					
						
							| 
									
										
										
										
											2020-07-31 00:33:56 -04:00
										 |  |  |     fig.tight_layout() | 
					
						
							|  |  |  |     return fig | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-15 02:08:10 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | # Frequency graphing | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # Calculate estimated "power spectral density" | 
					
						
							|  |  |  | def calc_freq_response(data, max_freq): | 
					
						
							| 
									
										
										
										
											2021-08-05 23:53:55 +02:00
										 |  |  |     if isinstance(data, shaper_calibrate.CalibrationData): | 
					
						
							|  |  |  |         return data | 
					
						
							| 
									
										
										
										
											2021-10-22 20:46:20 +02:00
										 |  |  |     helper = shaper_calibrate.ShaperCalibrate(printer=None) | 
					
						
							| 
									
										
										
										
											2020-10-15 02:08:10 +02:00
										 |  |  |     return helper.process_accelerometer_data(data) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def calc_specgram(data, axis): | 
					
						
							| 
									
										
										
										
											2021-08-05 23:53:55 +02:00
										 |  |  |     if isinstance(data, shaper_calibrate.CalibrationData): | 
					
						
							|  |  |  |         raise error("Cannot calculate the spectrogram using the processed" | 
					
						
							|  |  |  |                     " resonances, raw_data input is required") | 
					
						
							| 
									
										
										
										
											2020-10-15 02:08:10 +02:00
										 |  |  |     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 matplotlib.mlab.specgram( | 
					
						
							|  |  |  |                 x, Fs=Fs, NFFT=M, noverlap=M//2, window=window, | 
					
						
							|  |  |  |                 mode='psd', detrend='mean', scale_by_freq=False) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     d = {'x': data[:,1], 'y': data[:,2], 'z': data[:,3]} | 
					
						
							|  |  |  |     if axis != 'all': | 
					
						
							|  |  |  |         pdata, bins, t = _specgram(d[axis]) | 
					
						
							|  |  |  |     else: | 
					
						
							|  |  |  |         pdata, bins, t = _specgram(d['x']) | 
					
						
							|  |  |  |         for ax in 'yz': | 
					
						
							|  |  |  |             pdata += _specgram(d[ax])[0] | 
					
						
							|  |  |  |     return pdata, bins, t | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def plot_frequency(datas, lognames, max_freq): | 
					
						
							|  |  |  |     calibration_data = calc_freq_response(datas[0], max_freq) | 
					
						
							|  |  |  |     for data in datas[1:]: | 
					
						
							| 
									
										
										
										
											2021-04-03 23:45:42 +02:00
										 |  |  |         calibration_data.add_data(calc_freq_response(data, max_freq)) | 
					
						
							| 
									
										
										
										
											2020-10-15 02:08:10 +02:00
										 |  |  |     freqs = calibration_data.freq_bins | 
					
						
							|  |  |  |     psd = calibration_data.psd_sum[freqs <= max_freq] | 
					
						
							|  |  |  |     px = calibration_data.psd_x[freqs <= max_freq] | 
					
						
							|  |  |  |     py = calibration_data.psd_y[freqs <= max_freq] | 
					
						
							|  |  |  |     pz = calibration_data.psd_z[freqs <= max_freq] | 
					
						
							|  |  |  |     freqs = freqs[freqs <= max_freq] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     fig, ax = matplotlib.pyplot.subplots() | 
					
						
							|  |  |  |     ax.set_title("\n".join(wrap( | 
					
						
							|  |  |  |         "Frequency response (%s)" % (', '.join(lognames)), MAX_TITLE_LENGTH))) | 
					
						
							|  |  |  |     ax.set_xlabel('Frequency (Hz)') | 
					
						
							|  |  |  |     ax.set_ylabel('Power spectral density') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     ax.plot(freqs, psd, label='X+Y+Z', alpha=0.6) | 
					
						
							|  |  |  |     ax.plot(freqs, px, label='X', alpha=0.6) | 
					
						
							|  |  |  |     ax.plot(freqs, py, label='Y', alpha=0.6) | 
					
						
							|  |  |  |     ax.plot(freqs, pz, label='Z', alpha=0.6) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     ax.xaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator()) | 
					
						
							|  |  |  |     ax.yaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator()) | 
					
						
							|  |  |  |     ax.grid(which='major', color='grey') | 
					
						
							|  |  |  |     ax.grid(which='minor', color='lightgrey') | 
					
						
							|  |  |  |     ax.ticklabel_format(axis='y', style='scientific', scilimits=(0,0)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     fontP = matplotlib.font_manager.FontProperties() | 
					
						
							|  |  |  |     fontP.set_size('x-small') | 
					
						
							|  |  |  |     ax.legend(loc='best', prop=fontP) | 
					
						
							|  |  |  |     fig.tight_layout() | 
					
						
							|  |  |  |     return fig | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-14 14:58:29 +01:00
										 |  |  | def plot_compare_frequency(datas, lognames, max_freq, axis): | 
					
						
							| 
									
										
										
										
											2020-10-15 02:08:10 +02:00
										 |  |  |     fig, ax = matplotlib.pyplot.subplots() | 
					
						
							|  |  |  |     ax.set_title('Frequency responses comparison') | 
					
						
							|  |  |  |     ax.set_xlabel('Frequency (Hz)') | 
					
						
							|  |  |  |     ax.set_ylabel('Power spectral density') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     for data, logname in zip(datas, lognames): | 
					
						
							|  |  |  |         calibration_data = calc_freq_response(data, max_freq) | 
					
						
							|  |  |  |         freqs = calibration_data.freq_bins | 
					
						
							| 
									
										
										
										
											2021-03-14 14:58:29 +01:00
										 |  |  |         psd = calibration_data.get_psd(axis)[freqs <= max_freq] | 
					
						
							| 
									
										
										
										
											2020-10-15 02:08:10 +02:00
										 |  |  |         freqs = freqs[freqs <= max_freq] | 
					
						
							|  |  |  |         ax.plot(freqs, psd, label="\n".join(wrap(logname, 60)), alpha=0.6) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     ax.xaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator()) | 
					
						
							|  |  |  |     ax.yaxis.set_minor_locator(matplotlib.ticker.AutoMinorLocator()) | 
					
						
							|  |  |  |     ax.grid(which='major', color='grey') | 
					
						
							|  |  |  |     ax.grid(which='minor', color='lightgrey') | 
					
						
							|  |  |  |     fontP = matplotlib.font_manager.FontProperties() | 
					
						
							|  |  |  |     fontP.set_size('x-small') | 
					
						
							|  |  |  |     ax.legend(loc='best', prop=fontP) | 
					
						
							|  |  |  |     fig.tight_layout() | 
					
						
							|  |  |  |     return fig | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | # Plot data in a "spectrogram colormap" | 
					
						
							|  |  |  | def plot_specgram(data, logname, max_freq, axis): | 
					
						
							|  |  |  |     pdata, bins, t = calc_specgram(data, axis) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     fig, ax = matplotlib.pyplot.subplots() | 
					
						
							|  |  |  |     ax.set_title("\n".join(wrap("Spectrogram %s (%s)" % (axis, logname), | 
					
						
							|  |  |  |                  MAX_TITLE_LENGTH))) | 
					
						
							|  |  |  |     ax.pcolormesh(t, bins, pdata, norm=matplotlib.colors.LogNorm()) | 
					
						
							|  |  |  |     ax.set_ylim([0., max_freq]) | 
					
						
							|  |  |  |     ax.set_ylabel('frequency (hz)') | 
					
						
							|  |  |  |     ax.set_xlabel('Time (s)') | 
					
						
							|  |  |  |     fig.tight_layout() | 
					
						
							|  |  |  |     return fig | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | # CSV output | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def write_frequency_response(datas, output): | 
					
						
							| 
									
										
										
										
											2021-10-22 20:46:20 +02:00
										 |  |  |     helper = shaper_calibrate.ShaperCalibrate(printer=None) | 
					
						
							| 
									
										
										
										
											2020-10-15 02:08:10 +02:00
										 |  |  |     calibration_data = helper.process_accelerometer_data(datas[0]) | 
					
						
							|  |  |  |     for data in datas[1:]: | 
					
						
							| 
									
										
										
										
											2021-04-03 23:45:42 +02:00
										 |  |  |         calibration_data.add_data(helper.process_accelerometer_data(data)) | 
					
						
							| 
									
										
										
										
											2020-10-15 02:08:10 +02:00
										 |  |  |     helper.save_calibration_data(output, calibration_data) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def write_specgram(psd, freq_bins, time, output): | 
					
						
							|  |  |  |     M = freq_bins.shape[0] | 
					
						
							|  |  |  |     with open(output, "w") as csvfile: | 
					
						
							|  |  |  |         csvfile.write("freq\\t") | 
					
						
							|  |  |  |         for ts in time: | 
					
						
							|  |  |  |             csvfile.write(",%.6f" % (ts,)) | 
					
						
							|  |  |  |         csvfile.write("\n") | 
					
						
							|  |  |  |         for i in range(M): | 
					
						
							|  |  |  |             csvfile.write("%.1f" % (freq_bins[i],)) | 
					
						
							|  |  |  |             for value in psd[i,:]: | 
					
						
							|  |  |  |                 csvfile.write(",%.6e" % (value,)) | 
					
						
							|  |  |  |             csvfile.write("\n") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | # Startup | 
					
						
							|  |  |  | ###################################################################### | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def is_csv_output(output): | 
					
						
							|  |  |  |     return output and os.path.splitext(output)[1].lower() == '.csv' | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | def setup_matplotlib(output): | 
					
						
							| 
									
										
										
										
											2020-07-31 00:33:56 -04:00
										 |  |  |     global matplotlib | 
					
						
							| 
									
										
										
										
											2020-10-15 02:08:10 +02:00
										 |  |  |     if is_csv_output(output): | 
					
						
							|  |  |  |         # Only mlab may be necessary with CSV output | 
					
						
							|  |  |  |         import matplotlib.mlab | 
					
						
							|  |  |  |         return | 
					
						
							|  |  |  |     if output: | 
					
						
							| 
									
										
										
										
											2020-07-31 00:33:56 -04:00
										 |  |  |         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 | 
					
						
							| 
									
										
										
										
											2020-12-05 23:48:03 +01:00
										 |  |  |     usage = "%prog [options] <raw logs>" | 
					
						
							| 
									
										
										
										
											2020-07-31 00:33:56 -04:00
										 |  |  |     opts = optparse.OptionParser(usage) | 
					
						
							|  |  |  |     opts.add_option("-o", "--output", type="string", dest="output", | 
					
						
							|  |  |  |                     default=None, help="filename of output graph") | 
					
						
							| 
									
										
										
										
											2020-10-15 02:08:10 +02:00
										 |  |  |     opts.add_option("-f", "--max_freq", type="float", default=200., | 
					
						
							|  |  |  |                     help="maximum frequency to graph") | 
					
						
							|  |  |  |     opts.add_option("-r", "--raw", action="store_true", | 
					
						
							|  |  |  |                     help="graph raw accelerometer data") | 
					
						
							|  |  |  |     opts.add_option("-c", "--compare", action="store_true", | 
					
						
							|  |  |  |                     help="graph comparison of power spectral density " | 
					
						
							|  |  |  |                          "between different accelerometer data files") | 
					
						
							|  |  |  |     opts.add_option("-s", "--specgram", action="store_true", | 
					
						
							|  |  |  |                     help="graph spectrogram of accelerometer data") | 
					
						
							|  |  |  |     opts.add_option("-a", type="string", dest="axis", default="all", | 
					
						
							|  |  |  |                     help="axis to graph (one of 'all', 'x', 'y', or 'z')") | 
					
						
							| 
									
										
										
										
											2020-07-31 00:33:56 -04:00
										 |  |  |     options, args = opts.parse_args() | 
					
						
							| 
									
										
										
										
											2020-10-15 02:08:10 +02:00
										 |  |  |     if len(args) < 1: | 
					
						
							| 
									
										
										
										
											2020-07-31 00:33:56 -04:00
										 |  |  |         opts.error("Incorrect number of arguments") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # Parse data | 
					
						
							| 
									
										
										
										
											2020-12-05 23:48:03 +01:00
										 |  |  |     datas = [parse_log(fn, opts) for fn in args] | 
					
						
							| 
									
										
										
										
											2020-10-15 02:08:10 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |     setup_matplotlib(options.output) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if is_csv_output(options.output): | 
					
						
							|  |  |  |         if options.raw: | 
					
						
							|  |  |  |             opts.error("raw mode is not supported with csv output") | 
					
						
							|  |  |  |         if options.compare: | 
					
						
							|  |  |  |             opts.error("comparison mode is not supported with csv output") | 
					
						
							|  |  |  |         if options.specgram: | 
					
						
							| 
									
										
										
										
											2020-12-05 23:48:03 +01:00
										 |  |  |             if len(args) > 1: | 
					
						
							|  |  |  |                 opts.error("Only 1 input is supported in specgram mode") | 
					
						
							| 
									
										
										
										
											2020-10-15 02:08:10 +02:00
										 |  |  |             pdata, bins, t = calc_specgram(datas[0], options.axis) | 
					
						
							|  |  |  |             write_specgram(pdata, bins, t, options.output) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             write_frequency_response(datas, options.output) | 
					
						
							|  |  |  |         return | 
					
						
							| 
									
										
										
										
											2020-07-31 00:33:56 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # Draw graph | 
					
						
							| 
									
										
										
										
											2020-10-15 02:08:10 +02:00
										 |  |  |     if options.raw: | 
					
						
							| 
									
										
										
										
											2021-09-29 22:50:17 +02:00
										 |  |  |         fig = plot_accel(datas, args) | 
					
						
							| 
									
										
										
										
											2020-10-15 02:08:10 +02:00
										 |  |  |     elif options.specgram: | 
					
						
							| 
									
										
										
										
											2020-12-05 23:48:03 +01:00
										 |  |  |         if len(args) > 1: | 
					
						
							|  |  |  |             opts.error("Only 1 input is supported in specgram mode") | 
					
						
							| 
									
										
										
										
											2020-10-15 02:08:10 +02:00
										 |  |  |         fig = plot_specgram(datas[0], args[0], options.max_freq, options.axis) | 
					
						
							|  |  |  |     elif options.compare: | 
					
						
							| 
									
										
										
										
											2021-03-14 14:58:29 +01:00
										 |  |  |         fig = plot_compare_frequency(datas, args, options.max_freq, | 
					
						
							|  |  |  |                                      options.axis) | 
					
						
							| 
									
										
										
										
											2020-10-15 02:08:10 +02:00
										 |  |  |     else: | 
					
						
							|  |  |  |         fig = plot_frequency(datas, args, options.max_freq) | 
					
						
							| 
									
										
										
										
											2020-07-31 00:33:56 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     # 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() |