Appendix B.1
“““
@author: Sean.Donohoe
This is the main code used to extract ginning energy data and times from
high resolution power data. Tested using power data that had a 5hz data
aggregation rate.
rate.
“““
import pandas as pd
import main_functions as mf #module containing needed functions
import viz_func as vf #module containing needed functions
#Including Metadata Flag
META_D = 1
#Hyperparamaters for algorithm (reset must be <= set)
WINDOW_01 = 50 #Each count is one time step for meter used
WINDOW_02 = 50 #Each count is one time step for meter used
SET_01, RESET_01 = 50, 45 #num counts of window for start and end
SET_02, RESET_02 = 8, 5 #num counts of window start and end (D: 8, 5)
MYFS = 5 #sample rate
E_RISE, INI = 1.1, [0, 450] #power rise over base, initial fit conditions (D: 1.1)
STDEV = 3
WIDTH, RIPPLE = 0.5/2.5, 50 #Filter parameters
#Folders containing data
INPUT_FOLDER = ‘./dataset/’
POW_COL = ‘PT (W) (200 ms)’ #name of power column in dataset
USER = pd.read_csv(‘./other/user_input.csv’) #(Figure 3: Block A01) RUN_SHEET = pd.read_csv(‘./other/metadata.csv’) #(Figure 3: Block A02) meta_dataFrame = pd.DataFrame() #dataframe to store metadata
#Lists used for temp storage
base_average_List, total_average_List = [], []
BaseEnergy_List, TotalEnergy_List = [], []
times_list, RMSE_list = [], []
#Import Data: CSV files in input_folder into pandas, return filenames (Figure 2: Block A) filesList, raw_data = mf.import_data(INPUT_FOLDER)
#Power Data Filtering (Noise Removal): my_data is list of DataFrames (Figure 2: Block B) filtered_power_data, coef = mf.filter_data(raw_data, my_fs = MYFS,
name = POW_COL, width =
WIDTH, ripple = RIPPLE)
vf.plot_filter_response(coef, MYFS) #visualize response
vf.filt_comp(raw_data[0][POW_COL], filtered_power_data[0], ‘test_0’) #filter effect [0] entry
#Main control loop
for i, data in enumerate(filtered_power_data): #(Figure 2: Block C) #Get filename, relavant metadata, and user start/end (Figure 7: Block D01) name = filesList[i].split(‘/’)[−1]
if META_D == 1:
test_meta_data = RUN_SHEET[RUN_SHEET[‘File’] = =name[:−4]]
meta_dataFrame = pd.concat([meta_dataFrame, test_meta_data])
ustart, uend = mf.get_user_start_end(USER, name[0:−4])
#Approximate 2nd Derivative & Get Upper Limit for User Slice (Figure 7: Block D02) delta2_Sigma, ABS_Delta_Delta = mf.usr_calc(data, ustart[0], uend[0], STDEV)
#Estimate Idle Members: 2nd Derivative Below Upper Limit (Figure 7: Block D03) LowVal = mf.low_val_test(ABS_Delta_Delta, delta2_Sigma)
w, intervals = mf.hyster_fun(WINDOW_01, SET_01, RESET_01, LowVal, start = 1)
#Model Baseline Power: Fit Idle Members (Figure 7: Block D05) Base, me, se, myRMSE = mf.fit_base(data, intervals, INI)
#Determine Active Ginning (Figure 10: Block E01) Ginpossible = mf.idel_or_gin(data, Base, E_RISE)
#Apply hysteresis: results form bounds of integration (Figure 10: Block E02) w, Ginning = mf.hyster_fun(WINDOW_02, SET_02, RESET_02, Ginpossible)
#Integrate Active Ginning Intervals (seperate out regions first) (Figure 10: Block E03) total_pow_lists = mf.breakout(Ginning, data)
base_lists = mf.breakout(Ginning, Base)
BEnergy_Whr, Times, base_avg = mf.integrate2(base_lists, 1/MYFS)
TEnergy_Whr, Times_02, total_avg = mf.integrate2(total_pow_lists, 1/MYFS)
#For DEBUG purposes display various plots
vf.fit_plot(data, intervals, Base, [0,len(data)],
[ustart[0], uend[0]], name[0:−4]) #Visualize the fit
vf.processed(data, Base, Ginning,
[ustart[0], uend[0]], name[0:−4], 000) #Power and Baseline
r_pow = raw_data[i][‘PT (W) (200 ms)’] #Unfiltered power data
#vf.compare_plot(data, r_pow, Base, Ginning, name[0:−4],[0, len(data)])
vf.single_plot(r_pow, name[0:−4], Ginning, [0,len(r_pow)], y_lim = [250,850]) #raw data
#The limits here are for plotting example data, other data may not work
#vf.visualizerC(data, name[0:−4], Ginning, [1100,2000]) #total energy fill
#vf.visualizerD(data, Base, name[0:−4], Ginning, [1100,2000]) #AE fill
#vf.base_E_plot(data, Base, name[0:−4], Ginning, [1100,2000]) #BE fill
#Print names and number of runs
print(‘File: ‘+name)
print(‘Runs Found: ‘+str(len(base_lists)))
#Add Results to List (Energy, Avg Power, Ginning Times) (Figure 10: Block E04) RMSE_list.append(myRMSE)
BaseEnergy_List = BaseEnergy_List + BEnergy_Whr
TotalEnergy_List = TotalEnergy_List + TEnergy_Whr
times_list = times_list + Times
base_average_List = base_average_List + base_avg
total_average_List = total_average_List + total_avg
#Save RMSE to DataFrame
pd.DataFrame(RMSE_list, columns = [“RMSE”]).to_csv(‘RMSE_data.csv’)
#Build Output: Save energy data to DataFrame (Figure 13: Block F01) energyDF = pd.DataFrame(BaseEnergy_List, columns = [“B_Energy_Whr”])
energyDF[“T_Energy_Whr”] = TotalEnergy_List
energyDF[“Act_Energy”] = energyDF[“T_Energy_Whr”]-energyDF[“B_Energy_Whr”]
energyDF[“Time”] = times_list
energyDF[“Base_Avg_W”] = base_average_List
energyDF[“Total_Avg_W”] = total_average_List
if META_D == 1:
#Concat with the metadata
if len(test_meta_data) != len(base_lists):
print(‘CAUTION: Possible Run Mismatch’)
meta_dataFrameA = meta_dataFrame.reset_index(drop = True) #reset index
energyDF = pd.concat([energyDF,meta_dataFrameA], axis = 1, sort = False)
energyDF.to_csv(‘./output/processed_results.csv’) #save
Appendix B.2
“““
@author: Sean.Donohoe
Contains methods needed to filter data, detect ginning, and seperate
total energy used vs. active energy going to ginning (plus losses).
This should be named main_functions
“““
import math
import statistics
from os import listdir
from scipy import optimize, signal, integrate
from sklearn.metrics import mean_squared_error
import numpy as np
import pandas as pd
def import_data(input_folder):
‘‘‘
Method to read in data from folder return list of filenames.
and a list where each element is a DataFrame of the CSV contents
input_folder = string of folder path
‘‘‘
files_list = []
my_data = []
for filename in listdir(input_folder): #Get all Filename (Block A03)
#Import each CSV file into list of DataFrame (Block A04)
if filename[−4:] == ‘.csv’:
name = input_folder + filename
files_list.append(name)
my_data.append(pd.read_csv(name))
return (files_list, my_data)
def filter_data(my_data, my_fs, name, width = 0.5/2.5, ripple = 50):
‘‘‘
Method to filter each element of a list.
my_data = list of DataFrames where
my_fs = sampling frequency used by equipment
name = name of power column in dataset
width = Desired transition width (Hz) over nyquest freq
Ripple = ripple used in kaiserord: default 50
--> Returns filter coefficients and filtered data
‘‘‘
#Generate Filter Coefficients (Block B01)
ntaps, beta = signal.kaiserord(ripple, width) #window paramters
coef = signal.firwin(ntaps, 0.5, fs = my_fs, window = (‘kaiser’, beta))
print(‘ntaps: ‘+str(ntaps))
print(‘beta: ‘+str(beta))
#Control Loop (Block B02)
filtered_power_data = []
for f in my_data:
#Apply Filter to Power Column (Block B03)
filter_output2 = lowpass_alt2(f[name], coef)
#Left Shift Result: Removes FIR delay (Block B04)
shift_ammount = int(0.5*(ntaps − 1)) #total FIR delay
filter_output2list = filter_output2.tolist() #convert array to list
del filter_output2list[0:shift_ammount] #remove first elements
filter_output2_array = np.array(filter_output2list) #convert to array
#Add Filtered Data Array to List (Block B05)
filtered_power_data.append(filter_output2_array)
return (filtered_power_data, coef)
def lowpass_alt2(data, coef):
‘‘‘
Method to apply filter using the initial steady state value.
data = data to filter
coef = filter coefficients
‘‘‘
#Generate filter
yini = signal.lfilter_zi(coef, 1) #get initial steady state
y_vals, _ = signal.lfilter(coef, 1, data, zi = yini*data[0]) #apply filter
return y_vals
def get_user_start_end(data, name):
‘‘‘
Method to get user stand and end values from the dataframe
data = the datafame that contains the values
name = name of the file
‘‘‘
ustart = data[data[‘File’]==name][‘UserStart’].values
uend = data[data[‘File’]==name][‘UserEnd’].values
return (ustart, uend)
def usr_calc(data, my_start, my_end, num_sd = 3):
‘‘‘
Method takes data and user slice (start/end), applies np.diff twice to
get difference of differences (i.e., approx 2nd derivative), and
calculate cutoff based on user slice.
my_data = input data
my_start = start point
my_end = ending point
num_sd = number of standard deviations
‘‘‘
#Double apply np.diff, adding zeros so lenth does not change
abs_dif2 = abs(np.diff(np.hstack(([data[0],data[0]], data)), n = 2))
testvals = abs_dif2[my_start:my_end] #User slice
delta2_mean = statistics.mean(testvals) #User slice mean
delta2_stdev = statistics.stdev(testvals) #User slice stdev
#Cutoff based on user slice
beta = delta2_mean + num_sd*delta2_stdev
return (beta, abs_dif2)
def low_val_test(myinput, cutoff):
‘‘‘
Method to test if myinput data is less than cuttoff value.
myinput = data_ABS_Delta_Delta [array]
cutoff = delta2_PlusN_Sigma
Determine if the ABS_Delta_Delta values are below cutoff value
‘‘‘
return 1*(myinput < cutoff)
def hyster_fun(test_window, setpoint, reset, mydata, start = 0):
‘‘‘
Method to apply hysteresis to the given data.
testwindow = size of test window
setpoint = setpoint where value goes high
reset = value of rolling window that resets value low
mydata = list like item containing the data
start = if equals 1 will pad with NaN
--> Return tuple of window sums and data with hysteresis applied
‘‘‘
#convert list to pandas so can use rolling window
data = pd.DataFrame()
data[‘lowval’] = mydata
was_in_zone = 0 #Initial value
my_window = data[‘lowval’].rolling(test_window).sum() #Sum rolling window
the_zone = [] #List to hold outputs
for i, value in enumerate(my_window):
#In startup condition dont apply logic, buffer with NaN
if start == 1 and i < test_window:
the_zone.append(np.NaN)
else:
#Not currently in zone, but thrshold meet for start
if (value >= setpoint and was_in_zone == 0):
the_zone.append(1)
was_in_zone = 1
#Currently in zone, value not yet fallen below threshold
elif (value >= reset and was_in_zone == 1):
the_zone.append(1)
was_in_zone = 1
#Value below threshold, fell out of the zone
else:
was_in_zone = 0
the_zone.append(0)
return (my_window, the_zone)
def fit_base(inputdata, interval, ini):
‘‘‘
Method to fit curve to given datapoints. Typically used to fit low value
intervals such that it can model the baseline power.
inputdata = power data [array]
interval = array of intervals where “smooth” [list]
ini = initial parameters used by the fit
-->Return baseline fit, RMSE, mean error, stdev of error
‘‘‘
#Get index locations where “my_data[‘LowValue_Interval’]” == 1
#Get power data matching index locations
interval = np.array(interval)
condition = interval == 1 #condtion to find
index_to_fit = condition.nonzero()[0] #indicies of interval elements == 1
y_to_fit = inputdata[index_to_fit] #data @ indicies
#Fit the data
p_opt, *e = optimize.curve_fit(linear_curve, index_to_fit, y_to_fit, ini)
#Using fit paramaters, generate values for curve
x_base = np.linspace(0, len(inputdata−1), len(inputdata))
#Get baseline curve (use unpacked p_opt)
baseline = linear_curve(x_base, *p_opt)
#Get model evaluated @ indexes where fit was done
modeled_values = linear_curve(index_to_fit, *p_opt)
#Get error information
errors = modeled_values − y_to_fit
errors_mean = statistics.mean(errors)
stdev_errors = statistics.stdev(errors)
my_rmse = math.sqrt(mean_squared_error(y_to_fit, modeled_values)) #RMSE
return (baseline, errors_mean, stdev_errors, my_rmse)
def linear_curve(x_val, k_1, k_2):
‘‘‘
Method to define linear curve.
x_val = x-axis inputs, possibly list
k_1 = slope
k_2 = offset
‘‘‘
return k_1*np.array(x_val) + k_2
def idel_or_gin(power_data, baseline, rise = 1.1):
‘‘‘
Method to determine if idle or ginning based on power rise above baseline.
powerData = data to check, usually filtered power data [array]
baseline = baseline power data [array]
rise = % over baseline to count as ginning, default = 1.1 (i.e., 10% over)
‘‘‘
#Find all locations where idle (less than estimate OR null)
test1 = power_data < (rise*baseline)
test2 = pd.isnull(power_data)
check_power = test1 | test2
#Get ginning locations (i.e., where not idle)
return 1*np.invert(check_power)
def breakout(intervals, power_data):
‘‘‘
Method separates the active ginning intervals from the single long array.
Intervals = array like containing ginning intervals where 1 = ginning
PowerData = array like containing power data to break up
--> Returns each interval as a list entry. Typically, this is power data,
this will be integrated to get energy.
‘‘‘
output = []
lastval = 0 #used to track last state
count = −1 #count of total intervals found
for i,val in enumerate(intervals):
#starting new interval
if val == 1 and lastval == 0:
count = count + 1 #new interval, add to total count
output.append([]) #add new entry into list of lists
#add power data to interval
output[count].append(power_data[i])
lastval = 1 #update last state
#continuing interval
elif val == 1 and lastval == 1:
#add power data to interval
output[count].append(power_data[i])
lastval = 1 #update last state
else:
lastval = 0 #not in interval
return output
def integrate2(data_list, sample_rate, decimals = 5):
‘‘‘
Method to integrate the data and also calculate the average value.
dataList = list containing lists such that dataList[0]
is the first item to integrate, dataList[1] is the second
and so forth.
decimals = how to round result
sample_rate = sample rate of data in seconds per sample
--> Returns energy values in watt-hours and average values in watts.
‘‘‘
energy_result = []
run_time = []
average_power = []
for data in data_list:
#Endpoint of Integral (also gin time in seconds)
endpoint = len(data)*sample_rate
run_time.append(endpoint)
#X index is in seconds... linespace(Start, Stop, Number To Use)
seconds_index = np.linspace(0, endpoint, len(data))
#Integrate data to get Watt-Seconds (since x axis is in seconds)
integral_result = integrate.trapz(data,seconds_index)
#Divide by 3600 and round to get Watt-Hours
energy_result.append(round(integral_result/3600,decimals))
#Average of power based on Average Value Theorem of Calculus
average_power.append(round(integral_result/endpoint,decimals))
return (energy_result, run_time, average_power)
Appendix B.3
“““
@author: Sean.Donohoe
Contains methods to visualize various various parts of the data
This should be named viz_func
“““
import matplotlib.pyplot as plt
import matplotlib.ticker as smf
import numpy as np
from scipy.signal import freqz
def string_formatted_10k(x, pos):
‘‘‘
Used to format large values with commas
‘‘‘
if (x >= 10,000):
out = f”{x:,.0f}”
else:
out = f”{x:.0f}”
return out
def processed(data, baseline, ginning, u_val, filename, sample_size):
‘‘‘
Method to plot data along with fitted baseline and ginning detection.
data = filtered power data
baseline = fitted baseline power
ginning = True/False active ginning
u_val = list[0] is user sleected start, list[1] is end
filename = data_file_name[0:−4] (i.e., no file extension)
sample_size = size of sample that the data represents
‘‘‘
fig0 = plt.figure(figsize = (5.5, 4)) #(3, 3.25) #(5.5,4)
ax_1 = fig0.add_subplot(111)
ax_2 = ax_1.twinx()
ax_1.plot(data, color = ‘r’, label = ‘Power’)
xmin, xmax = ax_1.get_xlim()
span = xmax − xmin
ax_1.plot(baseline, color = ‘b’, linestyle = ‘dotted’, label = ‘Baseline’)
ax_2.plot(ginning, color = ‘y’, linestyle = ‘dashdot’, label = ‘Ginning T/F’)
plt.yticks([1.0, 0.0], [“True”, “False”]) #Set ticks on seconday axis
#show user selection
ax_2.axhline(0.5, (u_val[0]-xmin)/span, (u_val[1]-xmin)/span, color = ‘m’,
label = ‘User’)
#plt.title(‘Filtered Data + Baseline: ‘+filename+’:’+str(sample_size) + ‘g’)
ax_1.set_ylim([250, 850]) #[400, 700] good fit for example data
ax_1.set_xlabel(‘Aquisition Sample Point’)
ax_1.set_ylabel(‘Source Power [W]’)
ax_2.set_ylabel(‘Ginning True/False’)
#ax_1.set_title(‘b’, x = 0.9, y = 0.8, size = 12,
# bbox = dict(facecolo none’, edgecolor = ‘black’))
#format
ax_1.xaxis.set_major_formatter(smf.FuncFormatter(string_formatted_10k))
fig0.legend(loc = ‘upper center’, bbox_to_anchor = (0.5, 0.95), ncol = 4)
fig0.savefig(‘./output/’ + filename + ‘_processed.svg’, bbox_inches = ‘tight’,
format = ‘svg’)
def filt_comp(data1, data2, filename):
‘‘‘
Used to comapre fiiltered and unfiltered data
‘‘‘
font = {‘family’: ‘Arial’, ‘weight’: ‘normal’, ‘size’: 8}
plt.rc(‘font’, **font)
fig0 = plt.figure(figsize = (4.5,4))
#Source data upper
ax_3 = fig0.add_subplot(211)
ax_3.plot(data1, color = ‘r’)
ax_3.set_ylim([350,800])
ax_3.set_ylabel(‘Power [W]’)
ax_3.set_title(‘a’, x = 0.96, y = 0.72, size = 12,
bbox = dict(facecolor = ‘none’, edgecolor = ‘black’))
#plt.title(‘Filtered Data + Baseline: ‘+filename)
#Processed data lower
ax_1 = fig0.add_subplot(212)
ax_1.plot(data2, color = ‘r’)
ax_1.set_ylim([350,800])
ax_1.set_ylabel(‘Power [W]’)
ax_1.set_xlabel(‘Aquisition Sample Point’)
ax_1.set_title(‘b’, x = 0.96, y = 0.72, size = 12,
bbox = dict(facecolor = ‘none’, edgecolor = ‘black’))
#Format
ax_3.xaxis.set_major_formatter(smf.FuncFormatter(string_formatted_10k))
ax_1.xaxis.set_major_formatter(smf.FuncFormatter(string_formatted_10k))
fig0.savefig(‘./output/’ + filename + ‘_filter_comp.svg’, format = ‘svg’,
bbox_inches = ‘tight’, transparent = True)
def compare_plot(data1, data2, base, ginning, filename, limits):
‘‘‘
Does the same as “processed” method but also plots raw data to compare.
data1 = filtered power data
data2 = unfiltered power data
base = fitted baseline power
ginning = True/False active ginning
filename = data_file_name[0:−4] (i.e., no file extension)
‘‘‘
font = {‘family’: ‘Arial’, ‘weight’: ‘normal’, ‘size’: 8}
plt.rc(‘font’, **font)
lower_lim = limits[0]
upper_lim = limits[1]
span = upper_lim − lower_lim
index = np.linspace(lower_lim, upper_lim, span, endpoint = False)
fig0 = plt.figure(figsize = (4,2))
#Source data upper
ax_3 = fig0.add_subplot(121)
ax_3.plot(index, data2[lower_lim:upper_lim], color = ‘r’)
ax_3.set_ylim([400,800])
ax_3.set_ylabel(‘Source Power [W]’)
#plt.title(‘Filtered Data + Baseline: ‘ + filename)
#Processed data lower
ax_1 = fig0.add_subplot(122)
ax_2 = ax_1.twinx()
ax_1.plot(index, data1[lower_lim:upper_lim], color = ‘r’)
ax_1.plot(index, base[lower_lim:upper_lim], color = ‘b’, linestyle = ‘dotted’,
linewidth = 5, label = ‘Baseline’)
ax_1.yaxis.set_tick_params(labelbottom = False)
ax_2.plot(index, ginning[lower_lim:upper_lim], color = ‘y’,
linestyle = ‘dashdot’, label = ‘GinInt’)
plt.yticks([1.0, 0.0], [“True”, “False”]) #Set ticks on seconday axis
#ax_1.set_ylabel(‘Source Power [W]’)
ax_1.set_ylim([400,800])
#ax_2.set_ylabel(‘Ginning True/False’)
#ax_1.set_xlabel(‘Aquisition Sample Point’)
fig0.savefig(‘./output/’ + filename + ‘.svg’, format = ‘svg’,
bbox_inches = ‘tight’, transparent = True)
def single_plot(data1, filename, ginning, limits, y_lim = [300,800]):
‘‘‘
Used to plot single input
r_pow = my_data [8][‘PT (W) (200 ms)’]
visualizerB(r_pow, ‘name’, [0,len(r_pow)])
‘‘‘
font = {‘family’: ‘Arial’, ‘weight’: ‘normal’, ‘size’: 8}
plt.rc(‘font’, **font)
lower_lim = limits[0]
upper_lim = limits[1]
span = upper_lim − lower_lim
index = np.linspace(lower_lim, upper_lim, span, endpoint = False)
fig0 = plt.figure(figsize = (5.5,4)) #(5.5,4) #(3.2,3)
ax_3 = fig0.add_subplot(111)
ax_3.plot(index, data1[lower_lim:upper_lim], color = ‘r’, label = ‘Raw’)
ax_3.set_ylim([y_lim[0],y_lim[1]])
ax_3.set_ylabel(‘Source Power [W]’)
ax_3.set_xlabel(‘Aquisition Sample Point’)
#ax_3.set_title(‘b’, x = 0.9, y = 0.8, size = 12,
# bbox = dict(facecolor = ‘none’, edgecolor = ‘black’))
fig0.legend(loc = ‘upper center’, bbox_to_anchor = (0.5, 0.95), ncol = 4)
#format
ax_3.xaxis.set_major_formatter(smf.FuncFormatter(string_formatted_10k))
fig0.savefig(‘./output/’ + filename + ‘.svg’, format = ‘svg’,
bbox_inches = ‘tight’, transparent = True)
def visualizerC(data1, filename, ginning, plt_limits, y_lim = [300,800]):
‘‘‘
Used to plot a single input while also shading the area under the curve
based on the ginning regions detected (total energy).
‘‘‘
#limits of interest
lower_lim = plt_limits[0]
upper_lim = plt_limits[1]
span = upper_lim − lower_lim
#get index where ginning starts/stops
c_index, crossing_count = zero_crossing(ginning[lower_lim:upper_lim])
#Start plotting
font = {‘family’: ‘Arial’, ‘weight’: ‘normal’, ‘size’: 8}
plt.rc(‘font’, **font)
index = np.linspace(lower_lim, upper_lim, span, endpoint = False)
fig0 = plt.figure(figsize = (2,3))
ax_3 = fig0.add_subplot(111)
ax_3.plot(index, data1[lower_lim:upper_lim], color = ‘r’, label = ‘Power’)
ax_3.set_ylim([y_lim[0],y_lim[1]])
ax_3.set_ylabel(‘Source Power [W]’)
ax_3.set_xlabel(‘Aquisition Sample Point’)
ax_3.set_title(‘a’, x = 0.8, y = 0.8, size = 12,
bbox = dict(facecolor = ‘none’, edgecolor = ‘black’))
#Setup fill under for energy
for i in range(0, crossing_count, 2):
span = c_index[i + 1] − c_index[i]
#offset by the lower limit since cross_index referenced to original data
index = np.linspace(c_index[i] + lower_lim + 1, c_index[i + 1] + lower_lim, span)
ax_3.fill_between(index, data1[c_index[i] + lower_lim:c_index[i + 1] + lower_lim]
, color = “none”, hatch=‘X’*4, edgecolor=“b”, label=‘Energy’)
fig0.legend(loc=‘upper center’, bbox_to_anchor=(0.5, 0.98), ncol=2)
fig0.savefig(‘./output/’ + filename + ‘_viz_totalE.svg’, format = ‘svg’,
bbox_inches = ‘tight’, transparent = True)
def visualizerD(data1, data2, filename, ginning, plt_limits, y_lim = [300,800]):
‘‘‘
Used to plot a single input while also shading the area between data1
and data2 based on the ginning regions detected (active energy).
‘‘‘
#limits of interest
lower_lim = plt_limits[0]
upper_lim = plt_limits[1]
span = upper_lim − lower_lim
#get index where ginning starts/stops
c_index, crossing_count = zero_crossing(ginning[lower_lim:upper_lim])
#Start plotting
font = {‘family’: ‘Arial’, ‘weight’: ‘normal’, ‘size’: 8}
plt.rc(‘font’, **font)
index = np.linspace(lower_lim, upper_lim, span, endpoint = False)
fig0 = plt.figure(figsize = (2,3))
ax_3 = fig0.add_subplot(111)
ax_3.plot(index, data1[lower_lim:upper_lim], color = ‘r’, label = ‘Power’)
ax_3.set_ylim([y_lim[0],y_lim[1]])
ax_3.set_ylabel(‘Source Power [W]’)
ax_3.set_xlabel(‘Aquisition Sample Point’)
ax_3.set_title(‘c’, x = 0.8, y = 0.8, size = 12,
bbox = dict(facecolor = ‘none’, edgecolor = ‘black’))
#Setup fill under for energy
for i in range(0, crossing_count, 2):
span = c_index[i + 1] − c_index[i]
#offset by the lower limit since cross_index referenced to original data
index = np.linspace(c_index[i] + lower_lim + 1, c_index[i + 1] + lower_lim, span)
ax_3.fill_between(index, data1[c_index[i] + lower_lim:c_index[i + 1] + lower_lim],
data2[c_index[i] + lower_lim:c_index[i + 1] + lower_lim],
color = “none”, hatch=‘X’*4, edgecolor=“b”, label=“Energy”)
fig0.legend(loc=‘upper center’, bbox_to_anchor=(0.5, 0.98), ncol=2)
fig0.savefig(‘./output/’ + filename + ‘_viz_activeE.svg’, format = ‘svg’,
bbox_inches = ‘tight’, transparent = True)
def fit_plot(data, interval, baseline, my_range, u_val, filename):
‘‘‘
Used to visualize the fitment of the baseline power
‘‘‘
l_lim = my_range[0]
u_lim = my_range[1]
u_span = u_lim-l_lim
#X-values
index = np.linspace(l_lim, u_lim, u_span, endpoint = False)
#Plot data
fig0 = plt.figure(figsize = (5,3))
ax_3 = fig0.add_subplot(111)
ax_3.plot(index, data[l_lim:u_lim], color = ‘r’, label = ‘Power Data’)
#Plot points used in fit and the fit itself
interval = np.array(interval)
condition = interval[l_lim:u_lim] == 1 #condtion to find
index_to_fit = condition.nonzero()[0] #indicies of interval elements == 1
y_to_fit = data[index_to_fit + l_lim] #data @ indicies
ax_3.scatter(index_to_fit + l_lim,y_to_fit, color = ‘b’, label = ‘Fitted Points’)
ax_3.plot(index, baseline[l_lim:u_lim], color = ‘y’, linewidth = ‘5’, label = ‘Model’)
#show user selection
xmin, xmax = ax_3.get_xlim()
span = xmax − xmin
ax_3.axhline(500, (u_val[0]-xmin)/span, (u_val[1]-xmin)/span,
linestyle = ‘dotted’, color = ‘m’, label = ‘User’)
#Add labels
ax_3.set_ylabel(‘Source Power [W]’)
ax_3.set_xlabel(‘Aquisition Sample Point’)
ax_3.legend(loc = ‘upper center’, bbox_to_anchor = (0.5, 1.00), ncol = 4)
#Format
ax_3.xaxis.set_major_formatter(smf.FuncFormatter(string_formatted_10k))
#Save output
fig0.savefig(‘./output/’ + filename + ‘_fitment.svg’, format = ‘svg’,
bbox_inches = ‘tight’, transparent = True)
def base_E_plot(data1, data2, filename, ginning, plt_limits, y_lim = [300,800]):
‘‘‘
Used to plot base energy
‘‘‘
#limits of interest
lower_lim = plt_limits[0]
upper_lim = plt_limits[1]
span = upper_lim − lower_lim
#get index where ginning starts/stops
c_index, crossing_count = zero_crossing(ginning[lower_lim:upper_lim])
#Start plotting
font = {‘family’: ‘Arial’, ‘weight’: ‘normal’, ‘size’: 8}
plt.rc(‘font’, **font)
index = np.linspace(lower_lim, upper_lim, span, endpoint = False)
fig0 = plt.figure(figsize = (2,3))
ax_3 = fig0.add_subplot(111)
ax_3.plot(index, data1[lower_lim:upper_lim], color = ‘r’, label = ‘Power’)
ax_3.set_ylim([y_lim[0],y_lim[1]])
ax_3.set_ylabel(‘Source Power [W]’)
ax_3.set_xlabel(‘Aquisition Sample Point’)
ax_3.set_title(‘b’, x = 0.8, y = 0.8, size = 12,
bbox = dict(facecolor = ‘none’, edgecolor = ‘black’))
#Setup fill under for energy
for i in range(0, crossing_count, 2):
span = c_index[i + 1] − c_index[i]
#offset by the lower limit since cross_index referenced to original data
index = np.linspace(c_index[i] + lower_lim + 1, c_index[i + 1] + lower_lim, span)
ax_3.fill_between(index, data2[c_index[i] + lower_lim:c_index[i + 1] + lower_lim],
color = “none”, hatch = ‘X’*4, edgecolor = “b”, label = “Energy”)
fig0.legend(loc = ‘upper center’, bbox_to_anchor = (0.5, 0.98), ncol = 2)
fig0.savefig(‘./output/’ + filename + ‘_viz_baseE.svg’, format = ‘svg’,
bbox_inches = ‘tight’, transparent = True)
def zero_crossing(data):
‘‘‘
Function to calculate change in state for digital data (i.e., going from
0 to 1) or a zero crossing for analog data.
See this:
‘‘‘
#Convert data to array
my_data = np.array(data)
#Get boolean values for what is positive and what is 0 or less
pos = my_data > 0
#Logically compare current value with next value, use XOR
test = np.logical_xor(pos[1:],pos[:−1])
crossing_index = np.nonzero(test)[0]
crossing_count = int(test.sum())
return (crossing_index, crossing_count)
def plot_filter_response(coeff2, my_fs):
‘‘‘
Used to plot the response of the filter for testing purposes
‘‘‘
w, h = freqz(coeff2, fs = my_fs, worN = 512) #frequency response of digital filter
#Start plotting
font = {‘family’: ‘Arial’, ‘weight’: ‘normal’, ‘size’: 8}
plt.rc(‘font’, **font)
fig = plt.figure(figsize = (5,3))
ax1 = fig.add_subplot(111)
ax1.set_xlabel(‘Frequency [Hz]’)
ax1.plot(w, 20*np.log10(np.abs(h)), color = ‘b’, label = ‘Magnitude’) #Magnitude data
ax1.set_ylabel(‘Magnitude [dB]’, color = ‘b’)
ax2 = ax1.twinx()
angles = np.unwrap(np.angle(h))
ax2.plot(w, angles, ‘g’, linestyle = ‘dotted’, label = ‘Angle’) #Phase data
ax2.set_ylabel(‘Angle’, color = ‘g’)
fig.legend()
#Save Output
fig.savefig(‘./output/bode.svg’, format = ‘svg’, bbox_inches = ‘tight’,
transparent = True)