Skip to content

Commit

Permalink
Merge pull request #129 from noonchen/PrepareReleaseV4.0.0
Browse files Browse the repository at this point in the history
Prepare release v4.0.0
  • Loading branch information
noonchen authored Dec 12, 2022
2 parents ddb941f + 13b5128 commit 63183e3
Show file tree
Hide file tree
Showing 15 changed files with 2,468 additions and 145 deletions.
94 changes: 88 additions & 6 deletions STDF-Viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# Author: noonchen - [email protected]
# Created Date: December 13th 2020
# -----
# Last Modified: Thu Dec 08 2022
# Last Modified: Mon Dec 12 2022
# Modified By: noonchen
# -----
# Copyright (c) 2020 noonchen
Expand Down Expand Up @@ -41,7 +41,8 @@
from deps.uic_stdSettings import stdfSettings
from deps.uic_stdDutData import DutDataDisplayer
from deps.uic_stdDebug import stdDebugPanel

from deps.uic_stdConverter import StdfConverter
import rust_stdf_helper
# pyqt5
from deps.ui.stdfViewer_MainWindows import Ui_MainWindow
from PyQt5 import QtCore, QtWidgets, QtGui, QtSql
Expand Down Expand Up @@ -79,6 +80,9 @@
class signals4MainUI(QtCore.QObject):
dataInterfaceSignal = Signal(object) # get `DataInterface` from loader
statusSignal = Signal(str, bool, bool, bool) # status bar
showDutDataSignal_TrendHisto = Signal(list) # trend & histo
showDutDataSignal_Bin = Signal(list) # bin chart
showDutDataSignal_Wafer = Signal(list) # wafer


class MyWindow(QtWidgets.QMainWindow):
Expand Down Expand Up @@ -108,6 +112,9 @@ def __init__(self):
self.signals = signals4MainUI()
self.signals.dataInterfaceSignal.connect(self.updateData)
self.signals.statusSignal.connect(self.updateStatus)
self.signals.showDutDataSignal_TrendHisto.connect(self.onReadDutData_TrendHisto)
self.signals.showDutDataSignal_Bin.connect(self.onReadDutData_Bin)
self.signals.showDutDataSignal_Wafer.connect(self.onReadDutData_Wafer)
# sub windows
self.loader = stdfLoader(self.signals, self)
self.mergePanel = MergePanel(self)
Expand All @@ -134,6 +141,8 @@ def __init__(self):
self.ui.actionAbout.triggered.connect(self.onAbout)
self.ui.actionReadDutData_DS.triggered.connect(self.onReadDutData_DS)
self.ui.actionReadDutData_TS.triggered.connect(self.onReadDutData_TS)
self.ui.actionFetchDuts.triggered.connect(lambda: self.onFetchAllRows(self.ui.dutInfoTable))
self.ui.actionFetchDatalog.triggered.connect(lambda: self.onFetchAllRows(self.ui.datalogTable))
self.ui.actionAddFont.triggered.connect(self.onAddFont)
self.ui.actionToXLSX.triggered.connect(self.onToXLSX)
# init search-related UI
Expand Down Expand Up @@ -316,8 +325,12 @@ def onLoadSession(self):
directory=getSetting().recentFolder,
filter=self.tr("Database (*.db)"))
if p:
#TODO validation
self.loadDatabase(p)
isvalid, msg = validateSession(p)
if isvalid:
self.loadDatabase(p)
else:
QMessageBox.warning(self, self.tr("Warning"),
self.tr("This session cannot be loaded: \n{}\n\n{}").format(p, msg))


def onSaveSession(self):
Expand Down Expand Up @@ -406,11 +419,31 @@ def onAbout(self):


def onAddFont(self):
QMessageBox.information(self, self.tr("Notice"), self.tr("Not implemented yet..."))
p, _ = QFileDialog.getOpenFileName(self, caption=self.tr("Select a .ttf font file"),
directory=getSetting().recentFolder,
filter=self.tr("TTF Font (*.ttf)"))
if not p:
return

