Eye Diagram Viewer - 파이썬 소스
도구/기타 2018. 9. 16. 22:00XML 디자인:
XML 소스:
Python 소스:
import sys
from PyQt5.QtWidgets import *
from PyQt5 import uic
import csv
import matplotlib.pyplot as plt
import numpy as np
from scipy.interpolate import interp1d
from matplotlib.backends.qt_compat import QtCore, QtWidgets, is_pyqt5
if is_pyqt5():
from matplotlib.backends.backend_qt5agg import (
FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
else:
from matplotlib.backends.backend_qt4agg import (
FigureCanvas, NavigationToolbar2QT as NavigationToolbar)
def bres_segment_count(x0, y0, x1, y1, grid, endpoint):
"""
Bresenham's algorithm.
See http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
"""
nrows = grid.shape[0]
ncols = grid.shape[1]
if x1 > x0:
dx = x1 - x0
else:
dx = x0 - x1
if y1 > y0:
dy = y1 - y0
else:
dy = y0 - y1
sx = 0
if x0 < x1:
sx = 1
else:
sx = -1
sy = 0
if y0 < y1:
sy = 1
else:
sy = -1
err = dx - dy
while True:
# When endpoint is 0, this test occurs before we increment the
# grid value, so we don't count the last point.
if endpoint == 0 and x0 == x1 and y0 == y1:
break
if (0 <= x0 < nrows) and (0 <= y0 < ncols):
grid[x0, y0] += 1
if x0 == x1 and y0 == y1:
break
e2 = 2 * err
if e2 > -dy:
err -= dy
x0 += sx
if e2 < dx:
err += dx
y0 += sy
return 0
def bres_curve_count(x, y, grid):
for k in range(len(x)-1):
x0 = x[k]
y0 = y[k]
x1 = x[k+1]
y1 = y[k+1]
bres_segment_count(x0, y0, x1, y1, grid, 0)
if 0 <= x1 < grid.shape[0] and 0 <= y1 < grid.shape[1]:
# Count the last point in the curve.
grid[x1, y1] += 1
def grid_count(y, window_size, offset, bounds, size=None, fuzz=True):
"""
파라미터
----------
`y` - 1차원 신호 샘플 배열
`window_size` - eye diagram에서 수평으로 보여질 샘플 수. 통상 심볼(비트) 샘플의 2배
`offset` - eye diagram을 계산하기 전에 건너 뛸 초기 샘플 수. eye의 전반적 위상 조정
`bounds` - 두 부동소수 튜 플이어야 함. 돌려질 배열의 y 범위(ymin, ymax) 설정.
`size` - 두 정수 튜플이어야만 함. 배열의 크기(height, width) 설정. 기본값은(800,600)
`fuzz` - True일 경우, y 값이 랜덤 퍼즈 요소로 강제 인터폴레이션 됨. 알리아싱 감소
반환 값 - 정수 numpy 배열
"""
if size is None:
size = (800, 640)
width, height = size
dt = width / window_size
counts = np.zeros((width, height), dtype=np.int32)
start = offset
while start + window_size < len(y):
end = start + window_size
yy = y[start:end+1]
k = np.arange(len(yy))
xx = dt*k
if fuzz:
f = interp1d(xx, yy, kind='cubic')
jiggle = dt*(np.random.beta(a=3, b=3, size=len(xx)-2) - 0.5)
xx[1:-1] += jiggle
yd = f(xx)
else:
yd = yy
iyd = (height * (yd - bounds[0])/(bounds[1] - bounds[0])).astype(np.int32)
bres_curve_count(xx.astype(np.int32), iyd, counts)
start = end
return counts
def read_and_plot(appWin):
# 소수점 출력 자리수 설정
unitx = '.' + str(appWin.unitX) + 'f'
unity = '.' + str(appWin.unitY) + 'f'
samples_per_symbol = 20
npx = np.array(appWin.x) # numpy 배열로 변환
number_of_samples = int((npx[len(npx)-1]/appWin.ui)*samples_per_symbol) # 총샘플 수
ipx = np.linspace(0, npx[len(npx)-1], number_of_samples) # 총 샘플수에 맞게 x 샘플 수 조정
npy= np.array(appWin.y)
ipy = np.interp(ipx, npx, npy) # *** x 를 참조하여 y 샘플수를 조정
# ------ high level과 low level을 histogram을 사용하여 찾기 ------------------
n, bins, patches = plt.hist(ipy, bins=100)
peak = 0 # 히스토그램 최대 분포
high = 0 # high와 low 두 부분으로 나누어 진행
for i in range(50,99):
if n[i] > peak:
high = i
peak = 0
low = 0
for i in range(50,49):
if n[i] > peak:
low = i
high = (bins[high]+bins[high+1])/2
low = (bins[low]+bins[low+1])/2
mid = (high+low)/2
amp = high - low
appWin.lineEditHigh.setText(format(high, unity))
appWin.lineEditLow.setText(format(low, unity))
appWin.lineEditMid.setText(format(mid, unity))
# ------- 상승 및 하강 시간 찾기 ---------------------------------------------
p10 = amp*0.1 + low
p90 = amp*0.9 + low
tran = False
step = 0 # 누적 상승 구간 스텝 수
count = 0 # 상승 구간 개수
for i in range(0, len(ipy)-1):
if ipy[i] <= p10 and ipy[i+1] >= p10 and tran == False: # 10%를 지날 때 step 카운트 시작
tran = True
start = i
elif ipy[i] <= p90 and ipy[i+1] >= p90 and tran == True: # 90%를 지날 때 step 카운트 종료
tran = False
step = step + i - start
count = count + 1
appWin.lineEditRise.setText(format(appWin.ui*(step/count)/samples_per_symbol, unitx))
tran = False # 하강 구간에 대해서 동일하게
step = 0
count = 0
for i in range(0, len(ipy)-1):
if ipy[i] >= p90 and ipy[i+1] <= p90 and tran == False:
tran = True
start = i
elif ipy[i] >= p10 and ipy[i+1] <= p10 and tran == True:
tran = False
step = step + i - start
count = count + 1
appWin.lineEditFall.setText(format(appWin.ui*(step/count)/samples_per_symbol, unitx))
# eye diagram을 shift 시키기 위한 offset값. 반대방향으로 적용
offset = appWin.offset
while offset < 0: # grid count에서 offset은 +만 되므로 -로 변환
offset = offset + appWin.ui
offset = int(samples_per_symbol*offset/appWin.ui) # 시간 단위 옵셋을 샘플 수 단위로 변환
if offset > samples_per_symbol: # 옵셋이 UI를 넘으면 보정
offset = offset % samples_per_symbol
skip = samples_per_symbol - offset
if appWin.ampMinMax[0] == appWin.ampMinMax[1]:
ymax = ipy.max()
ymin = ipy.min()
yamp = ymax - ymin
min_y = ymin - 0.05*yamp
max_y = ymax + 0.05*yamp
else:
min_y = appWin.ampMinMax[0]
max_y = appWin.ampMinMax[1]
bounds = (min_y, max_y)
span = max_y - min_y
"""
plot an eye diagram using matplotlib by creating an image
and calling the 'imshow' function.
"""
counts = grid_count(ipy, 2*samples_per_symbol, skip, bounds)
counts = counts.astype(np.float32)
counts[counts==0] = np.nan
appWin.fig.clf()
appWin.verticalLayout.removeWidget(appWin.canvas)
appWin.canvas = FigureCanvas(appWin.fig)
appWin.verticalLayout.addWidget(appWin.canvas)
plt.imshow(counts.T[::-1, :],aspect='auto',
extent=[0, 2*appWin.ui, min_y, max_y],
cmap=plt.cm.RdYlGn)
plt.grid(color='#929591', linestyle=':')
#if colorbar:
# cb = plt.colorbar()
# cb.set_ticks([]) # tick 제거
plt.xlabel(appWin.matrix[0][0])
plt.ylabel(appWin.matrix[0][appWin.index])
#file_name = matrix[0][j] + '.png'
#plt.savefig(file_name)
#plt.show()
# ------------ eye width 찾기 ----------------------------------------------
if appWin.vh == None:
vh = mid
appWin.vh =mid
appWin.lineEditVH.setText(format(mid, unity))
else:
vh = appWin.vh
if appWin.vl == None:
vl = mid
appWin.vl =mid
appWin.lineEditVL.setText(format(mid, unity))
else:
vl = appWin.vl
vhi = int(639 * (vh - min_y)/span)
row_data_h = np.nan_to_num(counts[0:800, vhi])
vli = int(639 * (vl - min_y)/span)
row_data_l = np.nan_to_num(counts[0:800, vli])
zeros_h = 0
for i in range(400, 800):
if row_data_h[i] == 0:
zeros_h = zeros_h + 1
else:
break
zeros_l = 0
for i in range(400, 800):
if row_data_l[i] == 0:
zeros_l = zeros_l + 1
else:
break
if zeros_h > zeros_l:
zeros_right = zeros_l
else:
zeros_right = zeros_h
zeros_h = 0
for i in range(400, 0, -1):
if row_data_h[i] == 0:
zeros_h = zeros_h + 1
else:
break
zeros_l = 0
for i in range(400, 0, -1):
if row_data_l[i] == 0:
zeros_l = zeros_l + 1
else:
break
if zeros_h > zeros_l:
zeros_left = zeros_l
else:
zeros_left = zeros_h
zeros = zeros_right + zeros_left
eye_width = zeros * (appWin.ui * 2) / 800
appWin.lineEditWidth.setText(format(eye_width, unitx))
appWin.lineEditWidth2.setText(format(100*eye_width/appWin.ui, '.1f')+'%')
# -------------- eye height 찾기 -------------------------------------------
col_data = np.nan_to_num(counts[400+int((zeros_right-zeros_left)/2)])
sp = int(639 * (low-min_y)/span)
ep = int(639 * (high-min_y)/span)
if ep > 639:
ep = 639
cp = int((sp+ep)/2)
zeros = 0
for i in range(cp, ep):
if col_data[i] == 0:
zeros = zeros + 1
else:
break
for i in range(cp, sp, -1):
if col_data[i] == 0:
zeros = zeros + 1
else:
break
eye_height =zeros * span / 640
appWin.lineEditHeight.setText(format(eye_height, unity))
appWin.lineEditHeight2.setText(format(100*eye_height/amp, '.1f') + '%')
# -------------- VH, VL 활성화 결정
if eye_height > 0:
appWin.vh = vh
appWin.vl = vl
appWin.lineEditVH.setText(format(vh, unity))
appWin.lineEditVL.setText(format(vl, unity))
appWin.lineEditVH.setEnabled(True)
appWin.lineEditVL.setEnabled(True)
else:
appWin.vh = None
appWin.vl = None
appWin.lineEditVH.setText('')
appWin.lineEditVL.setText('')
appWin.lineEditVH.setEnabled(False)
appWin.lineEditVL.setEnabled(False)
def is_number(num):
try:
float(num)
return True # num을 float으로 변환할 수 있는 경우
except ValueError:
return False # num을 float으로 변환할 수 없는 경우
form_class = uic.loadUiType("eyediagram_viewer.ui")[0]
class ApplicationWindow(QMainWindow, form_class):
def __init__(self):
super().__init__()
self.setupUi(self)
self.fig = plt.figure(figsize=(10, 7.5), dpi=80)
self.canvas = FigureCanvas(self.fig)
self.verticalLayout.addWidget(self.canvas)
#self.addToolBar(NavigationToolbar(self.canvas, self))
self.offset = 0.0
self.ampMinMax = (0,0)
self.ui = 1.0
self.matrix = []
self.x = []
self.y = []
self.index = 1
self.vh = None
self.vl = None
self.unitX = 2
self.unitY = 2
self.actionOpen.triggered.connect(self.menu_open_checked)
self.actionExport_to_PNG.triggered.connect(self.menu_export_checked)
self.comboBox.currentIndexChanged.connect(self.combo_choose)
self.radioButtonAuto.clicked.connect(self.radio_auto)
self.radioButtonManual.clicked.connect(self.radio_manual)
self.lineEditMax.returnPressed.connect(self.edit_max)
self.lineEditMin.returnPressed.connect(self.edit_min)
self.lineEditUI.returnPressed.connect(self.edit_ui)
self.lineEditOffset.returnPressed.connect(self.edit_offset)
self.lineEditVH.returnPressed.connect(self.edit_vh)
self.lineEditVL.returnPressed.connect(self.edit_vl)
self.lineEditUnitX.returnPressed.connect(self.edit_unit_x)
self.lineEditUnitY.returnPressed.connect(self.edit_unit_y)
def menu_open_checked(self):
fname = QFileDialog.getOpenFileName(self)
if fname[0] == '':
return
self.matrix = []
with open(fname[0], 'r') as file:
reader = csv.reader(file)
for line in reader:
self.matrix.append(line)
self.x = []
try:
for i in range(1, len(self.matrix), 1):
newx = float(self.matrix[i][0])
self.x.append(newx)
self.comboBox.clear()
for j in range(1, len(self.matrix[0]), 1):
self.comboBox.addItem(self.matrix[0][j])
self.comboBox.setEnabled(True) # 파일을 읽었으므로 기능 활성화
self.radioButtonAuto.setEnabled(True)
self.radioButtonManual.setEnabled(True)
self.lineEditUI.setEnabled(True)
self.lineEditOffset.setEnabled(True)
self.actionExport_to_PNG.setEnabled(True)
except ValueError:
self.statusbar.showMessage('The file is not a eye diagram CSV.')
def menu_export_checked(self):
file_name = self.matrix[0][self.index] + '.png'
plt.savefig(file_name)
def radio_auto(self):
self.lineEditMin.setEnabled(False)
self.lineEditMax.setEnabled(False)
self.ampMinMax = (0, 0)
read_and_plot(self)
def radio_manual(self):
self.lineEditMin.setEnabled(True)
self.lineEditMax.setEnabled(True)
max = self.lineEditMax.text()
min = self.lineEditMin.text()
self.ampMinMax = (float(min), float(max))
read_and_plot(self)
def edit_max(self):
if is_number(self.lineEditMax.text()):
self.ampMinMax = (self.ampMinMax[0], float(self.lineEditMax.text()))
read_and_plot(self)
else:
self.lineEditMax.setText(str(self.ampMinMax[1]))
def edit_min(self):
if is_number(self.lineEditMin.text()):
self.ampMinMax = (float(self.lineEditMin.text()), self.ampMinMax[1])
read_and_plot(self)
else:
self.lineEditMin.setText(str(self.ampMinMax[0]))
def edit_ui(self):
if is_number(self.lineEditUI.text()):
self.ui = float(self.lineEditUI.text())
read_and_plot(self)
else:
self.lineEditUI.setText(str(self.ui))
def edit_offset(self):
if is_number(self.lineEditOffset.text()):
self.offset = float(self.lineEditOffset.text())
read_and_plot(self)
else:
self.lineEditOffset.setText(str(self.offset))
def edit_vh(self):
if is_number(self.lineEditVH.text()):
self.vh = float(self.lineEditVH.text())
read_and_plot(self)
else:
self.vh = None
self.lineEditVH.setText('')
def edit_vl(self):
if is_number(self.lineEditVL.text()):
self.vl = float(self.lineEditVL.text())
read_and_plot(self)
else:
self.vl = None
self.lineEditVL.setText('')
def edit_unit_x(self):
if is_number(self.lineEditUnitX.text()) and int(self.lineEditUnitX.text()) >= 0:
self.unitX = int(self.lineEditUnitX.text())
read_and_plot(self)
else:
self.lineEditUnitX.setText(str(self.unitX))
def edit_unit_y(self):
if is_number(self.lineEditUnitY.text()) and int(self.lineEditUnitY.text()) >= 0:
self.unitY = int(self.lineEditUnitY.text())
read_and_plot(self)
else:
self.lineEditUnitX.setText(str(self.unitY))
def combo_choose(self):
self.index = self.comboBox.currentIndex() + 1
if self.index > 0:
self.y = []
for i in range(1, len(self.matrix), 1):
newy = float(self.matrix[i][self.index])
self.y.append(newy)
read_and_plot(self)
if __name__ == "__main__":
app = QApplication(sys.argv)
myWindow = ApplicationWindow()
myWindow.show()
app.exec_()
샘플 transient simulation 데이터:
'도구 > 기타' 카테고리의 다른 글
사양 BOM 생성기 (매트릭스 버전) (0) | 2020.04.05 |
---|---|
사양 BOM 생성기 (0) | 2020.03.25 |
Wave 2 Table (0) | 2018.09.03 |
Spreadsheet Schematic Editor for Allegro (0) | 2016.12.05 |
패키지/커넥터 핀맵 작성기 (0) | 2016.11.21 |