Skip to content

Add calibrated k-factor quantification of real AlSi10Mg 5 kV EDS spectrum - #11

Open
sgbaird wants to merge 6 commits into
mainfrom
claude/issue-1-20260820-1711
Open

Add calibrated k-factor quantification of real AlSi10Mg 5 kV EDS spectrum#11
sgbaird wants to merge 6 commits into
mainfrom
claude/issue-1-20260820-1711

Conversation

@sgbaird

@sgbaird sgbaird commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Adds scripts/eds_kfactor_quant.py — a tested eXSpy workflow on the real 5 kV Apreo sum spectrum from issue #1: variance-floored Poisson-weighted model fit, bounded peak areas, Currie 3-sigma detection check, and k-factor calibration against a known composition. Includes the spectrum CSV and fit figure.

Addresses issue #1.

Generated with Claude Code

…trum

Co-authored-by: Ronnie Guymon <244881888+ronnie-guymon@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@sgbaird

sgbaird commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

@claude noting that edison should definitely be available now, in case there were things that needed to be run before (#1 (comment))

@claude

claude Bot commented Aug 20, 2026

Copy link
Copy Markdown

Claude finished @sgbaird's task in 27m 12s —— View job


Edison ran successfully — the deferred issue #1 literature task is done and committed

  • Verify EDISON_PLATFORM_API_KEY is populated (present, non-empty) — no pending/uncommitted task IDs found in the repo
  • Install edison_client (0.16.1) and submit the LITERATURE_HIGH task — task id 2a23935c-f686-488e-a0a6-0faada1cb90f
  • Wait for completion (single foreground blocking call) — status success after 20 min
  • Fetch and commit all task artifacts to this PR branch (commit 730b394, rebased over your 722512d header edit with no conflicts)
  • Summarize Edison's answer below

The one Edison item still outstanding from before the key went live was the original issue #1 question (July 16): how are EDS parameters decided — not "consult the standards," but how were the standards' values chosen? That session got a 422 from an empty API key and promised a re-run. This is that re-run, enriched with this PR's live case (Mg Kα at 0.5–1 wt% in AlSi10Mg, 5 kV, SDD).

Committed artifacts

Where each "standard" number actually comes from (Edison's synthesis, condensed)

  1. Overvoltage U₀ ≈ 2–3 — a genuine but broad physics optimum. The Bethe (1930) ionization cross-section peaks at overvoltage of a few, confirmed experimentally by Green & Cosslett's thick-target yield measurements (1961/1968). The counter-pressure is interaction volume growing as E₀^1.67 (Kanaya–Okayama 1972) while Castaing-style φ(ρz) tracer experiments show production moving deeper, raising absorption. The 2–3 band maximizes escaped signal per interaction volume; ISO 22309 codified a conservative floor of U₀ ≥ 1.8.
  2. Dead time 20–40% — the most "conventional" of the set. Paralyzable throughput theory (OCR = ICR·e^(−ICR·τ)) has its mathematical maximum at 63.2% dead time; the 20–40% band was the Si(Li)-era compromise below it, limited by pile-up and correction accuracy — not a published optimum. For modern SDDs Goldstein et al. push quantitative trace work to below 10% because sum peaks (including Si+Mg) grow fast above that, and Donovan et al. 2023 show the linear dead-time correction itself breaks above about 50 kcps. Directly relevant to your trace-Mg region.
  3. Process/shaping time — a genuine electronics optimum: the equivalent-noise-charge formula (Lechner 2004) has series noise ∝ 1/τ and parallel noise ∝ τ, giving a broad ENC minimum near 0.5–3 µs for SDDs. Exact vendor settings are partly convention; what is not optional is using the same process time for standards and unknowns.
  4. The 10⁴-count rule — pure Poisson identity (RSD = 1/√N), not convention. Empirical follow-up (Guyett et al. 2024) recommends on the order of 10⁶ total spectrum counts for quantitative major-element work — about 15 s on modern multi-detector SDDs.
  5. The "3σ" detection criterion — Currie's 1968 hypothesis-testing derivation: decision threshold L_C = 1.645σ and detection limit L_D = 3.29σ at α = β = 0.05. "3σ" is shorthand that conflates the two (and rounds 3.29 down). The Currie check in scripts/eds_kfactor_quant.py is the concentration form of exactly this, via Ziebold's 1967 sensitivity expression.
  6. Committee vs. physics — the notable negative result: Edison found no published round-robin that validated the specific recommended ranges. ISO TC 202 / ASTM E04 codified the physics derivations above plus practitioner consensus. The accuracy validation that does exist is protocol-level: Newbury & Ritchie (2015) showed the standards-based k-ratio SDD-EDS protocol matches the 1975 WDS accuracy distribution (about 95% of analyses within ±5% relative).

For your exact case (Mg Kα 1.254 keV at 0.5–1 wt%, 5 kV): overvoltage is ample (U₀ ≈ 4), so excitation is not the limit — background modeling, the Al Kα neighbor, large low-energy absorption corrections, and surface carbon are. The literature-backed prescription: several 10⁵ to 10⁶ total counts (the naive 10⁴ rule is not enough at trace level), conservative dead time, a moderate fixed process time, replicate acquisitions — and standards-based quantification, since early standardless testing put 95% of results only within ±25% relative, unusable at 0.5 wt%. That independently reinforces this PR's k-factor-calibration approach and the DTSA-II route from the earlier discussion.

Key commands and status

pip install edison_client                          # 0.16.1
client.create_task(TaskRequest(name=JobNames.LITERATURE_HIGH, query=...))
→ task 2a23935c-f686-488e-a0a6-0faada1cb90f
poll loop (single foreground call, 240 s interval) → "success" at 20m05s
fetch verbose response → answer.md / references.md / artifact-00 / task_response_verbose.json
git commit + rebase onto 722512d + push → 730b394  (your header edit is intact)

• branch claude/issue-1-20260820-1711

@sgbaird

sgbaird commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator Author

@claude also spot check that part about bulk corrections with eXSpy (see https://hyperspy.org/exspy/search.html?q=eds+bulk# for example)

@claude

claude Bot commented Aug 20, 2026

Copy link
Copy Markdown

Claude finished @sgbaird's task in 7m 34s —— View job


Spot check result: the bulk-corrections statements hold up against eXSpy 0.3.2

  • Locate the exact "bulk corrections" statement (PR files + issue EDS parameters #1 write-up)
  • Check eXSpy docs and installed source for bulk/matrix-correction (ZAF / φ(ρz)) support
  • Report spot-check verdict — one imprecision found and fixed, see below

I took "that part about bulk corrections" to be the claims in the issue #1 write-up that this PR's script is built on. I verified each against the eXSpy EDS user guide and, more strictly, against the installed eXSpy 0.3.2 source — a grep of the source is a superset of the docs search you linked, since the docs are generated from the docstrings.

Claim Verdict Evidence
eXSpy has no ZAF or φ(ρz) — no bulk matrix correction Confirmed Grep over the entire installed exspy 0.3.2 package: zero matches for ZAF or phi-rho-z
quantification() is TEM-only: Cliff–Lorimer, zeta, cross-section Confirmed EDSTEMSpectrum.quantification docstring: method: {'CL', 'zeta', 'cross_section'}
EDSSEMSpectrum has no quantification method Confirmed hasattr(EDSSEMSpectrum, "quantification") == False, re-verified on 0.3.2
eXSpy's absorption correction is not a bulk correction Confirmed User guide, "Absorption Correction": "At this stage, the absorption correction is only applicable for parallel-sided, thin-film samples."

On the search page you linked: it's client-side JS, so I checked what actually contains "bulk". In the EDS portion of eXSpy it appears only in the interaction-volume utilities — electron_range ("maximum electron range for a pure bulk material", Kanaya–Okayama) and its companion xray_range (Anderson–Hasler), documented under exspy.utils.eds — plus the test class Test_tools_bulk, which tests exactly those two functions. So eXSpy's only "bulk" support for SEM is estimating the interaction volume, not correcting intensities for it. That matches the write-up's division of labor: eXSpy for fitting and net intensities, DTSA-II/CalcZAF (or vendor eZAF) for the bulk matrix physics. The k-ratio/CalcZAF statements are about CalcZAF rather than eXSpy, so they were out of scope here; they agree with the standard references (Goldstein et al.).

One imprecision found (now fixed in the script header)

The write-up said the calibrated k-factors are "valid for any other spectrum taken at the same kV/detector/geometry and fitted with the same recipe." For a bulk sample that list is missing a fourth condition: composition near the calibrant. True Cliff–Lorimer k-factors are composition-independent only in the thin-film limit; effective k-factors calibrated on bulk AlSi10Mg embed that specific matrix's ZAF effects, which change with composition. That is fine for comparing other AlSi10Mg builds (the intended use) and increasingly wrong as composition departs from the calibrant. The header of scripts/eds_kfactor_quant.py now states this — commit 722512d, comment-only change, py_compile clean.

Key commands and status

pip install exspy                                    # 0.3.2 installed
hasattr(EDSSEMSpectrum, "quantification")            # False; TEM class: True, methods {'CL','zeta','cross_section'}
grep -riE "zaf|phi.?rho|bulk" site-packages/exspy/   # no ZAF/φ(ρz) anywhere; "bulk" only in electron_range + tests
python3 -m py_compile scripts/eds_kfactor_quant.py   # OK
git pull --rebase && git push                        # 722512d

Note: the Edison-availability comment is being handled by its own job run; it pushed 7073004 (an Edison task-id commit) to this branch while this check ran, and I rebased over it with no conflicts.

--- • branch claude/issue-1-20260820-1711

github-actions Bot and others added 2 commits August 20, 2026 19:40
Co-authored-by: Sterling G. Baird <45469701+sgbaird@users.noreply.github.com>
Co-authored-by: Sterling G. Baird <45469701+sgbaird@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@ronnie-guymon

Copy link
Copy Markdown
Collaborator

@claude from what you were saying here #1 (comment) "Where eXSpy still earns its keep in your workflow: it's the scriptable peak/background fitting and net-intensity extraction front end (and batch/map processing) — the numbers you then hand to DTSA-II or CalcZAF, which do the physics. If you'd rather do everything in one tool, DTSA-II alone covers both steps (it reads your .msa natively), and dropping eXSpy is a perfectly reasonable decision.", it made it seem oike DTSA-II can also do the scriptable peak/background fitting since you said DTSA-II covers both steps. So why not just used DTSA-II?

Also in resopnse to #1 (comment) I have java 25, and it still doesn't work. I get the message The system cannot find the path specified. When I run just "C:\Users\rongu\Downloads\dtsa\DTSA-II.exe", it does open the screen for a split second before closing still

@claude

claude Bot commented Aug 20, 2026

Copy link
Copy Markdown

Claude finished @ronnie-guymon's task in 16m 7s —— View job


DTSA-II alone is a legitimate choice for your goal — and your launch failure is two commands away from a diagnosis

  • Read the referenced issue EDS parameters #1 comments for prior context
  • Answer: if DTSA-II "covers both steps," why use eXSpy at all?
  • Inspect the DTSA-II launcher internals (NIST pages + the Polaris tag of usnistgov/DTSA-II) to decode the two symptoms
  • Provide concrete next-step commands for the launch failure
  • Check the deferred Edison literature task — it finished under its own job; see the note at the end

1. "Why not just use DTSA-II?" — you can, and for defensible wt% it's the better tool

That sentence meant exactly what you read: DTSA-II covers both steps, and dropping eXSpy is reasonable. The distinction I glossed over is what "scriptable" looks like in each:

  • DTSA-II's scripting is an embedded Jython 2.7 interpreter. The Polaris installer bundles jython-standalone-2.7.4.jar, ships an official Basic scripting in DTSA-II guide in its doc\ folder, and the app accepts a --script myscript.py argument at launch. So batch loops and scripted quant are well supported — but the scripts are Python-2 syntax running inside the desktop app, driving DTSA-II's Java objects. No numpy/scipy/matplotlib/pandas (C extensions can't load in Jython), no pip ecosystem, and no headless mode — whereas the numbers in this PR were produced by scripts/eds_kfactor_quant.py running on a Linux CI machine with no GUI, which is the mode this repo lives in.
  • The fits are different animals. DTSA-II's fitting is a filter-fit against standard spectra — it wants a measured (or DTSA-simulated) standard per element, because its whole design is standards-based quant with real φ(ρz) matrix physics afterward. eXSpy fits a standalone parametric model (Gaussians + background) needing no standards, which matched this PR's constraint: no standards library, just one known-composition alloy as calibrant.
  • Decision rule: interactive quant of a handful of point spectra, and you can supply standards → DTSA-II alone, one tool, best free physics. Version-controlled Python pipeline (CI, maps, batch reprocessing) → eXSpy front end, with DTSA-II/CalcZAF doing the matrix correction. Both are defensible; it's a workflow choice, not a correctness one. Bonus once DTSA-II runs for you: your AlSi10Mg spectrum itself can serve as a multi-element "type standard" in it — the formalized version of this PR's k-factor calibration, with real matrix correction on top.

2. Decoding the two symptoms (from the actual Polaris build config, not guesswork)

I pulled the launcher and build configuration from the Polaris tag of usnistgov/DTSA-II. Three verified facts explain what you're seeing:

  • DTSA-II.exe is a thin launcher (Launch4j config): it runs dtsa2-15.1.48.jar sitting next to it, preferring the bundled java-runtime\ subfolder (a trimmed JDK 24), and only falling back to a system Java meeting its minimum (24) if that folder is missing. It cannot work outside an intact install folder.
  • The "screen" that flashes is the splash image the Java VM itself draws — it's declared in the jar's manifest and appears before any DTSA-II code runs. So the flash only proves a JVM started; the app then died during initialization, and the error text went to a console that doesn't exist when you double-click. Your Java 25 is probably not the culprit: Polaris bytecode targets Java 21+ and the manifest carries the Add-Opens entries its startup self-test needs — though NIST builds and tests only against the bundled JDK 24, so the bundled runtime is the supported path. (My earlier "install Temurin 25" advice mattered for running the installer, and it's fine to keep — but it was never going to be what runs DTSA-II day to day.)
  • "The system cannot find the path specified" is cmd's error for a folder that doesn't exist — almost certainly from cd /d C:\DTSA-II in my earlier instructions. I hardcoded an assumed install path; yours is C:\Users\rongu\Downloads\dtsa. That one's on me.

3. Run this and paste back what it prints

From a Command Prompt:

cd /d C:\Users\rongu\Downloads\dtsa
dir /b

The listing is diagnostic on its own:

  • You see com, resources, META-INF → the folder is the unzipped installer, not an installation (dtsa2_polaris.jar is an installer you run, not an archive you extract). Delete the folder and run: java -jar "C:\Users\rongu\Downloads\dtsa2_polaris.jar", accept the default target (C:\Users\rongu\AppData\Local\NIST\NIST DTSA-II Polaris 2026-01-30), keep all packs, then launch from the Start Menu entry it creates.

  • You see DTSA-II.exe plus a dozen-odd .jar files but no java-runtime folder → you have the no-JRE variant; reinstall with "DTSA-II Polaris (includes Java Runtime Environment 24 for Windows)" from the NIST download page.

  • java-runtime is present → run the app exactly the way a healthy DTSA-II.exe would, but with the console attached so the real error is visible (match the exact dtsa2-*.jar name from dir /b):

    java-runtime\bin\java.exe -jar dtsa2-15.1.48.jar

    Either DTSA-II opens — which means the exe was picking the wrong JVM, and running DTSA-II.exe --l4j-debug once will write a launch4j.log in that folder showing which one — or you get a stack trace. Paste whatever it prints into a comment with @claude; from a real stack trace this is very likely a one-line fix (the usual suspects are the first-run report-directory setup and a wrong-JVM pick).

Aside: the deferred Edison literature task finished

While this ran, the earlier job's Edison LITERATURE_HIGH task (how the canonical EDS acquisition-parameter recommendations were originally derived) completed, and its artifacts are committed on this branch: outputs/issue-1-eds-parameters/answer.md plus references and the raw task response (commit 730b394). The summary lives in that job's comment above. This current job changed no files (Q&A only).

Key commands run: gh api on the two referenced issue comments and the Edison job run → fetched Polaris-tag build files from usnistgov/DTSA-II (Launch4j_config.template, build_install.bat, pom.template, izPack_install.template, DTSA2.java) → confirmed: v15.1.48, bytecode target 21, built/bundled with JDK 24, splash drawn from manifest, Jython 2.7.4 bundled.


• branch claude/issue-1-20260820-1711

…were derived

Deferred from the original issue #1 request (July 16), which could not run
because the EDISON_PLATFORM_API_KEY secret was empty at the time. Task
2a23935c-f686-488e-a0a6-0faada1cb90f completed with status success.

Co-authored-by: Sterling G. Baird <45469701+sgbaird@users.noreply.github.com>
@ronnie-guymon

Copy link
Copy Markdown
Collaborator

@claude here's the files I see

.installationinformation
derby-10.17.1.0.jar
derbyclient-10.17.1.0.jar
derbynet-10.17.1.0.jar
derbyshared-10.17.1.0.jar
derbytools-10.17.1.0.jar
doc
DTSA-II.exe
dtsa2-15.1.50.jar
epq-15.1.50.jar
flatlaf-3.0.jar
hamcrest-core-1.3.jar
jai-imageio-core-1.4.0.jar
jama-1.0.3.jar
java-runtime
jcalendar-1.4.jar
jgoodies-common-1.8.0.jar
jgoodies-forms-1.8.0.jar
jna-5.12.1.jar
junit-4.13.2.jar
jython-standalone-2.7.4.jar
Lib
mxparser-1.2.2.jar
Uninstaller
xmlpull-1.1.3.1.jar
xstream-1.4.21.jar

Here's the results I get
C:\Users\rongu\Downloads\dtsa>java-runtime\bin\java.exe -jar dtsa2-15.1.50.jar
Exception in thread "main" java.lang.NoClassDefFoundError: com/formdev/flatlaf/FlatDarculaLaf
at gov.nist.microanalysis.dtsa2.DTSA2.main(DTSA2.java:272)
Caused by: java.lang.ClassNotFoundException: com.formdev.flatlaf.FlatDarculaLaf
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(Unknown Source)
at java.base/java.lang.ClassLoader.loadClass(Unknown Source)
... 1 more

@claude

claude Bot commented Aug 21, 2026

Copy link
Copy Markdown

Claude encountered an error after 1s —— View job


I'll analyze this and get back to you.

@ronnie-guymon

Copy link
Copy Markdown
Collaborator

@claude here's the files I see

.installationinformation derby-10.17.1.0.jar derbyclient-10.17.1.0.jar derbynet-10.17.1.0.jar derbyshared-10.17.1.0.jar derbytools-10.17.1.0.jar doc DTSA-II.exe dtsa2-15.1.50.jar epq-15.1.50.jar flatlaf-3.0.jar hamcrest-core-1.3.jar jai-imageio-core-1.4.0.jar jama-1.0.3.jar java-runtime jcalendar-1.4.jar jgoodies-common-1.8.0.jar jgoodies-forms-1.8.0.jar jna-5.12.1.jar junit-4.13.2.jar jython-standalone-2.7.4.jar Lib mxparser-1.2.2.jar Uninstaller xmlpull-1.1.3.1.jar xstream-1.4.21.jar

Here's the results I get C:\Users\rongu\Downloads\dtsa>java-runtime\bin\java.exe -jar dtsa2-15.1.50.jar Exception in thread "main" java.lang.NoClassDefFoundError: com/formdev/flatlaf/FlatDarculaLaf at gov.nist.microanalysis.dtsa2.DTSA2.main(DTSA2.java:272) Caused by: java.lang.ClassNotFoundException: com.formdev.flatlaf.FlatDarculaLaf at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(Unknown Source) at java.base/java.lang.ClassLoader.loadClass(Unknown Source) ... 1 more

try this again

@claude

claude Bot commented Aug 21, 2026

Copy link
Copy Markdown

Claude encountered an error after 1s —— View job


I'll analyze this and get back to you.

@ronnie-guymon

Copy link
Copy Markdown
Collaborator
java-runtime\bin\java.exe -jar dtsa2-15.1.48.jar

@claude Here's the results I get
C:\Users\rongu\Downloads\dtsa>java-runtime\bin\java.exe -jar dtsa2-15.1.50.jar
Exception in thread "main" java.lang.NoClassDefFoundError: com/formdev/flatlaf/FlatDarculaLaf
at gov.nist.microanalysis.dtsa2.DTSA2.main(DTSA2.java:272)
Caused by: java.lang.ClassNotFoundException: com.formdev.flatlaf.FlatDarculaLaf
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(Unknown Source)
at java.base/java.lang.ClassLoader.loadClass(Unknown Source)
... 1 more

@claude

claude Bot commented Aug 21, 2026

Copy link
Copy Markdown

Claude encountered an error after 1s —— View job


I'll analyze this and get back to you.

@ronnie-guymon

Copy link
Copy Markdown
Collaborator

@claude are you there?

@claude

claude Bot commented Aug 21, 2026

Copy link
Copy Markdown

Claude encountered an error after 1s —— View job


I'll analyze this and get back to you.

@ronnie-guymon

Copy link
Copy Markdown
Collaborator

Here's what I had to run in the command prompt in order to get DTSA-II to open:
cd /d C:\Users\rongu\Downloads\dtsa
java-runtime\bin\java.exe --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.desktop/java.awt=ALL-UNNAMED -cp "dtsa2-15.1.50.jar;*" gov.nist.microanalysis.dtsa2.DTSA2

@ronnie-guymon

Copy link
Copy Markdown
Collaborator

@claude+opus using the k-ratios from the EDS report, I got the Mg wt% to be about 0.9% as well. Correct me if I'm wrong, but that seems to be pretty close to the range that commercial AlSi10Mg is connected to have. It is definitely better than the around 1.5 wt% we had measured before.

Is there a way to get the k-ratio directly from DTSA-II? I don't think so, and that is what I would have to use CalcZAF for? Becuase eXSpy doesn't return k-ratios? I would rather not use eXSpy if it only gives k-factors instead and is TEM centered, I want to be certain about the numbers I use and not have them be estimates or have to go through a conversion if possible. I would just upload the .msa file and use CalcZAF and use the k-ratios it gives me for the wt% input needed in DTSA-II. Help me understand the functions I would need from CalcZAF in order to get these wt%. Also, is the probe current 3.2nA that I uploaded correct? I know that's what the beam current was, I have just never seen "probe current" before.

Screen.Recording.2026-08-21.115836.mp4

@claude

claude Bot commented Aug 21, 2026

Copy link
Copy Markdown

Claude finished @ronnie-guymon's task in 14m 6s —— View job


DTSA-II does give you k-ratios directly — you picked the one wizard branch that asks you to supply them

  • Read this PR's script + issue EDS parameters #1 context for the assumed AlSi10Mg composition
  • Check whether 0.9 wt% Mg is inside the commercial AlSi10Mg spec range — it isn't; it's about 2× the top of the range
  • Verify: does DTSA-II produce k-ratios directly? — yes, verified in the Polaris source
  • Verify what CalcZAF actually takes as input / produces as output — you don't need it
  • Verify: does eXSpy return k-ratios or only k-factors? — your read is correct
  • Answer the "probe current 3.2 nA" question — only the ratio matters, and yours is right
  • Two setup bugs found in your screen recording (oxygen standard, .msa live time)

I decoded your screen recording frame-by-frame, so the answers below are against what you actually did, not a generic workflow.


1. The 0.9 wt% number: better, but not yet in spec

I have to correct the premise. Standard AlSi10Mg is Mg 0.20–0.45 wt%, Si 9.00–11.00 wt% — identical limits in DIN EN 1706 / EN AC-43000 and ASTM F3318, per the Nikon SLM Solutions MDS (p. 2), and the same 0.20–0.45 in the EOS and Carpenter/AMS7018 sheets. So 0.90 wt% is roughly double the top of the range, not inside it. 1.5 → 0.9 is real progress; it isn't convergence.

Your Si also lands at 11.15–11.33 wt%, just over the 11.00 ceiling. Mg high and Si high and Al low is a coherent pattern, not three independent flukes.

Before chasing physics: pull the certificate of analysis for that powder lot / part. The CoA is the ground truth here, the spec range is only a bound. If the CoA says 0.4, you have a measurement bias; if it says 0.8, you're done.

2. Yes — DTSA-II gives k-ratios directly. You used the other branch.

Your video shows the Quantification Alien dialog on "Specify the standards" → "Specify lines and k-ratios", typing 0.0151, 0.0097, 0.8324 by hand. That's Tools → Quantify, and its first page ("Select an operation") offers four modes (QuantificationWizard.java L4554–4558):

Radio button Direction What it needs
"Determine the composition of an 'unknown' spectrum by MLLSQ fitting to standards" spectrum → k-ratios → wt% standard spectra
"Determine the composition from k-ratios"you picked this k-ratios → wt% you type the k-ratios
STEM / ζ-factors TEM
STEM-in-SEM using bulk standards thin-in-SEM

The first one is the default (jRadioButton_MLSQ.setSelected(true), L4593) — you have to actively switch off it to reach the wizard you were in. Take that first branch and DTSA-II fits your .msa against standard spectra, and its Report tab prints Line / Z·A·F / k-ratio / mass fraction per element — the k-ratio row is emitted by QuantifyUsingStandards.tabulateResults() in EPQ. There is even a k-ratios-only mode in that class's constructor.

What it costs you: one standard spectrum per element, acquired on the same Octane Plus at the same kV with a known dose. Pure Al, pure Si, pure Mg, and something for O and C. Tools → Simulate can generate them ("Analytical model of a bulk, homogeneous material" / "Monte Carlo model of a bulk, homogeneous material") if you have no physical standards — but see section 6 for why a measured Al standard is the one that actually matters for you.

3. You do not need CalcZAF

The Quantification Alien k-ratio branch is CalcZAF's core function, already inside DTSA-II. Your result panel proves it: Algorithm: XPP - Pouchou & Pichoir Simplified (Non-normal), MAC: SuperSet[NIST-Chantler 2005, Sabbatucci & Salvat MACs].

And CalcZAF could not do the step you actually want anyway — it has no spectrum reader and no peak deconvolution. Its File menu opens .DAT tables, not .msa (calczaf.frm L173). Its four buttons (zaf.frm L210–256) are:

Button Input
Calculate Weight Concentrations From Intensities (k-ratio) "elemental (normalized) k-ratios (no standard is specified)" — i.e. pure-element-referenced. This is the eZAF Kratio column.
Calculate Weight Concentrations From Intensities (k-raw) raw k-ratios + a specified compound standard
Calculate Weight Concentrations From Intensities (counts) standard and unknown intensities in cps/nA
Calculate Intensities From Weight Concentrations forward model

So if you ever do want it, the button is the first one. Its only real value to you is as an independent check — it exposes many alternative ZAF/φ(ρz) formulations and MAC tables, where DTSA-II locked you into XPP + Chantler. That's a nice-to-have, not a dependency.

4. eXSpy: your instinct is right

Confirmed earlier in this PR against eXSpy 0.3.2 — no ZAF, no φ(ρz), and quantification() exists only on the TEM class ({'CL','zeta','cross_section'}). It returns net line intensities, not k-ratios. The "k-factors" in scripts/eds_kfactor_quant.py are calibrated sensitivity factors — a workaround for the missing matrix correction, a different quantity from a k-ratio, and composition-dependent on a bulk sample. If your goal is defensible wt% with no conversion step, DTSA-II alone is the right call.

5. "Probe current" — same thing as beam current, and only the ratio is used

Probe current = the electron current landing on the specimen (Faraday cup / picoammeter), in nA. NIST says "probe" because it's the current in the electron probe. DTSA-II pairs it with live time as probe dose (nA·s) to put unknown and standard on the same footing.

In the wizard you used, the arithmetic is one line (L978):

final double kr = krm.get(el).doubleValue() * rI.get(el).doubleValue() / kI.get(el).doubleValue();
//                 k-ratio you typed        × I(standards panel)      ÷ I(k-ratio panel)

So k_used = k_typed × I_standard / I_unknown. eZAF's Kratio column is already dose-normalized, so the only correct configuration is both columns equal — 3.2/3.2 is right, and 1.0/1.0 would give a bit-identical answer. The value is irrelevant; the match is everything.

⚠️ Your recording passes through two states where they don't match — standards at 3.2 with Carbon at 1.0, and later Oxygen at 3.2 with Mg/Al/Si at 1.0. That second one would have scaled three k-ratios by 1.0/3.2 = 0.31. Your final run was clean (Mg 0.0097 → 0.00900 confirms factor 1.0), but it's a silent failure mode worth knowing about. Probe current would matter absolutely in the MLLSQ branch, where DTSA-II reads counts from both spectra.

6. Two real bugs in the run, and the one that explains your total

(a) Oxygen standard is MgO, but you fed it a pure-element k-ratio. Every eZAF k-ratio in that report is pure-element-referenced — I checked all five rows and wt% = Kratio / (Z·A·F) reproduces the report exactly (Si: 0.1013/0.9078 = 11.16; Mg: 0.0097/1.0351 = 0.94; C: 0.0088/0.4416 = 1.99). Oxygen is 39.7 wt% of MgO, so DTSA-II scaled your O down by that factor — which is exactly why it reported 0.00644 instead of about 0.0162.

That single error accounts for your missing total:

as run (C dropped, O vs MgO):   0.644 + 0.900 + 83.617 + 11.327            = 96.49 wt%
with O on a pure-element basis and C added back:
                                1.62  + 0.900 + 83.617 + 11.327 + 1.99     = 99.45 wt%

An analytical total of 99.5% is what a self-consistent analysis should give. Fix: use pure-element materials for every row, or use Oxygen by stoichiometry, or drop O entirely.

(b) Live time mismatch. DTSA-II's properties panel shows Live time 30 s for AlSi10Mg EDS Map_1[1], while the report footer and this PR's spectrum are 327.7 s. Harmless in the k-ratio branch (dose cancels), fatal in the MLLSQ branch. Worth confirming the .msa you uploaded is the same acquisition as the report.

7. Why 0.9 is still 2× — and what would actually move it

DTSA-II (XPP, modern MACs) got 0.900 and eZAF got 0.937 from the same k-ratios: agreement to 4% relative. The matrix correction is not your problem. No amount of re-running ZAF will move 0.9 to 0.4. The error is in the k-ratios themselves, which means peak deconvolution and standardless intensity scaling.

Three things point the same way, and one dominates:

  • The Al Kα tail under Mg Kα. Mg Kα (1.254 keV) sits 233 eV below Al Kα (1.487 keV), on exactly the side where detector incomplete-charge-collection tailing lives — and Al is 84 wt% against Mg's 0.9. From your own report (Mg net 161.7, Al net 11578.8), misattributing 0.7% to 1.1% of the Al Kα net intensity to Mg spans the entire 0.45 → 0.20 spec range. A sub-1% error in one peak's tail model is the whole discrepancy.
  • Counting statistics are not the issue. Your Mg Error% is 5.83, i.e. ±0.055 wt%. That is 10× smaller than the gap. Longer acquisitions will not help.
  • 5 kV is maximally surface-sensitive. Kanaya–Okayama range in Al is 0.42 µm at 5 kV vs 2.6 µm at 15 kV, and low-energy X-rays escape from shallower still. Your C at 1.99 and O at 1.68 wt% prove a contamination + oxide layer is there, and Mg segregates into oxide films on Al–Mg alloys, so a Mg-enriched skin is over-weighted at 5 kV.

Ranked by value per hour:

  1. Acquire a pure Al standard at 5 kV on the Octane Plus, then run the MLLSQ branch. This is the only fix that measures your detector's actual Al tail and strips it from under Mg Kα. A simulated standard gets the physics right but uses a generic tail model, so it won't fully solve this specific overlap.
  2. Re-acquire at 15–20 kV on the same area. If Mg drops toward 0.3–0.4, the 5 kV result was surface-driven. Cheap, and it's a clean discriminator.
  3. Get the CoA. Ends the argument if the lot is on-spec.
  4. Fix the O standard and confirm the .msa live time before re-running anything.

Commands and verification

git clone --depth 1 -b Polaris https://github.com/usnistgov/DTSA-II.git      # v15.1.50 GUI source
  QuantificationWizard.java L4554-4558   4 quant modes; L4593 MLLSQ is default
  QuantificationWizard.java L978         kr = k_typed * I_std / I_unk
curl .../EPQ/.../QuantifyUsingStandards.java                                 # tabulateResults -> Line / Z.A.F / k-ratio rows
git clone --depth 1 https://github.com/openmicroanalysis/calczaf.git         # zaf.frm L210-256, calczaf.frm L173 (no .msa reader)
ffmpeg -i <attachment>.mp4 -vf fps=1/15 frame_%03d.jpg                       # 2m24s recording -> DTSA-II wizard + eZAF report

eZAF table read off the recording, used for every number above:

Element Weight % Net Int. Error % Kratio Z A F
C K 1.99 149.60 10.67 0.0088 1.2390 0.3564 1.0000
O K 1.68 306.00 6.58 0.0151 1.1582 0.7760 1.0000
Mg K 0.94 161.70 5.83 0.0097 1.0351 0.9893 1.0108
Al K 84.24 11578.80 5.01 0.8324 0.9901 0.9966 1.0014
Si K 11.15 1127.80 6.66 0.1013 1.0051 0.9030 1.0003

No files changed — this is analysis only. Also noting your --add-opens ... -cp "dtsa2-15.1.50.jar;*" launch works because the wildcard classpath picks up flatlaf-3.0.jar, which -jar ignores; DTSA-II.exe --l4j-debug would show whether the bundled launcher is picking a different JVM, but since you have a working command there's no need to chase it.
• branch claude/issue-1-20260820-1711

@ronnie-guymon

Copy link
Copy Markdown
Collaborator
  • 5 kV is maximally surface-sensitive. Kanaya–Okayama range in Al is 0.42 µm at 5 kV vs 2.6 µm at 15 kV, and low-energy X-rays escape from shallower still. Your C at 1.99 and O at 1.68 wt% prove a contamination + oxide layer is there, and Mg segregates into oxide films on Al–Mg alloys, so a Mg-enriched skin is over-weighted at 5 kV.

@claude+opus in response to this, I'm thinking the best thing to do might be just to make another polished sample and then try. @gage-erickson got a vacuum chamber we can store the samples in, we will just need to use the pump in the polishing lab. The first time we ran EDS on this sample we did use 15kv and ended up getting 1.64 wt%. There also seems to be double the counts in the first EDS run, I think I remember doing a 15 minute scan where as the one we just did was only 7 minutes, 64 count vs 128 count I think. Basically, I don't think increasing voltage to 15keV would do anything if there is still oxygen contamination. Then we can worry about segregating the Al tail counts and the Mg peak counts once we have better quality data?
image

@claude

claude Bot commented Aug 21, 2026

Copy link
Copy Markdown

Claude finished @ronnie-guymon's task in 11m 15s —— View job


Re-polishing is the right next move — but your 15 kV screenshot changes the diagnosis, and it doesn't say what you think it says

  • Read the attached 15 kV eZAF report and prior PR context
  • Reconcile 15 kV → 1.64 wt% vs 5 kV → 0.94 wt% (this retires one of my earlier recommendations)
  • Check whether either run's oxygen is physically plausible
  • Decode the counts / acquisition-time difference
  • Answer: does contamination invalidate a 15 kV re-run?
  • Answer: polish first, then worry about the Al tail?
  • Post a next-session checklist

1. You already ran the experiment I proposed, and it came back negative

Last comment I said: "Re-acquire at 15–20 kV. If Mg drops toward 0.3–0.4, the 5 kV result was surface-driven. Cheap, and it's a clean discriminator." You'd already done it, and Mg went up (0.94 → 1.64), not down. So that item is spent — scratch it off the list.

Working in net-intensity ratios (the closest thing to raw data in the two reports, and immune to how each run was normalized):

ratio to Al Kα net 5 kV 15 kV change
O / Al 0.02643 0.03526 ×1.33
Mg / Al 0.01397 0.02119 ×1.52
Si / Al 0.09740 0.06949 ×0.71

O and Mg move up together; Si moves the other way. eZAF's entire job is to turn those raw ratios into a kV-independent answer for a homogeneous sample — and it didn't: O disagrees by 4.8×, Mg by 1.8× between the two runs. That's not a matrix-correction subtlety, that's the two datasets describing different material.

2. The catch: your 15 kV numbers are internally implausible, so don't anchor on 1.64 either

O = 8.10 wt% cannot be a bulk composition. Bulk oxygen in AlSi10Mg powder runs 500–1250 ppm (0.05–0.125 wt%) — measured by inert-gas fusion across virgin and 30-month-recycled powder in Raza et al. 2021, Materials & Design 198, 109358. Your 15 kV value is 65–160× that. Your 5 kV value (1.68 wt%) is 13–34× — high, but in the range a surface film explains. 8.10 wt% is not.

Three things make the 15 kV run the weaker dataset:

  • No carbon in the element list. Your 5 kV run found 1.99 wt% C; the 15 kV table has O/Mg/Al/Si only. An unfitted contamination peak distorts the background model in exactly the 0.2–0.6 keV region where O lives, and normalization pushes everything else up.
  • A(O) = 0.4099 and A(Si) = 0.5925. At 15 kV, 59% of the O signal and 41% of the Si signal are absorbed before escaping, and the software divides them back in. Every error in the net intensity or the mass-absorption coefficients gets multiplied by 2.4× for O. At 5 kV those factors are 0.776 and 0.903 — a much smaller lever arm. (Those ZAF factors also independently confirm the higher kV, so I'm reading the right report.)
  • Dead time. See §3 — if your "15 minutes" recollection is right, that run was at about 49%.

That's also the direct answer to your instinct: you're right that 15 kV isn't the fix, but for a stronger reason than oxygen. Low kV is inherently the more accurate choice for light elements because the absorption correction — the dominant error amplifier — is far smaller. Going up in kV to escape the surface trades a surface problem for a bigger absorption problem. Keep 5 kV for the number; use higher kV only as a depth probe, run head-to-head on the same spot (§6).

3. "Double the counts" — and why counts aren't your problem

Live time was 460.8 s (15 kV) vs 327.7 s (5 kV), only 1.4×; the Al net is 2.2× higher mostly because 15 kV excites Al harder, not because the scan was longer. If the 15 min / 7 min wall-clock is right, the implied dead times are:

15 kV:  900 s real / 460.8 s live  ->  49% dead time
 5 kV:  420 s real / 327.7 s live  ->  22% dead time

49% is out of the regime quantitative EDS is calibrated for. The literature review committed on this branch (outputs/issue-1-eds-parameters/answer.md) found the paralyzable-throughput maximum sits at 63.2% dead time, but that modern SDD trace work should run below 10%, and that the linear dead-time correction itself breaks above about 50 kcps (Donovan 2023). Above 20% you get a pile-up continuum and degraded peak shape — right where a 0.9 wt% Mg peak sits on the flank of an 84 wt% Al peak. Fix by lowering probe current, not by shortening the scan.

But the headline is: more counts cannot help you. Your Mg Error% was 2.43 at 15 kV = ±0.040 wt%. The gap from 1.64 down to the 0.45 spec ceiling is 30σ of counting error. No scan length touches a 30σ systematic. "Better quality data" has to mean cleaner and better-referenced, not longer.

On "64 vs 128": I can't tell which APEX setting that is from the report alone (map frames? sweeps?) — it's recorded in the .spc/.msa header, worth pulling so both runs are documented. ⚠️ Related: the CSV exported for this PR has 652,400 total counts with a 26,295-count peak channel, while your 15 kV report shows 684K counts in the peak channel — a 26× difference, not 2×. Combined with DTSA-II reading 30 s live time where the report says 327.7 s, I'd verify what that export actually contains before anyone quantifies from it again.

4. Where the surface hypothesis actually stands — with numbers

Two things pull in opposite directions, so here's the arithmetic rather than a hand-wave.

Against a pure surface film: a thin film's apparent concentration must fall with kV, because the sampled mass grows as roughly E₀^1.67. From 5 → 15 kV that predicts a 6× drop in the O signal. You measured a 2.5× rise. A surface film alone cannot do that.

For a surface film (at 5 kV, where the data is trustworthy): the native oxide on Al is 2–4 nm, forms in air within milliseconds, and is self-limiting — you cannot polish it off in air (Evertsson et al., Appl. Surf. Sci. 2015). Folding that into the 0.42 µm interaction volume at 5 kV:

film geometric fraction of sampled mass apparent O (φ(ρz) weight 1–3×)
3 nm Al₂O₃ 0.80% 0.38–1.13 wt%
5 nm 1.34% 0.63–1.89 wt%
10 nm 2.68% 1.26–3.77 wt%

Measured: 1.68 wt%. So your 5 kV oxygen is about what an ordinary 3–10 nm native/hydrated oxide gives. That's the floor you can't polish below.

And that film is Mg-enriched. Raza et al. found Mg²⁺ concentrated at the top of the powder's surface oxide as MgO or MgAl₂O₄ spinel, growing with powder reuse — while Si is absent from the oxide scale. Carry that through:

film Mg in film apparent Mg
5 nm 10 wt% 0.13–0.40 wt%
5 nm 20 wt% 0.27–0.80 wt%
10 nm 10 wt% 0.27–0.80 wt%

The excess you need to explain at 5 kV is 0.59 wt% (0.94 − 0.35). A Mg-enriched oxide of realistic thickness lands squarely in that band. So re-polishing is a bigger lever than I credited last time — your instinct is good.

The part it can't explain: your Si is also high (11.15 vs an 11.00 ceiling), and Si is not in the oxide. Two of three excesses point at surface chemistry; the third points at standardless quantification. That's the case for doing both fixes, not one.

5. The polish + vacuum plan — yes, with three practical notes

⚠️ The pump matters. If the polishing lab's pump is an oil-sealed rotary-vane pump without a foreline trap, pumping a chamber with the sample inside can back-stream oil vapour onto the fresh surface — that is exactly the carbon contamination you're trying to avoid, and you'd be manufacturing it. Either use a dry scroll pump, fit a molecular-sieve foreline trap, or pump down and immediately valve off and store static. A plain desiccator with fresh silica gel is a fine substitute and has no back-streaming risk at all.

Polishing media matter more than storage. Colloidal silica adds Si and O; alumina suspension adds Al and O — both contaminate exactly what you're measuring, and both embed in soft Al. Finish with diamond (1 µm → 0.25 µm) in a non-aqueous / alcohol-based lubricant, ethanol rinse, N₂ blow-dry, no water (water drives oxide hydration and thickening).

Storage buys you preservation, not removal. Vacuum/desiccator storage stops the 2–4 nm film from thickening and hydrating over days and cuts adsorbed hydrocarbon. It cannot remove a film that re-forms in milliseconds. If you want a genuinely oxide-free surface, that's broad-ion-beam / cross-section polishing (Gatan Ilion, Hitachi IM4000, JEOL CP) — worth asking around for. A plasma clean immediately before loading is the highest-yield cheap step for carbon; prefer a gentle Ar/H₂ or downstream air plasma over a hard O₂ plasma on an Al alloy.

6. Don't defer the Al-tail work — it's about 10 minutes in the same session

Then we can worry about segregating the Al tail counts and the Mg peak counts once we have better quality data?

Sequencing them costs you a whole extra microscope session for no benefit. The tail work needs exactly one thing: a pure Al standard spectrum from your Octane Plus at the same kV, same process time, same probe current. That's a couple of minutes of scope time — and standards have to share a session with the unknown anyway (same detector state, same calibration), so acquiring them later is strictly worse than acquiring them alongside.

With Al + Si + Mg (or MgO) standards in hand, take DTSA-II's first radio button — "Determine the composition of an 'unknown' spectrum by MLLSQ fitting to standards", the default you switched off last time. It fits your .msa against those standards, strips your detector's actual Al tail out of the Mg window, and reports k-ratio + Z·A·F + wt% per element. That also retires the ±25%-relative standardless error bar, which is currently wide enough to swallow the entire spec range.

7. The cheapest way to end the argument

Neither of these needs a microscope:

  1. The CoA for that powder lot / part. Free. If it says 0.4, you have a measurement bias; if it says 0.8, you're done arguing.
  2. Independent bulk chemistry — ICP-OES on a chip of the same build (roughly $50–150 at a commercial lab), plus inert-gas fusion (LECO) for oxygen. No matrix correction, no peak overlap, no surface sensitivity. When you're arguing about a 2–4× discrepancy, an absolute reference is worth more than another EDS session.

Physics also says your sample should read low in Mg, not high: LPBF melt-pool peak temperature (about 1900 K) exceeds Mg's boiling point (1363 K), so Mg preferentially evaporates and as-built parts trend below the feedstock (Springer, doi:10.1007/s13632-020-00659-w). Measuring 2–4× above the ceiling runs against the process physics — one more vote for measurement bias over real composition. Spec confirmed again from the Nikon SLM MDS: Mg 0.20–0.45, Si 9.00–11.00, identical under DIN EN 1706 and ASTM F3318.


Next-session checklist (about 45 min of scope time)

# Step Why
1 Fresh polish, final step diamond in non-aqueous lubricant, ethanol rinse, N₂ dry no Si/Al/O from the media, no hydration
2 Load same day; plasma clean right before if available C is time- and chamber-dependent
3 Set probe current for 10–20% dead time at 5 kV stay inside the calibrated regime
4 Unknown at 5 kV, 3 replicate areas, element list = C, O, Mg, Al, Si replicates give a real uncertainty; C must be in the list
5 Same spot at 10 and 15 kV, same session proper depth profile — apparent O and Mg must fall with kV if it's a surface film
6 Pure Al, Si, Mg (or MgO) standards, same kVs / current / process time unlocks DTSA-II MLLSQ; the only thing that separates Al tail from real Mg
7 Record probe current + live time in every .msa the current export disagrees with itself

Step 5 is the controlled version of the accidental experiment you already ran — same spot, same day, same dead time, same element list — so it actually answers "surface or not."

Two things worth checking on your end

  1. Was the sample re-polished between the 15 kV run and the 5 kV run? If yes, then polishing alone took O from 8.10 → 1.68 and Mg from 1.64 → 0.94, and your plan is validated by your own data. If no, the two runs differ only in kV and dead time, and the 15 kV result is an artifact. This single fact changes the interpretation completely.
  2. How many times had that powder been recycled? Raza et al. show the Mg-in-oxide enrichment grows strongly with reuse — virgin powder had no detectable Mg²⁺ at the surface, 30-month-recycled powder had a lot.
Checked and dismissed: the Si escape peak of the Al pile-up peak

At 49% dead time the Al Kα sum peak (2×1.4867 = 2.973 keV) is real, and its Si escape peak lands at 1.233 keV — 20 eV from Mg Kα at 1.2536 keV, i.e. buried inside the Mg peak. Sounds alarming. But the sum-peak fraction (order 1% of parent at high dead time) times the Si escape probability near 3 keV (order 1%) puts it at order 1 count against a 427-count excess. Not your problem — recorded so nobody chases it.

No files changed — analysis only. Separately, scripts/eds_kfactor_quant.py still carries the 5 kV eZAF values as its KNOWN_WT placeholder, including the O row that was computed against an MgO standard — worth replacing once you have real standards. Fix this →

Sources: Raza et al. 2021, Materials & Design 198 (open PDF) · Evertsson et al., Appl. Surf. Sci. 2015 — native oxide thickness · Nikon SLM AlSi10Mg MDS · Understanding the LPBF of AlSi10Mg · this branch's EDS-parameter literature review
• branch claude/issue-1-20260820-1711

@ronnie-guymon

Copy link
Copy Markdown
Collaborator

@claude+opus the powder had never been recycled and the sample was not repolished. How do I get standards for Al, Si, and Mg, and what even are the standards? Like what info does it contain? Is it just how many counts there are for each pure element? I tried finding files I could use to upload to DTSA-II to try and find wt%, but wasn't able to. Can you find some through a reliable source from Edison?

@vertical-cloud-lab vertical-cloud-lab deleted a comment from claude Bot Aug 21, 2026
@claude

claude Bot commented Aug 21, 2026

Copy link
Copy Markdown

Claude finished @ronnie-guymon's task in 30m 47s —— View job


You can't download standards — DTSA-II refuses them in code. But two of the three you need cost nothing, and the answer PDF is already on your disk.

  • Fold in your two new facts (virgin powder, not repolished) — this changes the diagnosis
  • Explain what an EDS "standard" actually is (it is not just counts)
  • Answer: can you download standard spectra and upload them to DTSA-II?
  • Concrete routes to get Al / Si / Mg standards, with verified part numbers and prices
  • Edison literature task — 20cf0cb5-2cd4-49db-8dee-c2cf8e021bfc, success in 22 min, artifacts committed (4dc7d7c)

I answered from three sources: NIST's own DTSA-II/EPQ source code (what the software actually enforces), Ritchie's Standard Bundles.pdf that ships inside your install, and an Edison literature run for the published accuracy numbers and supplier evidence.


0. Your two facts move the diagnosis toward standards, not polishing

Not repolished is the bigger one. Last time I said this single fact changes the interpretation completely, and it does — against the plan. The 15 kV and 5 kV runs were the same surface, so nothing about the sample changed between them; only kV, dead time, and the element list did. That means the 15 kV O = 8.10 wt% is a measurement artifact, not evidence of a dirtier sample, and re-polishing cannot explain the 8.10 → 1.68 difference. Polishing is still worth doing — the 5 kV O of 1.68 wt% is a real 3–10 nm oxide + carbon film — but it is no longer the thing standing between you and a defensible number.

Virgin powder cuts the same way. The Mg-enrichment-in-surface-oxide mechanism I leaned on (Raza et al.) was measured on recycled powder; their virgin powder had no detectable surface Mg²⁺. Your powder is virgin, so the mechanism I used to explain your 0.59 wt% Mg excess is at its weakest possible strength.

Both facts push the same direction: the excess Mg is most likely instrumental, not real, and not surface. Which is exactly what standards fix. Your instinct to prioritize standards over another polish is right.


1. What a standard actually is — no, it is not just counts

I pulled this from NIST's own code rather than describing it generically. DTSA-II will reject a spectrum as a standard unless all four of these are present (StandardBundle.java L66–75, the actual error strings):

Requirement DTSA-II's error if missing
The spectrum itself "No standard spectrum has been specified."
A defined EDS detector "No EDS detector is defined for this standard."
Beam energy "The beam energy is not defined for this standard."
A certified composition containing the element "The standard composition is not defined for this spectrum." / "...does not contain the required element."

So a standard is: counts + which detector produced them + at what kV + from what certified material. The counts alone are meaningless — they only become a standard when paired with a known composition and a dose you can normalize against.

And there is a second kind of spectrum you're missing, which is the one that actually fixes your problem. DTSA-II distinguishes a standard from a reference:

  • A standard answers "how many counts does a known amount of this element give?" → sets the concentration scale.
  • A reference answers "what shape does this element's peak have on my detector, unobstructed?" → lets the fit separate overlapping peaks.

That distinction is the whole ballgame for you. DTSA-II automatically demands a reference whenever two elements' regions of interest intersect (QuantificationOutline.java L926 — when elmRoi.intersects(otherRoi), it adds both to the required-reference set). Mg Kα and Al Kα intersect. So DTSA-II will ask you for an Al reference and a Mg reference, and the Al reference is precisely the empirical measurement of your Octane Plus's low-energy tail that you currently do not have.

Ritchie's own worked example of how much this matters (K240 glass, Standard Bundles.pdf pp. 13 and 26):

O Mg Si Ti Zn Zr Ba
Simple standards, error −5% −11% −7% 0% 0% −3% +2%
Similar standards + references, error 0% 0% +1% +1% 0% −2% +1%

Mg went from −11% to 0% by adding references. Same software, same physics, same sample.

File format: a DTSA-II standard bundle is a .zstd file — actually just a ZIP (StandardBundle.write() L232–260) containing an XML Header (element, composition, detector GUID, strip list, reference map) plus the standard and every reference written as plain EMSA .msa files. Nothing exotic; you can open one with any unzip tool.


2. Why you couldn't find files to download — DTSA-II actively blocks them

This is not a "best practice." It is a hard exception in the loader (StandardBundle.java L281):

if (!(zfh.DetectorName.equals(guid) || zfh.DetectorName.equals(det.getName())))
   throw new EPQException("The standard does not appear to have been collected on the same detector as the unknown.");

If you had found a .zstd online, DTSA-II would have thrown that error at you. Same story for its internal database: Session.findStandards() filters by detector index and beam energy within ±1% (Session.java L1326) — that database is a place to save your own standards, not a shipped library.

The physics behind the refusal, from the Edison run: detector-to-detector differences in FWHM (122–150 eV at Mn Kα), window transmission (Moxtek AP3 vs AP5 differ in thickness and rib geometry; a Be window kills everything below about 1 keV), Si dead-layer thickness, and incomplete charge collection all reshape peaks — and the incomplete-charge-collection tail is the exact artifact you're fighting. A borrowed standard would import someone else's tail.

Newbury & Ritchie do describe a "remote standards" scheme using another instrument's library with efficiency corrections, but they note no dose calibration is attempted, so the analytical total loses all validity and you must normalize — throwing away the single best diagnostic you have. Not a route I'd take when the goal is confidence in the number.

What DTSA-II does ship is easy to mistake for a spectrum library: standards.sd2.xml (Tools → Edit standards database...). I parsed it — it is a catalogue of which physical standard blocks contain which materials, so the software can help you plan an acquisition:

Block in the shipped database Items Has pure Al / Si / Mg?
Geller 100 36 ✅ Al, Si, Mg (+ SiO₂)
SPI 44 Metal 44 ✅ Al, Si, Mg
SPI 53 Mineral 56 Albite, Dolomite, Periclase
SPI Rare Earth / RE Phosphate 15 each

Names of things to buy. Zero spectra.


3. How to actually get Al, Si and Mg standards

Two of the three are free and you probably have them in the building.

Element Cheapest good option Why it works
Al A chip of high-purity Al (99.99%+) foil/rod/sheet Pure, conductive, stable, polishes with your alloy
Si A snapped-off piece of a bare silicon wafer 99.9999% pure, atomically flat, conductive, free from any cleanroom. Hard to beat at any price.
Mg This one you buy — see below Pure Mg metal oxidizes too fast to be a standard

Mount the Al and Si pieces in the same puck as your AlSi10Mg and polish them together, in the same session, with the same media. That gives identical surface prep on standard and unknown, which Newbury & Ritchie call out as a requirement at low kV, and it costs an afternoon.

For Mg, the literature is unanimous: don't use Mg metal. Newbury & Ritchie's 2024 5 keV study used MgO for Mg, Al₂O₃ for Al, SiO₂ for Si. Pure Mg grows a surface MgO film in air and under the beam, which violates the homogeneity the k-ratio assumes.

Verified purchase options:

Item Supplier Part no. Price Contents
MgO, mounted SPI Supplies AS2140-AB (page) $178.60 Mg 60.300 / O 39.700 wt%, etched label
MgO, loose SPI Supplies AS2145-AB same material
42 metals + 4 compounds block SPI Supplies 02752-AB (page) $5,152.80 Al (5), Mg (4), Si (6) + 39 more; ≥99%, most >99.99%
44-item metal block, 25 mm Astimex METM25-44 (page) pure Al, Mg, Si among 49 elements
44-item metal block, 3× 12.5 mm Astimex METM12-44 same, sized for SEM stubs
Stoichiometric MgO / Al₂O₃ / SiO₂ Geller MicroAnalytical (Topsfield MA) quote named by Newbury & Ritchie 2024 as their source for microanalysis-prepared stoichiometric compounds

⚠️ Ritchie flags a trap you already hit once: in his own K240 example he attributes the residual error to charging on the MgO standard. MgO is an insulator. Coat it (and your unknown, and every other standard) with the same 8–10 nm carbon — mismatched coating thickness is a significant low-kV error source.

The single best value here is not on that list. NIST SRM 470 (K412 glass) contains O–Mg–Al–Si–Ca–Fe with certified values, and Newbury & Ritchie used it in their 5 keV challenge set. Run it as an unknown and you get an independent, certified answer to "does my instrument read Mg correctly at 5 kV in an Al–Si matrix?" That settles your argument without touching the AlSi10Mg.

Simulated standards — the option I'd steer you away from, with a number. Tools → Simulate offers "Analytical model of a bulk, homogeneous material" and "Monte Carlo model of a bulk, homogeneous material", and the bundle builder has an Auto button that fits an analytical model. Ritchie's own caption (Standard Bundles.pdf p. 33): "As convenient and tempting as 'Auto' can be, only use it when a reference can't be measured. Auto references won't produce as good results as measured references." Edison found the quantitative penalty: comparing measured against DTSA-II-simulated spectra for K411, discrepancies ran from +1.2% for Fe Kα to +19% for Mg K — the worst case in the study is your exact line. A simulated Al reference uses a generic tail model; your problem is your detector's specific tail.


4. The two documents you already have

Both shipped into C:\Users\rongu\Downloads\dtsa\doc\ (installer manifest L107–114):

  • Standard Bundles.pdf — Ritchie's 35-slide walkthrough, the source of the K240 table above
  • MT3_Standards_based_Quantication_Part1.pdf and MT4_..._Part2.pdf — the full standards-based quant tutorials
  • Newbury2014_Limits_of_Quantitative_EDS.pdf — bonus, directly on your accuracy question

Also there: MT1, MT2, Introduction to DTSA-II.pdf, Basic scripting in DTSA-II.pdf.


5. Workflow once you have the materials

  1. Acquire each standard at 5 kV, same probe current, same process time as the unknown, dead time below 10% (Al Kα is intense — turn the current down).
  2. 5 × 60 s per standard, not 1 × 300 s. Ritchie, p. 28: compare the five, discard any outlier, combine the rest. If all five match you've proven no contamination, drift, or bad geometry. If they don't, you've caught it before it poisons your answer.
  3. Tools → Make 'Standard Bundle' — enter the composition, tick which elements it stands for, supply references, save one .zstd per element.
  4. Tools → Quantifyfirst radio button, "Determine the composition of an 'unknown' spectrum by MLLSQ fitting to standards" (the default you switched off last time) → load the bundles.
  5. Read the residual spectrum in the Mg window. After Al and Si are fitted, whatever is left there is your real Mg. That plot is the direct answer to "is the Al tail eating my Mg?"
  6. Read the raw, un-normalized analytical total. It should be near 1.00 (at 5 kV, 0.8–1.2 is the realistic band). A total far from unity means a missing element, a surface layer, or a fitting artifact — the diagnostic the normalizing standardless route hides from you.

What this buys: Newbury & Ritchie (2024) ran this exact protocol at E₀ = 5 keV — 263 measurements, 39 elements, 113 materials — and got >98% within ±5% relative, 82% within ±2%. Against the roughly ±25% relative you're living with now, that is the difference between arguing about your number and reporting it.


6. The free experiment you can run before buying anything

You don't need an Mg standard to test the Al-tail hypothesis — only to convert it to wt%. So:

  1. Find any piece of high-purity Al. Polish it with your sample.
  2. Acquire it at 5 kV, same settings. That's about 10 minutes of scope time.
  3. In DTSA-II, fit it as an Al reference against your existing .msa and look at the residual in the Mg window.

If the residual Mg peak shrinks toward the 0.20–0.45 wt% band, you have your answer for the cost of a scope hour, and the MgO purchase becomes a formality to get the final number. Standards must be acquired in the same session as the unknown anyway (same detector state, same calibration), so pair this with whatever you run next rather than making it a separate trip.


What I verified, and what I didn't

Verified in NIST source: the four standard requirements and their error strings, the detector-GUID refusal, the ROI-intersection reference rule, the .zstd ZIP layout, the shipped standards.sd2.xml block contents, the doc-folder manifest, the simulation modes. Verified on supplier pages: SPI 02752-AB and AS2140-AB prices/contents, Astimex METM25-44/METM12-44 contents. From Edison's cited literature: the +19% Mg K simulation discrepancy, the 5 keV MgO/Al₂O₃/SiO₂ standard choices, and the >98%-within-±5% accuracy result.

Not verified: Astimex, Geller, Ted Pella, MAC and P&H current prices and catalogue IDs — get quotes. Edison also flagged that it could not confirm specific catalogue identifiers for those four suppliers from the literature, and I've kept that caveat rather than papering over it.

Committed artifacts (4dc7d7c)

pip install edison_client                                  # 0.16.1
client.create_task(TaskRequest(name=JobNames.LITERATURE_HIGH, query=...))
  -> task 20cf0cb5-2cd4-49db-8dee-c2cf8e021bfc
poll (single foreground call, 120 s interval)              -> "success" at 22m09s
git clone --depth 1 -b Polaris usnistgov/DTSA-II ; clone usnistgov/EPQ
  StandardBundle.java L66-75 (4 requirements), L281 (detector refusal), L232-260 (.zstd layout)
  QuantificationOutline.java L926 (ROI intersect -> reference required)
  Session.java L1326 (findStandards filters detector + beam energy +/-1%)
  standards.sd2.xml -> 5 blocks parsed; izPack_install.template L107-114 -> doc/ manifest
pdfplumber "Standard Bundles.pdf"                          # 35 pages, K240 tables p.13/p.26
git commit + push                                          -> 4dc7d7c

Sources: DTSA-II Polaris source · NIST EPQ library · SPI 42-metals block 02752-AB · SPI MgO AS2140-AB · Astimex metal standards · NIST DTSA-II download
• branch claude/issue-1-20260820-1711

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants