tsmap User Guide¶
tsmap loads semiconductor wafer map data from STDF, ATDF, CSV, JSON, and Parquet files and renders interactive yield maps, parametric heat maps, and statistical charts. It runs as a native desktop application on Linux, macOS, and Windows, and as a browser app at wafertools.github.io/tsmap/app/.
New to tsmap? Learn tsmap is a set of short slide lessons, each ending with a lab in the web app.
The guide follows the order you meet things in:
- Getting started — installing, opening your data, what happens next.
- Loading your data — scanning a folder, column mapping, the test selector, wafer names.
- Reading the map — what tsmap adds to the wafer map: dies with no position, findings, the log.
- Analysing your data — splits, grouping in Insights, sweeps, derived tests, bin definitions, wafer geometry.
- Reference — command line, opening from a URL, file associations, every definitions file format, column-name detection, saved settings, desktop vs browser.
In the app, this guide continues straight into the full wafer map guide in the same window — the wafer map toolbar, its plot modes, and every Insights tab panel (yield, per-test pass rate, bin pareto, process capability, boxplot, histogram, wafer-to-wafer trend, correlation, scatter) are covered there, with no separate page to open. Reading this outside the app (e.g. the docs site), see the wafer map guide directly for that part.
Getting started¶
Supported file formats¶
| Format | Extensions | Notes |
|---|---|---|
| STDF v4 | .stdf, .std |
Binary; PTR (parametric) and FTR (functional) records; multi-wafer lots |
| ATDF | .atdf, .atd |
ASCII equivalent of STDF; same data, same features |
| CSV | .csv, .txt, .dat |
Tab, semicolon, and comma auto-detected, and a decimal comma (0,25, as a European-locale export writes) in a tab- or semicolon-separated file; wide and long (pivot) formats |
| JSON | .json |
Flat array of die objects or nested [{ wafer, results: [{die}] }] |
| Parquet | .parquet |
Columnar, typed; wide and long (pivot) formats, same as CSV/JSON. snappy, gzip, lz4, and brotli compression on both desktop and web; zstd on desktop only |
| Gzip | .gz |
Transparent decompression — e.g. lot.stdf.gz |
| Bzip2 | .bz2 |
Transparent decompression — e.g. lot.stdf.bz2 |
| Zip | .zip |
All contained files extracted and loaded as a batch |
STDF and ATDF are always parsed natively — never attempt to open them in a text editor or spreadsheet. Multiple-result parametric records (MPR), which some testers use for tests that return several values at once (a pin group, a sweep), are not read yet: those tests do not appear, and the log says how many such records a file had (troubleshooting). CSV, JSON, and Parquet require a column mapping step before the data is parsed.
Installing past security warnings¶
tsmap's installers are not code-signed, so your operating system may warn that the app is from an unknown or unidentified developer the first time you run it. This is expected — it reflects the absence of a paid signing certificate, not a problem with the app. The steps below let you install anyway. If you would rather not install at all, the browser version runs with no download.
Windows — SmartScreen shows a blue "Windows protected your PC" dialog when you run
tsmap-<version>-windows-x64.msi (or the -setup.exe installer). Click More info, then
Run anyway. The warning fades as more people install the app.
macOS — Gatekeeper blocks the app with "tsmap can't be opened because it is from an
unidentified developer." After mounting the .dmg
(tsmap-<version>-macos-apple-silicon.dmg for M-series Macs, -macos-intel.dmg for Intel)
and dragging tsmap to Applications, right-click the app in Applications and choose
Open, then click Open in the dialog. You only need to do this once. If macOS instead
says the app is "damaged and can't be opened" — common on Apple Silicon for downloaded
unsigned apps — clear the quarantine flag in Terminal:
xattr -dr com.apple.quarantine /Applications/tsmap.app
Linux — the .deb, .rpm, and .AppImage builds run normally; any warning is just a
browser download nag. For the AppImage, mark it executable first:
chmod +x tsmap-*-linux-x86_64.AppImage
./tsmap-*-linux-x86_64.AppImage
The toolbar and colour theme¶

The colour theme picker sits at the right end of the toolbar, next to the help button. Choose Auto to follow your system's light/dark setting, or pick a theme explicitly. The list is grouped light-first:
- Light — Light, Light green, Solarized Light, Gruvbox Light, GitHub Light, Catppuccin Latte, High contrast.
- Dark — Dark, Nord, Solarized Dark, Dracula, Tokyo Night, GitHub Dark, Gruvbox Dark, Catppuccin Mocha.
Your choice is remembered. The theme applies to the whole app including the wafer map and its Insights charts, which follow tsmap's colours rather than keeping their own.
Loading sample data¶
The empty state has a Load sample data button that loads a bundled synthetic lot — 25 wafers across 5 process corners — through the normal load flow, so you can see tsmap working before opening your own files. Available on both desktop and browser. Its process-corner splits apply automatically, so the loaded lot is ready to explore with Group by → Split right away.
Sample files¶
The repository's sample_data/ folder holds synthetic lots for trying each feature. None contains real device data. The
web page has a few of them to download.
| File | What it shows | Where to look |
|---|---|---|
PVT-LOT-05.zip with PVT-LOT-05_splits.csv |
A full lot of 25 wafers across 5 process corners, as a zip of one STDF file per probe session. The five sessions differ in temperature, tester, program revision and burn-in time (each with its own effect on the data), on a 4-site probe card, with six parametric and eight functional tests. This is the lot behind Load sample data. | Group by → Split; the Insights correlation tab |
PVT-LOT-05_testdefs.csv, _testdefs_spec.csv, _testdefs_edgecases.csv |
Test-definitions files for that lot: names and test limits; test limits and spec limits on the same tests; and a robustness check with an unrecognised column and malformed lines, each reported in the log without dropping the rest of the file. | Setup ▾ → Tests… → load. The spec file adds LSL/USL lines and a capability judged against the spec limits |
RETEST-LOT-07.csv |
Dies probed more than once: most failing dies are retested and about two thirds recover, a few passing dies are re-probed and some of those fail, and a few recovered dies fail a third probe. | Setup ▾ → Retests…: each choice gives its own yield. The die tooltip's retest count; the log's note about repeated dies |
MPW-LOT-08.csv |
A multi-project wafer: a small cluster of dies repeats in every reticle. | Overlays ▾ → Compact layout and Layout diagnostics |
VALID-LOT-09.csv |
Long-format file with test limits and validity limits (lo_limit, hi_limit, lvl, uvl). A few readings per test are tester clamps: Idsat at its 5000 µA compliance clamp, Vth at 0 mV with no probe contact, Ioff at its ammeter rail. |
Setup ▾ → Exclude values outside limits…: switch between validity limits and off, and compare the map, the Test Values table's Excl. column and the Distributions charts. Yield does not change |
SWEEP-LOT-06.zip, _testdefs.csv, _plots.json |
A two-state switching lot of 8 wafers in four probe sessions (one STDF file each, in the zip) and more than 70 tests: readable names, 7 derived tests, and two sweeps. | Load the lot, load the definitions in the test selector, then Insights → My plots → Import plots… (or launch with --plots) |
EDGE-LOT-01, HY-LOT-04, CLUST-LOT-03, PARAM-LOT-02 (.zip for the lot, holding its session STDFs; _S1… .stdf/.atdf per probe session, _W01… per wafer, .csv merged, _splits.csv) |
Four lots, each with one story (an edge-ring defect, a high-yield lot, a failure cluster, a parametric shift) probed on four sites with five functional tests each, and its own wafer and die size: 300 mm with 10×8 mm dies (799 dies a wafer), 150 mm with 10 mm dies (145), 200 mm with 8×6 mm dies (581) and 300 mm with 5 mm dies (2,709). Each lot is probed in several sessions that differ in tester, handler, probe card, operator and date, and the wafers differ in a fab split; some of these change the data and one is a decoy (HY-LOT-04 has none that do). The lot's .stdf is the sessions written one after another, .csv is the same lot as one export, and _splits.csv is the fab's split. Open several together for a mixed gallery and the file filter. |
The Summary panel's findings; Open files ▾ → Scan a folder…; the gallery's Columns (keys 1 to 5, 0 for Auto) on the 2,709-die lot |
COORDLESS-LOT-01.stdf, TESTNUM-COORDLESS-01.{csv,json,parquet}, ALL-COORDLESS-01.csv |
Wafers whose dies have no reported position: one positioned, one mixed, one with none. | Dies with no position |
TESTNUM-LONG-01, TESTNUM-WIDE-01 (.csv, .json, .parquet) |
Real test numbers in long format (no name column, with validity limits and boundary values) and in wide format (numeric column headers). | The column mapping dialog's test number choice |
CORR-LOT-01.parquet, PARQUET-LOT-01.parquet, sample-lot.zip, large.zip |
Parquet with designed correlations, a general Parquet lot, the Load sample data lot (PVT-LOT-05.zip under another name), and a 66 MB zip holding a 358 MB STDF to try a large load. |
Open directly: a .zip, .gz or .bz2 file is read without unpacking |
Open files¶
Click Open files ▾ in the toolbar and choose how to find your data: Choose files… opens a file picker (select one file or several; a native OS dialog on the desktop, the browser's file dialog on the web), Scan a folder… reads a whole folder, and, on the desktop, your recent files are listed below. Add files ▾ offers the same two ways of picking, for appending to what is already loaded.
Drag and drop¶
Drop one or more files anywhere in the window. This is equivalent to selecting them through the file picker and is supported on both desktop and browser — the start screen says so, and the toolbar repeats it once data is loaded (on a wide enough window; the hint is dropped on narrow ones so the controls to its right stay reachable).
Dropping a folder scans it instead, exactly as Scan a folder… does. That part is desktop-only: a browser hands a dropped item's contents to the page and not its location, so there is no folder for the web build to walk. Drop a folder in the browser and tsmap says so — "a browser cannot read a dropped folder. Drop the files inside it, or use Scan a folder…" — rather than failing to parse something named after your directory. Files dropped alongside a folder still load; only the folder is skipped.
Recent files¶
Desktop only. The last 8 file sets you've opened are listed under Open files ▾, below
Choose files… and Scan a folder…, each showing when it was last loaded (Today 14:32,
Yesterday 09:05, or a date for older entries). They also appear on the start screen, where
there is room to show them without opening a menu.
Click an entry to reopen it — this replaces the current view the same way Open files does, so it is not a way to append, which is why recents are not offered under Add files ▾. Click the × beside an entry to remove it from the list.
They are reachable whether or not a file is currently loaded, since the Open files menu is always available. Not shown in the browser version: reopening needs a native file path, which browser file pickers do not provide.
Adding files to an existing lot¶
Once a file is loaded, the Add files button becomes active. Use it to append additional wafers to the current gallery — it goes through the same column mapping / test selector / rename steps as a fresh load, then shows a confirmation dialog before merging into the current gallery:

The dialog summarises the incoming wafers and warns about structural mismatches (different die count, different hard bin set, duplicate wafer IDs). With no mismatches, the button reads Add to gallery; if there's a warning to acknowledge, it reads Add anyway. Click Cancel to keep the current data unchanged.
Clearing data¶
Click Clear files to unload all data and return to the empty state.
What happens next¶
After selecting files, what happens depends on the format:
| Format | Next step |
|---|---|
| STDF / ATDF | Test selector overlay appears first for large files; small lots import every test directly |
| CSV / JSON / Parquet | Column mapping overlay appears first |
| Multiple files | Wafer rename overlay appears before rendering, unless the files are one lot |
Loading your data¶
Scanning a folder¶
When you have far more files than you want to load — a directory of hundreds of lots, say — you don't have to pick them in the file dialog at all. Point tsmap at the folder and let it tell you what's in there.
Both Open files ▾ and Add files ▾ offer Choose files… or Scan a folder…, so you can scan a folder whether you're replacing what's loaded or adding to it. Dropping a folder onto the window does the same thing (dropping files still loads them directly; a dropped folder always replaces, since a drag can't say "add").
However you get there, tsmap reads just the header metadata from every data file it finds: lot, part type, tester, program and whatever else the file carries, plus wafer and site counts for STDF/ATDF. No die data is read, so scanning a large batch is quick.
The table leads with what usually decides which files you want: the file's Name, its Lot, how many Wafers it holds, and when it was Tested — shown next to the file's Modified time, so a file copied long after testing stands out. Tested is the earliest wafer start time the tester recorded, or the lot's start time where no wafer times were recorded. It and the other optional columns appear only when at least one file has a value.
If the folder has subfolders, you're asked once whether to include them. They're left out by default — a recursive scan of a large tree is the one part of this that can take real time — and the scan stops at a safety limit on very large folders, saying so in the log rather than quietly showing you part of the answer.
The same table also appears when you pick a lot of files through Open files or Add files: over a handful, tsmap offers to filter them first.
The two dialogs deliberately ask different questions. The folder picker asks where to look; the table below asks which files you want. Whichever way you got here, whether the chosen files replace what's loaded or are added to it was already decided by the button you pressed — the table doesn't ask again.

The results appear in a table, one row per file, with a column for every metadata field found across the batch (alongside Name, Size and Modified). From there you can:
- Sort by clicking a column heading — click again to reverse, a third time to clear. Size and Modified sort by their real value, not by how they're written.
- Filter a column by clicking the ▾ in its heading and ticking the values to keep. Clear filters in the toolbar resets every column at once.
- Hide columns you don't care about with Columns ▾ — a batch can easily produce twenty-odd metadata columns. Hiding one only affects the display; if it has a filter set, that filter still applies (the picker marks it as filtered so you can tell).
- Resize a column by dragging the divider on the right edge of its heading. Columns auto-fit their content on each scan (filenames get the most room, since they are what the table exists to compare), but a column you have resized yourself keeps your width through the next scan rather than being re-fitted. The divider is focusable — Left/Right arrows resize it from the keyboard.
- Search across all columns with the search box.
- Select rows by clicking them — a click adds a row to the selection or removes it, and selected rows are highlighted. Shift-click a second row to apply the first row's state across everything between them — so shift-clicking after deselecting a row clears the range instead of selecting it. From the keyboard, ↑/↓ move between rows, Space selects or deselects, Shift+↑/↓ extends, Shift+Space extends to the focused row, and Ctrl/Cmd+A selects everything shown.
- Select all, Select none and Invert apply to the rows currently shown, so they respect the filter — as does Ctrl/Cmd+A, and as does a shift-click range.
Load selected… (or Add selected…, if you got here from Add files) takes the files you've ticked. It confirms first. The selected files then go through the normal load flow — column mapping, test selector, wafer rename and so on — just as if you had picked them directly.
Save filter… writes the current column filters and search text to a small JSON file, and Load filter… reads one back, re-selecting every row that matches. That makes a recurring selection ("this quarter's production lots") reusable across sessions and across different batches of files. If a saved filter names a column these particular files don't have, that column is ignored and the dialog says so, rather than quietly matching nothing.
If you pick more than five files through Open files or Add files, tsmap offers to route them into this table first, so you don't have to pick them twice.
Mixed formats are fine here. You can scan a directory holding STDF, CSV and Parquet
together — reading metadata doesn't care about the format, and a Format column appears
so you can sort and filter by it. A load still takes one kind of file, with STDF and ATDF
counting as one (they are the same records, binary and text) — so STDF and ATDF load
together, but not with CSV, JSON or Parquet, which share one column mapping per load. When
the scan holds more than one kind, buttons above the table choose between them; the dialog
also tells you if a selection still mixes kinds, and stays open so you can adjust. Zip
archives are expanded and their contents scanned individually, and .gz and .bz2 files are
read straight through.

Column mapping (CSV, JSON, and Parquet)¶

CSV and JSON files don't have a fixed schema, and Parquet files carry an arbitrary schema
of their own, so tsmap shows a column mapping overlay before parsing any of the three. It
lists every column in the file with a dropdown to assign its role. Common column names
(x, hbin, result, lo_limit, etc.) are detected automatically and pre-filled.
Parquet columns are typed (numbers, text, booleans) rather than plain text like CSV/JSON
cells, so the overlay adds one thing on top: a ⚠ next to a column's role dropdown if
you assign a numeric-only role (X position, Y position, Hard bin, Test value, etc.) to a
column whose values are actually text. This is a hint, not a hard stop — a numeric-looking
text column (e.g. "42") still parses fine — but it catches an obvious mismatch (e.g.
mapping a wafer-ID text column to X position) before a full parse silently produces empty
or wrong results.
A file with many columns can be set up in a few clicks. Filter columns… narrows the list to the columns whose names match what you type; Shown → Test value then gives every column still listed the Test value role, and Shown → Ignore leaves them all out of the load.
Nothing here is a hard rule — whatever role a column lands on, reassign it in the overlay before continuing; no data is imported until you confirm. The roles, and the column names that are detected automatically, are listed in the column mapping reference.
Wide vs long format¶
Wide format has one column per test (the most common layout from prober exports). Assign each test column the Test value role and fill in the test name.
Long format has one row per die per test (each row includes a test identity column and a result column). Assign Test result (long format), plus Test name (long format), Test number (long format), or both — a file that only has real test numbers and no descriptive names works just as well as one with only names; assigning both uses the real number as the test's identity paired with the given name. Optionally assign the limit and units columns too. tsmap detects likely long-format files automatically and shows a prompt if multiple rows share the same X/Y coordinates.
Examples:
Wide format — one column per test:
x, y, hbin, Vt_lin, Idsat_vg1
1, 1, 1, 452, 185
2, 1, 2, 438, 179
Long format — one row per die per test:
x, y, hbin, test_name, result
1, 1, 1, Vt_lin, 452
1, 1, 1, Idsat_vg1, 185
2, 1, 2, Vt_lin, 438
2, 1, 2, Idsat_vg1, 179
Dies with no position¶
X and Y are optional. Leave both unassigned and every die is treated as having no reported position; assigning only one of them is blocked. A confirmation explains what that means before you continue — see Dies with no reported position for the detail.
Pass bins¶
The Pass bin(s) field at the bottom of the overlay specifies which hard bin values are
treated as pass for yield calculation. Default is 1. Enter multiple bin numbers separated
by commas (e.g. 1,7).
CSV/JSON/Parquet have no equivalent of STDF/ATDF's HBR/SBR records, so bins have no names — just numbers — unless you supply them. Name bins…, next to the Pass bin(s) field, opens a table of the bins in this file: type a name or a hex colour for each and tick the hard bins that pass; the Pass bin(s) field follows what you tick. The list comes from the sample of rows the mapping window holds (the first rows and some spread through the file), so a rare bin can be missing; type its number and Add hard bin or Add soft bin. Once the file is loaded, Setup ▾ → Bin definitions… lists every bin it really has, with die counts. The Load bin definitions… button next to the Pass bin(s) field loads a bin-definitions CSV (real hard/soft bin names, and pass/fail flags that override this field) before you continue. See Bin definitions below for the file format, and for STDF/ATDF's own version of this (overriding what HBR/SBR already supplied, once loaded).
Saved mappings¶
The roles you confirm are remembered for that exact set of column headers, and the next file with the same headers opens with them; the dialog says so. Reset to auto-detected returns every column to detection and forgets the saved mapping. It is greyed out while the dialog already matches detection.
Test selector (STDF and ATDF)¶

STDF and ATDF files from production testers often contain hundreds of parametric and
functional tests. For a large file tsmap shows a test selector overlay before the full parse so
you can choose which tests to import. A small lot (a few million test-by-die values or fewer,
such as the bundled sample) imports every test at once and logs "N tests imported"; Setup ▾ →
Tests… opens the same selector afterwards. When you confirm a selection, tsmap keeps it for that set of tests: the next time a lot with the same tests is opened, the selector starts from your last choice (the log says so), and a lot from a different test program, or the same one with a test added or dropped, starts empty. The selector also always appears when --tests is
given. This keeps memory usage and load time proportional
to what you actually need.
tsmap uses a two-pass approach: a fast first pass reads only the test record headers (PTR/FTR) to enumerate all tests and their numbers, names, units, and test limits — without accumulating any die data. The selector is built from this scan. The full parse then runs only for the tests you selected, skipping accumulation for everything else. For a 25-wafer lot with 500 tests and 10 000 dies per wafer, selecting 20 tests instead of all 500 reduces the in-memory dataset by roughly 25×.
Controls¶
The first row narrows what you see; the second row selects from what's shown.
- Search — Filter the list by test name or test number. Results update as you type.
- Type filter — Show all tests, only Parametric (PTR), or only Functional (FTR). The count per type is shown on each button.
The three controls in the second row all act on the tests currently listed, so a search or type filter narrows what they can reach:
- Select all / Select none — Tick or untick everything currently shown.
- Range select — Type a numeric range (
1000-1099), a name-based range (Idsat_vg1-Idsat_vg5), or a comma-separated mix of ranges and single tests (1001-1010, 1050, vth_n), then press Enter or click Select range. A name range runs between the first and last match in list order, not alphabetically. Because it only reaches shown tests, a range naming real tests can select fewer than you expect — the dialog says so when that happens, and tells you how many the filter is hiding. - Shift-click — Click one checkbox, then Shift-click another to select or deselect the entire range between them. Which of the two it does follows the first checkbox: tick it and the range is selected, untick it and the range is cleared. From the keyboard, Shift+↑/↓ extends, Shift+Space extends to the focused row, and Ctrl/Cmd+A selects every test currently shown.
Each test row shows the test number (in dim monospace), the test name, and — where defined
in the file — the units and test limits. If a Load definitions (or --tests) has overridden a
test's limits, units, or type, the row shows those overridden values, with a tooltip noting
they were loaded from file.
Editing limits, units and type¶
Edit limits… (next to Save definitions) opens a table of every listed test: its name, test limits, spec limits, validity limits, units, and whether it is parametric or functional. Type into a cell to change it; a number cell takes a number, and the Data column reads changed for any row that differs from what the data file states. Hover a column heading for what that kind of limit does.
- A blank cell means the data's own value. Clear a cell and the data's value comes back; an edit can change a limit but cannot remove one the data states. Typing the data's value back removes the change.
- Use the data's values (tick rows first) discards every change on those rows; Revert on a row goes back to how the table opened, and Revert all does it for the whole table.
- A unit that differs from the data's only by an SI prefix (mV for V) converts the limits you give to the data's unit, exactly as it does in a definitions file. Functional tests are pass/fail only, so limits are ignored on them.
- A low limit above its high limit is refused when you press Apply.
Apply puts the changes in the selector, where they take effect with the tests, when you import; Save definitions then writes them. Derived tests are edited by their expression, not in this table.
Renaming a test¶
Click into a test's name to edit it directly — the field looks like plain text until you hover or focus it. Press Enter or click away to commit the new name; press Esc to discard the edit and restore the previous name. Renaming only changes the display name — nothing in the underlying data file changes — and the new name appears everywhere that test is shown: the selector, the map tooltip, and chart axis labels. Renames persist across Setup ▾ → Tests… re-opens and are included when you Save definitions.
Saving and loading test definitions¶
The Save definitions and Load definitions buttons let you persist a selection and reuse it across sessions or files from the same product. Beyond just the selection and display names, a test definitions file can also carry test limits and test type — useful when a test program ships without limits (common for characterisation/test-vehicle work, where limits come from simulation or are defined and adjusted separately), or when you need to correct a test's parametric/functional classification.
Saving writes a plain-text .csv file with every selected test's number, current display
name, and current effective limits/units/type. Loading reads that file back, restores the
selection, and applies whatever the file specifies as overrides on top of the parsed data —
so renamed tests stay renamed, and any loaded limits/type replace what the test program shipped
with (or fill in limits it didn't have at all).
The file format, and the list of recently used files, are described under Definitions files in the reference.
Memory advisory¶
The footer shows how many tests are selected and estimates the memory footprint (selected tests × total die count):
- Amber — large selection (roughly 50 million die×test pairs); the import will be slow.
- Red — very large selection (roughly 200 million die×test pairs); risk of running out of memory. You'll be asked to confirm before the import starts.
If you select no tests, tsmap asks you to confirm ("No tests selected — only bin data will be loaded. Continue?") before importing — the bin map is still fully usable with no tests selected.
Changing the selection after load¶
After a successful load, the toolbar's Setup ▾ menu offers Tests…. Click it to re-open the test selector at any time and change which tests are imported. The file is re-parsed with the new selection — bin and yield data is preserved regardless of which tests you select.
For multi-file batches, the selector is shown once and the same selection is applied to all files. By default the test definitions are scanned from the largest file only — a fast, representative default. If a test appears only in a smaller file (so it's missing from the list), click Scan all N files in the selector to re-scan every file and merge the full test definitions; your current selection is preserved. The "Tests…" dialog offers the same toggle if you didn't widen the scan at load time.
Setup ▾ → Tests… is the single place for everything about tests:
- Which tests are imported — tick and untick, search, select a range.
- What they are called, and their limits — rename a test in the list, or open Edit limits… to set test, spec and validity limits, units, and parametric/functional type in a table.
- Save definitions / Load definitions ▾ — the CSV round-trip described above.
Narrowing or relabelling applies immediately, in memory. Only widening the selection — asking for a test that was not imported — re-reads the files, and the button says Apply to N tests → rather than "Import" to reflect that.
One thing is refused, deliberately. If the loaded files disagree about what a test number measures — test 1001 being a threshold voltage in one file and a leakage current in another — a name or unit for that number cannot be applied honestly, so it is skipped and named in the log while the rest of the file still applies. A limit is not refused: stating the spec explicitly resolves the ambiguity rather than hiding it.
Wafer rename overlay¶

When loading multiple files (or a zip containing multiple files, or a single file whose
only wafer has a generic ID like W01 and no lot ID to go with it), tsmap shows a rename
overlay listing each wafer
with an editable label. Files that are one lot (every file names the same lot ID and no wafer ID
appears in two of them, as a lot tested in several sessions arrives, one file per session) need no
labels: they open straight away with their own wafer IDs, and a zip of them is named after the zip. Labels are pre-filled from whatever identifies the wafer in the
data — a distinctive wafer ID is used as-is; a generic one (W01) is combined with the
lot ID (LOT-A · W01) so wafers stay distinct within and across lots without you needing
to edit anything; with neither, it falls back to the file name. Edit any label that needs
changing, then click Continue →.
Files that record a test in a different unit prefix. If one file gives a test in mV and
another in V, tsmap converts the later file's values and limits to the unit the first file
uses, and says so in the log, so the test stays comparable across every wafer. Only a
difference of prefix on the same unit is converted (mV and V, nA and µA, kHz and MHz). Any
other disagreement, including one of letter case alone such as mV and MV, means the files
may describe different measurements: that test is left out of every view that compares
wafers, and the log names the files.
Reading the map¶
The wafer map view¶
After parsing, tsmap renders the wafer map. A single-wafer file shows one full-screen map with the summary panel open by default; a multi-wafer lot shows a side-by-side gallery. Help → Keyboard shortcuts…, or the ? key, lists the keys for maps, windows and menus.
For a full walkthrough of toolbar controls, plot modes, overlays, zoom and pan, die hover tooltips, the findings panel, the summary panel, and gallery controls, see the full wafer map guide — in the app it follows immediately below in this same window, not a separate page.
Multi-project wafers¶
On a multi-project wafer each reticle holds only a few of one product's dies, so the map is mostly empty and every die is tiny. When the occupied columns and rows repeat at a regular pitch, the map's Overlays menu offers Compact layout, which removes the empty rows and columns and outlines each group of dies; the full wafer map guide describes it. Every die is still counted, so yield and the findings do not change, and the labels give the original die coordinates.
If a layout is not recognised the way you expect, open Layout diagnostics in the same menu. It shows counts and scores only, with no die positions, bins, test values or wafer names. Save as file writes it with the native save dialog, and Copy puts it on the clipboard, so it can be sent to whoever supports tsmap without sharing the data.
Zooming the interface¶
Ctrl and + / − scale the whole interface — menus, panels, tables and the map together — and Ctrl+0 returns to 100%. On macOS use Cmd. This is the app's own chrome zoom, and it is a different control from the wafer map's zoom described in the guide above: that one magnifies the map inside its panel and leaves the surrounding UI alone, while this one resizes everything, which is what you want on a high-DPI display or when showing the app to a room.
In the browser version this is simply the browser's own page zoom, so it works the same way without tsmap doing anything.
Dies with no reported position¶
Not every file reports an X/Y position for every die. In a CSV, JSON or Parquet file, leave both X and Y unassigned in the column mapping and every die in the file is treated as having no reported position; a confirmation explains what that means before you continue. Assigning only one of X/Y (not both) is blocked — a die is either fully positioned or fully unpositioned, never half.
A row whose X or Y cell is blank or doesn't parse is handled the same way individually: kept as a coordinate-less die rather than dropped, even if most of the file's other rows do have a position (a "mixed" wafer). STDF and ATDF have the same rule applied to their own "no reported position" conventions (the STDF sentinel value and ATDF's blank PRR X/Y fields respectively).
What a positionless or mixed wafer looks like on screen, which toolbar controls it hides, and how findings treat it, is covered in the full wafer map guide ("Wafers and dies with no position data") — the same behaviour whether the file arrived as STDF, ATDF, CSV, JSON, or Parquet.
sample_data/COORDLESS-LOT-01.stdf (and .atdf) demonstrates all three wafer states in one lot:
W01 is fully positioned, W02 is a mixed wafer (~15% of dies unpositioned), and W03 is fully
coordinate-less. sample_data/TESTNUM-COORDLESS-01.{csv,json,parquet} is the same three-wafer
lot in the other formats. The Summary panel's Data tables button opens every die of the lot in
one table (with a wafer column), plus a statistics table and a table of wafers, each with its own
CSV export covering the whole lot.
Test-value findings¶
The wafer map's summary panel has a Findings list — statistically significant spatial patterns: regions of the wafer (edge ring, quadrants, clusters, test sites) with unusually low yield or distinctive bin patterns. These yield and bin findings are fast to compute and always on.
One category of finding takes longer to compute: regions that read unusually high or low on a specific test value ("the edge ring reads 8% high on VDD_CORE"). That comparison scales with the number of dies × tests, so it runs automatically only when the load is small enough to take about a second. A larger load shows a row at the top of the Findings list instead, saying the test-value findings are not included, how many tests and dies they would cover and roughly how long they would take, with an Analyse button. Analysing recomputes in place with no reload, for every wafer of the lot at once, and the findings stay for as long as the load is open.
Findings for tests that fail their limits more often in one region are included either way, as are the panel's per-test Min/Mean/Max statistics, the test-value maps and the Insights tab.
The log panel¶
A collapsible log panel sits at the bottom of the window. It shows timestamped messages from the parser and renderer: file load events, parse warnings, and any errors.

- Click Log to expand or collapse the panel.
- An error opens the panel automatically.
- Warnings and errors logged while the panel is closed are counted on the Log button — for example ▲ 3 new warnings — so a problem is never waiting unseen. Opening the panel clears the count; the next problem shows it again.
- Parser warnings (e.g. values outside the STDF ranges, unrecognised records) appear here rather than blocking the load.
- Right-click in the panel for Select all and Copy all. Copy all puts every entry on the clipboard, one per line, for pasting into a bug report.
Soft bin 65535 is the STDF value for "no soft bin assigned to this die". Those dies have no soft bin: they render as no-data in soft-bin views, and their hard bin is unaffected.
Analysing your data¶
Wafer splits¶
A split is a name you assign to a wafer that isn't in the file at all — most commonly a
process corner (TT, FF, SS, FS, SF), but it can be anything: an experiment
condition, a test-temperature group, anything you want to compare wafers by that your
tester didn't record. Once assigned, splits behave exactly like any other metadata field
in the Insights tab's Group by dropdown — split-vs-split
yield, boxplots, histograms, correlation, and scatter all work immediately with no extra
setup — and they can optionally be shown right on the wafer map/gallery labels too.
Splits are one of tsmap's four kinds of definitions file — see Definitions files in the reference for the formats of all of them, and for a template of each.
Once a file is loaded, open the toolbar's Setup ▾ menu and choose Splits… to open the assignment dialog. The banner at the top shows where things stand: with no assignments yet it points you at the two ways to get started (assign below, or load a saved CSV), and once splits exist it becomes a summary — e.g. "5 splits assigned to 25 of 25 wafers" — so a lot whose splits were restored automatically reads as already done:


Assigning splits¶
Tick the checkbox next to one or more wafers — click to toggle, Shift-click to select a
range, same as the test selector — type a split name (or click one of the chips below the
input to reuse an existing name, avoiding accidental near-duplicates like TT vs tt),
and click Assign to selected. Clear split removes the assignment from just the
checked rows; Clear all removes every wafer's assignment at once (after a confirmation,
since it's not scoped to your current selection). Every action applies immediately — there
is no separate save step, and Done just closes the window. The same keyboard gestures
work here as in the test selector: Shift+↑/↓, Shift+Space, and Ctrl/Cmd+A for
everything shown.
Unlike the test selector, ticking wafers here is never required to proceed — it only scopes the Assign to selected and Clear split buttons, which stay disabled until at least one wafer is ticked. Loading a CSV, Clear all, and Save splits… ignore the selection entirely.
Showing splits on the wafer map¶
The "Show split in wafer map labels" checkbox (on by default) appends the split, as
W02 · FF, wherever a wafer's ID is shown — gallery card headers, the single-wafer view,
the summary panel's Wafer Id row, and drilldown modal titles. Turn it off to see plain
wafer IDs again; the underlying assignments are unchanged either way.

Saving and loading splits¶
Save splits… writes every wafer's current assignment to a CSV file; Load splits… reads one back and applies it by matching each row to a loaded wafer — by lot and wafer ID, and by occurrence when the same wafer appears twice. Rows for wafers that aren't loaded are skipped, and a log message reports how many rows matched. This is the way to prepare a split definition ahead of time (e.g. from a fab's lot traveler) and apply it after loading the STDF, or to share a known-good corner mapping with a colleague. The file format is described under Splits file in the reference.
Restoring splits automatically¶
tsmap remembers split assignments per lot ID + wafer ID (plus part type, if present) — the physical wafer's identity, not the file it arrived in — so re-opening the same lot later restores them without reloading the CSV, even if that lot is split across several files (for example, one file per test temperature). With several lots loaded, each lot's W01 keeps its own assignment.
Wafers that share an ID are labelled apart. When two loaded wafers have the same wafer
ID, their gallery cards and the Splits dialog show what distinguishes them: the lot
(LOT-A · W01 / LOT-B · W01), then the file if the lot is the same, and as a last resort
the order they were loaded in (#1, #2) — for example a retest pass stored twice in one
file. This is only the label: reports and exports keep the real wafer ID, with the lot in its
own column.
This needs a lot ID. Data carrying none — a CSV export with no lot column, say — cannot be told apart from another file with the same wafer IDs, and W01–W03 is about as distinctive as wafer IDs get. Rather than risk applying one lot's splits to unrelated data, tsmap does not remember splits at all in that case: they apply to what is loaded, the Splits dialog says so in place of its usual "remembered for this lot" note, and Save splits… keeps them in a CSV you can reload deliberately. Map a lot column in the column mapping step and the automatic restore works as it does for STDF/ATDF. This restore is never silent: if any assignment is found for the wafers you just loaded, tsmap logs a message and automatically opens the Splits dialog so you can see exactly what was restored, edit it, or clear it — rather than silently changing chart groupings and map labels behind your back.
Clearing is remembered too. Clear all in the Splits dialog removes the assignments and forgets the saved copy for that lot, so re-opening it later comes back unassigned rather than restoring what you just cleared. That is the per-lot control; to forget saved splits for every lot at once — along with anything else tsmap remembers — use Preferences → Reset saved settings…, which stays reachable with nothing loaded.
Grouping data in the Insights tab¶
The Chart reference describes each chart, plot and table: what it shows, how to read it and where to find it.
The Insights side of the Maps | Insights switch at the far right of the toolbar row (both the single-wafer view and the gallery have their own) switches to a grid of statistical panels — yield, per-test pass rate, bin pareto, process capability, boxplot, histogram, wafer-to-wafer trend, correlation, and scatter — sharing the same parsed, in-memory data as the map, so switching never re-parses.
Every panel, its controls, and its grouping/drill-down behaviour are documented in full in the full wafer map guide — in the app, further down this same window. This section covers only the two things that are specific to how tsmap feeds data into it:
- What "Group by" takes: wafer attributes. A wafer attribute is a fact about a whole wafer: its lot, part type, program, temperature, test date, operator, or a split you've assigned (a split is one wafer attribute, like any other). They are attached to each wafer at load time. STDF and ATDF contribute every field in their MIR record (test station, mode, retest and protection codes, burn-in time, tester serial number, facility and floor, among others), the fields of the wafer's WIR and WRR, and for STDF the equipment its SDR names for the wafer's probe head (handler, probe card, load board and so on); CSV and JSON contribute the lot column plus any columns you mapped as metadata. Only attributes that actually vary across the loaded wafers appear in the dropdown, and each carries the name it has in the file filter (Part type, Program, Test date). Every attribute is offered except the wafer-configuration sizes and orientation, which describe the map rather than the wafer; a code is shown as the file has it, since what a mode or retest code means is each site's own convention. All of them show in the strip above the map and in the Wafers table.
- Group by pools wafers; Compare by acts on one plot. Group by separates whole wafers for every panel at once. A plot's own Colour and Categories (the Compare by choices) can also separate the dies within each wafer: by ring, quadrant, bin, site or a pass/fail test, as well as by a wafer attribute. See Your own plots.
- A single-wafer load has nothing to group by, so every panel simply shows that one wafer's own data and the Group by control doesn't appear.
If you loaded wafers to compare two process arms, the panel to reach for is per-test pass rate on the Overview: with Group by set to your split, it ranks the tests worst-first with one sub-bar per arm, which is the question a split experiment is usually run to answer. It reports rates rather than counts, so arms with different wafer counts stay comparable. The same comparison is written into the lot summary report, so it can leave the app.
Your own plots¶
The Insights My plots tab is a chart builder: + New plot opens a plot beside a large copy of its chart, where you choose the chart type, the fields for X, Y and colour (any measured test, a die's position, ring, quadrant, bin or site, the pass or fail of a functional test, a wafer's yield, or a wafer attribute such as split or temperature) and the titles and axis limits. A reticle set in Setup ▾ → Reticle… adds the reticle cell and reticle shot as fields too. How the controls work, and how a plot divides the data (its X, its colour, and the Show control above), is in the full wafer map guide.

Examples, then Add all, gives one plot of each chart type from the open lot.

Edit opens a plot large, beside its settings.
What is specific to tsmap is that your plots are kept. A plot is a recipe (which fields, which chart type), with nothing in it about the lot, so tsmap remembers the list on this machine and draws it again on the next lot you open. A plot that needs a test the open lot does not have stays in the list, dimmed, with the reason; it is not removed, and draws again on a lot that has the test. Right-click a selection of dies or a wafer and your plots are in the menu, drawn over just those dies.
Export plots… writes every plot to a file, and Import plots… adds the plots of such a file to yours (as
copies where one already exists, never replacing). That is how a set of plots moves to another machine or to a
colleague. To load a file when tsmap starts, name it with --plots. Preferences → Reset saved settings… lists your saved plots like any other remembered setting.
The filter (Filter…, the last button in the toolbar row of the map, the gallery and Insights alike) is kept the same way: tsmap remembers its conditions, and whether it was on or off, and applies it to the next lot and after a restart. It keeps only the wafers and dies that meet the conditions, in the gallery, on a single map and in every Insights tab at once, and the Summary panel and its findings are analysed again over what is kept. The line above the view always states it, so a filter carried over to a new lot is never applied unseen; one that names a field the new lot does not have filters nothing and says so. Remove on that line forgets it.
Filters you use again can be saved by name in the Filter window, and tsmap keeps that list on this machine too. Export…
there writes the list as a wafermap-filters file and Import… adds the filters of such a file (as copies where one already
exists, never replacing), with the file dialog of your system. The file is plain JSON, so a filter can be written by hand or
generated by a script; its format is described in the wafermap guide's Filtering wafers and dies.
Preferences → Reset saved settings… lists the saved filters, and the filter in force, like any other remembered setting.
Delete all plots… removes every saved plot, sweeps included, after asking you to confirm; the question says how many, and that Export plots… first keeps a copy. Undo brings the whole list back for ten seconds.
Tables and exports¶
Every number behind a chart is also a table. Insights' Data tab holds three, and the Summary panel's Data tables button opens the same three in a window, with no Insights needed:
- Statistics — per test: min, quartiles, median, mean, max, standard deviation, Ppk, limits and yield within the limits, plus pass and fail counts for functional tests, for the wafer or the whole lot.
- Dies — one row per die with its position, bins and every test value, plus the wafer's lot fields. Wide puts one column per test; Long puts one row per die and test, the shape JMP, Python and Excel pivot tables expect.
- Wafers — one row per wafer, for a lot: its lot fields (including any split), die counts and yield.
Copy puts a table on the clipboard and Export CSV saves it. Numbers are written at full precision, not as rounded on screen, and a table of hundreds of thousands of dies is written in pieces, so the app stays responsive. The desktop app asks where to save; the web app downloads the file. Click a row of the Dies table to open that die's wafer with the die ringed, showing the test whose column you clicked.
To tabulate just some dies, select them on a map (or drag a rectangle on the Insights scatter) and right-click: the menu offers the same tables for the selection. With Select the same dies on every wafer on in the gallery toolbar, one selection covers those positions on every wafer.
Sweeps¶
A sweep reads a run of tests — one quantity measured at a series of voltages, temperatures, load currents or cycle counts — as a curve rather than as separate tests. Two series side by side (a rising one and a falling one, typically) are measured against each other: where they cross, and how far apart they are, horizontally, at given levels (the width of the V they form). A sweep is a kind of plot: it is a card on Insights' My plots tab beside your other plots, started with + New plot and the Sweep chart type and changed in the plot editor, which shows the curve as you type; the card and the editor are described in the wafer map guide.
Sweeps are kept, exported and imported with your other plots (see Your own plots),
so they stay defined from one load to the next. A sweep that names none of a newly loaded lot's tests
stays in the list, dimmed, with the reason, and draws again on a lot that has them. A file of sweeps
can also be loaded at launch with --plots; its format is under
Sweeps in a plots file in the reference.
A test number identifies a test only within one test program, so a lot from another program can reuse the numbers your derived tests read, for different measurements — everything then computes, from the wrong tests. tsmap remembers the test names and program of the lot they were set up on; when a later lot uses the same numbers under different names, or states a different program, the log says so, with examples. A sweep records the names of its tests when you set it up (or edit it), and on a lot where one of those numbers now names a different test it does not draw that test and says so on its card, rather than draw another measurement as the same curve. A sweep written by hand without those names is not checked.
Derived tests¶
A test definitions file can also define derived tests: tests computed from other tests on the same die, rather than measured. A row with a value in the expression column is one:
num,name,loLimit,hiLimit,units,testType,expression
5116,Up-sweep I @ +1.2 V,,,uA,P,
910001,Up plateau,60,,uA,P,mean(t[5116..5120])
910002,State separation,1.5,,dec,P,"log10(t[910001] / max(t[5110], 0.01))"
910003,Sweep OK,,,,F,testPass[2001] and t[910001] > 50
From then on a derived test is an ordinary test — plot it on the map, colour it against its limits, see it in Insights and the reports. Wherever it is named it carries a † mark, and its tooltip shows the expression, so it is never mistaken for a measurement.
- What an expression can read:
t[5116]is a test's value,testPass[2001]the tester's recorded pass/fail,specPass[5116]whether the value is inside its limits, anddiePass()the die's bin verdict. The die's own fields aredieX(),dieY(),hbin(),sbin()andsite(), so a test can be cut by bin (hbin() == 5) or position (if(dieX() < 5, t[1010], -1)); a field the die lacks is no value, not zero.5116..5120names every test in that range that exists — so a program numbered in steps of two needs nothing special. A range must be reduced withmean,sum,min,max,all,any,none,countTrue,countFalseorcountKnown. The full list of operators and functions is in the wafer map guide. - A derived test can read another —
910002above reads910001. Order in the file does not matter. testTypeF makes a pass/fail test: the expression must be a true/false comparison.- The expression column must be last, and needs a header row. Because it is last, an expression containing commas reads the same with or without quotes, so a hand-edited file that forgot them still loads. tsmap writes the quotes itself when saving, as Excel does.
- Use test numbers the tester does not. A derived row whose number is already a measured test is ignored, and the log says so. Numbers from 910001 upwards are a safe convention.
- The tests it reads must be imported. A derived test reading an unselected test cannot be computed; it is left out, and the log names the test it needed.
- A derived test that cannot be computed — a typo, an unknown test, volts minus amps — is left out with the reason and the position in the log. It is never half-computed.
In the test selector derived tests are listed after the measured ones, marked † in front of the name — hover the mark for the expression. Select, deselect, rename and search them like any other test; a deselected derived test stays defined but is not computed. The Derived filter button shows only derived tests. It overlaps the other two: a derived test is also parametric or functional, and appears under that filter as well. Loading a definitions file replaces the derived tests along with the selection (the table above starts from whatever is defined); Save definitions writes the selected ones back. The footer counts them ("84 of 84 tests selected (7 derived)").
Writing derived tests in the app. Derived tests… in the selector footer opens a table of them,
each with a number, name, expression, units, test limits and type. Add derived test starts a row on
the next unused number from 900001 and puts the cursor in its expression; Delete removes a row.
Every expression is checked as you type, by the same rules the map is built with: the Check column
reads ✓ with Number or Verdict (functional), or ✗ with the reason and the position, and Reads lists
the tests it depends on by number and name. A derived test may read another. Apply is refused while any
test would be dropped, so what you apply is what gets computed — including a read of a test that is
listed but not selected for import, which the Check column names. Units that do not match across a
+ or - are flagged but not refused; give the derived test its own units. Changes take effect on
import, like the rest of the selector, and Save definitions writes them to the file.
Derived tests stay defined from one load to the next, so they are ready for the next lot from the same test program. Remove derived tests in the selector footer drops them all, for data they do not apply to. Like every other change in the selector it takes effect on import; Cancel keeps them. When none of them can be computed for a newly loaded lot, the log says so and points here.
Everywhere else a derived test is named — the map's plot-mode test list, the map title, tooltips, Insights charts and their test pickers, findings and reports — it carries the same † in front of its name, with the words Derived, not measured nearby.
Bin definitions¶
A bin definition is a human-readable name for a hard or soft bin number, plus (for hard
bins) whether it counts as pass or fail, and optionally the colour the bin is drawn in on every
map. STDF and ATDF already carry names and pass/fail in their own HBR/SBR
records, so a bin like 2 shows up as "Contact Open" without any extra step. CSV, JSON, and
Parquet have no equivalent record at all — every bin is just a bare number — unless you supply
definitions yourself.
For CSV/JSON/Parquet, name the bins in the column mapping overlay with Name bins…, or load a bin-definitions file with Load bin definitions… (both next to Pass bin(s)), before you continue past mapping.
For any format, once loaded, open the toolbar's Setup ▾ menu and choose Bin definitions…. It lists every hard and soft bin the lot has — those named by the file or a definitions file, and those the dies carry — with the number of dies the map counts (a retested die once, by your Retests choice, and partial or edge-excluded dies not at all), a name, a Pass toggle for hard bins and a colour. Type in a name or a hex colour; tick rows (Shift-click for a range) and use Mark pass or Mark fail to set several hard bins at once. Edited rows are marked, each with a Revert button, and Revert all goes back to how the dialog opened. For STDF/ATDF the table starts from the names and pass flags HBR/SBR supplied; for CSV/JSON/Parquet with nothing loaded it lists the bare bin numbers so each can be named.
Load… reads a bin definitions file into the table as edits — new bins become new rows — so you can check it before Apply writes it to the lot. Save… writes the table as it stands, edited or not.
Names and colours apply as soon as you press Apply. Pass flags are different: they decide which dies count as good, so every yield figure, finding and report follows them. When a pass flag is edited the dialog shows the lot's yield now and with the new pass bins, and Apply asks you to confirm; the log records the change. If the loaded files disagreed about a bin (one counted it pass, another fail), the toggle reads Mixed; choose Pass or Fail for it before a pass change can be applied, and your choice is used for every file. At least one hard bin must count as pass.
Colour choices are remembered. Whatever you pick in a map's Colour scheme menu — the bin colours, the value colours, and whether bin definition colours are used — carries over to the next file you open and to the next time tsmap starts. Bin colours and value colours are separate choices, so switching between a bin map and a value map never changes either. Clear them with Reset settings (they are listed there as Wafer map colours).
The file format is described under Bin definitions file in the reference.
Wafer diameter and edge exclusion¶
tsmap normally works out each wafer's physical size from the die positions in the file, or (for STDF/ATDF) from the file's own WCR record when present. Open the toolbar's Setup ▾ menu and choose Diameter & edge exclusion… when you need to override that — most commonly for data too sparse to infer a diameter from (a handful of dies near the centre, say), or to add an edge-exclusion band: a ring near the wafer edge, measured in from the physical boundary, whose dies are excluded from yield and shown dimmed on the map.
- Wafer diameter (mm) pre-fills from whichever source tsmap trusts most for the currently loaded file: a WCR record's own diameter (real data, shown with no confidence figure), or tsmap's own geometric estimate — but only when that estimate actually resolved physical units; a dimensionless grid-step count is never shown as if it were millimetres. If a value is already pinned and it disagrees with what the current file and estimate now say (for example, a different lot loaded since), a caption flags the mismatch without blocking Apply.
- Edge exclusion width (mm) is only meaningful relative to a confirmed diameter, so the field stays disabled — greyed out, with a "Set a diameter first" hint — until the diameter field holds a valid value. This is enforced live while typing, not just on Apply.
- Apply takes effect immediately, without reloading the file. Clear resets both fields together — leaving a pinned exclusion behind after the diameter reverts to auto-inferred would put the exclusion in an under-defined state, so the two always clear as a pair.
Once set, both values persist across restarts (like the theme picker) and apply to every wafer
in whatever's currently loaded — they're treated as fixed properties of your process, not
something that varies per wafer the way splits do. They stay in effect for
the next file you open too, until changed or cleared. --wafer-diameter/--edge-exclusion set
the same values from the command line at launch — see Command line.
Reticle¶
A reticle (or stepper field) is the group of dies exposed in one lithography step. Nothing in a wafer file says how many dies make a field, so tsmap draws no field grid until you say. Open Setup ▾ → Reticle… and give:
- Field width (dies) and Field height (dies): whole numbers.
- Corner die X / Y (optional): the die at the bottom-left corner of a field, in the die coordinates in the file. Leave both blank when die (0, 0) sits at a field corner. Give both or neither.
Apply takes effect at once, without reloading the file. Clear removes the reticle.
With a reticle set:
- the field grid is drawn on every map (Overlays ▾ → Reticle grid hides it), and a die's tooltip gives its position within its own field;
- the Summary panel looks for reticle-position findings: a yield or value signature that repeats at the same cell of every field, which points at the stepper (alignment drift, a mask defect) rather than at the wafer.
Like the diameter, the reticle is one value for every wafer loaded, is remembered
between sessions, and stays in effect for the next file you open. Preferences → Reset saved settings… lists it. --reticle
sets it for one launch from the command line.
Excluding values outside limits¶
A tester that runs out of range records its rail instead of a reading: a current held at the compliance clamp, a voltage at the
supply, an open-circuit value. These are numbers in the file and not measurements, and one of them stretches the colour scale and
moves the mean, σ and Cpk of the whole wafer. A test can carry validity limits, the range a real measurement lies in. They are a
third kind beside the test limits (which judge good against bad) and the spec limits, and a clamped value is neither good nor bad.
They come from a long-format file's lvl and uvl columns, or the loValid and hiValid columns of a
test-definitions file (map them in the column mapping if your names differ).
Open Setup ▾ → Exclude values outside limits… and choose which limit set a value must lie inside to be used:
- Validity limits (the default): values outside a test's validity limits are used as missing. Tests without validity limits are unaffected, so a file that has none behaves as before.
- Spec limits or Test limits: keep only values inside each test's spec or test limits.
- Off: every value is used as recorded.
Apply takes effect at once, without reloading the file, and Clear returns to the default. The choice is one setting for every wafer loaded and is remembered between sessions; Preferences → Reset saved settings… lists it.
An excluded value is missing for that test on that die: grey on the map, and left out of the statistics and every chart, and a
derived test is computed from what remains. Nothing is hidden: the tooltip on a grey die names the excluded value and the limit set,
and the count appears in the Summary panel's Test Values table (a total beside N and an Excl. column), in the chart captions,
the My plots tab's footnote, the report and the CSV exports. The wafer's bins and the tester's recorded pass/fail are unchanged, so yield
is the same either way. sample_data/VALID-LOT-09.csv has a few clamped readings and its limits; open it and switch between
Validity limits and Off to see what a clamp does to the scale.
Retests¶
A prober can contact a die more than once: a retest pass over the dies that failed, a check of a few that passed, a retry after a contact failure. The file then holds several results for one die position, and one of them has to stand for the die. Open Setup ▾ → Retests… and choose which:
- Last result (the default): the most recent probe, in the order the file records them.
- First result: the earliest probe.
- Best result: a pass over a fail; between two passes or two fails, the lower hard bin.
- Worst result: a fail over a pass; between two passes or two fails, the higher hard bin.
The chosen result is the die everywhere: its bin and test values on the map, the yield, the findings, the Summary panel, Insights, the data tables, the report and the exports. A result the tester marked as replacing an earlier one (the STDF part flags) always counts, whatever the choice. A retested die's tooltip says how many times it was probed, and for a CSV, JSON or Parquet file the log says how many positions were tested more than once. A die with no reported position is matched to its earlier probes by part ID instead, when the file's part IDs tell dies apart (see Dies with no reported position).
Apply takes effect at once, without reloading the file, and Clear returns to the default. The choice is one setting for every
wafer loaded and is remembered between sessions; Preferences → Reset saved settings… lists it. --retests sets it for one launch from
the command line. sample_data/RETEST-LOT-07.csv gives each choice its own yield: across the lot, 86.1% with the
last result, 79.2% with the first, 91.2% with the best and 75.8% with the worst.
Saving the setup¶
The settings above that belong to a product or a site rather than to one lot — the
wafer diameter and edge exclusion, the reticle, the
retest policy and the value filter (Exclude values outside limits…) — can be kept in one file.
Setup ▾ → Setup file… shows what is set now and offers Save… and Load… (with the recently used files
behind the ▾, as for the other definitions files). A load applies at once and is remembered, as if you had
set each value in its own dialog, and the log lists what it set. Help → Definitions file formats… saves a
filled-in example; the format is under Setup file in the reference. The row is in Setup ▾ because
the file holds the values of the rows above it; it needs a file open, so to start from one with nothing loaded use
--setup.
Loading changes only the settings the file states, and a blank value leaves a setting alone; Save writes only what is set (the retest policy and value filter are always written, since one is always in force). To clear a setting, use its own dialog.
--setup <file> applies a setup file for one launch, without remembering it. It is applied before
--wafer-diameter, --edge-exclusion, --reticle and --retests, so tsmap lot.stdf --setup site.csv --retests best
is the site's setup with one change. Like --plots, it needs no data file: sent to a tsmap that is already open, it applies to the lot on screen.
Reference¶
Command line¶
Desktop only. Launch tsmap with files already named, for scripting or automation:
tsmap lot1.stdf lot2.stdf # one or more data files
tsmap --list files.txt # a text file of paths, one per line
cat files.txt | tsmap # or piped via stdin
tsmap lot1.stdf --tests my-tests.csv # pre-fills the test selector (the selector is still shown — see below)
tsmap lot1.stdf --splits my-splits.csv # applies splits automatically, same as sample data
tsmap lot1.stdf --bins my-bins.csv # names, pass flags and colours over what the file supplies
tsmap lot1.stdf --plots my-plots.json # adds plots (sweeps included) to Insights → My plots (see [Sweeps](#sweeps))
tsmap --plots my-plots.json # sent to a running tsmap: adds them and redraws what is open
tsmap lot1.stdf --wafer-diameter 300 # sets the wafer diameter (mm) for this launch
tsmap lot1.stdf --wafer-diameter 300 --edge-exclusion 3 # plus an edge-exclusion band (mm)
tsmap lot1.stdf --reticle 4x3 # sets the stepper field (dies wide x high) for this launch
tsmap lot1.stdf --reticle 4x3@1,0 # the same, with die (1, 0) at a field's bottom-left corner
tsmap lot1.stdf --retests best # which result counts for a die probed more than once, for this launch
tsmap --url https://.../lot.stdf --url-format stdf # fetch and open a URL — see below
tsmap --help # full usage
tsmap --version # print the version and exit
--tests takes the same file a test selector's Save definitions button produces — selection,
renames, and optionally test limits/test type (see
Test definitions file) — and --splits the same CSV the
Splits… dialog saves and loads. --tests only
pre-fills the selector's checkboxes, renames, and limit/type overrides — the overlay still
appears and still needs a confirm click, even for a small lot; it just saves
re-picking (and re-entering limits for) tests you already set up before.
--bins takes the bin definitions file that Setup ▾ → Bin definitions… → Save…
writes, and lays it over the bin names and pass flags the data file supplies, as Load then Apply
would. A file that states pass flags changes the lot's yield, and the log says it was loaded. It belongs to the files in the same command, or to a lot that is already open; with neither it is ignored with a warning, and it is not kept for a later load.
--plots takes the file Export plots… writes, and adds its plots to the ones tsmap has saved. A plot
whose id is already saved is replaced, so launching with the same file again leaves one copy of each; the
log says what was added and replaced, and names anything in the file it cannot use. --sweeps is the same
flag, for a file written as a sweeps file. Unlike --tests and --splits it needs no data file with it —
sent to a tsmap that is already open, it applies to the lot on screen.
--wafer-diameter/--edge-exclusion set the same values as the
Diameter & edge exclusion… dialog — a bare number in mm,
not a file path. They apply to that session only: the geometry saved from the dialog is left as it was.
--edge-exclusion only takes effect once a diameter is known, from
--wafer-diameter in the same launch or one already persisted from a previous session; given
with no diameter available from either source, it's ignored with a logged warning rather than
silently applied against whatever tsmap would otherwise infer.
--reticle sets the same value as the Reticle… dialog: WxH is the field's width and height in dies,
and an optional @X,Y names the die at a field's bottom-left corner when it is not die (0, 0). Both are whole numbers
and the field is at least one die each way; anything else is refused when you launch. Like the geometry flags it
applies to that session only, and the setting saved from the dialog is left as it was.
--retests makes the same choice as the Retests… dialog: last, first, best or worst. Anything else is
refused when you launch. It applies to that session only, and the choice saved from the dialog is left as it was.
If tsmap is already running, launching it again with files hands them to the running window
instead of opening a second blank one: with nothing currently loaded they open right away;
with data already loaded, a dialog asks whether to replace it. Decline and the new files open
in a separate, independent tsmap window instead, so nothing is lost either way. A launch with
no data files — only --plots, --wafer-diameter, --edge-exclusion, --reticle or --retests — replaces nothing,
so it applies to the running window straight away without asking.
Opening data from a URL¶
Another application can hand tsmap a data URL directly, so it can fetch and load the file itself with no one retyping anything. This is aimed at a caller application that already knows where the data lives (its own database or API) — not at typing a URL in by hand. On desktop, the fetched response is briefly materialised as a temporary local file while it's loading (so it can flow through the same load pipeline as any other file); no data is uploaded to a tsmap service either way. In the browser build there's no local filesystem to write to, so the fetched bytes stay in memory.
On desktop, pass it on the command line, paired with the format:
In the browser, encode it in the page URL instead:
The format can also be zip: tsmap unpacks it and loads every file inside, exactly as if they
had been opened together. To send several STDF or ATDF lots from one URL, a zip with one file
per lot is the recommended form: each file keeps its own lot record and bin definitions. A
CSV, JSON or Parquet response can hold several lots in one table; map its lot column (see
the column mapping table below).
Or, to launch the desktop app directly from a web page (rather than requiring a script or
terminal to run the command above), a tsmap:// link works the same way once the desktop app
has been run at least once on that machine (which registers it as the link's handler):
<a href="tsmap://open?url=https%3A%2F%2Fexample.com%2Flot.stdf%3Fsig%3D...&format=stdf">
Open in tsmap
</a>
Enabling the tsmap:// handler. On Linux and Windows, tsmap registers itself as the
handler for the tsmap:// scheme every time it starts — installed via .deb/.msi or run
as a raw dev/portable binary, the effect is the same, and nothing needs to be configured by
hand. On macOS the registration instead comes from the app bundle at install time (via
Info.plist), so it's already in place the first time the app is launched, and there is no
separate runtime registration step. Either way, the practical rule is the same: run tsmap
at least once on a machine before a tsmap:// link there will do anything. The first time
a browser follows one of these links it will normally show its own "Open tsmap?"-style
confirmation prompt — that's the browser guarding external-app links in general, not
anything tsmap controls, and it typically offers a "always allow links from this site" option
so the prompt doesn't reappear.
Try both launch paths — including the tsmap:// one — with real sample data on the
live demo.
All three forms need the format spelled out explicitly (stdf, atdf, csv, json, or
parquet) — tsmap doesn't guess it from the URL. Once fetched, the file goes through the
exact same load as opening it locally: column mapping for CSV/JSON/Parquet, the test
selector for STDF/ATDF.
The dataUrl/dataFormat params are removed from the address bar right after the fetch is
attempted, so reloading the page won't re-trigger it.
Authentication¶
Browser build: no credentials are ever sent. tsmap does a plain, unauthenticated fetch — the URL itself must be self-authenticating (a presigned/signed link with the token already embedded), since there's no secure way for tsmap to accept a separate secret here — the page URL can end up in browser history, bookmarks, or a server's access log.
Desktop: --url-headers <FILE> sends custom headers with the fetch — e.g. an
Authorization bearer token for a data API that expects header-based auth rather than a
presigned link. Point it at a small text file, one header per line:
# auth.txt — blank lines and '#' comments are skipped
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
X-Api-Key: abc123
The header value never appears on the command line itself (so it won't sit in shell
history or a process list the way a --header "Authorization: ..." flag would) — only the
file path does. The file itself is still plain text on disk, exactly like the --tests/
--splits files already are; tsmap doesn't encrypt it or integrate with an OS credential
store. --url-headers requires --url; a malformed line (missing the : separator) or an
unreadable file fails the fetch immediately with a clear error rather than silently sending
an unauthenticated request. There's no browser-build equivalent — see the CORS section below
for why a header-based secret can't safely travel through a page URL.
The tsmap:// link has the same constraint as the browser build, not the same option as
--url-headers. A link URL — page URL or deep link — has nowhere safe to carry a header
value either, so tsmap:// links only work with a self-authenticating URL, the same as
?dataUrl=. --url-headers remains a local file the launching machine already has; there's
no way to pass its content through the link itself.
Browser build: the data server must allow cross-origin requests (CORS)¶
This only applies to the browser build's ?dataUrl= — the desktop app's --url never
hits this, see below.
Every browser enforces a rule called CORS (Cross-Origin Resource Sharing): a web page can only read the response from a different website's server if that server explicitly says it's OK. "Different website" is decided purely by the address — scheme, hostname, and port — never by network location. Being on the same office network, the same VPN, or even the same machine on a different port does not help — tsmap's page and the data server are still two different "origins" as far as the browser is concerned, and the browser blocks the read by default regardless of how reachable the server actually is.
Concretely: if tsmap is served from https://tsmap.example-corp.internal/ and the data URL is
https://wafer-data-api.example-corp.internal/lot123, the browser will refuse to hand the
response back to tsmap's JavaScript unless the data API's response includes a header like:
(or Access-Control-Allow-Origin: * to allow any site — simpler, but only appropriate if the
data behind that URL isn't sensitive to being fetched by any web page that knows the link).
Whether this is easy depends entirely on whether you control the data API:
- If you (or your team) run the API: this is normally a small, one-time config change —
a line of middleware (e.g. Express's
cors()package, Flask-CORS), or a header directive in the reverse proxy / API gateway in front of it (nginx'sadd_header, an API gateway's CORS setting). tsmap only ever sends a plainGETwith no custom headers, which keeps this to the simplest CORS case — no separate preflight request to also allow. - If it's a third-party or legacy API you can't change: the browser build's
dataUrlpath won't work against it, full stop — there is no per-request workaround, and tsmap cannot disable or bypass this check (it's enforced by the browser itself, not by tsmap). Use the desktop app's--url/--url-formatinstead — the fetch happens inside the desktop app's own Rust process, never inside a browser page, so CORS simply doesn't apply there.
If a dataUrl fetch fails, the log panel names CORS explicitly as a likely cause — that's the
first thing to check with whoever owns the data API.
File associations (desktop)¶
Preferences → File associations… lets you open .stdf, .atdf, and .parquet files in tsmap
automatically by double-clicking them in a file manager (Windows Explorer, GNOME Files,
Dolphin, etc.). CSV and JSON aren't offered here on purpose — those extensions are already
claimed by dozens of unrelated apps, and quietly becoming their default handler would be an
unwelcome surprise even behind a checkbox.

Each file type is a separate checkbox, and nothing is associated until you turn one on — this is never set automatically. It's a setting, not an installer choice, so you can change your mind at any time without reinstalling; toggling a checkbox off removes the association immediately.
If a checkbox fails to turn on, the dialog shows the reason inline rather than silently reverting. The most common cause on a managed/corporate machine: registry write access (Windows) or the relevant config directory (Linux) is restricted by IT policy — in that case file-type association simply isn't available on that machine, and there's no in-app workaround. This has nothing to do with tsmap's own permissions; it's the same restriction that would block any application from registering a default handler there.
Each row also shows the executable path currently registered for that file type — or, when unchecked, the path that would be registered if you turned it on. This is what the OS will actually launch on a cold double-click, which can silently drift from the tsmap you're running right now (for example, if the association was last turned on while running a development build) — a mismatch is flagged inline rather than left to be discovered as a launch failure.
Not available in the browser build — there's no equivalent to "the OS's default app for a file type" a web page can register.
Definitions files¶
tsmap has three kinds of definitions file — test definitions (including derived tests), wafer splits and bin definitions — each a small round-trip for one axis of a lot's metadata that either isn't in the raw data at all, or that you want to correct or extend after parsing. All three are CSV. Plots, sweeps included, are kept and shared as JSON (a sweep has series inside it) through the My plots tab rather than a Setup ▾ dialog.
Help → Definitions file formats… is reachable at any time, including with nothing loaded yet — unlike the Setup ▾ dialogs, which need a file open first. Each row (test definitions, splits, bin definitions, plots) has a Save template… button that writes a realistic, filled-in example of that file, using the exact same formatter its real Save button uses — so the column layout is discoverable without reading this guide, and without loading any data first. Useful for preparing a definitions file ahead of time (e.g. from a fab's lot traveler or a product's bin-name table) before you have real tsmap data to save one from.
Recently used definitions files¶
If you follow a product or test-program series, you are likely to reload the same definitions file for every dataset. Load definitions has a ▾ beside it listing the files you have loaded or saved recently, so you can reapply one in a click instead of walking the file picker each time. The same list appears on the Load… button in Setup ▾ → Tests… and Bin definitions…; each type keeps its own list, so a bin-definitions file is never offered where a test-definitions file belongs.
Each entry shows the filename and when you last used it. What a recent entry gives you differs between the desktop and browser builds, and the difference matters — read this once:
- Desktop — the file is re-read from disk, so you always get its current contents. If it has changed since you last used it, that is noted in the log. If it has been moved, renamed, or sits on a share that is no longer mounted, the copy remembered at the time is used instead and the log says so.
- Browser — a browser's file picker hands the page a file's contents, never its location. There is therefore nothing to re-read, and no way for tsmap to tell whether the file on your disk has since changed. A recent entry in the browser is the copy taken when you last loaded or saved that file — the menu says so above the list, and each row dates the copy.
Why that matters for test limits. A test-definitions file can set limits, and limits decide which dies fail their limits, what the capability figures are, and where the limit lines are drawn. Applying a superseded copy produces numbers that look entirely normal and are wrong. So in the browser, when you reapply a remembered file that sets limits, tsmap says so in the log — naming the file, how old the copy is, and that it could not be checked.
If the file may have changed, use "Choose a file…" and re-pick it. That costs one extra dialog and replaces the remembered copy, so the next reload is current again. The desktop build has no such caveat: it always reads the file itself.
A note on names: in the browser, entries are identified by filename alone, since there is no
path to tell two files apart. Two different files both called limits.csv will occupy one entry,
the more recent replacing the other. On the desktop they are distinct entries with their full
paths shown.
The list holds the six most recent files of each type, and lives in your browser or app profile — not in the data, and not on any server.
Test definitions file¶
The saved format is one test per line, with a header naming the columns:
# tsmap test definitions
# Saved: 2026-06-15T10:00:00.000Z
num,name,loLimit,hiLimit,loSpec,hiSpec,loValid,hiValid,units,testType,expression
1000,Idsat_vg1,0.1,1.5,0.05,1.8,0,10,mA,P,
1001,Idsat_vg2,,,,,,,mA,P,
1010,Vt_lin,,,,,,,,,
- Lines starting with
#are comments and are ignored on load. - Legacy format (still fully supported):
<test number> <display name>, with the number and name separated by a comma, semicolon, or plain whitespace, and no further columns. A line with just a number selects that test without overriding its name. Old saved files keep working unchanged. - Extended format: comma-separated, optionally starting with a header row that names each
column. Column names are matched ignoring case, spaces,
_and-, and common synonyms are recognized —typefor test type, and for the limits the names below. A header lets you list columns in any order, and omit ones you don't need — for example a pure limits file with no name column at all,num,lo_limit,hi_limit, is valid. Without a header, comma-separated rows are read positionally asnum,name,loLimit,hiLimit,units,testType. -
Two kinds of limit, never mixed. Test limits are what the tester judged each die pass/fail by (STDF
LO_LIMIT/HI_LIMIT); they drive pass/fail colouring, spec yield and the out-of-limit markers. Spec limits are the process specification (STDFLO_SPEC/HI_SPEC); Process Capability is measured against them when a test has both. Every column name belongs to exactly one kind, and a low limit only ever pairs with a high limit of the same kind:| Kind | Low | High | |---|---|---| | Test limits |
lo_limit,low_limit,lower_limit,lo_lim,low_lim,lower_lim,l_limit,l_lim,min_limit,min_lim,ll,test_lo,test_low, and barelo,low|hi_limit,high_limit,higher_limit,upper_limit,hi_lim,high_lim,upper_lim,h_limit,h_lim,max_limit,max_lim,ul,test_hi,test_high, and barehi,high| | Spec limits |lo_spec,low_spec,lower_spec,lo_spec_limit,low_spec_limit,lower_spec_limit,spec_lo,spec_low,min_spec,lsl|hi_spec,high_spec,higher_spec,upper_spec,hi_spec_limit,high_spec_limit,upper_spec_limit,spec_hi,spec_high,max_spec,usl| | Neither kind (ignored, with a warning) |min,max,lower,upper,limit,limits,spec,lower_bound,upper_bound,lo_bound,hi_bound,low_bound,high_bound,lo_threshold,hi_threshold,low_threshold,high_threshold| |
Names are matched ignoring case, spaces, _ and -, so Lo Limit and LoLimit are
lo_limit. The same names are recognized by the column mapping for long-format data
files, except the bare lo/hi/low/high, which are too loose to claim in a data file.
lsl/usl are spec limits (lower/upper spec limit), and the log says so when a file
uses them. A column that doesn't say which kind is ignored with a warning. Two columns naming the same limit (lsl
and spec_lo) are both ignored with a warning naming them; tsmap never guesses which
one was meant. A row whose low limit is above its high limit has that pair dropped with
a warning; the other pair still applies.
- Validity limits are a third kind, in their own columns: loValid and hiValid (or lvl and uvl). They state the range a real
measurement lies in, and a value outside it is a tester clamp that Exclude values outside limits…
uses as missing. They are never paired with the test or spec limits, and a validity limit given for a functional test is dropped with
a warning like the others.
- testType accepts P/p (parametric) or F/f (functional).
- Limits only make sense for parametric tests — a functional test is pass/fail with no
measured value to check a limit against. A test or spec limit given for a test that is
(or is being reclassified to) functional is dropped with a warning; the rest of that row's
overrides (name, units, type) still apply.
- A units value that differs from the data's only by prefix (the file says mV, the data
is in V) converts that row's test, spec and validity limits to the data's unit, and the log says so. A unit that
differs in any other way is applied as written, with a warning to check the limits: they
are judged against the data as recorded.
- A blank field means "don't override this" — it leaves the parsed value (or an override
already loaded earlier in the session) alone. It never clears an existing value back to
blank/zero. To drop an override, use Edit limits… (Use the data's values, or type the
data's value back); a fresh load of the data also starts without one.
- A field tsmap can't parse (garbage numeric value, invalid test type, an unrecognized header
column) is dropped with a log warning — the rest of that row, and the rest of the file, still
load normally.
- A row whose saved test number isn't in the current scan is not immediately given up on:
tsmap tries to recognise it by name instead, and if exactly one current test has that
name, recovers it under its current number. This is what lets a saved list keep working
across a CSV/JSON reload even though those two formats' test numbers are internal IDs,
not identities from the file — reordering or adding columns can change them. (STDF/ATDF
test numbers are real and don't change, so this mainly matters for CSV/JSON.) The log
panel reports all three outcomes separately: matched by number, recovered by name (worth
re-saving the list so it's back to matching by number directly), and genuinely not found.
A name matching more than one current test is never guessed — it's reported as
unresolved rather than silently picked.
You can hand-edit a list file to rename tests, or add/adjust limits, without changing anything
in the original data file — e.g. 1000,Threshold Voltage,0.2,1.2,mA,P. Those names, limits,
and type appear in the selector, on the map tooltip, in chart axis labels, and — for
limits — as histogram "Lo limit"/"Hi limit" lines and in the Process Capability panel. Spec limits (LSL/USL)
read from STDF or ATDF, or set by a definitions file's spec-limit columns, are used by the
Process Capability panel when a test has both.
Splits file¶
The format is a small CSV:
# tsmap wafer splits
# Saved: 2026-07-08T10:40:00.000Z
lot,waferId,occurrence,split
LOT-A,W01,,TT
LOT-A,W02,,FF
LOT-B,W01,,TT
- Lines starting with
#are comments and are ignored on load. lotandoccurrencemay be left blank, and the columns may come in any order. A file with justwaferId,split(header optional) also loads.occurrenceis only needed when one file holds the same wafer twice (a retest pass):1for the first appearance,2for the second. Save splits… fills it in only then.- A row with no lot, whose wafer ID is shared by several loaded wafers (W01 from two lots,
say), is skipped, not applied to all of them — the log says how many, and adding a
lotcolumn fixes it. - A wafer listed with an empty split value is treated as explicitly unassigned.
Setup file¶
A setting,value CSV, header row optional, # comments:
# tsmap setup
setting,value
waferDiameter,300
edgeExclusion,3
reticle,"4x3@1,0"
retests,best
valueFilter,validity
waferDiameterandedgeExclusionare millimetres (300or300 mm); the diameter must be above 0, and an edge exclusion with no diameter to apply it to is ignored with a warning.reticleis the field in dies,WxH, orWxH@X,Yto name the die at its corner when that is not die (0, 0) — the form--reticletakes. The comma needs quotes in a spreadsheet; tsmap reads it either way.retestsislast,first,bestorworst.valueFilterisvalidity,spec,testornone(offalso reads asnone).- Names are case-insensitive and accept the obvious synonyms (
diameter,exclusion,stepper,retestPolicy,excludeValues). A setting that is missing or blank is left as it is; a value that does not fit, or a name tsmap does not know, is dropped with a warning and the rest of the file still applies.
Bin definitions file¶
The file is a CSV with a required header row:
# tsmap bin definitions
# Saved: 2026-08-20T09:15:00.000Z
bin,type,name,pass,color
1,hard,Pass,P,#2ca02c
2,hard,Contact Open,F,#d62728
10,soft,Leakage Fail,F,
- Header required —
bin,type,name,pass,color, any order, any subset, case-insensitive, with recognised synonyms (hbin/hardbin,sbin/softbin,binnum,pf,colour, etc.). A header cell ofhbinorsbinalso implies that row'stype, so a file naming only hard bins doesn't need a separatetypecolumn at all. typeishardorsoft(h/salso accepted). Hard bin 1 and soft bin 1 are independent entries — the same rule STDF/ATDF's own HBR/SBR follow. Defaults tohardwhen omitted, since most single-axis CSV/JSON/Parquet loads only map one bin column.passacceptsP/F,Y/N,true/false, or1/0. Only meaningful for hard bins in practice — a soft-bin row'spassvalue is still read, but tsmap's own pass/fail logic looks at hard bin membership.coloris optional: a hex colour such as#1f77b4(or the short form#17b). A bin with a colour is drawn in it on every map, in place of the colour scheme's choice — the way to match a site's standard bin colour sheet. Bins without one still take colours from the scheme. To see the scheme's own colours instead, open the map's Colour scheme menu and untick Use colours from bin definitions.- A blank
name,passorcolorcell leaves whatever's already set for that bin alone; it never clears an existing value (clear one in the Bin definitions… table instead). Only a row whose bin number is unreadable is dropped entirely — a badtype,passorcolorcell drops just that field, with a warning, and the rest of the row still applies.
Sweeps in a plots file¶
A plots file is JSON, the file Export plots… writes. A sweep is a plot with "chart": "sweep", its
definition under sweep:
{
"format": "wafermap-plots",
"version": 1,
"plots": [
{
"id": "up-down",
"title": "Up / down switching",
"chart": "sweep",
"sweep": {
"xLabel": "Voltage (V)",
"yLabel": "Read current",
"separationAt": [10, 50],
"series": [
{"label": "Down", "tests": ["5000..5020"], "xValues": [-2.0, -1.8, …, 2.0]},
{"label": "Up", "tests": ["5100..5120"], "xValues": [-2.0, -1.8, …, 2.0]}
]
}
}
]
}
A file written as a sweeps file ("format": "tsmap-sweeps", a sweeps list with id and title beside
each sweep's settings) is read too, so earlier files load unchanged. The fields below are a sweep's
settings, as the sweep editor sets them; id and title belong to the plot.
| Field | |
|---|---|
series |
Two or more curves. Crossing and widths are measured between the first two |
series[].tests |
The tests in sweep order: numbers, or ranges like "5000..5020" — the same range syntax as a derived test, matching every test in that range that exists |
series[].xValues |
The real swept value for each test (volts, °C, cycles). One per test after ranges are expanded. Without it the x axis is simply test order |
series[].xFromName |
Read each test's swept value from its name instead, for programs that only write it into the test text — see below |
series[].testNames |
The name each test had when the sweep was set up, by test number ({"5000": "Iread 0.2V"}). The sweep editor fills it in. A test whose name now differs is not drawn and the card says why — see above |
separationAt |
Levels, in the tests' own units, at which to report the width between the first two curves |
crossing |
false to stop reporting a crossing, for curves that are not meant to meet |
xLabel / yLabel |
Axis titles |
xUnit |
The unit of the swept values, such as "Ω" or "V". The axis and the measurements then read 47.3 kΩ rather than 47300 |
xScale |
"log" for steps that grow by multiples (1K, 2K, 5K … 1M) — see below |
Swept values in the test names. Some programs record the swept value only in the test
text — Cum_share= LEVEL_12K / Total_n= …. xFromName reads it from there:
{
"id": "level-cdf", "title": "Level CDF — before vs after", "chart": "sweep",
"sweep": {
"xLabel": "Level threshold", "xUnit": "Ω", "xScale": "log", "yLabel": "Normalised share", "separationAt": [0.5],
"series": [
{"label": "Before", "tests": ["41200..41230"], "xFromName": "LEVEL_{x}"},
{"label": "After 0x5", "tests": ["41300..41330"], "xFromName": "LEVEL_{x}"}
]
}
}
{x}marks where the number is,*stands for any text and?for exactly one character; everything else must appear in the name as written, though capitals do not matter. The pattern is found anywhere in the name, so it needs only enough text around{x}to be unambiguous —LEVEL_{x}, not the whole name.{x}reads a unit prefix with the number:12Kis 12,000 and1Mis 1,000,000. Here capitals do matter —mis milli andMis mega. A letter only counts as a prefix when it stands on its own or comes before a unit (12K,12kΩ,5us), so12Kangaroosreads as 12. A name written all in capitals has lost that difference, so there a prefix before a unit is read either way (5NSis 5 ns) — exceptM, which could be milli or mega: a name likeV_2MVis reported rather than guessed. Put the letter into the pattern instead (V_{x}MV) and the unit into the axis title. To keep the numbers as the names write them (12 rather than 12,000), put the letter in the pattern —LEVEL_{x}K— and the unit in the axis title,"xLabel": "Level threshold (kΩ)".- It is not a regular expression, deliberately: a plots file is shared, and a regular expression can be written so that it freezes the app. This pattern cannot.
- A test whose name the pattern does not fit is named on the card, and the crossing and
widths are not measured. Unlike
xValues, each value stays with its own test, so a missing test costs one point rather than shifting every later one.
A log axis ("xScale": "log") spreads steps that grow by multiples evenly across the
chart; on a linear axis everything below 100K would crowd into the left edge. The crossing
is then found along the log axis, and a width is reported as a ratio between the two curves
— ×2.49 (15.9 kΩ → 39.7 kΩ) — because on a log axis the same shift is the same multiple
anywhere along it.
If a test in a range is missing — not in this lot, or not imported — the range comes up
one short of xValues. The card then lists the tests it did find and does not measure the
crossing or widths, rather than pairing the remaining values with the wrong tests.
A mistake in the file is reported in the log with where it is: a JSON syntax error by line
and column, a bad plot or series by its id and label. The good plots in the same file still
load. A sweep can read derived tests — sweeping log10(t[n]) per step is how to draw a curve
whose values span decades; "xScale": "log" is for swept values that do. The sweep sample's
endurance sweep does the latter, reading each cycle count (1e3) from its test name.
Column mapping reference¶
Wafer geometry in your columns. A column whose header is Wafer diameter, Die width, Die height, Wafer flat, Centre die X, Centre die Y, X increases or Y increases (the labels a wafer map's die-list export writes, or the STDF names WAFR_SIZ, DIE_WID, DIE_HT, WF_FLAT, CENTER_X, CENTER_Y, POS_X, POS_Y) is used to draw the map, the way an STDF file's own wafer configuration is, as well as shown as display info. A size must carry its unit (300 mm, 12 in): a bare number is left alone and the log says so. A side or direction is a word (Bottom, Right) or its letter. If wafers state different values, the first wafer's is used for all of them and the log says so. A diameter you set in Setup ▾ → Diameter & edge exclusion still takes precedence.
The roles a column can take in the column mapping overlay, and the header names that are filled in automatically.
Role reference¶
| Role | What it means |
|---|---|
| X position | Die column coordinate (prober step, integer). Optional — see Dies with no reported position below. |
| Y position | Die row coordinate (prober step, integer). Optional — see Dies with no reported position below. |
| Hard bin | Hard bin number per die |
| Soft bin | Soft bin number per die |
| Wafer ID | Identifies which wafer each row belongs to; splits rows into separate wafer maps |
| Lot ID | Lot identifier. Map it whenever the file can hold more than one lot: each wafer is identified by lot and wafer ID, so two lots' W01 stay two separate wafers. |
| Test site | Parallel-test site number for each die (the STDF site_num equivalent). Dies from all sites share one wafer map; the site appears in the die hover tooltip and can be used as a chart grouping/colour dimension. Numeric values only. |
| Test value | Numeric test result (wide format — one column per test); the Test name field to the right sets the display name for that test. If the column's own header is itself a bare number (e.g. 1001), that real number is used as the test's identity instead of a generated one. The same goes for a number written into the name, such as Test 2001 or test_07 (mV), when every test column has one and no two are the same; bracketed text such as units is ignored, and a number that is part of a quantity (Gain 3dB, Vdd 1.8) does not count |
| Pass/fail test | A column of verdicts rather than measurements (wide format, like Test value): PASS/FAIL or P/F in any case. The test name and number follow the same rules as a test value. Each die records a pass or a fail for the test (a blank cell records nothing), the test is a functional test, and it appears under Verdicts and Functional rather than with the measured values. A column holding only PASS/FAIL (or P/F) in its first rows is chosen automatically. Parquet and JSON booleans and 1/0 are read as verdicts when you choose this role by hand |
| Test name (long format) | Column containing the test name in a long/pivot layout. Optional if Test number is set instead — a file with only real test numbers and no descriptive names is fully supported; the number is used as the display name in that case |
| Test number (long format) | Column containing the test's real number in a long/pivot layout. Optional alongside Test name — set alone (no name column at all) or together (real number, given name). At least one of Test name/Test number is required, along with Test result |
| Test result (long format) | Column containing the numeric result in a long/pivot layout |
| Low test limit (long format) | Low test limit in a long-format file — the limit the tester judged pass/fail by |
| High test limit (long format) | High test limit in a long-format file |
| Low spec limit / LSL (long format) | Low spec limit in a long-format file — the process specification that Process Capability is measured against. Kept separate from the test limits: a low limit only ever pairs with a high limit of the same kind |
| High spec limit / USL (long format) | High spec limit in a long-format file |
| Low validity limit / LVL (long format) | Low validity limit in a long-format file — the lowest value that is a real measurement. A value below it is a tester clamp, and Setup ▾ → Exclude values outside limits… treats it as missing. A third kind, never paired with the test or spec limits |
| High validity limit / UVL (long format) | High validity limit in a long-format file |
| Units (long format) | Units string in a long-format file |
| Display info | An additional wafer attribute, captured for the strip above the map and for Group by and Compare by (and shown in tooltips). Values are recorded per wafer, so a file mixing temperatures or test programs labels each wafer with its own. If the same wafer appears more than once — tested at two temperatures, say — and one of these columns tells the passes apart, each pass becomes its own wafer map, and the log says which column did it; with nothing to tell them apart the repeats are treated as retests. A column whose value changes within a wafer (a per-die timestamp) is not shown as a wafer property, and the log says so. The Subdivide file by this column checkbox is a structural escape hatch for flat files that pack several wafers into one file with no wafer column — it subdivides the file into one wafer map per distinct value of the column. (Do not use it for parallel-test sites — map those to Test site instead.) |
| — ignore — | Column is not imported |
Auto-detected column names¶
The overlay pre-fills a role for any column whose header exactly matches one of these (case-insensitive):
| Role | Recognised column names |
|---|---|
| X position | x, die_x, x_loc, xloc, col, column, step_x, stepx, diex, xstep, x_step, xcoord, x_coord, xpos, x_pos, chip_x, chipx, position_x, positionx |
| Y position | y, die_y, y_loc, yloc, row, step_y, stepy, diey, ystep, y_step, ycoord, y_coord, ypos, y_pos, chip_y, chipy, position_y, positiony |
| Hard bin | hbin, hard_bin, h_bin, hardbin, hb, hbn, bin, hard_bin_num, hbin_num |
| Soft bin | sbin, soft_bin, s_bin, softbin, sb, sbn, soft_bin_num, sbin_num |
| Wafer ID | wafer, wafer_id, waferid, wafer_num, wafernum, wid, wafer_no, waferno, wfr, wfr_id, wnum |
| Lot ID | lot, lot_id, lotid, lot_num, lotnum, lot_no, lotno |
| Test site | site, site_num, sitenum, site_no, siteno, site_id, siteid |
| Test name (long format) | test_name, testname, param, parameter, param_name, measurement, test_item |
| Test number (long format) | test_num, testnum, tnum, test_number, testnumber, testno, test_no, t_num |
| Test result (long format) | result, value, val, measured, meas, reading, test_value, test_result, meas_value, meas_val |
| Low test limit (long format) | lo_limit, low_limit, lower_limit, lo_lim, low_lim, lower_lim, l_limit, l_lim, min_limit, min_lim, ll, test_lo, test_low |
| High test limit (long format) | hi_limit, high_limit, higher_limit, upper_limit, hi_lim, high_lim, upper_lim, h_limit, h_lim, max_limit, max_lim, ul, test_hi, test_high |
| Low spec limit / LSL (long format) | lo_spec, low_spec, lower_spec, lo_spec_limit, low_spec_limit, lower_spec_limit, spec_lo, spec_low, min_spec, lsl |
| High spec limit / USL (long format) | hi_spec, high_spec, higher_spec, upper_spec, hi_spec_limit, high_spec_limit, upper_spec_limit, spec_hi, spec_high, max_spec, usl |
| Low validity limit / LVL (long format) | lvl, lo_valid, low_valid, lower_valid, valid_lo, valid_low, min_valid |
| High validity limit / UVL (long format) | uvl, hi_valid, high_valid, upper_valid, valid_hi, valid_high, max_valid |
| Units (long format) | units, unit, uom, test_units, test_unit |
| Display info | testdate, test_date, date, temp, temperature, tst_temp, operator, oper, testprogram, test_program, job_nam, node, node_nam, tester, tstr_typ, part_typ, part_type, device, handler, hand_typ, sublot, sblot_id, exec_typ, exec_ver, serl_num, serial |
A header that misses this list is still often caught: a regex fallback recognises further
shapes of the same names (different separators/casing — Test Number, t-num), and
anything left over falls back to a fuzzy match — a numeric column becomes Test value
unless a fragment of its name (id, index, count, date, a unit like mm, …) marks it
as something other than a test. Run conditions and provenance fields are caught the same way
even when their values are numbers: a test or chuck temperature (Test Temp, Temp (C)), operator,
program, recipe, burn-in or stress time, slot, station, revision and similar come in as Display
info, not as tests. A bare Temp Sensor 1 or Frequency stays a test, since it can be a
measurement. Test value itself has no fixed name list for this reason:
it's the fallback role for a numeric column nothing else claimed, not a pattern match.
A column whose filled cells are all PASS/FAIL (or P/F) is a Pass/fail test. A column of true/false or 1/0 is left for you to
choose, since a flag or a count is as likely as a verdict.
A role that takes one column (X, Y, bins, wafer, lot, site and the long-format columns) is given to
the best match only. When two columns look like the same role, the stronger match keeps it, the
earlier column wins a tie, and the other becomes Display info with a note saying why.
Columns skipped by default. Some columns are worked out again on every load, so reading them
would only put a second copy beside the live one: Ring, Quadrant and Edge excluded, and a
derived test's column (its header ends in [derived from …]). These are what a wafer map's die-list
export writes, and they are matched by their words, so edge_excluded and EdgeExcluded count too.
They start as — ignore —, and the row says so. To keep one anyway, choose another role (for
example Display info, with Subdivide file by this column if it splits the file). The wafer
geometry columns an export also writes (Centre die X/Y, Die height, Wafer diameter and the
like) are not skipped: they come in as Display info.
A bare bin is the weakest claim on Hard bin: when a file also has Hard bin, hbin or
h-bin, that column is the hard bin and bin becomes Display info. Soft bins work the same way.
What tsmap remembers, and how to forget it¶
tsmap saves a few things between sessions so you don't have to set them up again: your colour
theme, your wafer-map preferences (colour schemes, overlays, legend position, number format and the
Insights charts' axis settings, but not a rotation, the compact layout or log scale, which belong to the
data on screen), recently opened files, recently used definitions files, column mappings (per column
layout), the tests you selected (per set of tests), the tests you pinned in the charts' test pickers, wafer split assignments (per lot), your saved plots, filter and saved filters, any wafer diameter and edge-exclusion override, the reticle, the limit set values must lie inside, and
the last file filter. The desktop app also remembers its window's size, position and whether it was
maximised, and opens the next launch the same way; a first launch opens centred at 80% of the screen.
This one is kept in the app's configuration folder rather than in the list below, so Reset saved
settings… does not include it — delete .window-state.json there to go back to the default placement.
All of it lives on this machine only — in your browser's storage, or the desktop app's own copy of it. Nothing is uploaded, and none of it travels with a file you share unless you export it yourself (below). Wafer data itself is never stored: it is re-read from your files each time.
Preferences → Reset saved settings… shows what is currently stored — only what actually exists, not everything tsmap could remember — with a line explaining each one. Tick what you want gone and choose Forget selected. Loaded wafer data is unaffected, and anything already on screen stays until the next load.
Useful when a saved column mapping is wrong and keeps being reapplied, when handing a shared machine to someone else, or when you simply want to see what the app is holding.
The gear beside Help is the Preferences menu, and it works with nothing loaded: Export or import settings…, Reset saved settings… and, on the desktop, File associations… are all in it. Its last row, under Troubleshooting, is Record load timings. It is for bug reports only: it adds a timing line to the Log panel at each stage of a load, so you can copy the lines into a report about a slow load. Leave it off otherwise.
On the desktop, the Updates rows control the new-version notice. Check for updates at startup is on by default: a few seconds after tsmap starts it asks GitHub which release is newest and, if yours is older, shows a notice with View release, Not now and Skip this version. It only opens the release page; nothing is downloaded or installed for you, and nothing about your data is sent. Turn it off on a machine that should not contact the internet. Check for updates now asks on demand whatever the setting, and says so when you are already up to date.
Taking settings to another machine¶
Preferences → Export or import settings… saves what tsmap remembers as files and reads them back, in two parts.
- Column mappings. Export… writes every saved layout, with its column headers, to a JSON file; Import… reads one. An import adds the file's layouts and replaces any you already have for the same headers; nothing else changes and nothing needs reloading. A layout whose roles point at a column its own headers do not have is left out with a message. Handy for a site where everyone loads the same exports.
- Other saved settings. Export… writes the theme, wafer-map preferences, pinned tests, saved plots, Insights filter and saved filters, and remembered test selections. The setup values (diameter, edge exclusion, reticle, retest policy, value filter) are not in it: they travel in the setup file. Import… reads a file and lists what it holds, each with a line saying what it replaces; tick what you want and choose Import. tsmap then reloads, which closes the lot that is open — saved plots, pins and preferences are held in memory while you work, and would otherwise write their old copy over the import.
Neither file carries recent files or recently used definitions (they hold paths on your machine and the contents of files), wafer split assignments (they belong to a lot; Setup ▾ → Splits… → Save keeps them), the last folder scan, or the timing-log switch. A file from a newer tsmap, a file that is not a settings file, and any value tsmap cannot read are refused or left out with a message; nothing is written until you confirm. The file is plain JSON: read it before you send it, as a mapping lists your column headers.
Desktop vs browser differences¶
| Feature | Desktop | Browser |
|---|---|---|
| File parsing | Native Rust (fast, off UI thread) | WASM in a Web Worker (same logic) |
| File picker | Native OS dialog | Browser dialog |
| Drag and drop | Yes | Yes |
| Folder scan and file filter | Yes (native folder picker or a dropped folder) | Yes (folder picker; scans at most 4 files at a time to bound memory) |
| PNG save | Native save dialog | Browser download folder |
| Zip extraction | Native Rust | In-browser (fflate) |
| Interface zoom (Ctrl +/−/0) | Yes | Yes (the browser's own page zoom) |
| Offline use | Yes | Yes — cached on first visit, then opens with no network |
| Install as an app | Yes (installer) | Yes in Chrome, Chromium and Edge; Firefox desktop cannot install web apps |
| Opening data from a URL | --url/--url-format CLI flags |
?dataUrl=&dataFormat= query params (subject to the target server's CORS policy) |
The browser version is functionally identical to the desktop app. Files are parsed entirely in your browser — nothing is sent to a server.
Browser requirements: Chrome 80+, Firefox 113+, Safari 16.4+, Edge 80+.