一、为什么使用移动面积算法

解:常规波峰判定是采用高低阈值的方法进行筛除,但会出现如图情况。左边噪声高于实际波峰(绿色)高度,甚至高于阈值(红色),会造成波峰高度的误判等。
二、移动面积算法的雏形与原理

选定矩形(mask),此处我设其宽为波峰的1/2,高为波峰最高,面积为S2。通过mask在I-V图中,从左到右移动,计算出每一次移动后mask捕捉到的波峰面积(黄色),黄色面积为S1。
通过ratio = S1 / S2,将得到每一个 mask 位置不一样的黄色面积占比,我称之为 ratio。如图,明显可见最右边的 mask 中,黄色占比大于50%(或0.5)。则阈值考量可以设置为0.5~1之间,具体须看实际数据。
ratio > 0.5 时,则 mask 捕捉到了波峰位置。
而且值得注意的是,图二和图一的峰的横坐标位置不一致,由此可见移动面积算法存在高灵活性的巨大优势。
三、代码注释0~7的细解:
0. 限制范围0.1V~1.1V (排除0.1V附近的干扰噪声)
1. 计算峰最高点位置,坐标(V, Imax)
2. 确定移动矩形(称之为mask) 的长和宽,delta_I 和delta_V
3. 确定 mask 移动的初始电压位置(X轴)为0.1V, 限制范围:0A<I<Imax(峰最高点纵坐标)
面积:
area = (Vn - Vn-1) * (In + In-1)/2 ;
比值: # mask从横坐标左往右移动,分别计算并保存每次的ratio值
ratio = 峰波形在mask 所占面积 / mask 总面积 ;
4. 从保存的所有ratio 值,求最大ratio(最大ratio值大于0.5,则可说明波峰存在)
5.五点法确定峰顶点位置
(1)从保存的所有ratio 值,求ratio>0.7 的数量 # 标准峰型通常为30个,此数量体现峰的宽度,筛除脉冲形状的峰
(2)五点法,前三点上升沿与后三点下降沿,判断峰是否为峰,代码中满足①②时皆为波峰顶点,③存在情况过多且不符。

