From 2bb9aecfc62cb597155f50b6bf22131ab33cb301 Mon Sep 17 00:00:00 2001 From: jaseg Date: Thu, 25 Mar 2021 12:15:38 +0100 Subject: Update notebook, add frequency meter --- prototype/fw/freqmeter.py | 6 +- .../Accelerometer Data Analysis.ipynb | 2732 +++++++++++++++++--- 2 files changed, 2427 insertions(+), 311 deletions(-) diff --git a/prototype/fw/freqmeter.py b/prototype/fw/freqmeter.py index 54c1655..430b73f 100644 --- a/prototype/fw/freqmeter.py +++ b/prototype/fw/freqmeter.py @@ -12,11 +12,11 @@ count = lambda le_iter: sum(1 for _ in le_iter) DEFAULT_SAMPLING_RATE = 1e6 # sps -def sigrok_capture(duration:'seconds'=1, sampling_rate=DEFAULT_SAMPLING_RATE, driver='dreamsourcelab-dslogic', config=None, channel=0): +def sigrok_capture(duration:'milliseconds', sampling_rate=DEFAULT_SAMPLING_RATE, driver='dreamsourcelab-dslogic', config=None, channel=0): proc = subprocess.run(['sigrok-cli', '--driver', driver, - '--time', f'{duration}s', + '--time', f'{duration}ms', '--config', (f'{config},' if config else '') + f'samplerate={int(sampling_rate/1e3)}k', '--channels', str(channel), '--output-format', 'csv'], check=True, stdout=subprocess.PIPE) @@ -54,7 +54,7 @@ def calc_frequency(intervals, sampling_rate=DEFAULT_SAMPLING_RATE): if __name__ == '__main__': while True: - capture = sigrok_capture() + capture = sigrok_capture(1500) intervals = list(debounce(capture)) intervals = intervals[2:-1] # ignore partial first and last intervals diff --git a/prototype/sensor-analysis/Accelerometer Data Analysis.ipynb b/prototype/sensor-analysis/Accelerometer Data Analysis.ipynb index 23f31b8..72f13c8 100644 --- a/prototype/sensor-analysis/Accelerometer Data Analysis.ipynb +++ b/prototype/sensor-analysis/Accelerometer Data Analysis.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 703, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -24,7 +24,7 @@ }, { "cell_type": "code", - "execution_count": 704, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -33,7 +33,7 @@ }, { "cell_type": "code", - "execution_count": 705, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -45,7 +45,23 @@ }, { "cell_type": "code", - "execution_count": 836, + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "sampling_rate = 10 # sps, set in firmware\n", + "mems_lsb_per_g = 68 # LSBs per 1g for our accelerometer\n", + "\n", + "g = 9.8066\n", + "g_to_ms = lambda x: x * g\n", + "ms_to_g = lambda x: x / g\n", + "\n", + "r_mems = 55e-3 # radius of our sensor from the axis of rotation in m" + ] + }, + { + "cell_type": "code", + "execution_count": 83, "metadata": { "scrolled": false }, @@ -1018,7 +1034,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -1050,6 +1066,7 @@ " filtered = scipy.signal.sosfiltfilt(sos, y / mems_lsb_per_g)\n", " ax.plot(t, filtered, color='darkblue')\n", " \n", + " ax.set_xlabel(r't [mm:ss]')\n", " ax.set_ylabel(r'$a\\; [g]$')\n", " secax_y = ax.secondary_yaxis(\n", " 'right', functions=(g_to_ms, ms_to_g))\n", @@ -1114,7 +1131,7 @@ }, { "cell_type": "code", - "execution_count": 1019, + "execution_count": 84, "metadata": { "scrolled": false }, @@ -2090,7 +2107,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -2165,22 +2182,14 @@ "source": [ "t, y, _1, _2 = load_run(40, plot=False)\n", "\n", - "sampling_rate = 10 # sps, set in firmware\n", - "mems_lsb_per_g = 68 # LSBs per 1g for our accelerometer\n", - "\n", "ivl_start, ivl_end = 0.5, 1\n", "ivl_start, ivl_end = int(ivl_start*60*sampling_rate), int(ivl_end*60*sampling_rate)\n", "\n", - "fig, ax = plt.subplots()\n", + "fig, ax = plt.subplots(figsize=(5, 3))\n", "#ax.axvspan(ivl_start/60/sampling_rate, ivl_end/60/sampling_rate, color='orange', alpha=0.5)\n", " \n", "plot_measurements(ax, t, y)\n", "\n", - "g = 9.8066\n", - "g_to_ms = lambda x: x * g\n", - "ms_to_g = lambda x: x / g\n", - "\n", - "r_mems = 55e-3 # radius of our sensor from the axis of rotation in m\n", "le_data = [(0, 50, 3.12), (1,50,5.55), (2,40, 8.2), (3, 30, 10.2), (4,15, 12.5), (5,10, 15.6),\n", " (6,10, 19.2), (7,11, 11.6), (8,15, 6.49)]\n", "avg_include = [True, True, True, True, True, False, True, True, True]\n", @@ -2191,10 +2200,13 @@ " omegan = 2*np.pi*f_actual # angular velocity\n", " acc = omegan**2 * r_mems # m/s^2\n", " acc_theory.append(acc / g)\n", - " ax.axvspan(ts_abs-ivl_w/2, ts_abs+ivl_w/2, zorder=1, color='red', alpha=0.1)\n", + " \n", " \n", " ts_abs = ts_m + ts_s/60\n", " ivl_w = 0.5\n", + " \n", + " #ax.axvspan(ts_abs-ivl_w/2, ts_abs+ivl_w/2, zorder=1, color='red', alpha=0.1)\n", + " \n", " idx = (ts_abs - ivl_w/2 < t) & (t < ts_abs + ivl_w/2)\n", " ivl_avg = (y / mems_lsb_per_g)[idx].mean()\n", " acc_meas.append(ivl_avg)\n", @@ -2219,7 +2231,7 @@ "print(f'Found sensor offset: {sensor_offx:.2f} g / {sensor_offx*g:.2f} m/s^2')\n", "print()\n", "\n", - "for theory, meas, interval, (_1, _2, f_actual) in zip(acc_theory, acc_meas, interval_speeds, le_data):\n", + "for theory, meas, (_1, _2, f_actual) in zip(acc_theory, acc_meas, le_data):\n", " ax.axhline(theory - sensor_offx, color='orange', alpha=1, zorder=1)\n", " meas += sensor_offx\n", " \n", @@ -2228,12 +2240,15 @@ " print(f' Measurement: {meas:.2f} g / {meas*g} m/s^2')\n", " print(f' Rel. Error: {(theory/meas - 1.0) * 100:.2f} %')\n", " print(f' Abs. Error: {theory-meas:.2f} g / {(theory-meas)*g:.2f} m/s^2')\n", - " print()" + " print()\n", + "\n", + "fig.tight_layout()\n", + "fig.savefig('fig-acc-trace-steps.pdf')" ] }, { "cell_type": "code", - "execution_count": 1022, + "execution_count": 22, "metadata": {}, "outputs": [ { @@ -3209,10 +3224,10 @@ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 1022, + "execution_count": 22, "metadata": {}, "output_type": "execute_result" } @@ -3246,7 +3261,7 @@ }, { "cell_type": "code", - "execution_count": 957, + "execution_count": 77, "metadata": { "scrolled": false }, @@ -3255,6 +3270,38 @@ "name": "stdout", "output_type": "stream", "text": [ + "Loading run #28 with 4918 packets total, 178 distinct over 161.5061011314392s\n", + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 611 ... 813\n", + "Loading run #37 with 3070 packets total, 144 distinct over 117.43898510932922s\n", + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 326 ... 473\n", + "Loading run #30 with 2208 packets total, 198 distinct over 166.86816382408142s\n", + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 1079 ... 1288\n", + "Loading run #31 with 2630 packets total, 157 distinct over 126.19650292396545s\n", + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 1316 ... 1474\n", + "Loading run #34 with 2714 packets total, 121 distinct over 97.30304408073425s\n", + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 165 ... 287\n", + "Loading run #38 with 2317 packets total, 122 distinct over 103.3755898475647s\n", + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 475 ... 604\n", + "Loading run #32 with 2643 packets total, 118 distinct over 98.81923913955688s\n", + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 3 ... 1583\n", + "Loading run #39 with 1429 packets total, 129 distinct over 104.99669194221497s\n", + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 606 ... 737\n", "Loading run #33 with 3178 packets total, 125 distinct over 99.94346904754639s\n", "Packet length: 40\n", "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", @@ -4222,7 +4269,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -4230,73 +4277,92 @@ }, "metadata": {}, "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Largest peak at 4.33 Hz / 260 rpm\n", - "Mixing product 1 at 5.67 Hz / 340 rpm\n", - "Mixing product 2 at 14.33 Hz / 860 rpm\n", - "Mixing product 3 at 15.67 Hz / 940 rpm\n", - "Mixing product 4 at 24.33 Hz / 1460 rpm\n", - "Mixing product 5 at 25.67 Hz / 1540 rpm\n", - "Mixing product 6 at 34.33 Hz / 2060 rpm\n" - ] - }, - { - "data": { - "text/plain": [ - "4.3273542600896855" - ] - }, - "execution_count": 957, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ - "def estimate_freq_fft(t, y, interval):\n", - " s_min, s_max = interval\n", - " fft_idx = (t*60 > s_min) & (t*60 < s_max)\n", - "\n", - " N = fft_idx.sum()\n", - " T = 1/sampling_rate\n", - " x = np.linspace(0.0, N*T, N)\n", - " y = y[fft_idx] / mems_lsb_per_g # cut out beginning and that time we tapped the thing\n", - " y *= scipy.signal.windows.blackmanharris(len(y))\n", - " yf = scipy.fftpack.fft(y)\n", - " xf = np.linspace(0.0, 1/(2*T), N//2)\n", - " mag = 2/N * np.abs(yf[:N//2])\n", + "run_spans = {\n", + " 28: (4.35, 70, 120),\n", + "# 29: (3.10, 70, 120), Data is missing initial quiet period\n", + " 30: (7.04, 40, 120),\n", + " 31: (7.57, 30, 100),\n", + " 32: (10.4, 40, 90),\n", + " 33: (16.1, 40, 75),\n", + " 34: (8.62, 20, 80),\n", + " 37: (6.67, 20, 90),\n", + " 38: (9.43, 20, 80),\n", + " 39: (13.1, 20, 85),\n", + "}\n", "\n", - " fig, ax = plt.subplots()\n", - " ax.grid()\n", - " ax.plot(xf, mag)\n", - " ax.set_ylabel('Magnitude [g]')\n", - " ax.set_xlabel('Frequency [Hz]')\n", + "def plot_acceleration(runs, figsize=None, save=None):\n", + " acc_theory, acc_meas, acc_meas_err_tot, acc_meas_err_x, acc_meas_err_y, freqs = [], [], [], [], [], []\n", + " for run_id, (freq_meas, t_start, t_end) in sorted(run_spans.items(), key=lambda x: x[1][0]):\n", + " freqs.append(freq_meas)\n", + " omegan = 2*np.pi*freq_meas# angular velocity\n", + " acc = omegan**2 * r_mems # m/s^2\n", + " acc_theory.append(acc / g)\n", + " \n", + " t, y, packet_delay, packet_times = load_run(run_id, plot=False)\n", + " data_slice = y[(t > t_start/60) & (t < t_end/60)] / mems_lsb_per_g\n", + " silence_slice = y[t < 10/60] / mems_lsb_per_g\n", + " acc_meas.append(data_slice.mean() - silence_slice.mean())\n", + " \n", + " freq_meas_delta = 0.5 # Hz estimated\n", + " delta_due_to_f = 2*4*(np.pi**2)*freq_meas*r_mems*freq_meas_delta / g\n", + " #\n", + " # Absolute offset errors are corrected by subtracting the silence period at the beginning. Sensor noise\n", + " # is measured directly from the data and includes mechanical noise due to device vibrations. We have to\n", + " # account for sensitivity error (incorrect scaling of all measurements) and nonlinearity error\n", + " # (per-measurement scale error) here.\n", + " #\n", + " # We can safely ignore sensor temperature drift.\n", + " #\n", + " mems_nonl_err = 0.01\n", + " mems_sens_err = 0.05\n", + " mems_position_tol = 2e-3 # m\n", + " mems_position_err = omegan**2 * mems_position_tol / g\n", + " data_err_sq = data_slice.std()**2 + mems_nonl_err**2 + mems_sens_err**2 + mems_position_err**2\n", + " err = np.sqrt(delta_due_to_f**2 + data_err_sq)\n", + " #print(f'{delta_due_to_f=} {data_slice.std()=} {err=}')\n", + " \n", + " acc_meas_err_tot.append(err)\n", + " acc_meas_err_x.append(freq_meas_delta)\n", + " acc_meas_err_y.append(np.sqrt(data_err_sq))\n", "\n", - " peaks, _ = scipy.signal.find_peaks(mag, height=.1, distance=1/T)\n", - " assert peaks.any()\n", + " acc_meas = np.array(acc_meas)\n", + " acc_meas_std = np.array(acc_meas_err_tot)\n", + " \n", + " fig, ax = plt.subplots(figsize=figsize)\n", + " #ax.plot(freqs, acc_theory, label='Theory')\n", + " \n", + " tfreqs = np.linspace(0, 17, 1000)\n", + " ax.plot(tfreqs, (2*np.pi*tfreqs)**2 * r_mems / g, label='Theory')\n", + " \n", + " ax.fill_between(freqs, acc_meas-acc_meas_err_tot, acc_meas+acc_meas_err_tot, color='orange', alpha=0.2, zorder=1)\n", + " ax.errorbar(freqs, acc_meas, xerr=acc_meas_err_x, yerr=acc_meas_err_y, marker='.', label='Measurements', zorder=1)\n", "\n", - " peak_data = sorted([ (-mag[idx], xf[idx]) for idx in peaks ])\n", - " largest_peak_f = peak_data[0][1]\n", - " print(f'Largest peak at {largest_peak_f:.2f} Hz / {largest_peak_f * 60:.0f} rpm')\n", - " for i in range(1,4):\n", - " mix1 = i*sampling_rate - largest_peak_f\n", - " mix2 = i*sampling_rate + largest_peak_f\n", - " print(f'Mixing product {2*i-1} at {mix1:.2f} Hz / {mix1 * 60:.0f} rpm')\n", - " print(f'Mixing product {2*i} at {mix2:.2f} Hz / {mix2 * 60:.0f} rpm')\n", + " ax.grid()\n", + " ax.set_xlabel('$f\\;[Hz]$')\n", + " ax2 = ax.twiny()\n", + " x1, x2 = ax.get_xlim()\n", + " ax2.set_xlim((x1*60, x2*60))\n", + " ax2.set_xlabel('$f\\;[rpm]$')\n", + " ax.set_ylabel('$a\\;[g]$')\n", + " ax3 = ax.twinx()\n", + " y1, y2 = ax.get_ylim()\n", + " ax3.set_ylim(y1*g, y2*g)\n", + " ax3.set_ylabel('$a\\;[ms^-1]$')\n", "\n", - " ax.axvline(largest_peak_f, color='orange')\n", - " return largest_peak_f\n", + " ax.legend()\n", + " fig.tight_layout()\n", + " if save:\n", + " fig.savefig(save)\n", " \n", - "t, y, packet_delays, packet_times = load_run(33, plot=False)\n", - "estimate_freq_fft(t, y, (30, 75))" + "plot_acceleration(run_spans, figsize=(5, 3), save='fig-acc-theory-meas.pdf')" ] }, { "cell_type": "code", - "execution_count": 958, + "execution_count": 12, "metadata": { "scrolled": false }, @@ -4305,7 +4371,10 @@ "name": "stdout", "output_type": "stream", "text": [ - "Loading run #33 with 3178 packets total, 125 distinct over 99.94346904754639s\n" + "Loading run #33 with 3178 packets total, 125 distinct over 99.94346904754639s\n", + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 35 ... 161\n" ] }, { @@ -5269,7 +5338,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -5282,21 +5351,79 @@ "name": "stdout", "output_type": "stream", "text": [ - "Packet length: 40\n", - "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", - "Sequence number range: 35 ... 161\n" + "Largest peak at 4.33 Hz / 260 rpm\n", + "Mixing product 1 at 5.67 Hz / 340 rpm\n", + "Mixing product 2 at 14.33 Hz / 860 rpm\n", + "Mixing product 3 at 15.67 Hz / 940 rpm\n", + "Mixing product 4 at 24.33 Hz / 1460 rpm\n", + "Mixing product 5 at 25.67 Hz / 1540 rpm\n", + "Mixing product 6 at 34.33 Hz / 2060 rpm\n" ] + }, + { + "data": { + "text/plain": [ + "4.3273542600896855" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ - "load_run(33, plot=True);" + "def estimate_freq_fft(t, y, interval):\n", + " s_min, s_max = interval\n", + " fft_idx = (t*60 > s_min) & (t*60 < s_max)\n", + "\n", + " N = fft_idx.sum()\n", + " T = 1/sampling_rate\n", + " x = np.linspace(0.0, N*T, N)\n", + " y = y[fft_idx] / mems_lsb_per_g # cut out beginning and that time we tapped the thing\n", + " y *= scipy.signal.windows.blackmanharris(len(y))\n", + " yf = scipy.fftpack.fft(y)\n", + " xf = np.linspace(0.0, 1/(2*T), N//2)\n", + " mag = 2/N * np.abs(yf[:N//2])\n", + "\n", + " fig, ax = plt.subplots()\n", + " ax.grid()\n", + " ax.plot(xf, mag)\n", + " ax.set_ylabel('Magnitude [g]')\n", + " ax.set_xlabel('Frequency [Hz]')\n", + "\n", + " peaks, _ = scipy.signal.find_peaks(mag, height=.1, distance=1/T)\n", + " assert peaks.any()\n", + "\n", + " peak_data = sorted([ (-mag[idx], xf[idx]) for idx in peaks ])\n", + " largest_peak_f = peak_data[0][1]\n", + " print(f'Largest peak at {largest_peak_f:.2f} Hz / {largest_peak_f * 60:.0f} rpm')\n", + " for i in range(1,4):\n", + " mix1 = i*sampling_rate - largest_peak_f\n", + " mix2 = i*sampling_rate + largest_peak_f\n", + " print(f'Mixing product {2*i-1} at {mix1:.2f} Hz / {mix1 * 60:.0f} rpm')\n", + " print(f'Mixing product {2*i} at {mix2:.2f} Hz / {mix2 * 60:.0f} rpm')\n", + "\n", + " ax.axvline(largest_peak_f, color='orange')\n", + " return largest_peak_f\n", + " \n", + "t, y, packet_delays, packet_times = load_run(33, plot=False)\n", + "estimate_freq_fft(t, y, (30, 75))" ] }, { "cell_type": "code", - "execution_count": 959, - "metadata": {}, + "execution_count": 13, + "metadata": { + "scrolled": false + }, "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loading run #33 with 3178 packets total, 125 distinct over 99.94346904754639s\n" + ] + }, { "data": { "application/javascript": [ @@ -6258,7 +6385,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -6268,29 +6395,23 @@ "output_type": "display_data" }, { - "data": { - "text/plain": [ - "[]" - ] - }, - "execution_count": 959, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 35 ... 161\n" + ] } ], "source": [ - "fig, ax = plt.subplots()\n", - "x = np.linspace(0, 1, 100000)\n", - "y = 1 - (1 + np.tanh((x - 0.05)/0.05 * np.pi))/2\n", - "ax.plot(x, y)" + "load_run(33, plot=True);" ] }, { "cell_type": "code", - "execution_count": 1000, - "metadata": { - "scrolled": false - }, + "execution_count": 14, + "metadata": {}, "outputs": [ { "data": { @@ -7253,7 +7374,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -7263,31 +7384,1026 @@ "output_type": "display_data" }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - " ===== Run 29: =====\n", - "Loading run #29 with 2387 packets total, 206 distinct over 194.01968002319336s\n", - "Packet length: 40\n", - "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", - "Sequence number range: 827 ... 1071\n", - "interval: 0.10719954325502887\n", - "scores: [0.0005558591029225243, 0.007937380545979303, -0.034688942841427636, 0.009128952125600649, 0.013406031099251599, 0.013555935298953624, 0.017543481008845643, 0.017016779430178704, 0.01657709203008989]\n", - "argmin: 3\n", - "min score -0.034688942841427636 0.006114450132147767\n", - "Average speed of rotation: 3.11 Hz / 187 rpm\n", - "\n", - " f_meas = 3.10 Hz; f_est = 3.11 Hz\n", - " Δabs = 0.01 Hz; Δrel = +0.3 %\n", - "\n", - " ===== Run 28: =====\n", - "Loading run #28 with 4918 packets total, 178 distinct over 161.5061011314392s\n", - "Packet length: 40\n", - "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", - "Sequence number range: 611 ... 813\n", - "interval: 0.11439170940941061\n", - "scores: [-0.020534204449181033, -0.03948834332572777, 0.008701765576176747, 0.013951642455491747, 0.014883935285322263, 0.013919464173338898, 0.013597797966206103, 0.01283288815134905, 0.012482191795223349]\n", + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "fig, ax = plt.subplots()\n", + "x = np.linspace(0, 1, 100000)\n", + "y = 1 - (1 + np.tanh((x - 0.05)/0.05 * np.pi))/2\n", + "ax.plot(x, y)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: fig.ratio });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " evt.data.type = 'image/png';\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " evt.data\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * this.ratio;\n", + " var y = canvas_pos.y * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.which === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.which;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.which !== 17) {\n", + " value += 'ctrl+';\n", + " }\n", + " if (event.altKey && event.which !== 18) {\n", + " value += 'alt+';\n", + " }\n", + " if (event.shiftKey && event.which !== 16) {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k';\n", + " value += event.which.toString();\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(msg['content']['data']);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " ===== Run 29: =====\n", + "Loading run #29 with 2387 packets total, 206 distinct over 194.01968002319336s\n", + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 827 ... 1071\n", + "interval: 0.10719954325502887\n", + "scores: [0.0005558591029225243, 0.007937380545979303, -0.034688942841427636, 0.009128952125600649, 0.013406031099251599, 0.013555935298953624, 0.017543481008845643, 0.017016779430178704, 0.01657709203008989]\n", + "argmin: 3\n", + "min score -0.034688942841427636 0.006114450132147767\n", + "Average speed of rotation: 3.11 Hz / 187 rpm\n", + "\n", + " f_meas = 3.10 Hz; f_est = 3.11 Hz\n", + " Δabs = 0.01 Hz; Δrel = +0.3 %\n", + "\n", + " ===== Run 28: =====\n", + "Loading run #28 with 4918 packets total, 178 distinct over 161.5061011314392s\n", + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 611 ... 813\n", + "interval: 0.11439170940941061\n", + "scores: [-0.020534204449181033, -0.03948834332572777, 0.008701765576176747, 0.013951642455491747, 0.014883935285322263, 0.013919464173338898, 0.013597797966206103, 0.01283288815134905, 0.012482191795223349]\n", "argmin: 2\n", "min score -0.03948834332572777 0.003962460736427981\n", "Average speed of rotation: 4.37 Hz / 262 rpm\n", @@ -7349,146 +8465,1166 @@ "Average speed of rotation: 8.71 Hz / 523 rpm\n", "\n", " f_meas = 8.62 Hz; f_est = 8.71 Hz\n", - " Δabs = 0.09 Hz; Δrel = +1.1 %\n", - "\n", - " ===== Run 38: =====\n", - "Loading run #38 with 2317 packets total, 122 distinct over 103.3755898475647s\n", - "Packet length: 40\n", - "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", - "Sequence number range: 475 ... 604\n", - "interval: 0.10489854346020394\n", - "scores: [-0.029808429081348096, -0.022247814228522737, 0.007496451366422777, 0.012847680228334563, 0.012120698169116668, 0.01148795434824668, 0.0108151380653151, 0.010214297061686485, 0.009676702479492458]\n", - "argmin: 1\n", - "min score -0.029808429081348096 -0.08125594859438061\n", - "Average speed of rotation: 9.53 Hz / 572 rpm\n", - "\n", - " f_meas = 9.43 Hz; f_est = 9.53 Hz\n", - " Δabs = 0.10 Hz; Δrel = +1.1 %\n", - "\n", - " ===== Run 32: =====\n", - "Loading run #32 with 2643 packets total, 118 distinct over 98.81923913955688s\n", - "Packet length: 40\n", - "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", - "Sequence number range: 3 ... 1583\n", - "interval: 0.09441178519979387\n", - "scores: [-0.013690197808912317, -0.008608471942849465, 0.00431677808670242, 0.007819524539326253, 0.007824114618254871, 0.007696014525605405, 0.007352130944008647, 0.007048798098339794, 0.006677808724742963]\n", - "argmin: 1\n", - "min score -0.013690197808912317 -0.038159149663930074\n", - "Average speed of rotation: 10.59 Hz / 636 rpm\n", - "\n", - " f_meas = 10.40 Hz; f_est = 10.59 Hz\n", - " Δabs = 0.19 Hz; Δrel = +1.8 %\n", - "\n", - " ===== Run 39: =====\n", - "Loading run #39 with 1429 packets total, 129 distinct over 104.99669194221497s\n", - "Packet length: 40\n", - "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", - "Sequence number range: 606 ... 737\n", - "interval: -0.009543333906374466\n", - "scores: [0.00016699475726220442, 0.00015527347855214557, 0.0006494893315487613, 0.00013309155304469622, 0.0008010865133540054, 0.0010090902176491685, 0.001234839163051902, 0.00010351565236809708, 0.0014523737093152392]\n", - "argmin: 8\n", - "min score 0.00010351565236809708 0.0013446078569502677\n", - "Average speed of rotation: 13.10 Hz / 786 rpm\n", - "\n", - " f_meas = 13.10 Hz; f_est = 13.10 Hz\n", - " Δabs = -0.00 Hz; Δrel = -0.0 %\n", - "\n", - " ===== Run 33: =====\n", - "Loading run #33 with 3178 packets total, 125 distinct over 99.94346904754639s\n", - "Packet length: 40\n", - "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", - "Sequence number range: 35 ... 161\n", - "interval: 0.09687252539136751\n", - "scores: [-0.004649921628008072, -0.012634045995330379, 0.0037816457952707543, 0.0017246285208280352, 0.009875890071400597, 0.011254914748918448, 0.012497966748572316, 0.013301204816616601, 0.012601141405215724]\n", - "argmin: 2\n", - "min score -0.012634045995330379 0.013042674363101692\n", - "Average speed of rotation: 5.16 Hz / 310 rpm\n", - "\n", - " f_meas = 16.10 Hz; f_est = 5.16 Hz\n", - " Δabs = -10.94 Hz; Δrel = -67.9 %\n" + " Δabs = 0.09 Hz; Δrel = +1.1 %\n", + "\n", + " ===== Run 38: =====\n", + "Loading run #38 with 2317 packets total, 122 distinct over 103.3755898475647s\n", + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 475 ... 604\n", + "interval: 0.10489854346020394\n", + "scores: [-0.029808429081348096, -0.022247814228522737, 0.007496451366422777, 0.012847680228334563, 0.012120698169116668, 0.01148795434824668, 0.0108151380653151, 0.010214297061686485, 0.009676702479492458]\n", + "argmin: 1\n", + "min score -0.029808429081348096 -0.08125594859438061\n", + "Average speed of rotation: 9.53 Hz / 572 rpm\n", + "\n", + " f_meas = 9.43 Hz; f_est = 9.53 Hz\n", + " Δabs = 0.10 Hz; Δrel = +1.1 %\n", + "\n", + " ===== Run 32: =====\n", + "Loading run #32 with 2643 packets total, 118 distinct over 98.81923913955688s\n", + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 3 ... 1583\n", + "interval: 0.09441178519979387\n", + "scores: [-0.013690197808912317, -0.008608471942849465, 0.00431677808670242, 0.007819524539326253, 0.007824114618254871, 0.007696014525605405, 0.007352130944008647, 0.007048798098339794, 0.006677808724742963]\n", + "argmin: 1\n", + "min score -0.013690197808912317 -0.038159149663930074\n", + "Average speed of rotation: 10.59 Hz / 636 rpm\n", + "\n", + " f_meas = 10.40 Hz; f_est = 10.59 Hz\n", + " Δabs = 0.19 Hz; Δrel = +1.8 %\n", + "\n", + " ===== Run 39: =====\n", + "Loading run #39 with 1429 packets total, 129 distinct over 104.99669194221497s\n", + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 606 ... 737\n", + "interval: -0.009543333906374466\n", + "scores: [0.00016699475726220442, 0.00015527347855214557, 0.0006494893315487613, 0.00013309155304469622, 0.0008010865133540054, 0.0010090902176491685, 0.001234839163051902, 0.00010351565236809708, 0.0014523737093152392]\n", + "argmin: 8\n", + "min score 0.00010351565236809708 0.0013446078569502677\n", + "Average speed of rotation: 13.10 Hz / 786 rpm\n", + "\n", + " f_meas = 13.10 Hz; f_est = 13.10 Hz\n", + " Δabs = -0.00 Hz; Δrel = -0.0 %\n", + "\n", + " ===== Run 33: =====\n", + "Loading run #33 with 3178 packets total, 125 distinct over 99.94346904754639s\n", + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 35 ... 161\n", + "interval: 0.09687252539136751\n", + "scores: [-0.004649921628008072, -0.012634045995330379, 0.0037816457952707543, 0.0017246285208280352, 0.009875890071400597, 0.011254914748918448, 0.012497966748572316, 0.013301204816616601, 0.012601141405215724]\n", + "argmin: 2\n", + "min score -0.012634045995330379 0.013042674363101692\n", + "Average speed of rotation: 5.16 Hz / 310 rpm\n", + "\n", + " f_meas = 16.10 Hz; f_est = 5.16 Hz\n", + " Δabs = -10.94 Hz; Δrel = -67.9 %\n" + ] + } + ], + "source": [ + "def calc_rspeed_deltas(target_deltas):\n", + " target_deltas = np.array(target_deltas)\n", + " target_deltas = target_deltas[target_deltas > 1/50]\n", + " target_deltas = target_deltas[0:int(len(target_deltas)*0.9)]\n", + " def fun(x):\n", + " rms = np.sqrt(np.mean([ ((val + 0.5*x[0]) % x[0] - 0.5*x[0])**2 for val in target_deltas ]))\n", + " #matches = (target_deltas > 0.5*x[0]).mean()\n", + " matches1 = ((target_deltas > (0.9*x[0])) & (target_deltas < (1.1*x[0]))).mean()\n", + " matches2 = ((target_deltas > (1.9*x[0])) & (target_deltas < (2.1*x[0]))).mean()\n", + " #matchesh= ((target_deltas > (0.45*x[0])) & (target_deltas < (0.55*x[0]))).mean()\n", + " #matches3 = ((target_deltas > (2.9*x[0])) & (target_deltas < (3.1*x[0]))).mean()\n", + " #penalty = 1 - (1 + np.tanh((matches1 - 0.05)/0.05 * np.pi))/2\n", + " return rms - 0.5*matches1 - 0.25*matches2 #+ 0.125*matches3 #+ 0.1*penalty\n", + "\n", + " #def accept(x_old, x_new, **kwargs):\n", + " # return 1/30 < x_new[0] and x_new[0] < 1\n", + " res = scipy.optimize.minimize(fun, 0.1)\n", + " #res = scipy.optimize.basinhopping(fun, max(1/largest_peak_f, 0.5), accept_test=accept, stepsize=0.2)\n", + " \n", + " interval = res.x[0]\n", + " scores = [ fun([i*interval]) * (1/(i+10)) for i in range(1, 10) ]\n", + " print('interval:', interval)\n", + " print('scores:', scores)\n", + " argmin = np.argmin(scores)+1\n", + " print('argmin:', argmin)\n", + " print('min score', min(scores), fun([interval*0.5]))\n", + " interval = np.abs(interval) * argmin\n", + " print(f'Average speed of rotation: {1/interval:.2f} Hz / {60 / interval:.0f} rpm')\n", + " return interval\n", + "\n", + "def estimate_freq_delay(delays, times, interval, ax=None):\n", + " s_min, s_max = interval\n", + " tsa = np.array(times)\n", + " idx = (tsa > s_min) & (tsa < s_max)\n", + "\n", + " target_deltas = sorted(np.array(delays)[idx[:-1]])\n", + " interval = calc_rspeed_deltas(target_deltas)\n", + " \n", + " if ax is not None:\n", + " ax.grid()\n", + " for i in range(1, int(max(target_deltas)//interval)):\n", + " ax.axhline(i*interval, color='orange')\n", + " ax.plot(target_deltas)\n", + " return 1/interval\n", + " \n", + "run_spans = {\n", + " 28: (4.35, 70, 120),\n", + " 29: (3.10, 70, 120),\n", + " 30: (7.04, 40, 120),\n", + " 31: (7.57, 30, 100),\n", + " 32: (10.4, 40, 90),\n", + " 33: (16.1, 40, 75),\n", + " 34: (8.62, 20, 80),\n", + " 37: (6.67, 20, 90),\n", + " 38: (9.43, 20, 80),\n", + " 39: (13.1, 20, 85),\n", + " }\n", + "\n", + "deltas = []\n", + "fig, axs = plt.subplots(5, 2, figsize=(9.5, 14))\n", + "for (run_id, (freq_meas, t_start, t_end)), ax in zip(sorted(run_spans.items(), key=lambda x: x[1][0]), axs.flatten()):\n", + " \n", + " print()\n", + " print(f' ===== Run {run_id}: =====')\n", + " t, y, packet_delay, packet_times = load_run(run_id, plot=False)\n", + " freq_est = estimate_freq_delay(packet_delay, packet_times, (t_start, t_end), ax=ax)\n", + " ax.set_title(f'Run {run_id} @ $f_{{meas}} = {freq_meas:02.2f} Hz$ / $f_{{est}} = {freq_est:02.2f} Hz$')\n", + " print()\n", + " print(f' f_meas = {freq_meas:02.2f} Hz; f_est = {freq_est:02.2f} Hz')\n", + " delta_abs = freq_est - freq_meas\n", + " delta_rel = freq_est/freq_meas-1\n", + " deltas.append((delta_abs, delta_rel))\n", + " print(f' Δabs = {delta_abs:02.2f} Hz; Δrel = {delta_rel*100:+.1f} %')\n", + "fig.tight_layout()" + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: fig.ratio });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (fig.ws.readyState == 1 && width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " evt.data.type = 'image/png';\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " evt.data\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * this.ratio;\n", + " var y = canvas_pos.y * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.which === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.which;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.which !== 17) {\n", + " value += 'ctrl+';\n", + " }\n", + " if (event.altKey && event.which !== 18) {\n", + " value += 'alt+';\n", + " }\n", + " if (event.shiftKey && event.which !== 16) {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k';\n", + " value += event.which.toString();\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(msg['content']['data']);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " ===== Run 34: =====\n", + "Loading run #34 with 2714 packets total, 121 distinct over 97.30304408073425s\n", + "Packet length: 40\n", + "Very approximate lower bound on baudrate: 129032.25806451612 bd\n", + "Sequence number range: 165 ... 287\n", + "interval: 0.11474772130686393\n", + "scores: [-0.030846569646590577, -0.017939041958299002, 0.007585993694854823, 0.013487796252691883, 0.01286325763359304, 0.012345763316803614, 0.011619541945226932, 0.010974011837158768, 0.010396432266781988]\n", + "argmin: 1\n", + "min score -0.030846569646590577 -0.09545980709610284\n", + "Average speed of rotation: 8.71 Hz / 523 rpm\n", + "\n", + " f_meas = 8.62 Hz; f_est = 8.71 Hz\n", + " Δabs = 0.09 Hz; Δrel = +1.1 %\n" ] } ], "source": [ - "def calc_rspeed_deltas(target_deltas):\n", - " target_deltas = np.array(target_deltas)\n", - " target_deltas = target_deltas[target_deltas > 1/50]\n", - " target_deltas = target_deltas[0:int(len(target_deltas)*0.9)]\n", - " def fun(x):\n", - " rms = np.sqrt(np.mean([ ((val + 0.5*x[0]) % x[0] - 0.5*x[0])**2 for val in target_deltas ]))\n", - " #matches = (target_deltas > 0.5*x[0]).mean()\n", - " matches1 = ((target_deltas > (0.9*x[0])) & (target_deltas < (1.1*x[0]))).mean()\n", - " matches2 = ((target_deltas > (1.9*x[0])) & (target_deltas < (2.1*x[0]))).mean()\n", - " #matchesh= ((target_deltas > (0.45*x[0])) & (target_deltas < (0.55*x[0]))).mean()\n", - " #matches3 = ((target_deltas > (2.9*x[0])) & (target_deltas < (3.1*x[0]))).mean()\n", - " #penalty = 1 - (1 + np.tanh((matches1 - 0.05)/0.05 * np.pi))/2\n", - " return rms - 0.5*matches1 - 0.25*matches2 #+ 0.125*matches3 #+ 0.1*penalty\n", - "\n", - " #def accept(x_old, x_new, **kwargs):\n", - " # return 1/30 < x_new[0] and x_new[0] < 1\n", - " res = scipy.optimize.minimize(fun, 0.1)\n", - " #res = scipy.optimize.basinhopping(fun, max(1/largest_peak_f, 0.5), accept_test=accept, stepsize=0.2)\n", - " \n", - " interval = res.x[0]\n", - " scores = [ fun([i*interval]) * (1/(i+10)) for i in range(1, 10) ]\n", - " print('interval:', interval)\n", - " print('scores:', scores)\n", - " argmin = np.argmin(scores)+1\n", - " print('argmin:', argmin)\n", - " print('min score', min(scores), fun([interval*0.5]))\n", - " interval = np.abs(interval) * argmin\n", - " print(f'Average speed of rotation: {1/interval:.2f} Hz / {60 / interval:.0f} rpm')\n", - " return interval\n", + "fig, ax = plt.subplots(figsize=(5, 3))\n", + "run_id = 34\n", + "freq_meas, t_start, t_end = run_spans[run_id]\n", "\n", - "def estimate_freq_delay(delays, times, interval, ax=None):\n", - " s_min, s_max = interval\n", - " tsa = np.array(times)\n", - " idx = (tsa > s_min) & (tsa < s_max)\n", + "print()\n", + "print(f' ===== Run {run_id}: =====')\n", + "t, y, packet_delay, packet_times = load_run(run_id, plot=False)\n", + "freq_est = estimate_freq_delay(packet_delay, packet_times, (t_start, t_end), ax=ax)\n", + "#ax.set_title(f'Run {run_id} @ $f_{{meas}} = {freq_meas:02.2f} Hz$ / $f_{{est}} = {freq_est:02.2f} Hz$')\n", + "print()\n", + "print(f' f_meas = {freq_meas:02.2f} Hz; f_est = {freq_est:02.2f} Hz')\n", + "delta_abs = freq_est - freq_meas\n", + "delta_rel = freq_est/freq_meas-1\n", + "deltas.append((delta_abs, delta_rel))\n", + "print(f' Δabs = {delta_abs:02.2f} Hz; Δrel = {delta_rel*100:+.1f} %')\n", "\n", - " target_deltas = sorted(np.array(delays)[idx[:-1]])\n", - " interval = calc_rspeed_deltas(target_deltas)\n", - " \n", - " if ax is not None:\n", - " ax.grid()\n", - " for i in range(1, int(max(target_deltas)//interval)):\n", - " ax.axhline(i*interval, color='orange')\n", - " ax.plot(target_deltas)\n", - " return 1/interval\n", - " \n", - "run_spans = {\n", - " 28: (4.35, 70, 120),\n", - " 29: (3.10, 70, 120),\n", - " 30: (7.04, 40, 120),\n", - " 31: (7.57, 30, 100),\n", - " 32: (10.4, 40, 90),\n", - " 33: (16.1, 40, 75),\n", - " 34: (8.62, 20, 80),\n", - " 37: (6.67, 20, 90),\n", - " 38: (9.43, 20, 80),\n", - " 39: (13.1, 20, 85),\n", - " }\n", + "ax.set_ylabel(r'$\\Delta t\\;[s]$')\n", + "ax.set_xlabel(r'Cumulative packets')\n", "\n", - "deltas = []\n", - "fig, axs = plt.subplots(5, 2, figsize=(9.5, 14))\n", - "for (run_id, (freq_meas, t_start, t_end)), ax in zip(sorted(run_spans.items(), key=lambda x: x[1][0]), axs.flatten()):\n", - " \n", - " print()\n", - " print(f' ===== Run {run_id}: =====')\n", - " t, y, packet_delay, packet_times = load_run(run_id, plot=False)\n", - " freq_est = estimate_freq_delay(packet_delay, packet_times, (t_start, t_end), ax=ax)\n", - " ax.set_title(f'Run {run_id} @ $f_{{meas}} = {freq_meas:02.2f} Hz$ / $f_{{est}} = {freq_est:02.2f} Hz$')\n", - " print()\n", - " print(f' f_meas = {freq_meas:02.2f} Hz; f_est = {freq_est:02.2f} Hz')\n", - " delta_abs = freq_est - freq_meas\n", - " delta_rel = freq_est/freq_meas-1\n", - " deltas.append((delta_abs, delta_rel))\n", - " print(f' Δabs = {delta_abs:02.2f} Hz; Δrel = {delta_rel*100:+.1f} %')\n", - "fig.tight_layout()" + "fig.tight_layout()\n", + "fig.savefig('fig-comms-delays.pdf')" ] }, { "cell_type": "code", - "execution_count": 995, + "execution_count": 17, "metadata": {}, "outputs": [ { @@ -8452,7 +10588,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -8467,7 +10603,7 @@ "Text(0.5, 0, '$f\\\\;[Hz]$')" ] }, - "execution_count": 995, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -8480,7 +10616,7 @@ "\n", "#ax = ax.twinx()\n", "ax.grid()\n", - "ax.plot(freqs[:-1], [ delta_rel*100 for _delta_abs, delta_rel in deltas[:-1] ])#, color='orange')\n", + "ax.plot(freqs, [ delta_rel*100 for _delta_abs, delta_rel in deltas[:-1] ])#, color='orange')\n", "ax.set_ylabel('$\\Delta_{rel}\\;[\\%]$')\n", "\n", "ax.set_xlabel('$f\\;[Hz]$')" @@ -8488,7 +10624,7 @@ }, { "cell_type": "code", - "execution_count": 718, + "execution_count": 19, "metadata": { "scrolled": false }, @@ -9467,20 +11603,23 @@ "source": [ "fig, ax = plt.subplots()\n", "ax.grid()\n", - "ax.magnitude_spectrum(reassembled_values[ivl_start:ivl_end]/mems_lsb_per_g, Fs=10);" + "ax.magnitude_spectrum(y[ivl_start:ivl_end]/mems_lsb_per_g, Fs=10);" ] }, { "cell_type": "code", - "execution_count": 719, + "execution_count": 20, "metadata": {}, "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "Centrifugal acceleration at 4.31 Hz: 40.42 m/s^2 / 4.12 g\n", - "Centrifugal acceleration at 15.73 Hz: 537.19 m/s^2 / 54.78 g\n" + "ename": "NameError", + "evalue": "name 'largest_peak_f' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0mr_mems\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m55e-3\u001b[0m \u001b[0;31m# radius of our sensor from the axis of rotation in m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mf\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mlargest_peak_f\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 3\u001b[0m \u001b[0momega\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m2\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0mnp\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mpi\u001b[0m\u001b[0;34m*\u001b[0m\u001b[0mf\u001b[0m \u001b[0;31m# angular velocity\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0mcentrifugal_acceleration\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0momega\u001b[0m\u001b[0;34m**\u001b[0m\u001b[0;36m2\u001b[0m \u001b[0;34m*\u001b[0m \u001b[0mr_mems\u001b[0m \u001b[0;31m# m/s^2\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mNameError\u001b[0m: name 'largest_peak_f' is not defined" ] } ], @@ -9500,45 +11639,11 @@ }, { "cell_type": "code", - "execution_count": 720, + "execution_count": null, "metadata": { "scrolled": false }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Centrifugal acceleration at 0.10 Hz: 0.02 m/s^2 / 0.00 g\n", - "Centrifugal acceleration at 0.20 Hz: 0.09 m/s^2 / 0.01 g\n", - "Centrifugal acceleration at 0.50 Hz: 0.54 m/s^2 / 0.06 g\n", - "Centrifugal acceleration at 1.00 Hz: 2.17 m/s^2 / 0.22 g\n", - "Centrifugal acceleration at 1.50 Hz: 4.89 m/s^2 / 0.50 g\n", - "Centrifugal acceleration at 2.00 Hz: 8.69 m/s^2 / 0.89 g\n", - "Centrifugal acceleration at 2.50 Hz: 13.57 m/s^2 / 1.38 g\n", - "Centrifugal acceleration at 3.00 Hz: 19.54 m/s^2 / 1.99 g\n", - "Centrifugal acceleration at 3.50 Hz: 26.60 m/s^2 / 2.71 g\n", - "Centrifugal acceleration at 4.00 Hz: 34.74 m/s^2 / 3.54 g\n", - "Centrifugal acceleration at 4.50 Hz: 43.97 m/s^2 / 4.48 g\n", - "Centrifugal acceleration at 5.00 Hz: 54.28 m/s^2 / 5.54 g\n", - "Centrifugal acceleration at 6.00 Hz: 78.17 m/s^2 / 7.97 g\n", - "Centrifugal acceleration at 7.00 Hz: 106.39 m/s^2 / 10.85 g\n", - "Centrifugal acceleration at 8.00 Hz: 138.96 m/s^2 / 14.17 g\n", - "Centrifugal acceleration at 9.00 Hz: 175.88 m/s^2 / 17.93 g\n", - "Centrifugal acceleration at 10.00 Hz: 217.13 m/s^2 / 22.14 g\n", - "Centrifugal acceleration at 11.00 Hz: 262.73 m/s^2 / 26.79 g\n", - "Centrifugal acceleration at 12.00 Hz: 312.67 m/s^2 / 31.88 g\n", - "Centrifugal acceleration at 13.00 Hz: 366.95 m/s^2 / 37.42 g\n", - "Centrifugal acceleration at 14.00 Hz: 425.58 m/s^2 / 43.40 g\n", - "Centrifugal acceleration at 15.00 Hz: 488.55 m/s^2 / 49.82 g\n", - "Centrifugal acceleration at 16.00 Hz: 555.86 m/s^2 / 56.68 g\n", - "Centrifugal acceleration at 17.00 Hz: 627.51 m/s^2 / 63.99 g\n", - "Centrifugal acceleration at 18.00 Hz: 703.51 m/s^2 / 71.74 g\n", - "Centrifugal acceleration at 19.00 Hz: 783.84 m/s^2 / 79.93 g\n", - "Centrifugal acceleration at 20.00 Hz: 868.53 m/s^2 / 88.57 g\n" - ] - } - ], + "outputs": [], "source": [ "for fn in [0.1, 0.2, 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0,\n", " 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 20.0]:\n", @@ -9550,7 +11655,7 @@ }, { "cell_type": "code", - "execution_count": 721, + "execution_count": 21, "metadata": {}, "outputs": [ { @@ -10514,7 +12619,7 @@ { "data": { "text/html": [ - "" + "" ], "text/plain": [ "" @@ -10522,6 +12627,17 @@ }, "metadata": {}, "output_type": "display_data" + }, + { + "ename": "NameError", + "evalue": "name 'centrifugal_acceleration' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0mfig\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0max\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mplt\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0msubplots\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0max\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0maxvspan\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mivl_start\u001b[0m\u001b[0;34m/\u001b[0m\u001b[0;36m60\u001b[0m\u001b[0;34m/\u001b[0m\u001b[0msampling_rate\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mivl_end\u001b[0m\u001b[0;34m/\u001b[0m\u001b[0;36m60\u001b[0m\u001b[0;34m/\u001b[0m\u001b[0msampling_rate\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcolor\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m'orange'\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0malpha\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m0.5\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 6\u001b[0;31m \u001b[0max\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0maxhline\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcentrifugal_acceleration\u001b[0m\u001b[0;34m/\u001b[0m\u001b[0mg\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcolor\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m'orange'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 7\u001b[0m \u001b[0max\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0maxhline\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mcentrifugal_acceleration2\u001b[0m\u001b[0;34m/\u001b[0m\u001b[0mg\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mcolor\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m'orange'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 8\u001b[0m \u001b[0minterval\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mNameError\u001b[0m: name 'centrifugal_acceleration' is not defined" + ] } ], "source": [ -- cgit