├── .github
├── FUNDING.yml
└── workflows
│ ├── python-publish.yml
│ └── python-test.yml
├── .gitignore
├── .project
├── Dockerfile
├── LICENSE.txt
├── README.txt
├── data
├── README
├── demofile.stdf
├── lot2.stdf
└── lot3.stdf
├── default.nix
├── docs
├── Makefile
└── source
│ ├── _templates
│ └── layout.html
│ ├── bugs.rst
│ ├── conf.py
│ ├── index.rst
│ ├── install.rst
│ ├── intro.rst
│ ├── license.rst
│ ├── pystdf_v4.rst
│ ├── sinks.rst
│ └── stdf.rst
├── pyproject.toml
├── pystdf
├── BinSummarizer.py
├── IO.py
├── Importer.py
├── Indexing.py
├── Mapping.py
├── OoHelpers.py
├── ParametricSummarizer.py
├── PartSummarizer.py
├── Pipeline.py
├── SummaryStatistics.py
├── TableTemplate.py
├── TestSummarizer.py
├── Types.py
├── V4.py
├── Writers.py
├── __init__.py
├── logexcept.py
└── scripts
│ ├── __init__.py
│ ├── rec_index.py
│ ├── stdf2excel.py
│ ├── stdf2text.py
│ ├── stdf2xml.py
│ └── stdf_slice.py
└── tests
├── __init__.py
├── test_BinSummarizer.py
└── test_IO.py
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: cmars
2 |
--------------------------------------------------------------------------------
/.github/workflows/python-publish.yml:
--------------------------------------------------------------------------------
1 | # This workflow will upload a Python Package using Twine when a release is created
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
3 |
4 | # This workflow uses actions that are not certified by GitHub.
5 | # They are provided by a third-party and are governed by
6 | # separate terms of service, privacy policy, and support
7 | # documentation.
8 |
9 | name: Upload Python Package
10 |
11 | on:
12 | release:
13 | types: [published]
14 |
15 | permissions:
16 | contents: read
17 |
18 | jobs:
19 | deploy:
20 |
21 | runs-on: ubuntu-latest
22 |
23 | steps:
24 | - uses: actions/checkout@v3
25 | - name: Set up Python
26 | uses: actions/setup-python@v3
27 | with:
28 | python-version: '3.x'
29 | - name: Install dependencies
30 | run: |
31 | python -m pip install --upgrade pip
32 | pip install build
33 | - name: Build package
34 | run: python -m build
35 | - name: Publish package
36 | uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
37 | with:
38 | user: __token__
39 | password: ${{ secrets.PYPI_API_TOKEN }}
40 |
--------------------------------------------------------------------------------
/.github/workflows/python-test.yml:
--------------------------------------------------------------------------------
1 | name: Python Tests
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 | pull_request:
7 | branches: [ "master" ]
8 |
9 | permissions:
10 | contents: read
11 |
12 | jobs:
13 | test:
14 | runs-on: ubuntu-latest
15 | strategy:
16 | matrix:
17 | python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
18 |
19 | steps:
20 | - uses: actions/checkout@v4
21 | - name: Set up Python ${{ matrix.python-version }}
22 | uses: actions/setup-python@v4
23 | with:
24 | python-version: ${{ matrix.python-version }}
25 | - name: Install dependencies
26 | run: |-
27 | python -m pip install --upgrade pip
28 | python -m pip install pytest
29 | python -m pip install .
30 | - name: Run tests
31 | run: |-
32 | python -m pytest tests/ -v
33 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | *.swp
3 | *.swo
4 | /build/
5 | /docs/build
6 | /.venv
7 | /dist
8 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | pystdf
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM python:3.8.9-slim
2 |
3 | COPY . /app
4 | WORKDIR /app
5 |
6 | RUN ["chmod", "-R", "+x", "scripts"]
7 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 |
294 | Copyright (C)
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | , 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
--------------------------------------------------------------------------------
/README.txt:
--------------------------------------------------------------------------------
1 | ===================================
2 | |PySTDF - The Pythonic STDF parser|
3 | ===================================
4 | Developed by Casey Marshall
5 |
6 | PySTDF is a parser for Standard Test Data Format (STDF) version 4 data files.
7 | I wrote PySTDF to get familiar with functional programming idioms and
8 | metaclasses in Python. As such, it uses some of the more powerful and
9 | expressive features of the Python language.
10 |
11 | PySTDF is an event-based parser. As an STDF file is parsed, you recieve
12 | record "events" in callback functions
13 |
14 | Refer to the provided command line scripts for ideas on how to use PySTDF:
15 |
16 | stdf2text, convert STDF to '|' delimited text format.
17 | stdf2excel, convert STDF to MS Excel.
18 | stdf_slice, an example of how to seek to a specific record offset in the STDF.
19 |
20 | I have also included a very basic STDF viewer GUI, StdfExplorer. I have plans
21 | to improve upon it further in Q4 2006 - Q5 2007.
22 |
23 | =========
24 | |INSTALL|
25 | =========
26 | Use the standard distutils setup.py.
27 |
28 | On Windows: "python setup.py install"
29 | On Unix: "sudo python setup.py install"
30 |
31 | ======
32 | |BUGS|
33 | ======
34 | PySTDF has no known bugs. However, it is my experience that every ATE vendor
35 | has its quirks and "special interpretation" of the STDFv4 specification.
36 |
37 | If you find a bug in PySTDF, please send me the STDF file that demonstrates it.
38 | This will help me improve the library.
39 |
40 | =========
41 | |LICENSE|
42 | =========
43 | PySTDF is released under the terms and conditions of the GPL version 2 license.
44 | You may freely use PySTDF, but you may not distribute it in closed-source
45 | proprietary applications. Please contact me if you are interested in
46 | purchasing an alternative license agreement to develop commercial software
47 | with PySTDF.
48 |
49 | If you need some STDF consulting/development work, I might be able to help you.
50 | I have over 5 years experience with STDF and semiconductor data analysis
51 | systems.
52 |
53 | If you're in the Austin area and just want to get some lunch, that is cool too :)
54 |
55 | ========
56 | |THANKS|
57 | ========
58 | Thanks for your interest in PySTDF. You're the reason I open-sourced it.
59 |
60 | Cheers,
61 | Casey
62 |
--------------------------------------------------------------------------------
/data/README:
--------------------------------------------------------------------------------
1 | The STDF files in this directory were originally provided on Galaxy
2 | Semiconductor's website for demonstration of their tools. They were
3 | obtained from this public URL, which I found via Google search:
4 |
5 | http://www.galaxysemi.com/examinator/support/install.htm
6 |
7 | I also welcome any user contributions of example STDF data, especially
8 | different ATE platforms and STDF versions.
9 |
--------------------------------------------------------------------------------
/data/demofile.stdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmars/pystdf/7c2c5dfac1124310e4b1426958f6e9324394ec16/data/demofile.stdf
--------------------------------------------------------------------------------
/data/lot2.stdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmars/pystdf/7c2c5dfac1124310e4b1426958f6e9324394ec16/data/lot2.stdf
--------------------------------------------------------------------------------
/data/lot3.stdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmars/pystdf/7c2c5dfac1124310e4b1426958f6e9324394ec16/data/lot3.stdf
--------------------------------------------------------------------------------
/default.nix:
--------------------------------------------------------------------------------
1 | { pkgs ? import {} }:
2 | pkgs.mkShell {
3 | nativeBuildInputs = with pkgs.buildPackages; [
4 | python38
5 | python38Packages.pip
6 | python38Packages.six
7 | python38Packages.wxPython_4_1
8 | ];
9 | shellHook = ''
10 | [ -d .venv ] || python3 -m venv .venv
11 | source .venv/bin/activate
12 | '';
13 | }
14 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = build
9 |
10 | # Internal variables.
11 | PAPEROPT_a4 = -D latex_paper_size=a4
12 | PAPEROPT_letter = -D latex_paper_size=letter
13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
14 | # the i18n builder cannot share the environment and doctrees with the others
15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
16 |
17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
18 |
19 | help:
20 | @echo "Please use \`make ' where is one of"
21 | @echo " html to make standalone HTML files"
22 | @echo " dirhtml to make HTML files named index.html in directories"
23 | @echo " singlehtml to make a single large HTML file"
24 | @echo " pickle to make pickle files"
25 | @echo " json to make JSON files"
26 | @echo " htmlhelp to make HTML files and a HTML help project"
27 | @echo " qthelp to make HTML files and a qthelp project"
28 | @echo " devhelp to make HTML files and a Devhelp project"
29 | @echo " epub to make an epub"
30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
31 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
32 | @echo " text to make text files"
33 | @echo " man to make manual pages"
34 | @echo " texinfo to make Texinfo files"
35 | @echo " info to make Texinfo files and run them through makeinfo"
36 | @echo " gettext to make PO message catalogs"
37 | @echo " changes to make an overview of all changed/added/deprecated items"
38 | @echo " linkcheck to check all external links for integrity"
39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
40 |
41 | clean:
42 | -rm -rf $(BUILDDIR)/*
43 |
44 | html:
45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
46 | @echo
47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
48 |
49 | dirhtml:
50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
51 | @echo
52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
53 |
54 | singlehtml:
55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
56 | @echo
57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
58 |
59 | pickle:
60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
61 | @echo
62 | @echo "Build finished; now you can process the pickle files."
63 |
64 | json:
65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
66 | @echo
67 | @echo "Build finished; now you can process the JSON files."
68 |
69 | htmlhelp:
70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
71 | @echo
72 | @echo "Build finished; now you can run HTML Help Workshop with the" \
73 | ".hhp project file in $(BUILDDIR)/htmlhelp."
74 |
75 | qthelp:
76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
77 | @echo
78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PySTDF.qhcp"
81 | @echo "To view the help file:"
82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PySTDF.qhc"
83 |
84 | devhelp:
85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
86 | @echo
87 | @echo "Build finished."
88 | @echo "To view the help file:"
89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/PySTDF"
90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PySTDF"
91 | @echo "# devhelp"
92 |
93 | epub:
94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
95 | @echo
96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
97 |
98 | latex:
99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
100 | @echo
101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
103 | "(use \`make latexpdf' here to do that automatically)."
104 |
105 | latexpdf:
106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
107 | @echo "Running LaTeX files through pdflatex..."
108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
110 |
111 | text:
112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
113 | @echo
114 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
115 |
116 | man:
117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
118 | @echo
119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
120 |
121 | texinfo:
122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
123 | @echo
124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
125 | @echo "Run \`make' in that directory to run these through makeinfo" \
126 | "(use \`make info' here to do that automatically)."
127 |
128 | info:
129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
130 | @echo "Running Texinfo files through makeinfo..."
131 | make -C $(BUILDDIR)/texinfo info
132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
133 |
134 | gettext:
135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
136 | @echo
137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
138 |
139 | changes:
140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
141 | @echo
142 | @echo "The overview file is in $(BUILDDIR)/changes."
143 |
144 | linkcheck:
145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
146 | @echo
147 | @echo "Link check complete; look for any errors in the above output " \
148 | "or in $(BUILDDIR)/linkcheck/output.txt."
149 |
150 | doctest:
151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
152 | @echo "Testing of doctests in the sources finished, look at the " \
153 | "results in $(BUILDDIR)/doctest/output.txt."
154 |
--------------------------------------------------------------------------------
/docs/source/_templates/layout.html:
--------------------------------------------------------------------------------
1 | {% extends "!layout.html" %}
2 | {% block extrahead %}
3 |
16 | {% endblock %}
17 |
18 |
--------------------------------------------------------------------------------
/docs/source/bugs.rst:
--------------------------------------------------------------------------------
1 | ====
2 | Bugs
3 | ====
4 |
5 | PySTDF has no known bugs. However, it is my experience that every ATE vendor
6 | has its quirks and "special interpretation" of the STDFv4 specification.
7 |
8 | If you find a bug in PySTDF, please send me (Casey Marshall
9 | ) the STDF file that demonstrates it.
10 | This will help me improve the library.
11 |
12 | You can also `open an issue `__
13 | on github.
14 |
--------------------------------------------------------------------------------
/docs/source/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # PySTDF documentation build configuration file, created by
4 | # sphinx-quickstart on Thu Feb 16 19:50:45 2012.
5 | #
6 | # This file is execfile()d with the current directory set to its containing dir.
7 | #
8 | # Note that not all possible configuration values are present in this
9 | # autogenerated file.
10 | #
11 | # All configuration values have a default; values that are commented out
12 | # serve to show the default.
13 |
14 | import sys, os
15 |
16 | # If extensions (or modules to document with autodoc) are in another directory,
17 | # add these directories to sys.path here. If the directory is relative to the
18 | # documentation root, use os.path.abspath to make it absolute, like shown here.
19 | sys.path.insert(0, os.path.abspath('../../'))
20 |
21 | # -- General configuration -----------------------------------------------------
22 |
23 | # If your documentation needs a minimal Sphinx version, state it here.
24 | #needs_sphinx = '1.0'
25 |
26 | # Add any Sphinx extension module names here, as strings. They can be extensions
27 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
28 | extensions = ['sphinx.ext.autodoc']
29 |
30 | # Add any paths that contain templates here, relative to this directory.
31 | templates_path = ['_templates']
32 |
33 | # The suffix of source filenames.
34 | source_suffix = '.rst'
35 |
36 | # The encoding of source files.
37 | #source_encoding = 'utf-8-sig'
38 |
39 | # The master toctree document.
40 | master_doc = 'index'
41 |
42 | # General information about the project.
43 | project = u'PySTDF'
44 | copyright = u'2012, Casey Marshall'
45 |
46 | # The version info for the project you're documenting, acts as replacement for
47 | # |version| and |release|, also used in various other places throughout the
48 | # built documents.
49 | #
50 | # The short X.Y version.
51 | version = '4'
52 | # The full version, including alpha/beta/rc tags.
53 | release = '4'
54 |
55 | # The language for content autogenerated by Sphinx. Refer to documentation
56 | # for a list of supported languages.
57 | #language = None
58 |
59 | # There are two options for replacing |today|: either, you set today to some
60 | # non-false value, then it is used:
61 | #today = ''
62 | # Else, today_fmt is used as the format for a strftime call.
63 | #today_fmt = '%B %d, %Y'
64 |
65 | # List of patterns, relative to source directory, that match files and
66 | # directories to ignore when looking for source files.
67 | exclude_patterns = []
68 |
69 | # The reST default role (used for this markup: `text`) to use for all documents.
70 | #default_role = None
71 |
72 | # If true, '()' will be appended to :func: etc. cross-reference text.
73 | #add_function_parentheses = True
74 |
75 | # If true, the current module name will be prepended to all description
76 | # unit titles (such as .. function::).
77 | #add_module_names = True
78 |
79 | # If true, sectionauthor and moduleauthor directives will be shown in the
80 | # output. They are ignored by default.
81 | #show_authors = False
82 |
83 | # The name of the Pygments (syntax highlighting) style to use.
84 | pygments_style = 'sphinx'
85 |
86 | # A list of ignored prefixes for module index sorting.
87 | #modindex_common_prefix = []
88 |
89 |
90 | # -- Options for HTML output ---------------------------------------------------
91 |
92 | # The theme to use for HTML and HTML Help pages. See the documentation for
93 | # a list of builtin themes.
94 | html_theme = 'nature'
95 |
96 | # Theme options are theme-specific and customize the look and feel of a theme
97 | # further. For a list of options available for each theme, see the
98 | # documentation.
99 | #html_theme_options = {}
100 |
101 | # Add any paths that contain custom themes here, relative to this directory.
102 | #html_theme_path = []
103 |
104 | # The name for this set of Sphinx documents. If None, it defaults to
105 | # " v documentation".
106 | #html_title = None
107 |
108 | # A shorter title for the navigation bar. Default is the same as html_title.
109 | #html_short_title = None
110 |
111 | # The name of an image file (relative to this directory) to place at the top
112 | # of the sidebar.
113 | #html_logo = None
114 |
115 | # The name of an image file (within the static path) to use as favicon of the
116 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
117 | # pixels large.
118 | #html_favicon = None
119 |
120 | # Add any paths that contain custom static files (such as style sheets) here,
121 | # relative to this directory. They are copied after the builtin static files,
122 | # so a file named "default.css" will overwrite the builtin "default.css".
123 | html_static_path = ['_static']
124 |
125 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
126 | # using the given strftime format.
127 | html_last_updated_fmt = '%b %d, %Y'
128 |
129 | # If true, SmartyPants will be used to convert quotes and dashes to
130 | # typographically correct entities.
131 | #html_use_smartypants = True
132 |
133 | # Custom sidebar templates, maps document names to template names.
134 | #html_sidebars = {}
135 |
136 | # Additional templates that should be rendered to pages, maps page names to
137 | # template names.
138 | #html_additional_pages = {}
139 |
140 | # If false, no module index is generated.
141 | #html_domain_indices = True
142 |
143 | # If false, no index is generated.
144 | #html_use_index = True
145 |
146 | # If true, the index is split into individual pages for each letter.
147 | #html_split_index = False
148 |
149 | # If true, links to the reST sources are added to the pages.
150 | #html_show_sourcelink = True
151 |
152 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
153 | #html_show_sphinx = True
154 |
155 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
156 | #html_show_copyright = True
157 |
158 | # If true, an OpenSearch description file will be output, and all pages will
159 | # contain a tag referring to it. The value of this option must be the
160 | # base URL from which the finished HTML is served.
161 | #html_use_opensearch = ''
162 |
163 | # This is the file name suffix for HTML files (e.g. ".xhtml").
164 | #html_file_suffix = None
165 |
166 | # Output file base name for HTML help builder.
167 | htmlhelp_basename = 'PySTDFdoc'
168 |
169 |
170 | # -- Options for LaTeX output --------------------------------------------------
171 |
172 | latex_elements = {
173 | # The paper size ('letterpaper' or 'a4paper').
174 | #'papersize': 'letterpaper',
175 |
176 | # The font size ('10pt', '11pt' or '12pt').
177 | #'pointsize': '10pt',
178 |
179 | # Additional stuff for the LaTeX preamble.
180 | #'preamble': '',
181 | }
182 |
183 | # Grouping the document tree into LaTeX files. List of tuples
184 | # (source start file, target name, title, author, documentclass [howto/manual]).
185 | latex_documents = [
186 | ('index', 'PySTDF.tex', u'PySTDF Documentation',
187 | u'Casey Marshall', 'manual'),
188 | ]
189 |
190 | # The name of an image file (relative to this directory) to place at the top of
191 | # the title page.
192 | #latex_logo = None
193 |
194 | # For "manual" documents, if this is true, then toplevel headings are parts,
195 | # not chapters.
196 | #latex_use_parts = False
197 |
198 | # If true, show page references after internal links.
199 | #latex_show_pagerefs = False
200 |
201 | # If true, show URL addresses after external links.
202 | #latex_show_urls = False
203 |
204 | # Documents to append as an appendix to all manuals.
205 | #latex_appendices = []
206 |
207 | # If false, no module index is generated.
208 | #latex_domain_indices = True
209 |
210 |
211 | # -- Options for manual page output --------------------------------------------
212 |
213 | # One entry per manual page. List of tuples
214 | # (source start file, name, description, authors, manual section).
215 | man_pages = [
216 | ('index', 'pystdf', u'PySTDF Documentation',
217 | [u'Casey Marshall'], 1)
218 | ]
219 |
220 | # If true, show URL addresses after external links.
221 | #man_show_urls = False
222 |
223 |
224 | # -- Options for Texinfo output ------------------------------------------------
225 |
226 | # Grouping the document tree into Texinfo files. List of tuples
227 | # (source start file, target name, title, author,
228 | # dir menu entry, description, category)
229 | texinfo_documents = [
230 | ('index', 'PySTDF', u'PySTDF Documentation', u'Casey Marshall',
231 | 'PySTDF', 'One line description of project.', 'Miscellaneous'),
232 | ]
233 |
234 | # Documents to append as an appendix to all manuals.
235 | #texinfo_appendices = []
236 |
237 | # If false, no module index is generated.
238 | #texinfo_domain_indices = True
239 |
240 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
241 | #texinfo_show_urls = 'footnote'
242 |
--------------------------------------------------------------------------------
/docs/source/index.rst:
--------------------------------------------------------------------------------
1 | .. PySTDF documentation master file
2 |
3 | =================================
4 | PySTDF - The Pythonic STDF parser
5 | =================================
6 |
7 | **Developed by** Casey Marshall
8 |
9 | **Code repository** https://github.com/cmars/pystdf
10 |
11 | PySTDF is a parser for Standard Test Data Format (STDF) version 4 data files.
12 | I wrote PySTDF to get familiar with functional programming idioms and
13 | metaclasses in Python. As such, it uses some of the more powerful and
14 | expressive features of the Python language.
15 |
16 | PySTDF is an event-based parser. As an STDF file is parsed, you recieve
17 | record *events* in callback functions
18 |
19 | Thanks for your interest in PySTDF. You're the reason I open-sourced it.
20 |
21 | Cheers,
22 | Casey
23 |
24 | Contents:
25 | =========
26 |
27 | .. toctree::
28 | :maxdepth: 2
29 |
30 | intro
31 | install
32 | sinks
33 | pystdf_v4
34 | stdf
35 | license
36 | bugs
37 |
38 | Indices and tables
39 | ==================
40 |
41 | * :ref:`genindex`
42 | * :ref:`modindex`
43 | * :ref:`search`
44 |
--------------------------------------------------------------------------------
/docs/source/install.rst:
--------------------------------------------------------------------------------
1 | =======
2 | Install
3 | =======
4 |
5 | Use the standard distutils setup.py.
6 |
7 | On Windows: "python setup.py install"
8 | On Unix: "sudo python setup.py install"
9 |
10 |
--------------------------------------------------------------------------------
/docs/source/intro.rst:
--------------------------------------------------------------------------------
1 | ============
2 | Introduction
3 | ============
4 |
5 | general introduction
6 |
--------------------------------------------------------------------------------
/docs/source/license.rst:
--------------------------------------------------------------------------------
1 | =======
2 | License
3 | =======
4 |
5 | PySTDF is released under the terms and conditions of the GPL version 2 license.
6 | You may freely use PySTDF, but you may not distribute it in closed-source
7 | proprietary applications. Please contact me if you are interested in
8 | purchasing an alternative license agreement to develop commercial software
9 | with PySTDF.
10 |
11 | If you need some STDF consulting/development work, I might be able to help you.
12 | I have over 5 years experience with STDF and semiconductor data analysis
13 | systems.
14 |
15 | If you're in the Austin area and just want to get lunch, that is cool too :)
16 |
17 |
--------------------------------------------------------------------------------
/docs/source/pystdf_v4.rst:
--------------------------------------------------------------------------------
1 | ====================================
2 | PySTDF stdf Version 4 record classes
3 | ====================================
4 |
5 | .. automodule:: pystdf.V4
6 | :members:
7 |
8 |
--------------------------------------------------------------------------------
/docs/source/sinks.rst:
--------------------------------------------------------------------------------
1 | =====
2 | Sinks
3 | =====
4 |
--------------------------------------------------------------------------------
/docs/source/stdf.rst:
--------------------------------------------------------------------------------
1 | ===========================================================
2 | Standard Test Data Format (STDF Specification Version 4)
3 | ===========================================================
4 |
5 | Introduction to STDF
6 | ====================
7 | As the ATE industry matures, many vendors offer networking systems that
8 | complement the test systems themselves and help customers get more out of their
9 | ATE investment. Many of these networking systems are converging on popular
10 | standards, such as EthernetÛ . A glaring hole in these standards has been
11 | the lack of test result data compatibility between test systems of different
12 | manufacturers, and sometimes within the product lines of a single manufacturer.
13 | In order to help overcome this problem, Teradyne has developed a simple,
14 | flexible, portable data format to which existing data files and formats
15 | can be easily and economically converted. Called the Standard Test Data
16 | Format (STDFÛ ), its specification is contained in the following document.
17 | It is our hope that both users and manufacturers of semiconductor ATE will
18 | find this standard useful, and will incorporate it into their own operations
19 | and products. Teradyne has adopted this standard for the test result output
20 | of all of its UNIXÛ operating system based testers, and offers conversion
21 | software for users of its Test System Director for our other semiconductor
22 | test systems. Teradyne derives no direct commercial benefit from propagating
23 | this standard, but we hope its usefulness, thoroughness, and full documentation
24 | will make all of us who work with ATE more productive.
25 |
26 |
27 | Teradyne's Use of the STDF Specification
28 | ----------------------------------------
29 | The Standard Test Data Format is intended as a comprehensive standard for the
30 | entire ATE industry, not as a description of how Teradyne writes or analyzes
31 | test result data. A test system can support STDF without using all the STDF
32 | record types or filling in all the fields of the record types it does use.
33 | Similarly, when the specification says that an STDF record type can be used
34 | to create a certain report, it cannot be assumed that Teradyne data analysis
35 | software always uses the record type to create its reports. In addition,
36 | the statement that a field or record is required or optional applies only
37 | to the definition of a valid STDF file; data analysis software may require a
38 | field that is declared optional in the specification. For this reason, the
39 | STDF specification is not the final reference on how any piece of Teradyne
40 | software implements the specification. To determine how a Teradyne test
41 | system fills in the STDF record types, please refer to the documentation
42 | for that test system's executive software. To determine what STDF fields
43 | are used by a Teradyne data analysis tool, refer to the documentation for
44 | the data analysis product.
45 |
46 | STDF Design Objectives
47 | ======================
48 | As ATE networking continues to emerge into a heterogeneous environment
49 | involving various sophisticated computers and operating systems, it becomes
50 | necessary to define a common ground that allows testers, database and database
51 | management systems, and data analysis software to store and communicate test
52 | data in a form that is useful, general, and flexible.
53 |
54 | The Standard Test Data Format (STDF) described in this document provides
55 | such a form. STDF is flexible enough to meet the needs of the different
56 | testers that generate raw test data, the databases that store the data, and
57 | the data analysis programs that use the data. The fact that it is a single,
58 | coherent standard also facilitates the sharing and communicating of the data
59 | among these various components of the complete ATE system.
60 |
61 | STDF is not an attempt to specify a database architecture for either testers
62 | or the centralized database engines. Instead, it is a set of logical record
63 | types. Because data items are described in terms of logical record types,
64 | the record types can be used as the underlying data abstraction, whether the
65 | data resides in a data buffer, resides on a mass storage device, or is being
66 | propagated in a network message. It is independent of network or database
67 | architecture. Furthermore, the STDF logical record types may be treated as a
68 | convenient data object by any of the software, either networking or database,
69 | that may be used on a tester or database engine.
70 |
71 | Using a standard but flexible test data format makes it possible for a single
72 | data formatting program running on the centralized database engine to accept
73 | data from a wide range of testers, whether the testers come from one vendor
74 | or from different vendors or are custom-built by the ATE user. In addition,
75 | adherence to a standard format permits the exporting of data from the central
76 | database and data analysis engine to the user's in-house network for further
77 | analysis in a form that is well documented and thoroughly debugged. Finally,
78 | the standard makes it possible to develop portable software for data reporting
79 | and analysis on both the testers and the centralized database engine.
80 |
81 |
82 | The following list summarizes the major objectives that guided the design
83 | of STDF:
84 |
85 | * Be capable of storing test data for all semiconductor testers and trimmers.
86 | * Provide a common format for storage and transmission of data.
87 | * Provide a basis for portable data reporting and analysis software.
88 | * Decouple data message format and database format to allow enhancements to
89 | either, independently of the other.
90 | * Provide support for optional (missing or invalid) data.
91 | * Provide complete and concise documentation for developers and users.
92 | * Make it easy for customers to write their own reports or reformat data for
93 | their own database.
94 |
95 | STDF is already a standard within Teradyne:
96 |
97 | * All Teradyne semiconductor testers produce raw data in a format that conforms
98 | to STDF.
99 | * The Manufacturing Data Pipeline and Insight Series software can process any
100 | data written in conformance with STDF.
101 |
102 | STDF Record Structure
103 | =====================
104 | This section describes the basic STDF V4 record structure.
105 |
106 | It describes the following general topics, which
107 | are applicable to all the record types:
108 |
109 | * STDF record header
110 | * Record types and subtypes
111 | * Data type codes and representation
112 | * Optional fields and missing/invalid data
113 |
114 | STDF Record Header
115 | ------------------
116 |
117 | Each STDF record begins with a record header consisting of the following
118 | three fields:
119 |
120 | ======= =======================================================================
121 | Field Description
122 | ======= =======================================================================
123 | REC_LEN The number of bytes of data following the record header. REC_LEN does
124 | not include the four bytes of the record header.
125 | REC_TYP An integer identifying a group of related STDF record types.
126 | REC_SUB An integer identifying a specific STDF record type within each REC_TYP
127 | group. On REC_TYP and REC_SUB , see the next section.
128 | ======= =======================================================================
129 |
130 | Record Types and Subtypes
131 | -------------------------
132 |
133 | The header of each STDF record contains a pair of fields called **REC_TYP** and
134 | **REC_SUB**. Each **REC_TYP** value identifies a group of related STDF record
135 | types. Each **REC_SUB** value identifies a single STDF record type within a
136 | **REC_TYP** group. The combination of **REC_TYP** and **REC_SUB** values
137 | uniquely identifies each record type. This design allows groups of related
138 | records to be easily identified by data analysis programs, while providing
139 | unique identification for each type of record in the file.
140 |
141 | All **REC_TYP** and **REC_SUB** codes less than 200 are reserved for future use
142 | by Teradyne. All codes greater than 200 are available for custom applications
143 | use. The codes are all in decimal values. The official list of codes and
144 | documentation for their use is maintained by Teradyne's Semiconductor CIM
145 | Division (SCD).
146 |
147 | The following table lists the meaning of the **REC_TYP** codes currently defined
148 | by Teradyne, as well as the **REC_SUB** codes defined in the STDF specification.
149 |
150 | ======= ==================================================================
151 | REC_TYP Meaning and STDFREC_SUB Codes
152 | ======= ==================================================================
153 | 0 Information about the STDF file
154 | * 10 File Attributes Record (FAR - :class:`pystdf.V4.Far`)
155 | * 20 Audit Trail Record (ATR - :class:`pystdf.V4.Atr`)
156 | 1 Data collected on a per lot basis
157 | * 10 Master Information Record (MIR - :class:`pystdf.V4.Mir`)
158 | * 20 Master Results Record (MRR - :class:`pystdf.V4.Mrr`)
159 | * 30 Part Count Record (PCR - :class:`pystdf.V4.Pcr`)
160 | * 40 Hardware Bin Record (HBR - :class:`pystdf.V4.Hbr`)
161 | * 50 Software Bin Record (SBR - :class:`pystdf.V4.Sbr`)
162 | * 60 Pin Map Record (PMR - :class:`pystdf.V4.Pmr`)
163 | * 62 Pin Group Record (PGR - :class:`pystdf.V4.Pgr`)
164 | * 63 Pin List Record (PLR - :class:`pystdf.V4.Plr`)
165 | * 70 Retest Data Record (RDR - :class:`pystdf.V4.Rdr`)
166 | * 80 Site Description Record (SDR - :class:`pystdf.V4.Sdr`)
167 | 2 Data collected per wafer
168 | * 10 Wafer Information Record (WIR - :class:`pystdf.V4.Wir`)
169 | * 20 Wafer Results Record (WRR - :class:`pystdf.V4.Wrr`)
170 | * 30 Wafer Configuration Record (WCR - :class:`pystdf.V4.Wcr`)
171 | 5 Data collected on a per part basis
172 | * 10 Part Information Record (PIR - :class:`pystdf.V4.Pir`)
173 | * 20 Part Results Record (PRR - :class:`pystdf.V4.Prr`)
174 | 10 Data collected per test in the test program
175 | * 30 Test Synopsis Record (TSR - :class:`pystdf.V4.Tsr` )
176 | 15 Data collected per test execution
177 | * 10 Parametric Test Record (PTR - :class:`pystdf.V4.Ptr`)
178 | * 15 Multiple-Result Parametric Record (MPR - :class:`pystdf.V4.Mpr`)
179 | * 20 Functional Test Record (FTR - :class:`pystdf.V4.Ftr`)
180 | 20 Data collected per program segment
181 | * 10 Begin Program Section Record (BPS - :class:`pystdf.V4.Bps`)
182 | * 20 End Program Section Record (EPS - :class:`pystdf.V4.Eps`)
183 | 50 Generic Data
184 | * 10 Generic Data Record (GDR - :class:`pystdf.V4.Gdr`)
185 | * 30 Datalog Text Record (DTR - :class:`pystdf.V4.Dtr`)
186 | 180 Reserved for use by Image software
187 | 181 Reserved for use by IG900 software
188 | ======= ==================================================================
189 |
190 | Data Type Codes and Representation
191 | ----------------------------------
192 |
193 | The STDF specification uses a set of data type codes that are concise and
194 | easily recognizable. For example, R*4 indicates a REAL (float) value stored
195 | in four bytes. A byte consists of eight bits of data. For purposes of this
196 | document, the low order bit of each byte is designated as bit 0 and the high
197 | order bit as bit 7. The following table gives the complete list of STDF data
198 | type codes, as well as the equivalent C language type specifier.
199 |
200 | ====== =================================================== ===================
201 | Code Description C Type Specifier
202 | ====== =================================================== ===================
203 | C*12 Fixed length character string: char[12]
204 | If a fixed length character string does not fill
205 | the entire field, it must be left-justified and
206 | padded with spaces.
207 | C*n Variable length character string: char[]
208 | first byte = unsigned count of bytes to follow
209 | (maximum of 255 bytes)
210 | C*f Variable length character string: char[]
211 | string length is stored in another field
212 | U*1 One byte unsigned integer unsigned char
213 | U*2 Two byte unsigned integer unsigned short
214 | U*4 Four byte unsigned integer unsigned long
215 | I*1 One byte signed integer char
216 | I*2 Two byte signed integer short
217 | I*4 Four byte signed integer long
218 | R*4 Four byte floating point number float
219 | R*8 Eight byte floating point number long float (double)
220 | B*6 Fixed length bit-encoded data char[6]
221 | V*n Variable data type field:
222 | The data type is specified by a code in the
223 | first byte, and the data follows
224 | (maximum of 255 bytes)
225 | B*n Variable length bit-encoded field: char[]
226 | First byte = unsigned count of bytes to follow
227 | (maximum of 255 bytes).
228 | First data item in least significant bit of the
229 | second byte of the array (first byte is count.)
230 | D*n Variable length bit-encoded field: char[]
231 | First two bytes = unsigned count of bits to
232 | follow (maximum of 65,535 bits).
233 | First data item in least significant bit of the
234 | third byte of the array (first two bytes are
235 | count).
236 | Unused bits at the high order end of the last
237 | byte must be zero.
238 | N*1 Unsigned integer data stored in a nibble. char
239 | First item in low 4 bits, second item in high
240 | 4 bits. If an odd number of nibbles is indicated,
241 | the high nibble of the byte will be zero. Only
242 | whole bytes can be written to the STDF file.
243 | kxTYPE Array of data of the type specified. TYPE[]
244 | The value of *k* (the number of elements in the
245 | array) is defined in an earlier field in the
246 | record. For example, an array of short unsigned
247 | integers is defined as kxU*2.
248 | ====== =================================================== ===================
249 |
250 | Note on Time and Date Usage
251 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
252 | The date and time field used in this specification is defined as a four byte
253 | (32 bit) unsigned integer field measuring the number of seconds since midnight
254 | on January 1st, 1970, in the local time zone. This is the UNIX standard base
255 | time, adjusted to the local time zone. Refer to the Glossary for definitions
256 | of Setup time, Start time, and Finish time as used in STDF.
257 |
258 | Note on Data Representation
259 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^
260 | When data is shared among systems with unlike central processors, the problem
261 | arises that there is little or no standardization of data representation (that
262 | is, the bit ordering of various data types) among the various processors of
263 | the world. For example, the data representations for DEC, Motorola, Intel,
264 | and IBM computers are all different, even though at least two of them adhere
265 | to the IEEE floating point standard. Moreover, different processors made by
266 | the same company sometimes store data in incompatible ways.
267 |
268 | To address this problem, the STDF specification uses a field calledCPU_TYPE in
269 | the File Attributes Record (FAR). This field indicates the type of processor
270 | that wrote the data (for example, Sun series or DEC-11 series). The field
271 | is used as follows:
272 |
273 | * When writing an STDF file, a system uses its own native data representation.
274 | The type of the writing processor is stored in theCPU_TYPE field.
275 | * When reading an STDF file, a system must convert the records to its own
276 | native data representation as it reads them, if necessary. To do so, it checks
277 | the value of the CPU_TYPE field in the FAR, which is the first record in the
278 | file. Then, if the writing CPU's data representation
279 | is incompatible with its own, it uses a subroutine that reads the next (or
280 | selected) record and converts the records to its own data representation as
281 | it reads them.
282 |
283 | This approach has the following advantages:
284 |
285 | * All testers, trimmers, and hosts can read and write local data using their
286 | native data representation.
287 | * Testing and local data analysis are not slowed down by performing data
288 | conversions on any tester.
289 | * Use of a read subroutine makes data conversion transparent at read time.
290 |
291 | This approach works for any combination of host and tester processors, provided
292 | that the machines are capable of storing and reading the test data in eight bit
293 | bytes.
294 |
295 | Optional Fields and Missing/Invalid Data
296 | ----------------------------------------
297 |
298 | Certain fields in STDF records are defined as optional. An optional field
299 | must be present in the record, but there are ways to indicate that its value
300 | is not meaningful, that is, that its data should be considered missing or
301 | invalid. There are two such methods:
302 |
303 | * Some optional fields have a predefined value that means that the data for the
304 | field is missing. For example, if the optional field is a variable-length
305 | character string, a length byte of 0 means that the data is missing. If
306 | the field is numeric, a value of -1 may be defined as meaning that the
307 | data is missing.
308 | * For other optional fields, all possible stored values, including -1, are
309 | legal. In this case, the STDF specification for the record defines an
310 | Optional Data bit field. Each bit is used to designate whether an optional
311 | field in the record contains valid or invalid data. Usually, if the bit
312 | for an optional field is set, any data in the field is invalid and should
313 | be ignored.
314 |
315 | Optional fields at the end of a record may be omitted in order to save space
316 | on the storage medium. To be omitted, an optional field must have missing
317 | or invalid data, and all the fields following it must be optional fields
318 | containing missing or invalid data. It is never legal to omit an optional
319 | field from the middle of the record.
320 |
321 | The specification of each STDF record has a column labelled **Missing/Invalid
322 | Data Flag**.An entry in this column means that the field is optional, and that
323 | the value shown is the way to flag the field's data as missing or invalid. If
324 | the column does not have an entry, the field is required.
325 |
326 | Each data type has a standard way of indicating missing or invalid data,
327 | as the following table shows:
328 |
329 | +-------------------------------+--------------------------------------------+
330 | | Data Type | Missing/Invalid Data Flag |
331 | +===============================+============================================+
332 | | Variable-length string | Set the length byte to 0. |
333 | +-------------------------------+--------------------------------------------+
334 | | Fixed-length character string | Fill the field with spaces. |
335 | +-------------------------------+--------------------------------------------+
336 | | Fixed-length binary string | Set a flag bit in an Optional Data byte. |
337 | +-------------------------------+--------------------------------------------+
338 | | Time and date fields | Use a binary 0. |
339 | +-------------------------------+--------------------------------------------+
340 | | Signed and unsigned integers | Use the indicated reserved value |
341 | | and floating point values | or set a flag bit in an OptionalDatabyte. |
342 | +-------------------------------+--------------------------------------------+
343 |
344 | Note on *Required* and *Optional*
345 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
346 | The distinction between required and optional fields applies only to the
347 | definition of a minimally valid STDF file.Itis not a statement about whether
348 | any software (even Teradyne software) requires the field. A field that
349 | is marked optional in the specification may be required by software that
350 | reads or analyzes the STDF file, even if Teradyne has written the software.
351 | In most cases, a minimally valid STDF file will not provide sufficient input
352 | for a piece of analysis software. You will need to fill in some fields or
353 | records that are not marked as required here. This specification is not
354 | intended to define the data requirements for any analysis software. The only
355 | authority on whether a piece of software requires a certain STDF field or
356 | record is the documentation for that software.
357 |
358 |
359 | STDF Record Types
360 | =================
361 |
362 | This section contains the definitions for the STDF record types. The following
363 | information is provided for each record type:
364 |
365 | * a statement of function: how the record type is used in the STDF file.
366 | * a table defining the data fields: first the standard STDF header, then the
367 | fields specific to this record type. The information includes the field name,
368 | the data type (see the previous section for the data type codes), a brief
369 | description of the field, and the flag to indicate missing or invalid data
370 | (see the previous section for a discussion of optional fields).
371 | * any additional notes on specific fields.
372 | * possible uses for this record type in data analysis reports. Note that this
373 | entry states only where the record type can be used. It is not a statement
374 | that the reports listed always use this record type, even if Teradyne has
375 | written those reports. For definitive information on how any data analysis
376 | software uses the STDF file, see the documentation for the data analysis
377 | software.
378 | * frequency with which the record type appears in the STDF file: for example,
379 | once per lot, once per wafer, one per test, and so forth.
380 | * the location of the record type in the STDF file. See the note on
381 | *initial sequence* on the next page.
382 |
383 | Note on *Initial Sequence*
384 | --------------------------
385 |
386 | For several record types, the *Location* says that the record must appear
387 | *after the initial sequence*. The phrase *initial sequence* refers to
388 | therecords that must appear at thebeginning of the STDFfile. The requirements
389 | for the initial sequence are as follows:
390 |
391 | * Every file must contain one File Attributes Record (FAR), one Master
392 | Information Record (MIR), one or more Part Count Records (PCR), and one
393 | Master Results Record (MRR ). All other records are optional.
394 | * The first record in the STDF file must be the File Attributes Record (FAR).
395 | * If one or more Audit Trail Records (ATRs) are used, they must appear
396 | immediately after the FAR.
397 | * The Master Information Record (MIR) must appear in every
398 | STDF file. Its location must be after the FAR and the ATR s(if ATRs are used).
399 | * If the Retest Data Record (RDR ) is used, it must appear immediately
400 | after the MIR.
401 | * If one or more Site Description Records (SDRs) are used,
402 | they must appear immediately after the MIR and RDR (if the RDR is used).
403 |
404 | Given these requirements, every STDF record must contain one of these
405 | initial sequences:
406 |
407 | * FAR - MIR
408 | * FAR - ATRs - MIR
409 | * FAR - MIR- RDR
410 | * FAR - ATRs - MIR- RDR
411 | * FAR - MIR - SDRs
412 | * FAR - ATRs - MIR - SDRs
413 | * FAR - MIR- RDR - SDRs
414 | * FAR - ATRs - MIR- RDR- SDRs
415 |
416 | All other STDF record types appear after the initial sequence.
417 |
418 | Alphabetical Listing
419 | --------------------
420 |
421 | In this section, the STDF record types appear in order of ascending record
422 | type and record subtype codes. For easier reference, the record types are
423 | listed on this page in alphabetical order, by the three-letter abbreviations
424 | for the record types.
425 |
426 | ====== ==================================== ============
427 | Record Type PySTDF Class
428 | ====== ==================================== ============
429 | ATR Audit Trail Record :class:`pystdf.V4.Atr`
430 | BPS Begin Program Section Record :class:`pystdf.V4.Bps`
431 | DTR Datalog Text Record :class:`pystdf.V4.Dtr`
432 | EPS End Program Section Record :class:`pystdf.V4.Eps`
433 | FAR File Attributes Record :class:`pystdf.V4.Far`
434 | FTR Functional Test Record :class:`pystdf.V4.Ftr`
435 | GDR Generic Data Record :class:`pystdf.V4.Gdr`
436 | HBR Hardware Bin Record :class:`pystdf.V4.Hbr`
437 | MIR Master Information Record :class:`pystdf.V4.Mir`
438 | MPR Multiple-Result Parametric Record :class:`pystdf.V4.Mpr`
439 | MRR Master Results Record :class:`pystdf.V4.Mrr`
440 | PCR Part Count Record :class:`pystdf.V4.Pcr`
441 | PGR Pin Group Record :class:`pystdf.V4.Pgr`
442 | PIR Part Information Record :class:`pystdf.V4.Pir`
443 | PLR Pin List Record :class:`pystdf.V4.Plr`
444 | PMR Pin Map Record :class:`pystdf.V4.Pmr`
445 | PRR Part Results Record :class:`pystdf.V4.Prr`
446 | PTR Parametric Test Record :class:`pystdf.V4.Ptr`
447 | RDR Retest Data Record :class:`pystdf.V4.Rdr`
448 | SBR Software Bin Record :class:`pystdf.V4.Sbr`
449 | SDR Site Description Record :class:`pystdf.V4.Sdr`
450 | TSR Test Synopsis Record :class:`pystdf.V4.Tsr`
451 | WCR Wafer Configuration Record :class:`pystdf.V4.Wcr`
452 | WIR Wafer Information Record :class:`pystdf.V4.Wir`
453 | WRR Wafer Results Record :class:`pystdf.V4.Wrr`
454 | ====== ==================================== ============
455 |
456 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | [build-system]
2 | requires = ["hatchling"]
3 | build-backend = "hatchling.build"
4 |
5 | [project]
6 | name = "pystdf"
7 | version = "1.3.4"
8 | description = "Python module for working with STDF files"
9 | readme = {content-type = "text/plain", text = """
10 | PySTDF is a Python module that makes it easy to work with STDF (Teradyne"s Standard Test Data Format). STDF is a commonly used file format in semiconductor test -- automated test equipment (ATE) from such vendors as Teradyne, Verigy, LTX, Credence, and others support this format.
11 |
12 | PySTDF provides event-based stream parsing of STDF version 4, along with indexers that can help you rearrange the data into a more useful tabular form, as well as generate missing summary records or new types of derivative records.
13 |
14 | The parser architecture is very flexible and can easily be extended to support STDF version 3 as well as custom record types.
15 |
16 | Potential applications of PySTDF include:
17 | * Debugging a vendor"s STDF implementation
18 | * Straight conversion to ASCII-readable form
19 | * Repairing STDF files
20 | * Developing an application that leverages STDF
21 | - Conversion to tabular form for statistical analysis tools
22 | - Loading data into a relational database
23 |
24 | PySTDF is released under a GPL license. Applications developed with PySTDF can only be released with a GPL-compatible license. Commercial applications can purchase an alternate license agreement for closed-source distribution.
25 | """}
26 | authors = [{name = "Casey Marshall", email="casey.marshall@gmail.com"}]
27 | url="https://github.com/cmars/pystdf"
28 | dependencies = [
29 | 'numpy',
30 | 'pandas',
31 | 'openpyxl',
32 | ]
33 | packages=["pystdf", "pystdf.scripts"]
34 | classifiers=[
35 | "Development Status :: 4 - Beta",
36 | "Environment :: Console",
37 | "License :: Free for non-commercial use",
38 | "License :: OSI Approved :: GNU General Public License (GPL)",
39 | "Operating System :: OS Independent",
40 | "Programming Language :: Python",
41 | "Intended Audience :: Manufacturing",
42 | "Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)",
43 | "Topic :: Utilities",
44 | "Topic :: Software Development :: Libraries :: Python Modules",
45 | "Topic :: Software Development :: Pre-processors",
46 | ]
47 |
48 | [project.scripts]
49 | stdf_slice = "pystdf.scripts.stdf_slice:main"
50 | rec_index = "pystdf.scripts.rec_index:main"
51 | stdf2excel = "pystdf.scripts.stdf2excel:main"
52 | stdf2text = "pystdf.scripts.stdf2text:main"
53 | stdf2xml = "pystdf.scripts.stdf2xml:main"
54 |
--------------------------------------------------------------------------------
/pystdf/BinSummarizer.py:
--------------------------------------------------------------------------------
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 |
149 |
--------------------------------------------------------------------------------
/pystdf/IO.py:
--------------------------------------------------------------------------------
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 | import sys
21 |
22 | import struct
23 | import re
24 |
25 | from pystdf.Types import *
26 | from pystdf import V4
27 |
28 | from pystdf.Pipeline import DataSource
29 |
30 | def appendFieldParser(fn, action):
31 | """Append a field parsing function to a record parsing function.
32 | This is used to build record parsing functions based on the record type specification."""
33 | def newRecordParser(*args):
34 | fields = fn(*args)
35 | try:
36 | fields.append(action(*args))
37 | except EndOfRecordException: pass
38 | return fields
39 | return newRecordParser
40 |
41 | class Parser(DataSource):
42 | def readAndUnpack(self, header, fmt):
43 | size = struct.calcsize(fmt)
44 | if (size > header.len):
45 | self.inp.read(header.len)
46 | header.len = 0
47 | raise EndOfRecordException()
48 | buf = self.inp.read(size)
49 | if len(buf) == 0:
50 | self.eof = 1
51 | raise EofException()
52 | if len(buf) < size:
53 | header.len = 0
54 | raise EndOfRecordException()
55 | header.len -= len(buf)
56 | val,=struct.unpack(self.endian + fmt, buf)
57 | if isinstance(val,bytes):
58 | return val.decode("ascii")
59 | else:
60 | return val
61 |
62 | def readAndUnpackDirect(self, fmt):
63 | size = struct.calcsize(fmt)
64 | buf = self.inp.read(size)
65 | if len(buf) == 0:
66 | self.eof = 1
67 | raise EofException()
68 | val,=struct.unpack(self.endian + fmt, buf)
69 | return val
70 |
71 | def readField(self, header, stdfFmt):
72 | return self.readAndUnpack(header, packFormatMap[stdfFmt])
73 |
74 | def readFieldDirect(self, stdfFmt):
75 | return self.readAndUnpackDirect(packFormatMap[stdfFmt])
76 |
77 | def readCn(self, header):
78 | if header.len == 0:
79 | raise EndOfRecordException()
80 | slen = self.readField(header, "U1")
81 | if slen > header.len:
82 | self.inp.read(header.len)
83 | header.len = 0
84 | raise EndOfRecordException()
85 | if slen == 0:
86 | return ""
87 | buf = self.inp.read(slen);
88 | if len(buf) == 0:
89 | self.eof = 1
90 | raise EofException()
91 | header.len -= len(buf)
92 | val,=struct.unpack(str(slen) + "s", buf)
93 | return val.decode("ascii")
94 |
95 | def readBn(self, header):
96 | blen = self.readField(header, "U1")
97 | bn = []
98 | for i in range(0, blen):
99 | bn.append(self.readField(header, "B1"))
100 | return bn
101 |
102 | def readDn(self, header):
103 | dbitlen = self.readField(header, "U2")
104 | dlen = dbitlen / 8
105 | if dbitlen % 8 > 0:
106 | dlen+=1
107 | dn = []
108 | for i in range(0, int(dlen)):
109 | dn.append(self.readField(header, "B1"))
110 | return dn
111 |
112 | def readVn(self, header):
113 | vlen = self.readField(header, "U2")
114 | vn = []
115 | for i in range(0, vlen):
116 | fldtype = self.readField(header, "B1")
117 | if fldtype in self.vnMap:
118 | vn.append(self.vnMap[fldtype](header))
119 | return vn
120 |
121 | def readArray(self, header, indexValue, stdfFmt):
122 | if (stdfFmt == 'N1'):
123 | return self.readArray(header, indexValue/2+indexValue%2, 'U1')
124 | arr = []
125 | for i in range(int(indexValue)):
126 | arr.append(self.unpackMap[stdfFmt](header, stdfFmt))
127 | return arr
128 |
129 | def readHeader(self):
130 | hdr = RecordHeader()
131 | hdr.len = self.readFieldDirect("U2")
132 | hdr.typ = self.readFieldDirect("U1")
133 | hdr.sub = self.readFieldDirect("U1")
134 | return hdr
135 |
136 | def __detectEndian(self):
137 | self.eof = 0
138 | header = self.readHeader()
139 | if header.typ != 0 and header.sub != 10:
140 | raise InitialSequenceException()
141 | cpuType = self.readFieldDirect("U1")
142 | if self.reopen_fn:
143 | self.inp = self.reopen_fn()
144 | else:
145 | self.inp.seek(0)
146 | if cpuType == 2:
147 | return '<'
148 | else:
149 | return '>'
150 |
151 | def header(self, header): pass
152 |
153 | def parse_records(self, count=0):
154 | i = 0
155 | self.eof = 0
156 | fields = None
157 | try:
158 | while self.eof==0:
159 | header = self.readHeader()
160 | self.header(header)
161 | if (header.typ, header.sub) in self.recordMap:
162 | recType = self.recordMap[(header.typ, header.sub)]
163 | recParser = self.recordParsers[(header.typ, header.sub)]
164 | fields = recParser(self, header, [])
165 | if len(fields) < len(recType.columnNames):
166 | fields += [None] * (len(recType.columnNames) - len(fields))
167 | self.send((recType, fields))
168 | if header.len > 0:
169 | print(
170 | "Warning: Broken header. Unprocessed data left in record of type '%s'. Working around it." % recType.__class__.__name__,
171 | file=sys.stderr,
172 | )
173 | self.inp.read(header.len)
174 | header.len = 0
175 | else:
176 | self.inp.read(header.len)
177 | if count:
178 | i += 1
179 | if i >= count: break
180 | except EofException: pass
181 |
182 | def auto_detect_endian(self):
183 | if self.inp.tell() == 0:
184 | self.endian = '@'
185 | self.endian = self.__detectEndian()
186 |
187 | def parse(self, count=0):
188 | self.begin()
189 |
190 | try:
191 | self.auto_detect_endian()
192 | self.parse_records(count)
193 | self.complete()
194 | except Exception as exception:
195 | self.cancel(exception)
196 | raise
197 |
198 | def getFieldParser(self, fieldType):
199 | if (fieldType.startswith("k")):
200 | fieldIndex, arrayFmt = re.match('k(\d+)([A-Z][a-z0-9]+)', fieldType).groups()
201 | return lambda self, header, fields: self.readArray(header, fields[int(fieldIndex)], arrayFmt)
202 | else:
203 | parseFn = self.unpackMap[fieldType]
204 | return lambda self, header, fields: parseFn(header, fieldType)
205 |
206 | def createRecordParser(self, recType):
207 | fn = lambda self, header, fields: fields
208 | for stdfType in recType.fieldStdfTypes:
209 | fn = appendFieldParser(fn, self.getFieldParser(stdfType))
210 | return fn
211 |
212 | def __init__(self, recTypes=V4.records, inp=sys.stdin, reopen_fn=None, endian=None):
213 | DataSource.__init__(self, ['header']);
214 | self.eof = 1
215 | self.recTypes = set(recTypes)
216 | self.inp = inp
217 | self.reopen_fn = reopen_fn
218 | self.endian = endian
219 |
220 | self.recordMap = dict(
221 | [ ( (recType.typ, recType.sub), recType )
222 | for recType in recTypes ])
223 |
224 | self.unpackMap = {
225 | "C1": self.readField,
226 | "B1": self.readField,
227 | "U1": self.readField,
228 | "U2": self.readField,
229 | "U4": self.readField,
230 | "U8": self.readField,
231 | "I1": self.readField,
232 | "I2": self.readField,
233 | "I4": self.readField,
234 | "I8": self.readField,
235 | "R4": self.readField,
236 | "R8": self.readField,
237 | "Cn": lambda header, fmt: self.readCn(header),
238 | "Bn": lambda header, fmt: self.readBn(header),
239 | "Dn": lambda header, fmt: self.readDn(header),
240 | "Vn": lambda header, fmt: self.readVn(header)
241 | }
242 |
243 | self.recordParsers = dict(
244 | [ ( (recType.typ, recType.sub), self.createRecordParser(recType) )
245 | for recType in recTypes ])
246 |
247 | self.vnMap = {
248 | 0: lambda header: self.inp.read(header, 1),
249 | 1: lambda header: self.readField(header, "U1"),
250 | 2: lambda header: self.readField(header, "U2"),
251 | 3: lambda header: self.readField(header, "U4"),
252 | 4: lambda header: self.readField(header, "I1"),
253 | 5: lambda header: self.readField(header, "I2"),
254 | 6: lambda header: self.readField(header, "I4"),
255 | 7: lambda header: self.readField(header, "R4"),
256 | 8: lambda header: self.readField(header, "R8"),
257 | 10: lambda header: self.readCn(header),
258 | 11: lambda header: self.readBn(header),
259 | 12: lambda header: self.readDn(header),
260 | 13: lambda header: self.readField(header, "U1")
261 | }
262 |
--------------------------------------------------------------------------------
/pystdf/Importer.py:
--------------------------------------------------------------------------------
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.keys():
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.keys():
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
87 |
--------------------------------------------------------------------------------
/pystdf/Indexing.py:
--------------------------------------------------------------------------------
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.OoHelpers import abstract
21 | from pystdf import V4
22 |
23 | class StreamIndexer:
24 | def before_header(self, dataSource, header):
25 | self.position = dataSource.inp.tell() - 4
26 | self.header = header
27 |
28 | class SessionIndexer:
29 | def getSessionID(self):
30 | return self.sessionid
31 |
32 | def before_begin(self, dataSource):
33 | self.sessionid = self.createSessionID()
34 |
35 | def createSessionID(self): abstract()
36 |
37 | class DemoSessionIndexer(SessionIndexer):
38 | def createSessionID(self): return 0
39 |
40 | class RecordIndexer:
41 | def getRecID(self):
42 | return self.recid
43 |
44 | def before_begin(self, dataSource):
45 | self.recid = 0
46 |
47 | def before_send(self, dataSource, data):
48 | self.recid += 1
49 |
50 | class MaterialIndexer:
51 | def getCurrentWafer(self, head):
52 | return self.currentWafer.get(head, 0)
53 |
54 | def getCurrentInsertion(self, head):
55 | return self.currentInsertion.get(head, 0)
56 |
57 | def getCurrentPart(self, head, site):
58 | return self.currentPart.get((head, site), 0)
59 |
60 | def before_begin(self, dataSource):
61 | self.currentPart = dict()
62 | self.currentInsertion = dict()
63 | self.closingInsertion = False
64 | self.currentWafer = dict()
65 | self.lastPart = 0
66 | self.lastInsertion = 0
67 | self.lastWafer = 0
68 |
69 | def before_send(self, dataSource, data):
70 | recType, fields = data
71 |
72 | if not isinstance(recType, V4.Prr) and self.closingInsertion:
73 | for head in self.currentInsertion.keys():
74 | self.currentInsertion[head] = 0
75 | self.closingInsertion = False
76 |
77 | if isinstance(recType, V4.Pir):
78 | headSite = (fields[V4.Pir.HEAD_NUM], fields[V4.Pir.SITE_NUM])
79 | self.onPir(headSite)
80 | elif isinstance(recType, V4.Wir):
81 | headSite = (fields[V4.Wir.HEAD_NUM], None) # fields[V4.Wir.SITE_NUM] Does not exist
82 | self.onWir(headSite)
83 |
84 | def after_send(self, dataSource, data):
85 | recType, fields = data
86 | if isinstance(recType, V4.Prr):
87 | headSite = (fields[V4.Prr.HEAD_NUM], fields[V4.Prr.SITE_NUM])
88 | self.onPrr(headSite)
89 | elif isinstance(recType, V4.Wrr):
90 | headSite = (fields[V4.Wrr.HEAD_NUM], None) # fields[V4.Wrr.SITE_NUM] Does not exist
91 | self.onWrr(headSite)
92 |
93 | def onPir(self, headSite):
94 | # Increment part count per site
95 | self.lastPart += 1
96 | self.currentPart[headSite] = self.lastPart
97 |
98 | # Increment insertion count once per head
99 | if self.currentInsertion.get(headSite[0], 0) == 0:
100 | self.lastInsertion += 1
101 | self.currentInsertion[headSite[0]] = self.lastInsertion
102 |
103 | def onPrr(self, headSite):
104 | self.currentPart[headSite] = 0
105 | self.closingInsertion = True
106 |
107 | def onWir(self, headSite):
108 | if self.currentWafer.get(headSite[0], 0) == 0:
109 | self.lastWafer += 1
110 | self.currentWafer[headSite[0]] = self.lastWafer
111 |
112 | def onWrr(self, headSite):
113 | self.currentWafer[headSite[0]]
114 |
115 |
--------------------------------------------------------------------------------
/pystdf/Mapping.py:
--------------------------------------------------------------------------------
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.Types import *
21 | from pystdf.Indexing import *
22 | from pystdf import V4
23 |
24 | class StreamMapper(StreamIndexer):
25 |
26 | def __init__(self, types=V4.records):
27 | self.indexes = []
28 | self.types = []
29 | self.__rec_map = dict([((recType.typ, recType.sub), recType)
30 | for recType in types])
31 |
32 | def before_header(self, dataSource, header):
33 | StreamIndexer.before_header(self, dataSource, header)
34 | self.indexes.append(self.position)
35 | key = (self.header.typ, self.header.sub)
36 | rectype = self.__rec_map.get(key, UnknownRecord(*key))
37 | self.types.append(rectype)
38 |
39 | class MaterialMapper(MaterialIndexer):
40 | indexable_types = set([V4.wir, V4.wrr, V4.pir, V4.prr, V4.ptr, V4.mpr, V4.ftr])
41 | per_part_types = set([V4.pir, V4.prr, V4.ptr, V4.mpr, V4.ftr])
42 |
43 | def before_begin(self, dataSource):
44 | MaterialIndexer.before_begin(self, dataSource)
45 | self.waferid = []
46 | self.insertionid = []
47 | self.partid = []
48 |
49 | def before_send(self, dataSource, data):
50 | MaterialIndexer.before_send(self, dataSource, data)
51 | rectype, rec = data
52 | if rectype in self.indexable_types:
53 | head = rec[rectype.HEAD_NUM]
54 | self.waferid.append(self.getCurrentWafer(head))
55 | self.insertionid.append(self.getCurrentInsertion(head))
56 | if rectype in self.per_part_types:
57 | site = rec[rectype.SITE_NUM]
58 | self.partid.append(self.getCurrentPart(head, site))
59 | else:
60 | self.partid.append(None)
61 | else:
62 | self.waferid.append(None)
63 | self.insertionid.append(None)
64 | self.partid.append(None)
65 |
66 | if __name__ == '__main__':
67 | from pystdf.IO import Parser
68 | from pystdf.Writers import AtdfWriter
69 | import pystdf.V4
70 |
71 | filename, = sys.argv[1:]
72 | f = open(filename, 'rb')
73 | p=Parser(inp=f)
74 | record_mapper = StreamMapper()
75 | p.addSink(record_mapper)
76 | p.parse()
77 | f.close()
78 |
79 | for index, rectype in zip(record_mapper.indexes, record_mapper.types):
80 | print(index, rectype)
81 |
--------------------------------------------------------------------------------
/pystdf/OoHelpers.py:
--------------------------------------------------------------------------------
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 | def abstract():
21 | import inspect
22 | caller = inspect.getouterframes(inspect.currentframe())[1][3]
23 | raise NotImplementedError(caller + ' must be implemented in subclass')
24 |
25 |
--------------------------------------------------------------------------------
/pystdf/ParametricSummarizer.py:
--------------------------------------------------------------------------------
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.SummaryStatistics import SummaryStatistics
21 | from pystdf.V4 import ptr, mpr
22 | from pystdf.Pipeline import EventSource
23 |
24 | class ParametricSummarizer(EventSource):
25 |
26 | def __init__(self):
27 | EventSource.__init__(self, ['parametricSummaryReady'])
28 |
29 | def parametricSummaryReady(self, dataSource): pass
30 |
31 | def getAllRows(self):
32 | return self.summaryMap.iteritems()
33 |
34 | def before_begin(self, dataSource):
35 | self.rawMap = dict()
36 | self.summaryMap = None
37 |
38 | def before_complete(self, dataSource):
39 | self.summaryMap = dict()
40 | for key, values in self.rawMap.iteritems():
41 | values.sort()
42 | self.summaryMap[key] = SummaryStatistics(values)
43 | self.parametricSummaryReady(dataSource)
44 |
45 | def before_send(self, dataSource, data):
46 | table, row = data
47 | if table.name == ptr.name:
48 | self.onPtr(row)
49 | elif table.name == mpr.name:
50 | self.onMpr(row)
51 |
52 | def onPtr(self, row):
53 | values = self.rawMap.setdefault((
54 | row[ptr.SITE_NUM],row[ptr.TEST_NUM],0), [])
55 | values.append(row[ptr.RESULT])
56 |
57 | def onMpr(self, row):
58 | for i in range(row[mpr.RSLT_CNT]):
59 | values = self.rawMap.setdefault((row[ptr.SITE_NUM],row[ptr.TEST_NUM],i), [])
60 | values.append(row[mpr.RTN_RSLT][i])
61 |
62 |
--------------------------------------------------------------------------------
/pystdf/PartSummarizer.py:
--------------------------------------------------------------------------------
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 import Pipeline
21 | from pystdf.SummaryStatistics import SummaryStatistics
22 | from pystdf.V4 import prr, pcr
23 |
24 | def filterNull(value):
25 | if value == 4294967295:
26 | return None
27 | return value
28 |
29 | class PartSummarizer(Pipeline.EventSource):
30 |
31 | FLAG_SYNTH = 0x80
32 | FLAG_FAIL = 0x08
33 | FLAG_UNKNOWN = 0x02
34 | FLAG_OVERALL = 0x01
35 |
36 | def __init__(self):
37 | EventSource.__init__(self, ['partSummaryReady'])
38 |
39 | def partSummaryReady(self, dataSource): pass
40 |
41 | def getOverall(self):
42 | return self.overall
43 |
44 | def getSiteCounts(self):
45 | return self.pcSummary.values()
46 |
47 | def getSiteSynthCounts(self):
48 | for site, info in self.pcSynth.iteritems():
49 | partCnt, goodCnt, abrtCnt = info
50 | yield [0, site, partCnt[0], None,
51 | abrtCnt[0], goodCnt[0], None]
52 |
53 | def synthOverall(self):
54 | result = None
55 | for row in self.pcSummary.values():
56 | if result is None:
57 | result = [value for value in row]
58 | else:
59 | for i, value in enumerate(row):
60 | if i > pcr.SITE_NUM and row[i] is not None:
61 | if result[i] is None:
62 | result[i] = row[i]
63 | else:
64 | result[i] += row[i]
65 | return result
66 |
67 | def before_begin(self, dataSource):
68 | self.pcSynth = dict()
69 | self.pcSummary = dict()
70 | self.overall = None
71 |
72 | def before_complete(self, dataSource):
73 | self.partSummaryReady(dataSource)
74 |
75 | def before_send(self, dataSource, data):
76 | table, row = data
77 | if table.name == prr.name:
78 | self.onPrr(row)
79 | elif table.name == pcr.name:
80 | self.onPcr(row)
81 |
82 | def onPrr(self, row):
83 | partCnt, goodCnt, abrtCnt = self.pcSynth.setdefault(row[prr.SITE_NUM],
84 | ([0], [0], [0]))
85 | partCnt[0] += 1
86 | if row[prr.PART_FLG] & 0x08 == 0:
87 | goodCnt[0] += 1
88 | if row[prr.PART_FLG] & 0x04 == 0:
89 | abrtCnt[0] += 1
90 |
91 | def onPcr(self, row):
92 | if row[pcr.HEAD_NUM] == 255:
93 | self.overall = [
94 | filterNull(value) for value in row]
95 | else:
96 | self.pcSummary[row[pcr.SITE_NUM]] = [
97 | filterNull(value) for value in row]
98 |
99 |
--------------------------------------------------------------------------------
/pystdf/Pipeline.py:
--------------------------------------------------------------------------------
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 | import sys
21 |
22 | def appendPrefixAction(fn, ds, action):
23 | """Create a function that injects a call to 'action' prior to given function 'fn'"""
24 | def new_fn(*args):
25 | action(ds, *args)
26 | fn(*args)
27 | return new_fn
28 |
29 | def appendSuffixAction(fn, sink, action):
30 | """Create a function that injects a call to 'action' following given function 'fn'"""
31 | def new_fn(*args):
32 | fn(*args)
33 | action(sink, *args)
34 | return new_fn
35 |
36 | class EventSource:
37 | """EventSource
38 | A generic base class for something that originates events (a source)
39 | and broadcasts them to receivers (the sinks). Events are propagated
40 | as method calls. Event sinks can receieve notification before or
41 | after the event occurs.
42 |
43 | Registration is achieved by a contract of method name convention.
44 | The sink defines methods based on the event name in order to receive it.
45 | Event method names in the sink with a 'before_' prefix will be invoked
46 | prior to the event occuring, similarly, a method with the 'after_' suffix
47 | will be invoked after the event occurs."""
48 |
49 | def __init__(self, eventNames):
50 | self.eventNames = eventNames
51 |
52 | def addSink(self, sink):
53 | "Register a DataSink to receive the events it has defined"
54 | for eventName in self.eventNames:
55 | preEventName = 'before_' + eventName
56 | postEventName = 'after_' + eventName
57 | if hasattr(sink, preEventName):
58 | setattr(self, eventName,
59 | appendPrefixAction(
60 | getattr(self, eventName),
61 | self, getattr(sink, preEventName)))
62 | if hasattr(sink, postEventName):
63 | setattr(self, eventName,
64 | appendSuffixAction(
65 | getattr(self, eventName),
66 | self, getattr(sink, postEventName)))
67 |
68 | class DataSource(EventSource):
69 |
70 | def __init__(self, add_events):
71 | EventSource.__init__(self, ['begin', 'send', 'complete', 'cancel'] + add_events)
72 |
73 | def begin(self): pass
74 |
75 | def send(self, data): pass
76 |
77 | def complete(self): pass
78 |
79 | def cancel(self, exception): pass
80 |
81 |
--------------------------------------------------------------------------------
/pystdf/SummaryStatistics.py:
--------------------------------------------------------------------------------
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 | class SummaryStatistics:
21 | def __init__(self, values):
22 | self.values = values
23 | self.min = min(values)
24 | self.max = max(values)
25 | self.count = len(values)
26 | self.sum = sum(values)
27 | self.sumsqrs = sum([value*value for value in values])
28 | self.mean = self.sum / float(self.count)
29 | self.median = self.q2 = self.values[self.count / 2]
30 | self.q1 = self.values[self.count / 4]
31 | self.q3 = self.values[3 * (self.count / 4)]
32 |
33 |
--------------------------------------------------------------------------------
/pystdf/TableTemplate.py:
--------------------------------------------------------------------------------
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 | class TableTemplate(object):
21 | def __init__(self, columnNames, columnTypes, name=None):
22 | if name is None:
23 | self.name = self.__module__ + '.' + self.__class__.__name__
24 | else:
25 | self.name = name
26 | self.columnNames = columnNames
27 | self.columnTypes = columnTypes
28 |
29 |
30 |
--------------------------------------------------------------------------------
/pystdf/TestSummarizer.py:
--------------------------------------------------------------------------------
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.V4 import ptr, mpr, ftr, tsr
22 |
23 | def filterNull(value):
24 | if value == 4294967295:
25 | return None
26 | return value
27 |
28 | class TestSummarizer(EventSource):
29 |
30 | FLAG_SYNTH = 0x80
31 | FLAG_OVERALL = 0x01
32 |
33 | PTR_TEST_TXT = 0x00
34 | MPR_TEST_TXT = 0x01
35 | FTR_TEST_TXT = 0x02
36 | TSR_TEST_NAM = 0x03
37 | TSR_SEQ_NAME = 0x04
38 | TSR_TEST_LBL = 0x05
39 |
40 | def __init__(self):
41 | EventSource.__init__(self, ['testSummaryReady'])
42 |
43 | def testSummaryReady(self, dataSource): pass
44 |
45 | def getOverallTsrs(self):
46 | return self.overallTsrs.values()
47 |
48 | def getSiteTsrs(self):
49 | return self.summaryTsrs.values()
50 |
51 | def getSiteSynthTsrs(self):
52 | for siteTest, execCnt in self.testExecs.iteritems():
53 | site, test = siteTest
54 | tsrRow = [0, site, ' ', test,
55 | execCnt[0],
56 | self.testFails.get(siteTest, [0])[0],
57 | self.testInvalid.get(siteTest, [0])[0],
58 | None, None, None]
59 | yield tsrRow
60 |
61 | def before_begin(self, dataSource):
62 | self.testExecs = dict()
63 | self.testFails = dict()
64 | self.testInvalid = dict()
65 | self.summaryTsrs = dict()
66 | self.overallTsrs = dict()
67 |
68 | # Map of all test numbers to test names
69 | self.testAliasMap = dict()
70 | self.unitsMap = dict()
71 | self.limitsMap = dict()
72 |
73 | # Functional summary information
74 | self.cyclCntMap = dict()
75 | self.relVadrMap = dict()
76 | self.failPinMap = dict()
77 |
78 | def before_complete(self, dataSource):
79 | testKeys = set(self.testFails.keys())
80 | summaryTsrKeys = set(self.summaryTsrs.keys())
81 |
82 | # Determine which summary bin records need to be synthed
83 | # from part records.
84 | self.synthSummaryTsrKeys = testKeys - summaryTsrKeys
85 |
86 | # Determine which overall bin records need to be synthed
87 | # for siteTest, row in self.summaryTsrs.iteritems():
88 | # if not self.overallTsrs.has_key(siteTest[1]):
89 | # overallCount = self.synthOverallTsrs.setdefault(siteTest[1], [0])
90 | # overallCount[0] += row[tsr.FAIL_CNT]
91 | # for siteTest, partCount in self.testFails.iteritems():
92 | # if not self.overallTsrs.has_key(siteTest[1]):
93 | # overallCount = self.synthOverallTsrs.setdefault(siteTest[1], [0])
94 | # overallCount[0] += partCount[0]
95 | self.testSummaryReady(dataSource)
96 |
97 | def before_send(self, dataSource, data):
98 | table, row = data
99 | if table.name == ptr.name:
100 | self.onPtr(row)
101 | elif table.name == mpr.name:
102 | self.onMpr(row)
103 | elif table.name == ftr.name:
104 | self.onFtr(row)
105 | elif table.name == tsr.name:
106 | self.onTsr(row)
107 |
108 | def onPtr(self, row):
109 | execCount = self.testExecs.setdefault(
110 | (row[ptr.SITE_NUM], row[ptr.TEST_NUM]), [0])
111 | execCount[0] += 1
112 | if row[ptr.TEST_FLG] & 0x80 > 0:
113 | failCount = self.testFails.setdefault(
114 | (row[ptr.SITE_NUM], row[ptr.TEST_NUM]), [0])
115 | failCount[0] += 1
116 | if row[ptr.TEST_FLG] & 0x41 > 0:
117 | invalidCount = self.testInvalid.setdefault(
118 | (row[ptr.SITE_NUM], row[ptr.TEST_NUM]), [0])
119 | invalidCount[0] += 1
120 | aliases = self.testAliasMap.setdefault(row[ptr.TEST_NUM], set())
121 | aliases.add((row[ptr.TEST_TXT], self.PTR_TEST_TXT))
122 | if ptr.UNITS < len(row) and row[ptr.UNITS]:
123 | units = self.unitsMap.setdefault(row[ptr.TEST_NUM], [None])
124 | units[0] = row[ptr.UNITS]
125 | if row[ptr.OPT_FLAG] is not None and row[ptr.OPT_FLAG] & 0x40 == 0:
126 | loLimit = row[ptr.LO_LIMIT]
127 | else:
128 | loLimit = None
129 | if row[ptr.OPT_FLAG] is not None and row[ptr.OPT_FLAG] & 0x80 == 0:
130 | hiLimit = row[ptr.HI_LIMIT]
131 | else:
132 | hiLimit = None
133 | if loLimit is not None or hiLimit is not None:
134 | limits = self.limitsMap.setdefault(row[ptr.TEST_NUM], set())
135 | limits.add((loLimit, hiLimit))
136 |
137 | def onMpr(self, row):
138 | if row[mpr.TEST_FLG] & 0x80 > 0:
139 | failCount = self.testFails.setdefault(
140 | (row[mpr.SITE_NUM], row[mpr.TEST_NUM]), [0])
141 | failCount[0] += 1
142 | if row[ptr.TEST_FLG] & 0x41 > 0:
143 | invalidCount = self.testInvalid.setdefault(
144 | (row[ptr.SITE_NUM], row[ptr.TEST_NUM]), [0])
145 | invalidCount[0] += 1
146 | aliases = self.testAliasMap.setdefault(row[mpr.TEST_NUM], set())
147 | aliases.add((row[mpr.TEST_TXT], self.MPR_TEST_TXT))
148 | if mpr.UNITS < len(row) and row[mpr.UNITS]:
149 | units = self.unitsMap.setdefault(row[mpr.TEST_NUM], [None])
150 | units[0] = row[mpr.UNITS]
151 | if row[mpr.OPT_FLAG] is not None and row[mpr.OPT_FLAG] & 0x40 == 0:
152 | loLimit = row[mpr.LO_LIMIT]
153 | else:
154 | loLimit = None
155 | if row[mpr.OPT_FLAG] is not None and row[mpr.OPT_FLAG] & 0x80 == 0:
156 | hiLimit = row[mpr.HI_LIMIT]
157 | else:
158 | hiLimit = None
159 | if loLimit is not None or hiLimit is not None:
160 | limits = self.limitsMap.setdefault(row[mpr.TEST_NUM], set())
161 | limits.add((loLimit, hiLimit))
162 |
163 | def onFtr(self, row):
164 | if row[ftr.TEST_FLG] & 0x80 > 0:
165 | countList = self.testFails.setdefault(
166 | (row[ftr.SITE_NUM], row[ftr.TEST_NUM]), [0])
167 | countList[0] += 1
168 |
169 | if row[ftr.OPT_FLAG] is not None:
170 | if row[ftr.OPT_FLAG] & 0x01 > 0:
171 | countList = self.cyclCntMap.setdefault((row[ftr.TEST_NUM], row[ftr.CYCL_CNT]), [0])
172 | countList[0] += 1
173 | if row[ftr.OPT_FLAG] & 0x02 > 0:
174 | countList = self.relVadrMap.setdefault((row[ftr.TEST_NUM], row[ftr.REL_VADR]), [0])
175 | countList[0] += 1
176 | if ftr.RTN_STAT < len(row) and ftr.RTN_INDX < len(row) \
177 | and row[ftr.RTN_STAT] and row[ftr.RTN_INDX]:
178 | for i, rtnStat in enumerate(row[ftr.RTN_STAT]):
179 | if rtnStat > 4 and i < len(row[ftr.RTN_INDX]): # A failing return state...
180 | pmrIndx = row[ftr.RTN_INDX][i]
181 | countList = self.failPinMap.setdefault((row[ftr.TEST_NUM], pmrIndx), [0])
182 | countList[0] += 1
183 |
184 | aliases = self.testAliasMap.setdefault(row[ftr.TEST_NUM], set())
185 | aliases.add((row[ftr.TEST_TXT], self.FTR_TEST_TXT))
186 |
187 | def onTsr(self, row):
188 | if row[tsr.HEAD_NUM] == 255:
189 | self.overallTsrs[row[tsr.TEST_NUM]] = [
190 | filterNull(value) for value in row]
191 | else:
192 | self.summaryTsrs[(row[tsr.SITE_NUM],row[tsr.TEST_NUM])] = [
193 | filterNull(value) for value in row]
194 | aliases = self.testAliasMap.setdefault(row[tsr.TEST_NUM], set())
195 | aliases.add((row[tsr.TEST_NAM], self.TSR_TEST_NAM))
196 | aliases.add((row[tsr.SEQ_NAME], self.TSR_SEQ_NAME))
197 | aliases.add((row[tsr.TEST_LBL], self.TSR_TEST_LBL))
198 |
199 |
--------------------------------------------------------------------------------
/pystdf/Types.py:
--------------------------------------------------------------------------------
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 | import sys
21 |
22 | from pystdf import TableTemplate
23 |
24 | import pdb
25 |
26 | logicalTypeMap = {
27 | "C1": "Char",
28 | "B1": "UInt8",
29 | "U1": "UInt8",
30 | "U2": "UInt16",
31 | "U4": "UInt32",
32 | "U8": "UInt64",
33 | "I1": "Int8",
34 | "I2": "Int16",
35 | "I4": "Int32",
36 | "I8": "Int64",
37 | "R4": "Float32",
38 | "R8": "Float64",
39 | "Cn": "String",
40 | "Bn": "List",
41 | "Dn": "List",
42 | "Vn": "List"
43 | }
44 |
45 | packFormatMap = {
46 | "C1": "c",
47 | "B1": "B",
48 | "U1": "B",
49 | "U2": "H",
50 | "U4": "I",
51 | "U8": "Q",
52 | "I1": "b",
53 | "I2": "h",
54 | "I4": "i",
55 | "I8": "q",
56 | "R4": "f",
57 | "R8": "d"
58 | }
59 |
60 | def stdfToLogicalType(fmt):
61 | if fmt.startswith('k'):
62 | return 'List'
63 | else:
64 | return logicalTypeMap[fmt]
65 |
66 | class RecordHeader:
67 | def __init__(self):
68 | self.len=0
69 | self.typ=0
70 | self.sub=0
71 |
72 | def __repr__(self):
73 | return "" % (self.typ, self.sub, self.len)
74 |
75 | class RecordType(TableTemplate):
76 | def __init__(self):
77 | TableTemplate.__init__(self,
78 | [name for name, stdfType in self.fieldMap],
79 | [stdfToLogicalType(stdfTyp) for name, stdfTyp in self.fieldMap])
80 |
81 | class UnknownRecord(TableTemplate):
82 | def __init__(self, rec_typ, rec_sub):
83 | TableTemplate.__init__(self, [], [], 'UnknownRecord')
84 | self.rec_typ = rec_typ
85 | self.rec_sub = rec_sub
86 |
87 | class EofException(Exception): pass
88 |
89 | class EndOfRecordException(Exception): pass
90 |
91 | class InitialSequenceException(Exception): pass
92 |
93 | class StdfRecordMeta(type):
94 | """Generate the necessary plumbing for STDF record classes
95 | based on simple, static field defintions.
96 | This enables a simple, mini-DSL (domain-specific language)
97 | approach to defining STDF records.
98 | I did this partly to learn what metaclasses are good for,
99 | partly for fun, and partly because I wanted end users to be
100 | able to easily define their own custom STDF record types.
101 | """
102 | def __init__(cls, name, bases, dct):
103 |
104 | # Map out field definitions
105 | fieldMap = dct.get('fieldMap', [])
106 | for i, fieldDef in enumerate(fieldMap):
107 | setattr(cls, fieldDef[0], i)
108 | setattr(cls, 'fieldFormats', dict(fieldMap))
109 | setattr(cls, 'fieldNames', [field_name for field_name, field_type in fieldMap])
110 | setattr(cls, 'fieldStdfTypes', [field_type for field_name, field_type in fieldMap])
111 |
112 | # Add initializer for the generated class
113 | setattr(cls, '__init__', lambda _self: RecordType.__init__(_self))
114 |
115 | # Proceed with class generation
116 | return super(StdfRecordMeta, cls).__init__(name, bases, dct)
117 |
118 |
--------------------------------------------------------------------------------
/pystdf/Writers.py:
--------------------------------------------------------------------------------
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 | import sys
21 | from time import strftime, localtime
22 | from xml.sax.saxutils import quoteattr
23 | from pystdf import V4
24 |
25 | def format_by_type(value, field_type):
26 | if field_type in ('B1', 'N1'):
27 | return '%02X' % (value)
28 | else:
29 | return str(value)
30 |
31 | class TextWriter:
32 | def __init__(self, stream=sys.stdout, delimiter='|'):
33 | self.stream = stream
34 | self.delimiter = delimiter
35 |
36 | def text_format(self, rectype, field_index, value):
37 | field_type = rectype.fieldStdfTypes[field_index]
38 | if value is None:
39 | return ""
40 | elif rectype is V4.gdr:
41 | return self.delimiter.join([str(v) for v in value])
42 | elif field_type[0] == 'k': # An Array of some other type
43 | return ','.join([format_by_type(v, field_type[2:]) for v in value])
44 | elif rectype is V4.mir or rectype is V4.mrr:
45 | field_name = rectype.fieldNames[field_index]
46 | if field_name.endswith('_T'): # A Date-Time in an MIR/MRR
47 | return strftime('%H:%M:%S %d-%b-%Y', localtime(value))
48 | else:
49 | return str(value)
50 | else:
51 | return str(value)
52 |
53 | def after_send(self, dataSource, data):
54 | line = '%s%s%s\n' % (data[0].__class__.__name__.upper(),self.delimiter,
55 | self.delimiter.join([self.text_format(data[0], i, val) for i, val in enumerate(data[1])]))
56 | self.stream.write(line)
57 |
58 | def after_complete(self, dataSource):
59 | self.stream.flush()
60 |
61 | class XmlWriter:
62 | extra_entities = {'\0': ''}
63 |
64 | @staticmethod
65 | def xml_format(rectype, field_index, value):
66 | field_type = rectype.fieldStdfTypes[field_index]
67 | if value is None:
68 | return ""
69 | elif rectype is V4.gdr:
70 | return ';'.join([str(v) for v in value])
71 | elif field_type[0] == 'k': # An Array of some other type
72 | return ','.join([format_by_type(v, field_type[2:]) for v in value])
73 | elif rectype is V4.mir or rectype is V4.mrr:
74 | field_name = rectype.fieldNames[field_index]
75 | if field_name.endswith('_T'): # A Date-Time in an MIR/MRR
76 | return strftime('%H:%M:%ST%d-%b-%Y', localtime(value))
77 | else:
78 | return str(value)
79 | else:
80 | return str(value)
81 |
82 | def __init__(self, stream=sys.stdout):
83 | self.stream = stream
84 |
85 | def before_begin(self, dataSource):
86 | self.stream.write('\n')
87 |
88 | def after_send(self, dataSource, data):
89 | self.stream.write('<%s' % (data[0].__class__.__name__))
90 | for i, val in enumerate(data[1]):
91 | fmtval = self.xml_format(data[0], i, val)
92 | self.stream.write(' %s=%s' % (data[0].fieldNames[i], quoteattr(fmtval, self.extra_entities)))
93 | self.stream.write('/>\n')
94 |
95 | def after_complete(self, dataSource):
96 | self.stream.write('\n')
97 | self.stream.flush()
98 |
--------------------------------------------------------------------------------
/pystdf/__init__.py:
--------------------------------------------------------------------------------
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.TableTemplate import TableTemplate
21 |
22 | from pystdf.BinSummarizer import BinSummarizer
23 | from pystdf.ParametricSummarizer import ParametricSummarizer
24 | from pystdf.PartSummarizer import PartSummarizer
25 | from pystdf.Indexing import *
26 |
--------------------------------------------------------------------------------
/pystdf/logexcept.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: iso-8859-1 -*-
3 | ################################################################################
4 | #
5 | # Utility functions for formatting exceptions and stack traces so that they are
6 | # guaranteed to fit in a single line and contain only chars in specified encoding.
7 | # Very useful for logging and handling dead end exceptions.
8 | #
9 | # Written by Dmitry Dvoinikov (c) 2005
10 | # Distributed under MIT license.
11 | #
12 | # Sample (test.py), line numbers added for clarity:
13 | #
14 | # 1. from exc_string import *
15 | # 2: set_exc_string_encoding("ascii")
16 | # 3: class foo(object):
17 | # 4: def __init__(self):
18 | # 5: raise Exception("z\xffz\n") # note non-ascii char in the middle and newline
19 | # 6: try:
20 | # 7: foo()
21 | # 8: except:
22 | # 9: assert exc_string() == "Exception(\"z?z \") in __init__() (test.py:5) <- ?() (test.py:7)"
23 | #
24 | # The (2 times longer) source code with self-tests is available from:
25 | # http://www.targeted.org/python/recipes/exc_string.py
26 | #
27 | # (c) 2005 Dmitry Dvoinikov
28 | #
29 | # Permission is hereby granted, free of charge, to any person obtaining a copy
30 | # of this software and associated documentation files (the "Software"), to deal
31 | # in the Software without restriction, including without limitation the rights to
32 | # use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
33 | # of the Software, and to permit persons to whom the Software is furnished to do
34 | # so, subject to the following conditions:
35 | #
36 | # The above copyright notice and this permission notice shall be included in all
37 | # copies or substantial portions of the Software.
38 | #
39 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
40 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
41 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
42 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
43 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
44 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
45 | # THE SOFTWARE.
46 | #
47 | ################################################################################
48 |
49 | __all__ = [ "exc_string", "trace_string", "force_string",
50 | "get_exc_string_encoding", "set_exc_string_encoding" ]
51 |
52 | ###############################################################################
53 |
54 | from sys import exc_info
55 | from traceback import extract_stack, extract_tb
56 | from os import path
57 |
58 | ###############################################################################
59 |
60 | exc_string_encoding = "windows-1251"
61 |
62 | def get_exc_string_encoding():
63 | return exc_string_encoding
64 |
65 | def set_exc_string_encoding(encoding):
66 | global exc_string_encoding
67 | exc_string_encoding = encoding
68 |
69 | ###############################################################################
70 |
71 | force_string_translate_map = " ????????\t ?? ??????????????????" + "".join([ chr(i) for i in range(32, 256) ])
72 |
73 | def force_string(v):
74 | if isinstance(v, str):
75 | v = v.decode(exc_string_encoding, "replace").encode(exc_string_encoding, "replace")
76 | return v.translate(force_string_translate_map)
77 | elif isinstance(v, unicode):
78 | v = v.encode(exc_string_encoding, "replace")
79 | return v.translate(force_string_translate_map)
80 | else:
81 | try:
82 | v = str(v)
83 | except:
84 | return "unable to convert %s to string, str() failed" % v.__class__.__name__
85 | else:
86 | return force_string(v)
87 |
88 | ###############################################################################
89 |
90 | def _reversed(r):
91 | result = list(r)
92 | result.reverse()
93 | return result
94 |
95 | def trace_string(tb = None):
96 | return " <- ".join([ force_string("%s() (%s:%s)" % (m, path.split(f)[1], n))
97 | for f, n, m, u in _reversed(tb or extract_stack()[:-1]) ])
98 |
99 | ###############################################################################
100 |
101 | def exc_string():
102 |
103 | try:
104 |
105 | t, v, tb = exc_info()
106 | if t is None:
107 | return "no exception"
108 | if v is not None:
109 | v = force_string(v)
110 | else:
111 | v = force_string(t)
112 | if hasattr(t, "__name__"):
113 | t = t.__name__
114 | else:
115 | t = type(t).__name__
116 |
117 | return "%s(\"%s\") in %s" % (t, v, trace_string(extract_tb(tb)))
118 |
119 | except:
120 | return "exc_string() failed to extract exception string"
121 |
122 | ################################################################################
123 | # EOF
124 |
--------------------------------------------------------------------------------
/pystdf/scripts/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cmars/pystdf/7c2c5dfac1124310e4b1426958f6e9324394ec16/pystdf/scripts/__init__.py
--------------------------------------------------------------------------------
/pystdf/scripts/rec_index.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # PySTDF - The Pythonic STDF Parser
4 | # Copyright (C) 2006 Casey Marshall
5 | #
6 | # This program is free software; you can redistribute it and/or
7 | # modify it under the terms of the GNU General Public License
8 | # as published by the Free Software Foundation; either version 2
9 | # of the License, or (at your option) any later version.
10 | #
11 | # This program is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU General Public License
17 | # along with this program; if not, write to the Free Software
18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 | #
20 |
21 | from __future__ import print_function
22 | import sys, os, re
23 |
24 | try:
25 | import gzip
26 | have_gzip = True
27 | except ImportError:
28 | have_gzip = False
29 | try:
30 | import bz2
31 | have_bz2 = True
32 | except ImportError:
33 | have_bz2 = False
34 |
35 | from pystdf.IO import Parser
36 | from pystdf.Indexing import RecordIndexer
37 | import pystdf.V4
38 |
39 | #def info(type, value, tb):
40 | # import traceback, pdb
41 | # # You are not in interactive mode; print the exception
42 | # traceback.print_exception(type, value, tb)
43 | # print
44 | # # ... then star the debugger in post-mortem mode
45 | # pdb.pm()
46 | #sys.excepthook = info
47 |
48 | gzPattern = re.compile('\.g?z', re.I)
49 | bz2Pattern = re.compile('\.bz2', re.I)
50 |
51 | def process_file(fn):
52 | filename, = sys.argv[1:]
53 |
54 | reopen_fn = None
55 | if filename is None:
56 | f = sys.stdin
57 | elif gzPattern.search(filename):
58 | if not have_gzip:
59 | print("gzip is not supported on this system", file=sys.stderr)
60 | sys.exit(1)
61 | reopen_fn = lambda: gzip.open(filename, 'rb')
62 | f = reopen_fn()
63 | elif bz2Pattern.search(filename):
64 | if not have_bz2:
65 | print("bz2 is not supported on this system", file=sys.stderr)
66 | sys.exit(1)
67 | reopen_fn = lambda: bz2.BZ2File(filename, 'rb')
68 | f = reopen_fn()
69 | else:
70 | f = open(filename, 'rb')
71 | p=Parser(inp=f, reopen_fn=reopen_fn)
72 | p.addSink(RecordIndexer())
73 | p.parse()
74 | f.close()
75 |
76 | def main():
77 | if len(sys.argv) < 2:
78 | print("Usage: %s " % (sys.argv[0]))
79 | else:
80 | process_file(sys.argv[1])
81 |
82 | if __name__ == '__main__':
83 | main()
84 |
85 |
--------------------------------------------------------------------------------
/pystdf/scripts/stdf2excel.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # PySTDF - The Pythonic STDF Parser
4 | # Copyright (C) 2006 Casey Marshall
5 | #
6 | # This program is free software; you can redistribute it and/or
7 | # modify it under the terms of the GNU General Public License
8 | # as published by the Free Software Foundation; either version 2
9 | # of the License, or (at your option) any later version.
10 | #
11 | # This program is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU General Public License
17 | # along with this program; if not, write to the Free Software
18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 | #
20 | # Modified: 2017 Minh-Hai Nguyen
21 | #
22 | import sys, os
23 | from pystdf.Importer import STDF2DataFrame
24 | import pystdf.V4 as V4
25 | import pandas as pd
26 |
27 |
28 | def toExcel(fname,tables):
29 | """ Export the tables from toTables to Excel
30 | """
31 | with pd.ExcelWriter(fname) as writer:
32 | for k,v in tables.items():
33 | # Make sure the order of columns complies the specs
34 | record = [r for r in V4.records if r.__class__.__name__.upper()==k]
35 | if len(record)==0:
36 | print("Ignore exporting table %s: No such record type exists." %k)
37 | else:
38 | columns = [field[0] for field in record[0].fieldMap]
39 | v.to_excel(writer,sheet_name=k,columns=columns,index=False,na_rep="N/A")
40 |
41 | def main():
42 | if len(sys.argv)==1:
43 | print("Usage: %s " % (sys.argv[0]))
44 | else:
45 | fin = sys.argv[1]
46 | if len(sys.argv)>2:
47 | fout = sys.argv[2]
48 | else:
49 | fout = fin[:fin.rfind('.')]+".xlsx"
50 | print("Importing %s" %fin)
51 | dfs= STDF2DataFrame(fin)
52 | print("Exporting to %s" %fout)
53 | toExcel(fout,dfs)
54 |
55 | if __name__ == '__main__':
56 | main()
57 |
58 |
--------------------------------------------------------------------------------
/pystdf/scripts/stdf2text.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # PySTDF - The Pythonic STDF Parser
4 | # Copyright (C) 2006 Casey Marshall
5 | #
6 | # This program is free software; you can redistribute it and/or
7 | # modify it under the terms of the GNU General Public License
8 | # as published by the Free Software Foundation; either version 2
9 | # of the License, or (at your option) any later version.
10 | #
11 | # This program is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU General Public License
17 | # along with this program; if not, write to the Free Software
18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 | #
20 |
21 | from __future__ import print_function
22 | import sys, re
23 |
24 | try:
25 | import gzip
26 | have_gzip = True
27 | except ImportError:
28 | have_gzip = False
29 | try:
30 | import bz2
31 | have_bz2 = True
32 | except ImportError:
33 | have_bz2 = False
34 |
35 | from pystdf.IO import Parser
36 | from pystdf.Writers import TextWriter
37 | import pystdf.V4
38 |
39 | gzPattern = re.compile('\.g?z', re.I)
40 | bz2Pattern = re.compile('\.bz2', re.I)
41 |
42 | def process_file(fnames):
43 | filename = fnames[0]
44 |
45 | reopen_fn = None
46 | if filename is None:
47 | f = sys.stdin
48 | elif gzPattern.search(filename):
49 | if not have_gzip:
50 | print("gzip is not supported on this system", file=sys.stderr)
51 | sys.exit(1)
52 | reopen_fn = lambda: gzip.open(filename, 'rb')
53 | f = reopen_fn()
54 | elif bz2Pattern.search(filename):
55 | if not have_bz2:
56 | print("bz2 is not supported on this system", file=sys.stderr)
57 | sys.exit(1)
58 | reopen_fn = lambda: bz2.BZ2File(filename, 'rb')
59 | f = reopen_fn()
60 | else:
61 | f = open(filename, 'rb')
62 | p=Parser(inp=f, reopen_fn=reopen_fn)
63 | if len(fnames)<2:
64 | p.addSink(TextWriter())
65 | p.parse()
66 | else:
67 | with open(fnames[1],'w') as fout:
68 | p.addSink(TextWriter(stream=fout))
69 | p.parse()
70 | f.close()
71 |
72 | def main():
73 | if len(sys.argv) < 2:
74 | print("Usage: %s " % (sys.argv[0]))
75 | else:
76 | process_file(sys.argv[1:])
77 |
78 | if __name__ == '__main__':
79 | main()
80 |
81 |
--------------------------------------------------------------------------------
/pystdf/scripts/stdf2xml.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # PySTDF - The Pythonic STDF Parser
4 | # Copyright (C) 2006 Casey Marshall
5 | #
6 | # This program is free software; you can redistribute it and/or
7 | # modify it under the terms of the GNU General Public License
8 | # as published by the Free Software Foundation; either version 2
9 | # of the License, or (at your option) any later version.
10 | #
11 | # This program is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU General Public License
17 | # along with this program; if not, write to the Free Software
18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 | #
20 |
21 | from __future__ import print_function
22 | import sys, re
23 |
24 | try:
25 | import gzip
26 | have_gzip = True
27 | except ImportError:
28 | have_gzip = False
29 | try:
30 | import bz2
31 | have_bz2 = True
32 | except ImportError:
33 | have_bz2 = False
34 |
35 | from pystdf.IO import Parser
36 | from pystdf.Writers import XmlWriter
37 | import pystdf.V4
38 |
39 | gzPattern = re.compile('\.g?z', re.I)
40 | bz2Pattern = re.compile('\.bz2', re.I)
41 |
42 | def process_file(fn):
43 | filename, = sys.argv[1:]
44 |
45 | reopen_fn = None
46 | if filename is None:
47 | f = sys.stdin
48 | elif gzPattern.search(filename):
49 | if not have_gzip:
50 | print("gzip is not supported on this system", file=sys.stderr)
51 | sys.exit(1)
52 | reopen_fn = lambda: gzip.open(filename, 'rb')
53 | f = reopen_fn()
54 | elif bz2Pattern.search(filename):
55 | if not have_bz2:
56 | print("bz2 is not supported on this system", file=sys.stderr)
57 | sys.exit(1)
58 | reopen_fn = lambda: bz2.BZ2File(filename, 'rb')
59 | f = reopen_fn()
60 | else:
61 | f = open(filename, 'rb')
62 | p=Parser(inp=f, reopen_fn=reopen_fn)
63 | p.addSink(XmlWriter())
64 | p.parse()
65 | f.close()
66 |
67 | def main():
68 | if len(sys.argv) < 2:
69 | print("Usage: %s " % (sys.argv[0]))
70 | else:
71 | process_file(sys.argv[1])
72 |
73 | if __name__ == '__main__':
74 | main()
75 |
76 |
--------------------------------------------------------------------------------
/pystdf/scripts/stdf_slice.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # PySTDF - The Pythonic STDF Parser
4 | # Copyright (C) 2006 Casey Marshall
5 | #
6 | # This program is free software; you can redistribute it and/or
7 | # modify it under the terms of the GNU General Public License
8 | # as published by the Free Software Foundation; either version 2
9 | # of the License, or (at your option) any later version.
10 | #
11 | # This program is distributed in the hope that it will be useful,
12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | # GNU General Public License for more details.
15 | #
16 | # You should have received a copy of the GNU General Public License
17 | # along with this program; if not, write to the Free Software
18 | # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 | #
20 |
21 | from pystdf.IO import Parser
22 | from pystdf.Mapping import *
23 | from pystdf.Writers import *
24 |
25 | def main():
26 | filename, start, count = sys.argv[1:4]
27 | start = int(start)
28 | count = int(count)
29 |
30 | f = open(filename, 'rb')
31 | p=Parser(inp=f)
32 | record_mapper = StreamMapper()
33 | p.addSink(record_mapper)
34 | p.parse(count=start+count)
35 | p.addSink(AtdfWriter())
36 | f.seek(record_mapper.indexes[start])
37 | p.parse(count=count)
38 |
39 | if __name__ == '__main__':
40 | main()
41 |
42 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/tests/test_BinSummarizer.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | from pystdf.BinSummarizer import BinSummarizer
3 | from pystdf.V4 import prr, hbr, sbr
4 |
5 | class MockDataSource:
6 | def __init__(self):
7 | self.name = "MockDataSource"
8 |
9 | class TestBinSummarizer(unittest.TestCase):
10 | def setUp(self):
11 | self.summarizer = BinSummarizer()
12 | self.dataSource = MockDataSource()
13 | self.summarizer.before_begin(self.dataSource)
14 |
15 | def test_flags(self):
16 | # Test hard bin flags
17 | hbr_row = [0] * len(hbr.fieldNames)
18 | hbr_row[hbr.HBIN_PF] = 'F'
19 | self.assertEqual(self.summarizer.getHPfFlags(hbr_row), BinSummarizer.FLAG_FAIL)
20 |
21 | hbr_row[hbr.HBIN_PF] = 'P'
22 | self.assertEqual(self.summarizer.getHPfFlags(hbr_row), 0)
23 |
24 | hbr_row[hbr.HBIN_PF] = 'X'
25 | self.assertEqual(self.summarizer.getHPfFlags(hbr_row), BinSummarizer.FLAG_UNKNOWN)
26 |
27 | # Test soft bin flags
28 | sbr_row = [0] * len(sbr.fieldNames)
29 | sbr_row[sbr.SBIN_PF] = 'F'
30 | self.assertEqual(self.summarizer.getSPfFlags(sbr_row), BinSummarizer.FLAG_FAIL)
31 |
32 | sbr_row[sbr.SBIN_PF] = 'P'
33 | self.assertEqual(self.summarizer.getSPfFlags(sbr_row), 0)
34 |
35 | sbr_row[sbr.SBIN_PF] = 'X'
36 | self.assertEqual(self.summarizer.getSPfFlags(sbr_row), BinSummarizer.FLAG_UNKNOWN)
37 |
38 | def test_bin_storage(self):
39 | # Test HBR storage
40 | hbr_row = [0] * len(hbr.fieldNames)
41 | hbr_row[hbr.HEAD_NUM] = 255 # Overall bin
42 | hbr_row[hbr.HBIN_NUM] = 1
43 | self.summarizer.onHbr(hbr_row)
44 | self.assertEqual(len(self.summarizer.getOverallHbins()), 1)
45 |
46 | hbr_row[hbr.HEAD_NUM] = 1 # Site-specific bin
47 | hbr_row[hbr.SITE_NUM] = 1
48 | self.summarizer.onHbr(hbr_row)
49 | self.assertEqual(len(self.summarizer.getSiteHbins()), 1)
50 |
51 | # Test SBR storage
52 | sbr_row = [0] * len(sbr.fieldNames)
53 | sbr_row[sbr.HEAD_NUM] = 255 # Overall bin
54 | sbr_row[sbr.SBIN_NUM] = 1
55 | self.summarizer.onSbr(sbr_row)
56 | self.assertEqual(len(self.summarizer.getOverallSbins()), 1)
57 |
58 | sbr_row[sbr.HEAD_NUM] = 1 # Site-specific bin
59 | sbr_row[sbr.SITE_NUM] = 1
60 | self.summarizer.onSbr(sbr_row)
61 | self.assertEqual(len(self.summarizer.getSiteSbins()), 1)
62 |
63 | def test_part_tracking(self):
64 | prr_row = [0] * len(prr.fieldNames)
65 | prr_row[prr.SITE_NUM] = 1
66 | prr_row[prr.HARD_BIN] = 1
67 | prr_row[prr.SOFT_BIN] = 1
68 | prr_row[prr.PART_FLG] = 0 # Pass
69 |
70 | # Test part counting and pass/fail tracking
71 | self.summarizer.onPrr(prr_row)
72 |
73 | # Check hard bin tracking
74 | count, status = self.summarizer.hbinParts[(1, 1)]
75 | self.assertEqual(count[0], 1)
76 | self.assertEqual(status[0], 'P')
77 |
78 | # Check soft bin tracking
79 | count, status = self.summarizer.sbinParts[(1, 1)]
80 | self.assertEqual(count[0], 1)
81 | # Soft bins initialize with False, so they'll get ' ' status
82 | self.assertEqual(status[0], ' ')
83 |
84 | # Test fail case
85 | prr_row[prr.PART_FLG] = 0x08 # Fail
86 | self.summarizer.onPrr(prr_row)
87 |
88 | # Check status becomes mixed (' ') when both pass and fail seen
89 | count, status = self.summarizer.hbinParts[(1, 1)]
90 | self.assertEqual(count[0], 2)
91 | self.assertEqual(status[0], ' ')
92 |
93 | if __name__ == '__main__':
94 | unittest.main()
95 |
--------------------------------------------------------------------------------
/tests/test_IO.py:
--------------------------------------------------------------------------------
1 | import unittest
2 | import io
3 | import sys
4 | from pystdf.IO import Parser, appendFieldParser
5 | from pystdf.Types import RecordHeader, EofException, EndOfRecordException
6 | from pystdf import V4
7 |
8 | class TestIO(unittest.TestCase):
9 | def setUp(self):
10 | sys.stderr.write('Setting up test...\n')
11 | self.test_stream = io.BytesIO()
12 | self.parser = Parser(recTypes=V4.records, inp=self.test_stream, endian='<')
13 | self.parser.eof = 0 # Reset EOF flag
14 | sys.stderr.write('Setup complete.\n')
15 |
16 | def write_to_stream(self, data):
17 | """Helper method to write bytes to the test stream and reset position"""
18 | self.test_stream.write(data)
19 | self.test_stream.seek(0)
20 |
21 | def test_read_field_types(self):
22 | sys.stderr.write('Starting field types test...\n')
23 | # Set up header
24 | header = RecordHeader()
25 |
26 | # Test U1 (unsigned 1-byte integer)
27 | test_data = bytes([42])
28 | self.write_to_stream(test_data)
29 | header.len = 1
30 | value = self.parser.readField(header, "U1")
31 | sys.stderr.write(f'U1 value: {value}\n')
32 | self.assertEqual(value, 42)
33 |
34 | def test_read_header(self):
35 | header_data = bytes([
36 | 0x0A, 0x00, # Length (10)
37 | 0x15, # Type (21)
38 | 0x20 # Sub-type (32)
39 | ])
40 | self.write_to_stream(header_data)
41 | header = self.parser.readHeader()
42 | self.assertEqual(header.len, 10)
43 | self.assertEqual(header.typ, 21)
44 | self.assertEqual(header.sub, 32)
45 |
46 | def test_read_field_types(self):
47 | print('Starting field types test...')
48 | # Set up header
49 | header = RecordHeader()
50 |
51 | # Test U1 (unsigned 1-byte integer)
52 | test_data = bytes([42])
53 | self.write_to_stream(test_data)
54 | header.len = 1
55 | value = self.parser.readField(header, "U1")
56 | self.assertEqual(value, 42)
57 |
58 | # Test U2 (unsigned 2-byte integer)
59 | self.test_stream.seek(0)
60 | self.test_stream.truncate()
61 | test_data = bytes([0x2A, 0x00])
62 | self.write_to_stream(test_data)
63 | header.len = 2
64 | value = self.parser.readField(header, "U2")
65 | self.assertEqual(value, 42)
66 |
67 | # Test I1 (signed 1-byte integer)
68 | self.test_stream.seek(0)
69 | self.test_stream.truncate()
70 | test_data = bytes([0xFF]) # -1 in two's complement
71 | self.write_to_stream(test_data)
72 | header.len = 1
73 | value = self.parser.readField(header, "I1")
74 | self.assertEqual(value, -1)
75 |
76 | def test_read_string(self):
77 | print('Starting string test...')
78 | # Set up header
79 | header = RecordHeader()
80 | header.len = 6 # 1 byte length + 5 bytes string
81 |
82 | # Test Cn (variable-length string)
83 | test_str = b"Hello"
84 | test_data = bytes([len(test_str)]) + test_str # String length + string data
85 | self.write_to_stream(test_data)
86 | value = self.parser.readCn(header)
87 | self.assertEqual(value, "Hello")
88 |
89 | # Test empty string
90 | self.test_stream.seek(0)
91 | self.test_stream.truncate()
92 | test_data = bytes([0]) # Length 0
93 | self.write_to_stream(test_data)
94 | header.len = 1
95 | value = self.parser.readCn(header)
96 | self.assertEqual(value, "")
97 |
98 | def test_read_array(self):
99 | print('Starting array test...')
100 | # Test array of U1
101 | test_data = bytes([10, 20, 30])
102 | self.write_to_stream(test_data)
103 | header = RecordHeader()
104 | header.len = 3
105 | values = self.parser.readArray(header, 3, "U1")
106 | self.assertEqual(values, [10, 20, 30])
107 |
108 | def test_append_field_parser(self):
109 | print('Starting append field parser test...')
110 | def base_parser(*args):
111 | return [1, 2]
112 |
113 | def field_action(*args):
114 | return 3
115 |
116 | new_parser = appendFieldParser(base_parser, field_action)
117 | result = new_parser()
118 | self.assertEqual(result, [1, 2, 3])
119 |
120 | def test_end_of_record(self):
121 | print('Starting end of record test...')
122 | # Test handling of premature end of record
123 | test_data = bytes([0x02]) # Only 1 byte when 2 are expected
124 | self.write_to_stream(test_data)
125 | header = RecordHeader()
126 | header.len = 2
127 | with self.assertRaises(EndOfRecordException):
128 | self.parser.readField(header, "U2")
129 |
130 | def test_eof(self):
131 | print('Starting EOF test...')
132 | # Test handling of EOF
133 | self.write_to_stream(bytes([])) # Empty stream
134 | header = RecordHeader()
135 | header.len = 1
136 | with self.assertRaises(EofException):
137 | self.parser.readField(header, "U1")
138 |
139 | if __name__ == '__main__':
140 | unittest.main()
141 |
--------------------------------------------------------------------------------