1#!/usr/bin/env python3 2# 3# Copyright 2020 Google, Inc. 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import os 18import logging 19import numpy 20import math 21 22from bokeh.layouts import layout 23from bokeh.models import CustomJS, ColumnDataSource 24from bokeh.models import tools as bokeh_tools 25from bokeh.models.widgets import DataTable, TableColumn 26from bokeh.plotting import figure, output_file, save 27 28 29def current_waveform_plot(samples, voltage, dest_path, plot_title): 30 """Plot the current data using bokeh interactive plotting tool. 31 32 Plotting power measurement data with bokeh to generate interactive plots. 33 You can do interactive data analysis on the plot after generating with the 34 provided widgets, which make the debugging much easier. To realize that, 35 bokeh callback java scripting is used. 36 37 Args: 38 samples: a list of tuples in which the first element is a timestamp and 39 the second element is the sampled current in milli amps at that time. 40 voltage: the voltage that was used during the measurement. 41 dest_path: destination path. 42 plot_title: a filename and title for the plot. 43 Returns: 44 plot: the plotting object of bokeh, optional, will be needed if multiple 45 plots will be combined to one html file. 46 dt: the datatable object of bokeh, optional, will be needed if multiple 47 datatables will be combined to one html file. 48 """ 49 logging.info('Plotting the power measurement data.') 50 51 time_relative = [sample[0] for sample in samples] 52 duration = time_relative[-1] - time_relative[0] 53 current_data = [sample[1] * 1000 for sample in samples] 54 avg_current = sum(current_data) / len(current_data) 55 56 color = ['navy'] * len(samples) 57 58 # Preparing the data and source link for bokehn java callback 59 source = ColumnDataSource( 60 data=dict(x=time_relative, y=current_data, color=color)) 61 s2 = ColumnDataSource( 62 data=dict(a=[duration], 63 b=[round(avg_current, 2)], 64 c=[round(avg_current * voltage, 2)], 65 d=[round(avg_current * voltage * duration, 2)], 66 e=[round(avg_current * duration, 2)])) 67 # Setting up data table for the output 68 columns = [ 69 TableColumn(field='a', title='Total Duration (s)'), 70 TableColumn(field='b', title='Average Current (mA)'), 71 TableColumn(field='c', title='Average Power (4.2v) (mW)'), 72 TableColumn(field='d', title='Average Energy (mW*s)'), 73 TableColumn(field='e', title='Normalized Average Energy (mA*s)') 74 ] 75 dt = DataTable(source=s2, 76 columns=columns, 77 width=1300, 78 height=60, 79 editable=True) 80 81 output_file(os.path.join(dest_path, plot_title + '.html')) 82 tools = 'box_zoom,box_select,pan,crosshair,redo,undo,reset,hover,save' 83 # Create a new plot with the datatable above 84 plot = figure(plot_width=1300, 85 plot_height=700, 86 title=plot_title, 87 tools=tools) 88 plot.add_tools(bokeh_tools.WheelZoomTool(dimensions='width')) 89 plot.add_tools(bokeh_tools.WheelZoomTool(dimensions='height')) 90 plot.line('x', 'y', source=source, line_width=2) 91 plot.circle('x', 'y', source=source, size=0.5, fill_color='color') 92 plot.xaxis.axis_label = 'Time (s)' 93 plot.yaxis.axis_label = 'Current (mA)' 94 95 # Callback JavaScript 96 source.selected.js_on_change( 97 "indices", 98 CustomJS(args=dict(source=source, mytable=dt), 99 code=""" 100 const inds = source.selected.indices; 101 const d1 = source.data; 102 const d2 = mytable.source.data; 103 var ym = 0 104 var ts = 0 105 var min=d1['x'][inds[0]] 106 var max=d1['x'][inds[0]] 107 d2['a'] = [] 108 d2['b'] = [] 109 d2['c'] = [] 110 d2['d'] = [] 111 d2['e'] = [] 112 if (inds.length==0) {return;} 113 for (var i = 0; i < inds.length; i++) { 114 ym += d1['y'][inds[i]] 115 d1['color'][inds[i]] = "red" 116 if (d1['x'][inds[i]] < min) { 117 min = d1['x'][inds[i]]} 118 if (d1['x'][inds[i]] > max) { 119 max = d1['x'][inds[i]]} 120 } 121 ym /= inds.length 122 ts = max - min 123 d2['a'].push(Math.round(ts*1000.0)/1000.0) 124 d2['b'].push(Math.round(ym*100.0)/100.0) 125 d2['c'].push(Math.round(ym*4.2*100.0)/100.0) 126 d2['d'].push(Math.round(ym*4.2*ts*100.0)/100.0) 127 d2['e'].push(Math.round(ym*ts*100.0)/100.0) 128 source.change.emit(); 129 mytable.change.emit(); 130 """)) 131 132 # Layout the plot and the datatable bar 133 save(layout([[dt], [plot]])) 134 return plot, dt 135 136 137def monsoon_histogram_plot(samples, dest_path, plot_title): 138 """ Creates a histogram from a monsoon result object. 139 140 Args: 141 samples: a list of tuples in which the first element is a timestamp and 142 the second element is the sampled current in milli amps at that time. 143 dest_path: destination path 144 plot_title: a filename and title for the plot. 145 Returns: 146 a tuple of arrays containing the values of the histogram and the 147 bin edges. 148 """ 149 milli_amps = [sample[1] * 1000 for sample in samples] 150 hist, edges = numpy.histogram(milli_amps, 151 bins=math.ceil(max(milli_amps)), 152 range=(0, max(milli_amps))) 153 154 output_file(os.path.join(dest_path, plot_title + '.html')) 155 156 plot = figure(title=plot_title, 157 y_axis_type='log', 158 background_fill_color='#fafafa') 159 160 plot.quad(top=hist, 161 bottom=0, 162 left=edges[:-1], 163 right=edges[1:], 164 fill_color='navy') 165 166 plot.y_range.start = 0 167 plot.xaxis.axis_label = 'Instantaneous current [mA]' 168 plot.yaxis.axis_label = 'Count' 169 plot.grid.grid_line_color = 'white' 170 171 save(plot) 172 173 return hist, edges 174 175 176def monsoon_tx_power_sweep_plot(dest_path, plot_title, currents, txs): 177 """ Creates average current vs tx power plot 178 179 Args: 180 dest_path: destination path 181 plot_title: a filename and title for the plot. 182 currents: List of average currents measured during power sweep 183 txs: List of uplink input power levels specified for each measurement 184 """ 185 186 output_file(os.path.join(dest_path, plot_title + '.html')) 187 188 plot = figure(title=plot_title, 189 y_axis_label='Average Current [mA]', 190 x_axis_label='Tx Power [dBm]', 191 background_fill_color='#fafafa') 192 193 plot.line(txs, currents) 194 plot.circle(txs, currents, fill_color='white', size=8) 195 plot.y_range.start = 0 196 197 save(plot) 198