stdf-reader 0.10.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of stdf-reader might be problematic. Click here for more details.
- pystdf/BinSummarizer.py +148 -0
- pystdf/IO.py +382 -0
- pystdf/Importer.py +86 -0
- pystdf/Indexing.py +114 -0
- pystdf/Mapping.py +80 -0
- pystdf/OoHelpers.py +24 -0
- pystdf/ParametricSummarizer.py +61 -0
- pystdf/PartSummarizer.py +98 -0
- pystdf/Pipeline.py +80 -0
- pystdf/SummaryStatistics.py +32 -0
- pystdf/TableTemplate.py +29 -0
- pystdf/TestSummarizer.py +198 -0
- pystdf/Types.py +117 -0
- pystdf/V4.py +2636 -0
- pystdf/Writers.py +99 -0
- pystdf/__init__.py +25 -0
- pystdf/logexcept.py +123 -0
- stdf_reader/Backend.py +682 -0
- stdf_reader/FileRead.py +985 -0
- stdf_reader/Threads.py +280 -0
- stdf_reader/__init__.py +3 -0
- stdf_reader/analysis.py +909 -0
- stdf_reader/cli.py +455 -0
- stdf_reader/gui.py +2066 -0
- stdf_reader-0.10.0.dist-info/METADATA +218 -0
- stdf_reader-0.10.0.dist-info/RECORD +30 -0
- stdf_reader-0.10.0.dist-info/WHEEL +5 -0
- stdf_reader-0.10.0.dist-info/entry_points.txt +5 -0
- stdf_reader-0.10.0.dist-info/licenses/LICENSE +339 -0
- stdf_reader-0.10.0.dist-info/top_level.txt +2 -0
pystdf/BinSummarizer.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
#
|
|
2
|
+
# PySTDF - The Pythonic STDF Parser
|
|
3
|
+
# Copyright (C) 2006 Casey Marshall
|
|
4
|
+
#
|
|
5
|
+
# This program is free software; you can redistribute it and/or
|
|
6
|
+
# modify it under the terms of the GNU General Public License
|
|
7
|
+
# as published by the Free Software Foundation; either version 2
|
|
8
|
+
# of the License, or (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with this program; if not, write to the Free Software
|
|
17
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
18
|
+
#
|
|
19
|
+
|
|
20
|
+
from pystdf.Pipeline import EventSource
|
|
21
|
+
from pystdf.SummaryStatistics import SummaryStatistics
|
|
22
|
+
from pystdf.V4 import prr, hbr, sbr
|
|
23
|
+
|
|
24
|
+
def ifElse(cond, trueVal, falseVal):
|
|
25
|
+
if cond:
|
|
26
|
+
return trueVal
|
|
27
|
+
else:
|
|
28
|
+
return falseVal
|
|
29
|
+
|
|
30
|
+
class BinSummarizer(EventSource):
|
|
31
|
+
|
|
32
|
+
FLAG_SYNTH = 0x80
|
|
33
|
+
FLAG_FAIL = 0x08
|
|
34
|
+
FLAG_UNKNOWN = 0x02
|
|
35
|
+
FLAG_OVERALL = 0x01
|
|
36
|
+
|
|
37
|
+
def __init__(self):
|
|
38
|
+
EventSource.__init__(self, ['binSummaryReady'])
|
|
39
|
+
|
|
40
|
+
def binSummaryReady(self, dataSource): pass
|
|
41
|
+
|
|
42
|
+
def getHPfFlags(self, row):
|
|
43
|
+
flag = 0
|
|
44
|
+
if row[hbr.HBIN_PF] == 'F':
|
|
45
|
+
flag |= self.FLAG_FAIL
|
|
46
|
+
elif row[hbr.HBIN_PF] != 'P':
|
|
47
|
+
flag |= self.FLAG_UNKNOWN
|
|
48
|
+
return flag
|
|
49
|
+
|
|
50
|
+
def getSPfFlags(self, row):
|
|
51
|
+
flag = 0
|
|
52
|
+
if row[sbr.SBIN_PF] == 'F':
|
|
53
|
+
flag |= self.FLAG_FAIL
|
|
54
|
+
elif row[sbr.SBIN_PF] != 'P':
|
|
55
|
+
flag |= self.FLAG_UNKNOWN
|
|
56
|
+
return flag
|
|
57
|
+
|
|
58
|
+
def getOverallHbins(self):
|
|
59
|
+
return self.overallHbrs.values()
|
|
60
|
+
|
|
61
|
+
def getSiteHbins(self):
|
|
62
|
+
return self.summaryHbrs.values()
|
|
63
|
+
|
|
64
|
+
def getSiteSynthHbins(self):
|
|
65
|
+
for siteBin, info in self.hbinParts.iteritems():
|
|
66
|
+
site, bin = siteBin
|
|
67
|
+
partCount, isPass = info
|
|
68
|
+
if isPass[0]:
|
|
69
|
+
pf = 'P'
|
|
70
|
+
else:
|
|
71
|
+
pf = 'F'
|
|
72
|
+
row = [0, site, bin, partCount[0], pf, None]
|
|
73
|
+
yield row
|
|
74
|
+
|
|
75
|
+
def getOverallSbins(self):
|
|
76
|
+
return self.overallSbrs.values()
|
|
77
|
+
|
|
78
|
+
def getSiteSbins(self):
|
|
79
|
+
return self.summarySbrs.values()
|
|
80
|
+
|
|
81
|
+
def getSiteSynthSbins(self):
|
|
82
|
+
for siteBin, info in self.sbinParts.iteritems():
|
|
83
|
+
site, bin = siteBin
|
|
84
|
+
partCount, isPass = info
|
|
85
|
+
if isPass[0]:
|
|
86
|
+
pf = 'P'
|
|
87
|
+
else:
|
|
88
|
+
pf = 'F'
|
|
89
|
+
row = [0, site, bin, partCount[0], pf, None]
|
|
90
|
+
yield row
|
|
91
|
+
|
|
92
|
+
def before_begin(self, dataSource):
|
|
93
|
+
self.hbinParts = dict()
|
|
94
|
+
self.sbinParts = dict()
|
|
95
|
+
self.summaryHbrs = dict()
|
|
96
|
+
self.summarySbrs = dict()
|
|
97
|
+
self.overallHbrs = dict()
|
|
98
|
+
self.overallSbrs = dict()
|
|
99
|
+
|
|
100
|
+
def before_complete(self, dataSource):
|
|
101
|
+
self.binSummaryReady(dataSource)
|
|
102
|
+
|
|
103
|
+
def before_send(self, dataSource, data):
|
|
104
|
+
table, row = data
|
|
105
|
+
if table.name == prr.name:
|
|
106
|
+
self.onPrr(row)
|
|
107
|
+
elif table.name == hbr.name:
|
|
108
|
+
self.onHbr(row)
|
|
109
|
+
elif table.name == sbr.name:
|
|
110
|
+
self.onSbr(row)
|
|
111
|
+
|
|
112
|
+
def ifElse(cond, trueVal, falseVal):
|
|
113
|
+
if cond:
|
|
114
|
+
return trueVal
|
|
115
|
+
else:
|
|
116
|
+
return falseVal
|
|
117
|
+
|
|
118
|
+
def onPrr(self, row):
|
|
119
|
+
countList, passList = self.hbinParts.setdefault(
|
|
120
|
+
(row[prr.SITE_NUM], row[prr.HARD_BIN]), ([0], [None]))
|
|
121
|
+
countList[0] += 1
|
|
122
|
+
if passList[0] is None:
|
|
123
|
+
passList[0] = ifElse(row[prr.PART_FLG] & 0x08 == 0, 'P', 'F')
|
|
124
|
+
elif passList[0] != ' ':
|
|
125
|
+
if passList[0] != ifElse(row[prr.PART_FLG] & 0x08 == 0, 'P', 'F'):
|
|
126
|
+
passList[0] = ' '
|
|
127
|
+
|
|
128
|
+
countList, passList = self.sbinParts.setdefault(
|
|
129
|
+
(row[prr.SITE_NUM], row[prr.SOFT_BIN]), ([0], [False]))
|
|
130
|
+
countList[0] += 1
|
|
131
|
+
if passList[0] is None:
|
|
132
|
+
passList[0] = ifElse(row[prr.PART_FLG] & 0x08 == 0, 'P', 'F')
|
|
133
|
+
elif passList[0] != ' ':
|
|
134
|
+
if passList[0] != ifElse(row[prr.PART_FLG] & 0x08 == 0, 'P', 'F'):
|
|
135
|
+
passList[0] = ' '
|
|
136
|
+
|
|
137
|
+
def onHbr(self, row):
|
|
138
|
+
if row[hbr.HEAD_NUM] == 255:
|
|
139
|
+
self.overallHbrs[row[hbr.HBIN_NUM]] = row
|
|
140
|
+
else:
|
|
141
|
+
self.summaryHbrs[(row[hbr.SITE_NUM], row[hbr.HBIN_NUM])] = row
|
|
142
|
+
|
|
143
|
+
def onSbr(self, row):
|
|
144
|
+
if row[sbr.HEAD_NUM] == 255:
|
|
145
|
+
self.overallSbrs[row[sbr.SBIN_NUM]] = row
|
|
146
|
+
else:
|
|
147
|
+
self.summarySbrs[(row[sbr.SITE_NUM], row[sbr.SBIN_NUM])] = row
|
|
148
|
+
|
pystdf/IO.py
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
#
|
|
2
|
+
# PySTDF - The Pythonic STDF Parser
|
|
3
|
+
# Copyright (C) 2006 Casey Marshall
|
|
4
|
+
#
|
|
5
|
+
# This program is free software; you can redistribute it and/or
|
|
6
|
+
# modify it under the terms of the GNU General Public License
|
|
7
|
+
# as published by the Free Software Foundation; either version 2
|
|
8
|
+
# of the License, or (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with this program; if not, write to the Free Software
|
|
17
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
18
|
+
#
|
|
19
|
+
import io
|
|
20
|
+
import sys
|
|
21
|
+
|
|
22
|
+
import struct
|
|
23
|
+
import re
|
|
24
|
+
import chardet
|
|
25
|
+
|
|
26
|
+
from pystdf.Types import *
|
|
27
|
+
from pystdf import V4
|
|
28
|
+
|
|
29
|
+
from pystdf.Pipeline import DataSource
|
|
30
|
+
|
|
31
|
+
def appendFieldParser(fn, action):
|
|
32
|
+
"""Append a field parsing function to a record parsing function.
|
|
33
|
+
This is used to build record parsing functions based on the record type specification."""
|
|
34
|
+
def newRecordParser(*args):
|
|
35
|
+
fields = fn(*args)
|
|
36
|
+
try:
|
|
37
|
+
fields.append(action(*args))
|
|
38
|
+
except EndOfRecordException: pass
|
|
39
|
+
return fields
|
|
40
|
+
return newRecordParser
|
|
41
|
+
|
|
42
|
+
class Parser(DataSource):
|
|
43
|
+
def readAndUnpack(self, header, fmt):
|
|
44
|
+
size = struct.calcsize(fmt)
|
|
45
|
+
if (size > header.len):
|
|
46
|
+
self.inp.read(header.len)
|
|
47
|
+
header.len = 0
|
|
48
|
+
raise EndOfRecordException()
|
|
49
|
+
buf = self.inp.read(size)
|
|
50
|
+
if len(buf) == 0:
|
|
51
|
+
self.eof = 1
|
|
52
|
+
raise EofException()
|
|
53
|
+
header.len -= len(buf)
|
|
54
|
+
val,=struct.unpack(self.endian + fmt, buf)
|
|
55
|
+
if isinstance(val,bytes):
|
|
56
|
+
return val.decode("ascii")
|
|
57
|
+
else:
|
|
58
|
+
return val
|
|
59
|
+
|
|
60
|
+
def readAndUnpackDirect(self, fmt):
|
|
61
|
+
size = struct.calcsize(fmt)
|
|
62
|
+
buf = self.inp.read(size)
|
|
63
|
+
if len(buf) == 0:
|
|
64
|
+
self.eof = 1
|
|
65
|
+
raise EofException()
|
|
66
|
+
val,=struct.unpack(self.endian + fmt, buf)
|
|
67
|
+
return val
|
|
68
|
+
|
|
69
|
+
def readField(self, header, stdfFmt):
|
|
70
|
+
return self.readAndUnpack(header, packFormatMap[stdfFmt])
|
|
71
|
+
|
|
72
|
+
def readFieldDirect(self, stdfFmt):
|
|
73
|
+
return self.readAndUnpackDirect(packFormatMap[stdfFmt])
|
|
74
|
+
|
|
75
|
+
def readCn(self, header):
|
|
76
|
+
if header.len == 0:
|
|
77
|
+
raise EndOfRecordException()
|
|
78
|
+
slen = self.readField(header, "U1")
|
|
79
|
+
if slen > header.len:
|
|
80
|
+
self.inp.read(header.len)
|
|
81
|
+
header.len = 0
|
|
82
|
+
raise EndOfRecordException()
|
|
83
|
+
if slen == 0:
|
|
84
|
+
return ""
|
|
85
|
+
buf = self.inp.read(slen);
|
|
86
|
+
if len(buf) == 0:
|
|
87
|
+
self.eof = 1
|
|
88
|
+
raise EofException()
|
|
89
|
+
header.len -= len(buf)
|
|
90
|
+
val,=struct.unpack(str(slen) + "s", buf)
|
|
91
|
+
try:
|
|
92
|
+
return val.decode("ascii")
|
|
93
|
+
except Exception as e:
|
|
94
|
+
# to process when UnicodeDecodeError
|
|
95
|
+
print(e)
|
|
96
|
+
result = chardet.detect(val)
|
|
97
|
+
encoding = result['encoding']
|
|
98
|
+
print("The unknow format is: " + encoding)
|
|
99
|
+
try:
|
|
100
|
+
return val.decode(encoding)
|
|
101
|
+
except UnicodeDecodeError:
|
|
102
|
+
return val.decode("utf-8", errors="replace when UnicodeDecodeError")
|
|
103
|
+
|
|
104
|
+
def readBn(self, header):
|
|
105
|
+
blen = self.readField(header, "U1")
|
|
106
|
+
bn = []
|
|
107
|
+
for i in range(0, blen):
|
|
108
|
+
bn.append(self.readField(header, "B1"))
|
|
109
|
+
return bn
|
|
110
|
+
|
|
111
|
+
def readDn(self, header):
|
|
112
|
+
dbitlen = self.readField(header, "U2")
|
|
113
|
+
dlen = dbitlen / 8
|
|
114
|
+
if dbitlen % 8 > 0:
|
|
115
|
+
dlen+=1
|
|
116
|
+
dn = []
|
|
117
|
+
for i in range(0, int(dlen)):
|
|
118
|
+
dn.append(self.readField(header, "B1"))
|
|
119
|
+
return dn
|
|
120
|
+
|
|
121
|
+
def readVn(self, header):
|
|
122
|
+
vlen = self.readField(header, "U2")
|
|
123
|
+
vn = []
|
|
124
|
+
for i in range(0, vlen):
|
|
125
|
+
fldtype = self.readField(header, "B1")
|
|
126
|
+
if fldtype in self.vnMap:
|
|
127
|
+
vn.append(self.vnMap[fldtype](header))
|
|
128
|
+
return vn
|
|
129
|
+
|
|
130
|
+
def readArray(self, header, indexValue, stdfFmt):
|
|
131
|
+
if (stdfFmt == 'N1'):
|
|
132
|
+
self.readArray(header, indexValue/2+indexValue%2, 'U1')
|
|
133
|
+
return
|
|
134
|
+
arr = []
|
|
135
|
+
for i in range(int(indexValue)):
|
|
136
|
+
arr.append(self.unpackMap[stdfFmt](header, stdfFmt))
|
|
137
|
+
return arr
|
|
138
|
+
|
|
139
|
+
def readHeader(self):
|
|
140
|
+
hdr = RecordHeader()
|
|
141
|
+
hdr.len = self.readFieldDirect("U2")
|
|
142
|
+
hdr.typ = self.readFieldDirect("U1")
|
|
143
|
+
hdr.sub = self.readFieldDirect("U1")
|
|
144
|
+
return hdr
|
|
145
|
+
|
|
146
|
+
def __detectEndian(self):
|
|
147
|
+
self.eof = 0
|
|
148
|
+
header = self.readHeader()
|
|
149
|
+
if header.typ != 0 and header.sub != 10:
|
|
150
|
+
raise InitialSequenceException()
|
|
151
|
+
cpuType = self.readFieldDirect("U1")
|
|
152
|
+
if self.reopen_fn:
|
|
153
|
+
self.inp = self.reopen_fn()
|
|
154
|
+
else:
|
|
155
|
+
self.inp.seek(0)
|
|
156
|
+
if cpuType == 2:
|
|
157
|
+
return '<' #'<'
|
|
158
|
+
else:
|
|
159
|
+
return '>'
|
|
160
|
+
|
|
161
|
+
def header(self, header): pass
|
|
162
|
+
|
|
163
|
+
def parse_records(self, count=0, skipType=""):
|
|
164
|
+
i = 0
|
|
165
|
+
self.eof = 0
|
|
166
|
+
fields = None
|
|
167
|
+
|
|
168
|
+
# Convert skipType to a set for efficient O(1) lookup
|
|
169
|
+
# Support both single string and iterable of strings
|
|
170
|
+
if isinstance(skipType, str):
|
|
171
|
+
skip_types = {skipType} if skipType else set()
|
|
172
|
+
else:
|
|
173
|
+
skip_types = set(skipType) if skipType else set()
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
while self.eof==0:
|
|
177
|
+
header = self.readHeader()
|
|
178
|
+
self.header(header)
|
|
179
|
+
curRec = self.inp.read(header.len)
|
|
180
|
+
bakup = self.inp # backup current position
|
|
181
|
+
self.inp = io.BytesIO(curRec) # make current position to the beginning of type
|
|
182
|
+
|
|
183
|
+
if (header.typ, header.sub) in self.recordMap:
|
|
184
|
+
recType = self.recordMap[(header.typ, header.sub)]
|
|
185
|
+
recParser = self.recordParsers[(header.typ, header.sub)]
|
|
186
|
+
# add skipType to bypass parse some certain recType
|
|
187
|
+
if recType.name in skip_types:
|
|
188
|
+
self.inp = bakup # restore file position
|
|
189
|
+
continue
|
|
190
|
+
|
|
191
|
+
fields = recParser(self, header, [])
|
|
192
|
+
if len(fields) < len(recType.columnNames):
|
|
193
|
+
fields += [None] * (len(recType.columnNames) - len(fields))
|
|
194
|
+
self.send((recType, fields))
|
|
195
|
+
if header.len > 0:
|
|
196
|
+
print(
|
|
197
|
+
"Warning: Broken header. Unprocessed data left in record of type '%s'. Working around it." % recType.__class__.__name__,
|
|
198
|
+
file=sys.stderr,
|
|
199
|
+
)
|
|
200
|
+
self.inp.read(header.len)
|
|
201
|
+
header.len = 0
|
|
202
|
+
else:
|
|
203
|
+
self.inp.read(header.len)
|
|
204
|
+
self.inp = bakup # restore file position
|
|
205
|
+
if count:
|
|
206
|
+
i += 1
|
|
207
|
+
if i >= count: break
|
|
208
|
+
except EofException: pass
|
|
209
|
+
|
|
210
|
+
def auto_detect_endian(self):
|
|
211
|
+
if self.inp.tell() == 0:
|
|
212
|
+
self.endian = '@'
|
|
213
|
+
self.endian = self.__detectEndian()
|
|
214
|
+
|
|
215
|
+
def parse(self, count=0, skipType=""):
|
|
216
|
+
self.begin()
|
|
217
|
+
|
|
218
|
+
try:
|
|
219
|
+
self.auto_detect_endian()
|
|
220
|
+
self.parse_records(count, skipType)
|
|
221
|
+
self.complete()
|
|
222
|
+
except Exception as exception:
|
|
223
|
+
self.cancel(exception)
|
|
224
|
+
raise
|
|
225
|
+
|
|
226
|
+
def getFieldParser(self, fieldType):
|
|
227
|
+
if (fieldType.startswith("k")):
|
|
228
|
+
fieldIndex, arrayFmt = re.match('k(\d+)([A-Z][a-z0-9]+)', fieldType).groups()
|
|
229
|
+
return lambda self, header, fields: self.readArray(header, fields[int(fieldIndex)], arrayFmt)
|
|
230
|
+
else:
|
|
231
|
+
parseFn = self.unpackMap[fieldType]
|
|
232
|
+
return lambda self, header, fields: parseFn(header, fieldType)
|
|
233
|
+
|
|
234
|
+
def createRecordParser(self, recType):
|
|
235
|
+
# Special handling for STR record with conditional fields
|
|
236
|
+
if hasattr(recType, 'typ') and hasattr(recType, 'sub') and recType.typ == 15 and recType.sub == 30:
|
|
237
|
+
return self.createStrRecordParser(recType)
|
|
238
|
+
|
|
239
|
+
fn = lambda self, header, fields: fields
|
|
240
|
+
for stdfType in recType.fieldStdfTypes:
|
|
241
|
+
fn = appendFieldParser(fn, self.getFieldParser(stdfType))
|
|
242
|
+
return fn
|
|
243
|
+
|
|
244
|
+
def createStrRecordParser(self, recType):
|
|
245
|
+
"""
|
|
246
|
+
Custom parser for STR (Scan Test Record) that handles conditional fields.
|
|
247
|
+
MASK_MAP and FAL_MAP fields are only present when FMU_FLG > 0.
|
|
248
|
+
"""
|
|
249
|
+
def strParser(self, header, fields):
|
|
250
|
+
# Field indices for STR record (from V4.py)
|
|
251
|
+
# 0-12: Fixed fields up to FMU_FLG
|
|
252
|
+
fixed_field_indices = [
|
|
253
|
+
0, # CONT_FLG
|
|
254
|
+
1, # TEST_NUM
|
|
255
|
+
2, # HEAD_NUM
|
|
256
|
+
3, # SITE_NUM
|
|
257
|
+
4, # PSR_REF
|
|
258
|
+
5, # TEST_FLG
|
|
259
|
+
6, # LOG_TYP
|
|
260
|
+
7, # TEST_TXT
|
|
261
|
+
8, # ALARM_ID
|
|
262
|
+
9, # PROG_TXT
|
|
263
|
+
10, # RSLT_TXT
|
|
264
|
+
11, # Z_VAL
|
|
265
|
+
12 # FMU_FLG
|
|
266
|
+
]
|
|
267
|
+
|
|
268
|
+
# Read fixed fields (0-12)
|
|
269
|
+
for i in fixed_field_indices:
|
|
270
|
+
try:
|
|
271
|
+
field_value = self.unpackMap[recType.fieldStdfTypes[i]](header, recType.fieldStdfTypes[i])
|
|
272
|
+
fields.append(field_value)
|
|
273
|
+
except EndOfRecordException:
|
|
274
|
+
break
|
|
275
|
+
|
|
276
|
+
# Check FMU_FLG value (field 12)
|
|
277
|
+
fmu_flg = fields[12] if len(fields) > 12 else 0
|
|
278
|
+
|
|
279
|
+
# Fields 13-14: MASK_MAP and FAL_MAP (conditional, only if FMU_FLG > 0)
|
|
280
|
+
if fmu_flg > 0:
|
|
281
|
+
# Read MASK_MAP (field 13)
|
|
282
|
+
try:
|
|
283
|
+
mask_map = self.readDn(header)
|
|
284
|
+
fields.append(mask_map)
|
|
285
|
+
except EndOfRecordException:
|
|
286
|
+
fields.append(None)
|
|
287
|
+
|
|
288
|
+
# Read FAL_MAP (field 14)
|
|
289
|
+
try:
|
|
290
|
+
fal_map = self.readDn(header)
|
|
291
|
+
fields.append(fal_map)
|
|
292
|
+
except EndOfRecordException:
|
|
293
|
+
fields.append(None)
|
|
294
|
+
else:
|
|
295
|
+
# FMU_FLG = 0, these fields don't exist in data
|
|
296
|
+
# Insert None placeholders to maintain field positions
|
|
297
|
+
fields.append(None) # MASK_MAP
|
|
298
|
+
fields.append(None) # FAL_MAP
|
|
299
|
+
|
|
300
|
+
# Read remaining fields (15 onwards)
|
|
301
|
+
# These are fixed fields, need to handle array fields specially
|
|
302
|
+
for i in range(15, len(recType.fieldStdfTypes)):
|
|
303
|
+
try:
|
|
304
|
+
field_type = recType.fieldStdfTypes[i]
|
|
305
|
+
|
|
306
|
+
# Check if it's an array field (starts with 'k')
|
|
307
|
+
if field_type.startswith('k'):
|
|
308
|
+
match = re.match('k(\d+)([A-Z][a-z0-9]+)', field_type)
|
|
309
|
+
if match:
|
|
310
|
+
field_index = int(match.group(1))
|
|
311
|
+
array_fmt = match.group(2)
|
|
312
|
+
|
|
313
|
+
# For array fields, the index refers to the field position
|
|
314
|
+
# When FMU_FLG = 0, fields 13-14 are None, but indices are preserved
|
|
315
|
+
# So we can directly access fields[field_index]
|
|
316
|
+
try:
|
|
317
|
+
array_value = self.readArray(header, fields[field_index], array_fmt)
|
|
318
|
+
fields.append(array_value)
|
|
319
|
+
except (IndexError, EndOfRecordException):
|
|
320
|
+
fields.append(None)
|
|
321
|
+
else:
|
|
322
|
+
# Regular field
|
|
323
|
+
field_value = self.unpackMap[field_type](header, field_type)
|
|
324
|
+
fields.append(field_value)
|
|
325
|
+
except EndOfRecordException:
|
|
326
|
+
# If we run out of data, append None for remaining fields
|
|
327
|
+
fields.append(None)
|
|
328
|
+
|
|
329
|
+
return fields
|
|
330
|
+
|
|
331
|
+
return strParser
|
|
332
|
+
|
|
333
|
+
def __init__(self, recTypes=V4.records, inp=sys.stdin, reopen_fn=None, endian=None):
|
|
334
|
+
DataSource.__init__(self, ['header']);
|
|
335
|
+
self.eof = 1
|
|
336
|
+
self.recTypes = set(recTypes)
|
|
337
|
+
self.inp = inp
|
|
338
|
+
self.reopen_fn = reopen_fn
|
|
339
|
+
self.endian = endian
|
|
340
|
+
|
|
341
|
+
self.recordMap = dict(
|
|
342
|
+
[ ( (recType.typ, recType.sub), recType )
|
|
343
|
+
for recType in recTypes ])
|
|
344
|
+
|
|
345
|
+
self.unpackMap = {
|
|
346
|
+
"C1": self.readField,
|
|
347
|
+
"B1": self.readField,
|
|
348
|
+
"U1": self.readField,
|
|
349
|
+
"U2": self.readField,
|
|
350
|
+
"U4": self.readField,
|
|
351
|
+
"U8": self.readField,
|
|
352
|
+
"I1": self.readField,
|
|
353
|
+
"I2": self.readField,
|
|
354
|
+
"I4": self.readField,
|
|
355
|
+
"I8": self.readField,
|
|
356
|
+
"R4": self.readField,
|
|
357
|
+
"R8": self.readField,
|
|
358
|
+
"Cn": lambda header, fmt: self.readCn(header),
|
|
359
|
+
"Bn": lambda header, fmt: self.readBn(header),
|
|
360
|
+
"Dn": lambda header, fmt: self.readDn(header),
|
|
361
|
+
"Vn": lambda header, fmt: self.readVn(header)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
self.recordParsers = dict(
|
|
365
|
+
[ ( (recType.typ, recType.sub), self.createRecordParser(recType) )
|
|
366
|
+
for recType in recTypes ])
|
|
367
|
+
|
|
368
|
+
self.vnMap = {
|
|
369
|
+
0: lambda header: self.inp.read(header, 1),
|
|
370
|
+
1: lambda header: self.readField(header, "U1"),
|
|
371
|
+
2: lambda header: self.readField(header, "U2"),
|
|
372
|
+
3: lambda header: self.readField(header, "U4"),
|
|
373
|
+
4: lambda header: self.readField(header, "I1"),
|
|
374
|
+
5: lambda header: self.readField(header, "I2"),
|
|
375
|
+
6: lambda header: self.readField(header, "I4"),
|
|
376
|
+
7: lambda header: self.readField(header, "R4"),
|
|
377
|
+
8: lambda header: self.readField(header, "R8"),
|
|
378
|
+
10: lambda header: self.readCn(header),
|
|
379
|
+
11: lambda header: self.readBn(header),
|
|
380
|
+
12: lambda header: self.readDn(header),
|
|
381
|
+
13: lambda header: self.readField(header, "U1")
|
|
382
|
+
}
|
pystdf/Importer.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
#
|
|
2
|
+
# PySTDF - The Pythonic STDF Parser
|
|
3
|
+
# Copyright (C) 2006 Casey Marshall
|
|
4
|
+
#
|
|
5
|
+
# This program is free software; you can redistribute it and/or
|
|
6
|
+
# modify it under the terms of the GNU General Public License
|
|
7
|
+
# as published by the Free Software Foundation; either version 2
|
|
8
|
+
# of the License, or (at your option) any later version.
|
|
9
|
+
#
|
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
+
# GNU General Public License for more details.
|
|
14
|
+
#
|
|
15
|
+
# You should have received a copy of the GNU General Public License
|
|
16
|
+
# along with this program; if not, write to the Free Software
|
|
17
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
18
|
+
#
|
|
19
|
+
# Modified: 2017 Minh-Hai Nguyen
|
|
20
|
+
#
|
|
21
|
+
|
|
22
|
+
import numpy as np
|
|
23
|
+
import pandas as pd
|
|
24
|
+
from pystdf.IO import Parser
|
|
25
|
+
from pystdf.Writers import TextWriter
|
|
26
|
+
|
|
27
|
+
class MemoryWriter:
|
|
28
|
+
def __init__(self):
|
|
29
|
+
self.data = []
|
|
30
|
+
def after_send(self, dataSource, data):
|
|
31
|
+
self.data.append(data)
|
|
32
|
+
def write(self,line):
|
|
33
|
+
self.data.append(line)
|
|
34
|
+
def flush(self):
|
|
35
|
+
pass # Do nothing
|
|
36
|
+
|
|
37
|
+
def ImportSTDF(fname):
|
|
38
|
+
with open(fname,'rb') as fin:
|
|
39
|
+
p = Parser(inp=fin)
|
|
40
|
+
storage = MemoryWriter()
|
|
41
|
+
p.addSink(storage)
|
|
42
|
+
p.parse()
|
|
43
|
+
return storage.data
|
|
44
|
+
|
|
45
|
+
def STDF2Text(fname,delimiter='|'):
|
|
46
|
+
""" Convert STDF to a list of text representation
|
|
47
|
+
"""
|
|
48
|
+
with open(fname,'rb') as fin:
|
|
49
|
+
p = Parser(inp=fin)
|
|
50
|
+
storage = MemoryWriter()
|
|
51
|
+
p.addSink(TextWriter(storage,delimiter=delimiter))
|
|
52
|
+
p.parse()
|
|
53
|
+
return storage.data
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
def STDF2Dict(fname):
|
|
57
|
+
""" Convert STDF to a list of dictionary objects
|
|
58
|
+
"""
|
|
59
|
+
data = ImportSTDF(fname)
|
|
60
|
+
data_out = []
|
|
61
|
+
for datum in data:
|
|
62
|
+
datum_out = {}
|
|
63
|
+
RecType = datum[0].__class__.__name__.upper()
|
|
64
|
+
datum_out['RecType'] = RecType
|
|
65
|
+
for k,v in zip(datum[0].fieldMap,datum[1]):
|
|
66
|
+
datum_out[k[0]] = v
|
|
67
|
+
data_out.append(datum_out)
|
|
68
|
+
return data_out
|
|
69
|
+
|
|
70
|
+
def STDF2DataFrame(fname):
|
|
71
|
+
""" Convert STDF to a dictionary of DataFrame objects
|
|
72
|
+
"""
|
|
73
|
+
data = ImportSTDF(fname)
|
|
74
|
+
BigTable = {}
|
|
75
|
+
for datum in data:
|
|
76
|
+
RecType = datum[0].__class__.__name__.upper()
|
|
77
|
+
if RecType not in BigTable:
|
|
78
|
+
BigTable[RecType] = {}
|
|
79
|
+
Rec = BigTable[RecType]
|
|
80
|
+
for k,v in zip(datum[0].fieldMap,datum[1]):
|
|
81
|
+
if k[0] not in Rec:
|
|
82
|
+
Rec[k[0]] = []
|
|
83
|
+
Rec[k[0]].append(v)
|
|
84
|
+
for k,v in BigTable.items():
|
|
85
|
+
BigTable[k] = pd.DataFrame(v)
|
|
86
|
+
return BigTable
|