if QtGui.QFontDatabase.addApplicationFont(p) < 0:
QMessageBox.warning(self, self.tr("Warning"), self.tr("This font cannot be loaded:\n{}").format(p))
else:
shutil.copy(src=p,
dst=os.path.join(sys.rootFolder, "fonts"),
follow_symlinks=True)
# manually refresh font list
loadFonts()
self.settingUI.refreshFontList()
QMessageBox.information(self, self.tr("Completed"), self.tr("Load successfully, change font in settings to take effect"))


def onToXLSX(self):
QMessageBox.information(self, self.tr("Notice"), self.tr("Not implemented yet..."))
cv = StdfConverter(self)
cv.setupConverter(self.tr("STDF to XLSX Converter"),
self.tr("XLSX Path Selection"),
".xlsx",
rust_stdf_helper.stdf_to_xlsx)
cv.showUI()


def onExit(self):
Expand Down Expand Up @@ -487,6 +520,47 @@ def onReadDutData_TS(self):
QMessageBox.information(None, self.tr("No DUTs selected"), self.tr("You need to select DUT row(s) first"), buttons=QMessageBox.Ok)


def onFetchAllRows(self, activeTable: QtWidgets.QTableView):
model = activeTable.model()
if isinstance(model, DutSortFilter):
# dut summary uses proxy model
model = model.sourceModel()
if isinstance(model, QtSql.QSqlQueryModel):
self.signals.statusSignal.emit(self.tr("Fetching all..."), False, False, False)
while model.canFetchMore():
model.fetchMore()
self.signals.statusSignal.emit(self.tr("Fetch Done!"), False, False, False)


@Slot(list)
def onReadDutData_TrendHisto(self, selectedDutIndex: list):
'''
selectedDutIndex: a list of (fid, dutIndex)
'''
if selectedDutIndex:
self.showDutDataTable(selectedDutIndex)


@Slot(list)
def onReadDutData_Bin(self, selectedBin: list):
'''
selectedBin: a list of (fid, isHBIN, [bin_num])
'''
selectedDutIndex = self.data_interface.DatabaseFetcher.getDUTIndexFromBin(selectedBin)
if selectedDutIndex:
self.showDutDataTable(selectedDutIndex)


@Slot(list)
def onReadDutData_Wafer(self, selectedDie: list):
'''
selectedDie: a list of (waferInd, fid, (x, y))
'''
selectedDutIndex = self.data_interface.DatabaseFetcher.getDUTIndexFromXY(selectedDie)
if selectedDutIndex:
self.showDutDataTable(selectedDutIndex)


def enableDragDrop(self):
for obj in [self.ui.TestList, self.ui.tabControl, self.ui.dataTable]:
obj.setAcceptDrops(True)
Expand Down Expand Up @@ -553,7 +627,9 @@ def init_DataTable(self):
# datalog info table
self.tmodel_datalog = DatalogSqlQueryModel(self, 13 if isMac else 10)
self.ui.datalogTable.setModel(self.tmodel_datalog)
self.ui.datalogTable.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) # select row only
self.ui.datalogTable.setItemDelegate(StyleDelegateForTable_List(self.ui.datalogTable))
self.ui.datalogTable.addAction(self.ui.actionFetchDatalog)
# test data table
self.tmodel_data = TestDataTableModel()
self.ui.rawDataTable.setModel(self.tmodel_data)
Expand All @@ -568,6 +644,7 @@ def init_DataTable(self):
self.ui.dutInfoTable.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) # select row only
self.ui.dutInfoTable.setItemDelegate(StyleDelegateForTable_List(self.ui.dutInfoTable))
self.ui.dutInfoTable.addAction(self.ui.actionReadDutData_DS) # add context menu for reading dut data
self.ui.dutInfoTable.addAction(self.ui.actionFetchDuts)
# file header table
self.tmodel_info = QtGui.QStandardItemModel()
self.ui.fileInfoTable.setModel(self.tmodel_info)
Expand All @@ -584,6 +661,7 @@ def init_DataTable(self):
self.ui.dutInfoTable.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
self.ui.dutInfoTable.setHorizontalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
self.ui.fileInfoTable.setVerticalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)
self.ui.fileInfoTable.setHorizontalScrollMode(QAbstractItemView.ScrollMode.ScrollPerPixel)