6. 计算单峰面积(不包括其余噪声,即从峰宽两侧算起) # 面积算法(包括噪声)
此计算公式存在些许不足,有一定偏差,可忽视不用。
7. fitter 滤波。
拟合波形,多项式拟合法。作参考。 # 目前屏蔽,暂时不用
四、全篇代码:
def get_moverectangleratiovalue(volts, diffcrnts, weno, movevspos=0.1, detwidth=0.1, noise_line=0, ratiothreshold=0.40, ratiocntlimit=0): #--Mei
"""Get moving rectangle mask ratio value.(Area of interest is called mask.)
The area occupancy ratio
Args:
volts (list) -- List of voltages (float) in volts
diffcrnts (list) -- List of hybrid-to-base current differences (float) in amps
weno(int) -- we number
detwidth (float) -- The width of the interval used to detect the calculated mask voltage range(Usually 0~0.2V)
movevspos (float) -- The starting voltage value used to move the mask (Usually 0~0.5V)
noise_line (float) -- Below the apparent noise line is noise, and the ampere value of the noise line is used as
the baseline for mask calculation
ratiothreshold (float) -- In the mask, the threshold of the ratio of the waveform area to the total area of the mask
rectangle (threshold size: 0~1) If there is a peak, usually 0.5~1
ratiocntlimit (int) -- Determines the peak width.If the ratio count(>0.5)is greater than the limit,
Returns: result (bool) -- Determine whether there is a peak by this ratio and return the result.
If the ratio is higher than "ratiothreshold" return True,
and lower than "ratiothreshold" return False.
ratio (float*1.00) -- The ratio of the area of the waveform to the area of the mask
ampere_max (float) -- Maximum current value
peakmainarea (float) -- Peak area except for noise range(not the peak range)
"""
# init parameter
result = False
result_code = 0 # Tracing the cause of NEG results (0 ~ 5) 0 means POS result. Other means NEG.
limit_volts = [] # V>0
limit_amps = [] # when V>0
peak_pos = (0, 0) # max peak pos (V, A)
mask_wavearea = 0
mask_waveareamovelist = []
ratio_list = []
list_area = []
ratio_cnt = 0
peakposlis = []
def find_cmax(volts, diffcrnts, detv1=0.1, detv2=1.0):
# Find max diff current value
maxcrnt = -1e20
maxv = 9 # Impossibly large value
for x, volt in enumerate(volts):
if volt < detv1:
continue
if volt > detv2:
break
if diffcrnts[x] > maxcrnt:
maxcrnt = diffcrnts[x]
maxv = volts[x]
return (maxcrnt, maxv)
# Normally we will set the noise to 0 or to 2.5e-8 A(real noise) in order to count.
for index, volt in enumerate(volts):
if volt >= movevspos: # Usually the point before 0.1V is not used as a reference.
limit_volts.append(volt)
limit_amps.append(diffcrnts[index])
if len(limit_amps) <= 0:
limit_amps.append(0)
# current_max = max(limit_amps)
# current_max = get_peakdetectionvalue
# 0. Ensure that 'limit_volts' and 'ratio_list' list have the same number of indexes
current_max, volt_max = find_cmax(volts, diffcrnts)
if current_max < noise_line: # noise line is 0 to 2.5e-8(A)
print("return False: Max ampere is lower than noise line")
result_code = 1
return False, 0.05, 0, 0
# print("===================================================================")
# 1.The first "for" loop is for finding the peak pos
for x, volt in enumerate(volts):
# Now I limit the first voltage start value (volt > 0).
if diffcrnts[x] == current_max and volt >= 0:
# find max peak point pos (volt, ampere)
peak_volt = volts[x]
peak_pos = (peak_volt, current_max)
# 2.The second "for" loop is for finding the valley pos.
# Now that the valley pos is not used, if there is a better way to judge the valley pos ??
for x, volt in enumerate(volts):
if volt > 0 and volt < peak_pos[0] and diffcrnts[x-1] < noise_line and diffcrnts[x] >= noise_line:
valley_pos = (volt, diffcrnts[x])
# Mask is the valley pos(x_min, y_min) between peak pos(x_max,y_max) of rectangle area.
delta_V = detwidth #(peak_pos[0] - valley_pos[0])
# Don't pay attention to the impact of noise line for now.(Reserved for subsequent development.)
delta_A = (peak_pos[1] - noise_line) # eliminate noise about 0.25e-7 or lower.
mask_totalarea = delta_V * delta_A
# print("mask total area = %s" % mask_totalarea)
# 3. The third "for" loop is for calculating every moving wave area on mask.
def mask_algorithm(volts, diffcrnts, noise_line, movevspos):
for x, volt in enumerate(volts):
# Determine the starting point pos of the mask
# checking mask range
mask_Vmax = volt + delta_V # new range from max volt of mask
mask_rect = (volt, noise_line, mask_Vmax, peak_pos[1]) # (Vmin,Amin,Vmax,Amax)
if volt >= movevspos: # movevspos or the first volt.This is defined mask moving start pos.
# count the wave area on mask
mask_wavearea = 0 # reset new wave area is 0
for index, v in enumerate(volts):
# Use this FOR loop to count waveform surface base in rectangular area.
# Only count the waveform area of the rectangular area.
if v >= volt and v <= mask_Vmax:
# Each cycle calculates a slice in the mask,
# and each slice is determined by two adjacent points of VOLT.
ampere2 = diffcrnts[index]
ampere1 = diffcrnts[index - 1]
# Ensure that diffcrnts are larger than the noise line.Otherwise equal to the noise line.
if diffcrnts[index] < noise_line:
ampere2 = noise_line
if diffcrnts[index - 1] < noise_line:
ampere1 = noise_line
if diffcrnts[index] > peak_pos[1]:
ampere2 = peak_pos[1]
if diffcrnts[index - 1] > peak_pos[1]:
ampere1 = peak_pos[1]
# Only count ampere more than noise_line 0.25(e-7)
# area = (V1-V2)*(A2+A1-3*A0)/2; A0 = 0
area = (v - volts[index - 1]) * (ampere2 + ampere1 - (3 * noise_line)) / 2
# print("area = (%s - %s) * (%s + %s -3(%s)) / 2 = %s" % (volts[index], volts[index-1], ampere2, ampere1, noise_line, area))
mask_wavearea += area
list_area.append(area)
# print("every mask area = %s" % mask_wavearea)
# print("********************* every wave area is equaled all slice area ************************")
mask_waveareamovelist.append(mask_wavearea)
# print("mask wave area = %s" % mask_wavearea)
# 3.1 Save the waveform to the mask area ratio
for singlearea in mask_waveareamovelist:
ratio_list.append(round(singlearea / mask_totalarea, 5))
# print(ratio_list)
# print("++++++++++++++++++++++++++ all for loop end +++++++++++++++++++++++++++++++++ ")
# 3. Calculating every moving wave area on mask.
mask_algorithm(volts, diffcrnts, noise_line, movevspos)
# 4.When there is more than one peak or no peak, the judgment result is False.
ratio = max(ratio_list)
def multipeaks_screen(ratio, ratio_cnt, result, result_code):
maxratio = 0
if ratio >= ratiothreshold:
# if someone scale more than ratio threshold(usually 0.5) 计数:比值大于阈值的情况
for rat in ratio_list:
if rat >= ratiothreshold:
ratio_cnt += 1
if ratio_cnt > 30: # ratio_cnt 体现的是峰的宽度,ratio_cnt 越大峰越宽。
result_code = 3
result = False
maxratio = 0
# print("weno = %s, ratio count = %s, max-ratio = %s" % (weno, ratio_cnt, ratio))
# Normally it is 30. You can also use the PRINT above to check the ratio count of the normal peak shape.
if ratio_cnt > ratiocntlimit and ratio_cnt <= 30:
# If the ratio count is greater than the limit, the waveform is too wide.
result = False
maxratio = ratio
# If the waveform is too wide, we need confirm the waveform is whether the waveform is multiple peaks.
if ratio_cnt <= 25 and ratio_cnt > 0:
peak_cnt = 0
volts_xlist = []
for i, v in enumerate(volts):
volts_xlist.append(i)
for x, ratio_x in enumerate(ratio_list):
# If there is not noly one peak(maybe more), I set a limit for cheaking a thing called'AS A PEAK'.
if ratio_x > ratiothreshold: # 五点法确定峰顶点位置
if x > 2 and x+2 <= len(ratio_list)-1 :#and x % 2 == 0:
if (ratio_x - ratio_list[x-1]) >= 0 and (ratio_x - ratio_list[x+1]) >= 0:
if (ratio_list[x-2] - ratio_list[x-1]) < 0 and (ratio_list[x+1] - ratio_list[x+2]) > 0:
# Limit the peak appearing before 0.2V, which is regarded as invalid.
if limit_volts[x] > 0.4 and limit_volts[x] < 1.1:
peak_cnt += 1
maxratio = ratio_x
# peakposlis.append((volts[x], diffcrnts[x])) # log peak pos(Shaped like a peak)
# print("Peak pos = (%s, %s); index = %s" % (volts[x], diffcrnts[x], x))
# print("Peak volt pos= %s" % (limit_volts[x]))
# print("peak cnt = %s" % peak_cnt)
if peak_cnt > 1 or peak_cnt == 0:
result = False
maxratio = 0
result_code = 4
if peak_cnt == 1:
result = True
# else:
# result = True
else:
result_code = 2
return maxratio, ratio_cnt, result, result_code
# 5. Multi peaks screening
ratio, ratio_cnt, result, result_code = multipeaks_screen(ratio, ratio_cnt, result, result_code)
def calculate_area(x, noiseline, volts):
# Area calculation formula
a2 = diffcrnts[x]
a1 = diffcrnts[x - 1]
area = (volts[x] - volts[x - 1]) * (a2 + a1 - (3 * noiseline)) / 2
return area
# 6. Calculate the only one peak area
if result: # 多峰筛除之后,Ture结果进行
# calculate peak area 计算单峰面积
peakposlis.append(peak_pos)
# print(peak_pos)
peakarealis = []
temp_min = peak_pos[1]/2
log_index = 0
if peakposlis != []:
for m, (x, y) in enumerate(peakposlis):
for n, volt in enumerate(volts):
if volt > 0.3:
value = abs(diffcrnts[n] - (y/2)) # half peak high 半峰高度与
if value <= temp_min:
temp_min = value
log_index = n
# print(peakposlis[m][0])
# print(volts[log_index])
min_halfwidth = round(abs(peakposlis[m][0] - volts[log_index]), 4)
# print("min_halfwidth=%s" % min_halfwidth)
x_min = peakposlis[m][0] - (min_halfwidth * 2)
x_max = peakposlis[m][0] + (min_halfwidth * 2)
# print("x_min=%s, x_max=%s" % (x_min,x_max))
peakarea = 0
for index, v in enumerate(volts):
if v >= x_min and v <= x_max:
peakarea += calculate_area(index, noise_line, volts)
# print("peak area = %s" % peakarea)
peakarealis.append(peakarea)
peakmainarea = 0
maxv = 0
for peakarea in peakarealis:
if maxv < peakarea:
maxv = peakarea
peakmainarea = maxv
if peakmainarea == 0:
result = False
result_code = 4
ra_index = ratio_list.index(max(ratio_list, key=abs))
# print("ratio max index = %s " % ra_index)
# print("===========================================================================")
else:
peakmainarea = 0
# 7.
if not result and dglobs.fiter_enable and False: # debug fitting and sum res
# Before fitting debug
befo_maxratio = ratio
befo_res = result
calculate_y = fitter.polynomial_fittodraw(volts, diffcrnts, 12, weno) # only a fiter and draw to show
# Fit the original data and output fitted current data
diffs_fited = fitter.polynomial_fitting(volts, diffcrnts, 15) # complex degree = 15
mask_algorithm(volts, diffs_fited, noise_line, movevspos)
after_maxratio = max(ratio_list)
after_res = None
if after_maxratio > ratiothreshold:
after_res = True
else:
after_res = False
result_code = 5
result = after_res
# print("enable fitter and adjust result")
# getamps_leastsquarefitting(volts, diffcrnts)
# print("-----------------Area-Mei----------------")
# print("we: %s, res: %s, ratio: %s, A max: %s, peak area: %s"
# % (weno, result, ratio, current_max, peakmainarea))
# debug+
# calculate_y = fitter.polynomial_fittodraw(volts, diffcrnts, 12, weno) # only a fiter and draw to show
# time.sleep(1)
# Save volt (value/pos) of the max ampere
# for index, volt in enumerate(volts):
# if diffcrnts[index] == current_max:
# dglobs.allwes_peakpos.append(volt)
# debug+
# print("weno = %s, ratio count = %s, max-ratio = %s" % (weno, ratio_cnt, ratio))
# print("result code = %s" % result_code)
return result, ratio, current_max, peakmainarea, result_code
