def init_Head_SiteCheckbox(self):
Expand Down Expand Up @@ -986,20 +1064,23 @@ def genPlot(self, testTuple: tuple, head: int, selectSites: list[int], tabType:
tchart = TrendChart()
tchart.setTrendData(tdata)
if tchart.validData:
tchart.setShowDutSignal(self.signals.showDutDataSignal_TrendHisto)
return tchart

elif tabType == tab.Histo:
tdata = self.data_interface.getTrendChartData(testTuple, head, selectSites)
hchart = HistoChart()
hchart.setTrendData(tdata)
if hchart.validData:
hchart.setShowDutSignal(self.signals.showDutDataSignal_TrendHisto)
return hchart

elif tabType == tab.Wafer:
wdata = self.data_interface.getWaferMapData(testTuple, selectSites)
wchart = WaferMap()
wchart.setWaferData(wdata)
if wchart.validData:
wchart.setShowDutSignal(self.signals.showDutDataSignal_Wafer)
return wchart

elif tabType == tab.Bin:
Expand All @@ -1010,6 +1091,7 @@ def genPlot(self, testTuple: tuple, head: int, selectSites: list[int], tabType:
bchart = BinChart()
bchart.setBinData(bdata)
if bchart.validData:
bchart.setShowDutSignal(self.signals.showDutDataSignal_Bin)
bcharts.append(bchart)
return bcharts

Expand Down
48 changes: 29 additions & 19 deletions deps/ChartWidgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# Author: noonchen - [email protected]
# Created Date: November 25th 2022
# -----
# Last Modified: Sun Dec 11 2022
# Last Modified: Mon Dec 12 2022
# Modified By: noonchen
# -----
# Copyright (c) 2022 noonchen
Expand Down Expand Up @@ -219,8 +219,7 @@ def onShowDut(self):
selectedData = []
for view in self.view_list:
selectedData.extend(view.getSelectedDataForDutTable())
#TODO send data to main UI
print(selectedData)
# send data to main UI
if self.showDutSignal:
self.showDutSignal.emit(selectedData)

Expand Down Expand Up @@ -382,7 +381,9 @@ def getSelectedDataForDutTable(self):
dataSet = set()
dutIndexArray, _ = self.slpoints.getData()
# remove duplicates
_ = [dataSet.add((self.fileID, i)) for i in dutIndexArray]
# array is float type, must convert to int
# otherwise will cause crash in `TestDataTable`
_ = [dataSet.add((self.fileID, int(i))) for i in dutIndexArray]
return list(dataSet)


Expand Down Expand Up @@ -505,14 +506,14 @@ def getSelectedDataForDutTable(self) -> list:
class BinViewBox(SVBarViewBox):
def getSelectedDataForDutTable(self) -> list:
'''
For BinChart, return [(fid, isHBIN, duts)]
For BinChart, return [(fid, isHBIN, bins)]
'''
tmp = {}
for _, binTup in self.slbars.getRects():
isHBIN, bin_num = binTup
tmp.setdefault( (self.fileID, isHBIN), []).append(bin_num)
return [(fid, isHBIN, duts)
for (fid, isHBIN), duts
return [(fid, isHBIN, bins)
for (fid, isHBIN), bins
in tmp.items()]


Expand Down Expand Up @@ -559,7 +560,7 @@ def getSelectedDataForDutTable(self):
# remove duplicates
_ = [dataSet.add((self.waferInd,
self.fileID,
(x, y)))
(int(x), int(y))))
for x, y
in zip(xData, yData)]
return list(dataSet)
Expand Down Expand Up @@ -602,7 +603,11 @@ def setTrendData(self, trendData: dict):
y_min_list.extend([i_file["LLimit"], i_file["LSpec"]])
y_max_list.extend([i_file["HLimit"], i_file["HSpec"]])
for d_site in d_file.values():
# TODO dynamic limits
# dynamic limits
if d_site.get("dyLLimit", {}):
y_min_list.append(min(d_site["dyLLimit"].values()))
if d_site.get("dyHLimit", {}):
y_max_list.append(max(d_site["dyHLimit"].values()))
y_min_list.append(d_site["Min"])
y_max_list.append(d_site["Max"])
# at least one site data should be valid
Expand All @@ -612,10 +617,6 @@ def setTrendData(self, trendData: dict):
# set the flag to True and put it in top GUI
if not self.validData:
return
# # if these two list is empty, means no test value
# # is found in all files. this is is rare but I've encountered
# if len(y_min_list) == 0 or len(y_max_list) == 0:
# return
y_min = np.nanmin(y_min_list)
y_max = np.nanmax(y_max_list)
# add 15% as overhead
Expand All @@ -627,10 +628,6 @@ def setTrendData(self, trendData: dict):
if y_min == y_max:
y_min -= 1
y_max += 1
# # there is a possibility that y_min or y_max
# # is nan, in this case we cannot draw anything
# if np.isnan(y_min) or np.isnan(y_max):
# return
# add title
self.plotlayout.addLabel(f"{test_num} {test_name}", row=0, col=0,
rowspan=1, colspan=len(testInfo),
Expand All @@ -655,6 +652,8 @@ def setTrendData(self, trendData: dict):
continue
x_min_list = []
x_max_list = []
dyL = {}
dyH = {}
for site, data_per_site in sitesData.items():
x = data_per_site["dutList"]
y = data_per_site["dataList"]
Expand All @@ -664,6 +663,8 @@ def setTrendData(self, trendData: dict):
continue
x_min_list.append(np.nanmin(x))
x_max_list.append(np.nanmax(x))
dyL.update(data_per_site.get("dyLLimit", {}))
dyH.update(data_per_site.get("dyHLimit", {}))
fsymbol = settings.fileSymbol[fid]
siteColor = settings.siteColor[site]
# test value
Expand Down Expand Up @@ -701,6 +702,13 @@ def setTrendData(self, trendData: dict):
label=f"{name} = {{value:0.2f}}",
labelOpts={"position":pos, "color": pen.color(),
"movable": True, "anchors": anchors})
# dynamic limits
for (dyDict, name, pen, enabled) in [(dyL, "Dynamic Low Limit", self.lolimitPen, settings.showLL_trend),
(dyH, "Dynamic High Limit", self.hilimitPen, settings.showHL_trend)]:
if enabled and len(dyDict) > 0:
x = np.array(sorted(dyDict.keys()))
dylims = np.array([dyDict[i] for i in x])
pitem.addItem(pg.PlotDataItem(x=x, y=dylims, pen=pen, name=name, color=pen.color()))
# labels and file id
unit = infoDict["Unit"]
pitem.getAxis("left").setLabel(f"Test Value" + f" ({unit})" if unit else "")
Expand Down Expand Up @@ -1009,7 +1017,10 @@ def setWaferData(self, waferData: dict):

settings = ss.getSetting()
self.validData = True
waferInd, fid = waferData["ID"]
waferView = WaferViewBox()
waferView.setWaferIndex(waferInd)
waferView.setFileID(fid)
pitem = pg.PlotItem(viewBox=waferView)
# put legend in another view
view_legend = pg.ViewBox()
Expand All @@ -1020,15 +1031,14 @@ def setWaferData(self, waferData: dict):
verSpacing=5,
labelTextSize="15pt")
xyData = waferData["Data"]
isStackMap = waferData["Stack"]
stackColorMap = pg.ColorMap(pos=None, color=["#00EE00", "#EEEE00", "#EE0000"])
sortedKeys = sorted(xyData.keys())

for num in sortedKeys:
xyDict = xyData[num]
# for stack map, num = fail counts
# for wafer map, num = sbin number
if isStackMap:
if waferInd == -1:
color = stackColorMap.mapToQColor(num/sortedKeys[-1])
tipFunc = f"XY: ({{x:.0f}}, {{y:.0f}})\nFail Count: {num}".format
legendString = f"Fail Count: {num}"
Expand Down
Loading

0 comments on commit 63183e3

Please sign in to comment.