Skip to content

Post processing EBSD data - #8

Open
ronnie-guymon wants to merge 8 commits into
mainfrom
claude/issue-2-20260724-1601
Open

Post processing EBSD data#8
ronnie-guymon wants to merge 8 commits into
mainfrom
claude/issue-2-20260724-1601

Conversation

@ronnie-guymon

Copy link
Copy Markdown
Collaborator

Learn about the most prevalent methods of post processing EBSD data and which one(s) would be best to use for CALIBER

Explains why EBSD acquisition parameters take the values recorded in
standards (ASTM E2627 / ISO 13067), grounded in backscatter physics and
sampling statistics rather than deferring to the standards themselves.

Addresses issue #2.

Co-authored-by: Ronnie Guymon <244881888+ronnie-guymon@users.noreply.github.com>
@ronnie-guymon

Copy link
Copy Markdown
Collaborator Author

@claude what are the methods of EBSD post processing data used most commonly by research professionals? Are there any where I can upload the raw patterns, then constantly tweak parameters in the software to get the best grain characterization possible?

@claude

claude Bot commented Jul 24, 2026

Copy link
Copy Markdown

Claude Code is working…

I'll analyze this and get back to you.

View job run

@ronnie-guymon

Copy link
Copy Markdown
Collaborator Author

@claude what are the methods of EBSD post processing data used most commonly by research professionals? Are there any where I can upload the raw patterns, then constantly tweak parameters in the software to get the best grain characterization possible?

@claude

claude Bot commented Jul 24, 2026

Copy link
Copy Markdown

Claude finished @ronnie-guymon's task in 1m 34s —— View job


EBSD Post-Processing — Common Methods & Iterative-Tweaking Tools

Short answer to your second question: yes — this workflow exists and it's exactly where the field is heading. It's called pattern reprocessing / re-indexing, and it requires that you save the raw EBSPs (diffraction patterns) during acquisition, not just the indexed orientation map. Once you have the raw patterns on disk, you can re-run indexing and grain reconstruction as many times as you like, tweaking parameters each pass, without ever going back to the microscope.


1. The two stages of EBSD post-processing

Professionals treat post-processing as two distinct stages. It helps to keep them separate because your "tweak parameters" question lives mostly in Stage A.

Stage A — Indexing (raw pattern → crystal orientation). Three approaches dominate:

Method How it works When professionals use it
Hough/Radon indexing Detects Kikuchi bands, solves orientation from band positions. Fast (real-time). The default for clean, well-diffracting materials.
Dictionary Indexing (DI) Compares each measured pattern against a dictionary of simulated master patterns; picks best match. Slow but robust. Noisy patterns, deformed material, fine/overlapping phases, low signal — where Hough fails.
Spherical-harmonic / pattern matching (EMSphInx, AZtec MapSweeper) Cross-correlation in the spherical-harmonic domain — DI-quality accuracy at much higher speed. Large maps needing DI robustness without the DI time cost.

Stage B — Microstructure analysis (orientation map → grains, texture, GND, etc.): grain reconstruction (misorientation-threshold flood-fill, typically ~5°), clean-up (neighbour-orientation correlation, grain dilation, CI/MAD/band-contrast thresholding), grain-size statistics, texture/ODF, KAM/GND, phase maps. This is the downstream analysis your existing docs/ebsd-parameter-selection.md §4, §8, §9 already reasons about.


2. "Upload raw patterns, then constantly tweak parameters" — the tools that do this

This is the re-indexing / pattern-matching workflow. Prerequisite: acquire with "save patterns" enabled (Oxford .ebsp, EDAX .up1/.up2, or HDF5). Then:

Commercial (GUI, interactive tweaking):

  • Oxford AZtecCrystalMapSweeper re-indexes stored EBSPs by simulated pattern matching; built for very large CMOS-detector datasets. Closest to your "upload and keep tuning in the software" description. (product page)
  • EDAX/TSL OIM Analysis and Bruker ESPRIT — mature GUIs for re-indexing + reconstruction + clean-up, all interactively adjustable.

Open-source (scriptable — best fit for a Python project like CALIBER):

  • kikuchipy — Python/HyperSpy toolbox for loading raw patterns, background correction, dictionary indexing, hybrid indexing, and orientation refinement. Reads .ebsp, .up1/2, HDF5. This is the natural programmatic home for an iterate-on-parameters loop.
  • PyEBSDIndex (US Naval Research Lab) — GPU Hough/Radon indexing, up to ~45,000 patterns/s, so re-indexing a whole map with new parameters takes seconds.
  • EMsoft / EMSphInx — reference dictionary-indexing and spherical-harmonic engines; kikuchipy's DI is based on EMsoft.
  • MTEX — the standard MATLAB toolbox for Stage B (grain reconstruction, texture), fully parameter-driven and reproducible.
  • orix — crystal-orientation/symmetry backend that kikuchipy builds on.

3. Recommendation for CALIBER

Given CALIBER is Python and the goal is sample-specific parameter recommendation with an iterate-to-best loop, the strongest foundation is:

  • kikuchipy + PyEBSDIndex + orix for the re-indexing engine (Stage A), with EMsoft/EMSphInx if you need full dictionary-indexing accuracy on hard samples, and
  • MTEX (or an orix-based reconstruction) for Stage B grain reconstruction and statistics.

This maps cleanly onto CALIBER's premise: keep raw patterns, then let the software sweep the trade-off curves documented in ebsd-parameter-selection.md (indexing band count, Hough binning, CI/MAD clean-up thresholds, misorientation/step-size for grain reconstruction) and report the parameter set that maximizes an objective grain-characterization metric (e.g. indexing success rate + CI/MAD + reconstruction stability) — all offline, no re-acquisition needed. The one hard requirement to design in early: acquisition must save the raw EBSPs, otherwise re-indexing is impossible and you're limited to Stage B clean-up only.

Happy to draft a short docs/ebsd-post-processing.md capturing this two-stage framing and the recommended stack if useful — just say the word.

Sources: AZtecCrystal 4.0 · kikuchipy (pattern matching) · PyEBSDIndex · EMsoft · EMSphInx · MTEX grain reconstruction · DI hands-on tutorial


  • Read existing docs/ebsd-parameter-selection.md
  • Research common EBSD post-processing methods
  • Identify tools supporting raw-pattern upload + iterative tweaking
  • Recommend best fit for CALIBER
  • Post answer

This was a question, so no code changes were made.

@ronnie-guymon

Copy link
Copy Markdown
Collaborator Author

Claude Code is working… 435229200-5ac382c7-e004-429b-8e35-7feb3e8f9c6f.gif?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3ODQ5MTE5MDYsIm5iZiI6MTc4NDkxMTYwNiwicGF0aCI6Ii8zMDU5MjQ5LzQzNTIyOTIwMC01YWMzODJjNy1lMDA0LTQyOWItOGUzNS03ZmViM2U4ZjljNmYuZ2lmP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI2MDcyNCUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNjA3MjRUMTY0NjQ2WiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9OGUxNDE5NWNjNzk2ODFkZTM1NDBhYjVmMzhjMmY1ZGRlOGY2NDdlZjZiNWFkYmI2YzNjY2ZiN2UxYzE5NDMwMSZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QmcmVzcG9uc2UtY29udGVudC10eXBlPWltYWdlJTJGZ2lmIn0.azD20r_Bpby48Qx8WihjvmgTe7vD-mbcWZUg5afjPfs

I'll analyze this and get back to you.

View job run

@claude it looks like this job failed, if anything is still running for this job stop it.

@claude

claude Bot commented Jul 24, 2026

Copy link
Copy Markdown

Claude finished @ronnie-guymon's task in 1m 33s —— View job


EBSD post-processing: common methods & re-indexable "tweak-the-parameters" software

On the failed job: nothing is still running that I can stop. GitHub Actions runs are ephemeral — the two earlier runs (30108560064, 30110458534) have already terminated on their own, and I have no ability to cancel Actions jobs anyway. This is a fresh run answering your question directly.


Short answer to your key question: yes — the capability you're describing (upload the raw patterns, then keep re-tuning parameters until grain characterization looks right) is a real, well-established workflow. The enabling requirement is that the raw Kikuchi patterns (EBSPs) are saved to disk (usually an HDF5 .h5/.oh5/.h5oina container). Once you have the patterns, indexing becomes an offline, repeatable step you can re-run with different settings as many times as you want — you are not limited to the single on-the-fly solution the microscope produced during acquisition.

The most common post-processing methods (in workflow order)

  1. Indexing — turning each pattern into an orientation:
    • Hough/Radon-based indexing — the classic, fast default in every vendor package. Detects Kikuchi bands, votes for the best orientation. Great throughput, struggles on noisy/deformed/low-symmetry patterns.
    • Dictionary Indexing (DI) — matches each experimental pattern against a library of simulated master patterns by normalized cross-correlation. Far more robust on noisy/deformed data, but compute-heavy. This is the modern gold standard for hard samples.
    • Spherical / pattern-matching indexing — variants of the same simulate-and-correlate idea (e.g. Oxford's MapSweeper, EMsoft's spherical indexing).
  2. Clean-up / repair — grain-dilation, neighbour-orientation correlation, and re-indexing of unsolved pixels using CI/MAD/band-contrast thresholds (see docs/ebsd-parameter-selection.md §9). Must be reported, because over-aggressive clean-up biases results.
  3. Grain reconstruction — flood-fill segmentation with a misorientation threshold (typically ~5°) plus a minimum points-per-grain rule.
  4. Derived analysis — grain size/shape, texture (ODF/pole figures), KAM/GND density, phase maps, grain-boundary character (CSL), and parent-grain reconstruction (recovering prior-austenite/-β from transformed microstructures).

Software that lets you re-index stored patterns and iterate

Commercial (pattern-matching re-indexing built in):

  • Oxford AZtecCrystal — MapSweeper (nano.oxinst.com/azteccrystal, MapSweeper). Explicitly designed for exactly your use case: it stores the raw EBSPs and runs simulated-pattern-matching repair/re-index sweeps offline, iteratively fixing mis-indexed and unsolved pixels. Also has parent-grain reconstruction.
  • EDAX OIM Analysis and Bruker ESPRIT — both index/re-index stored patterns and produce ODFs, grain maps, KAM, phase maps. (overview)

Open-source (fully scriptable — best fit for CALIBER):

  • kikuchipy (Python) — loads raw pattern HDF5, does static background correction, dictionary indexing, hybrid indexing, orientation refinement. This is the natural programmatic backbone for a "load patterns → sweep parameters → score → repeat" loop.
  • EMsoft — simulates the dynamical master patterns that DI/spherical indexing rely on.
  • MTEX (MATLAB) — the standard for post-indexing orientation analysis: grain reconstruction, ODF/texture, KAM, parent-grain reconstruction. It consumes orientation data rather than raw patterns, so it pairs with kikuchipy/EMsoft.

Recommendation for CALIBER

The key architectural choice is to save raw patterns during acquisition (HDF5), which decouples indexing from the microscope and makes every downstream parameter tunable and re-runnable. For an automated, parameter-optimizing tool the strongest foundation is the open-source Python stack — kikuchipy (dictionary/hybrid indexing) + EMsoft (master patterns) + MTEX or orix (grain/texture analysis) — because it's fully scriptable, so CALIBER can programmatically sweep indexing/clean-up/reconstruction parameters and score grain-characterization quality automatically. AZtecCrystal/MapSweeper is the best turnkey GUI equivalent if you'd rather tune interactively before automating.

I've left the committed doc as-is (it covers acquisition parameter selection); if you'd like, I can add a companion docs/ebsd-postprocessing-methods.md capturing the above and wire in kikuchipy as a concrete dependency.

Sources: kikuchipy pattern matching · kikuchipy hybrid indexing · A Dictionary Approach to EBSD Indexing (arXiv) · EMsoft DI wiki · AZtecCrystal · MapSweeper pattern matching · Parent grain reconstruction · EBSD platform considerations


  • Read request and existing doc
  • Confirm failed-job status (earlier runs already terminated; Actions jobs can't be cancelled by me)
  • Research common EBSD post-processing methods
  • Identify software for re-indexing raw patterns with tweakable parameters
  • Post answer

@ronnie-guymon

Copy link
Copy Markdown
Collaborator Author

@sgbaird is there software you have used or are aware of that I should look into using for our own post processing? If not that's ok, I'll just experiment and find one.

@ronnie-guymon

Copy link
Copy Markdown
Collaborator Author

Claude finished @ronnie-guymon's task in 1m 34s —— View job

EBSD Post-Processing — Common Methods & Iterative-Tweaking Tools

Short answer to your second question: yes — this workflow exists and it's exactly where the field is heading. It's called pattern reprocessing / re-indexing, and it requires that you save the raw EBSPs (diffraction patterns) during acquisition, not just the indexed orientation map. Once you have the raw patterns on disk, you can re-run indexing and grain reconstruction as many times as you like, tweaking parameters each pass, without ever going back to the microscope.

1. The two stages of EBSD post-processing

Professionals treat post-processing as two distinct stages. It helps to keep them separate because your "tweak parameters" question lives mostly in Stage A.

Stage A — Indexing (raw pattern → crystal orientation). Three approaches dominate:

Method How it works When professionals use it
Hough/Radon indexing Detects Kikuchi bands, solves orientation from band positions. Fast (real-time). The default for clean, well-diffracting materials.
Dictionary Indexing (DI) Compares each measured pattern against a dictionary of simulated master patterns; picks best match. Slow but robust. Noisy patterns, deformed material, fine/overlapping phases, low signal — where Hough fails.
Spherical-harmonic / pattern matching (EMSphInx, AZtec MapSweeper) Cross-correlation in the spherical-harmonic domain — DI-quality accuracy at much higher speed. Large maps needing DI robustness without the DI time cost.
Stage B — Microstructure analysis (orientation map → grains, texture, GND, etc.): grain reconstruction (misorientation-threshold flood-fill, typically ~5°), clean-up (neighbour-orientation correlation, grain dilation, CI/MAD/band-contrast thresholding), grain-size statistics, texture/ODF, KAM/GND, phase maps. This is the downstream analysis your existing docs/ebsd-parameter-selection.md §4, §8, §9 already reasons about.

2. "Upload raw patterns, then constantly tweak parameters" — the tools that do this

This is the re-indexing / pattern-matching workflow. Prerequisite: acquire with "save patterns" enabled (Oxford .ebsp, EDAX .up1/.up2, or HDF5). Then:

Commercial (GUI, interactive tweaking):

  • Oxford AZtecCrystalMapSweeper re-indexes stored EBSPs by simulated pattern matching; built for very large CMOS-detector datasets. Closest to your "upload and keep tuning in the software" description. (product page)
  • EDAX/TSL OIM Analysis and Bruker ESPRIT — mature GUIs for re-indexing + reconstruction + clean-up, all interactively adjustable.

Open-source (scriptable — best fit for a Python project like CALIBER):

  • kikuchipy — Python/HyperSpy toolbox for loading raw patterns, background correction, dictionary indexing, hybrid indexing, and orientation refinement. Reads .ebsp, .up1/2, HDF5. This is the natural programmatic home for an iterate-on-parameters loop.
  • PyEBSDIndex (US Naval Research Lab) — GPU Hough/Radon indexing, up to ~45,000 patterns/s, so re-indexing a whole map with new parameters takes seconds.
  • EMsoft / EMSphInx — reference dictionary-indexing and spherical-harmonic engines; kikuchipy's DI is based on EMsoft.
  • MTEX — the standard MATLAB toolbox for Stage B (grain reconstruction, texture), fully parameter-driven and reproducible.
  • orix — crystal-orientation/symmetry backend that kikuchipy builds on.

3. Recommendation for CALIBER

Given CALIBER is Python and the goal is sample-specific parameter recommendation with an iterate-to-best loop, the strongest foundation is:

  • kikuchipy + PyEBSDIndex + orix for the re-indexing engine (Stage A), with EMsoft/EMSphInx if you need full dictionary-indexing accuracy on hard samples, and
  • MTEX (or an orix-based reconstruction) for Stage B grain reconstruction and statistics.

This maps cleanly onto CALIBER's premise: keep raw patterns, then let the software sweep the trade-off curves documented in ebsd-parameter-selection.md (indexing band count, Hough binning, CI/MAD clean-up thresholds, misorientation/step-size for grain reconstruction) and report the parameter set that maximizes an objective grain-characterization metric (e.g. indexing success rate + CI/MAD + reconstruction stability) — all offline, no re-acquisition needed. The one hard requirement to design in early: acquisition must save the raw EBSPs, otherwise re-indexing is impossible and you're limited to Stage B clean-up only.

Happy to draft a short docs/ebsd-post-processing.md capturing this two-stage framing and the recommended stack if useful — just say the word.

Sources: AZtecCrystal 4.0 · kikuchipy (pattern matching) · PyEBSDIndex · EMsoft · EMSphInx · MTEX grain reconstruction · DI hands-on tutorial

  • Read existing docs/ebsd-parameter-selection.md
  • Research common EBSD post-processing methods
  • Identify tools supporting raw-pattern upload + iterative tweaking
  • Recommend best fit for CALIBER
  • Post answer

This was a question, so no code changes were made.

@claude I have raw patterns I can use to try this out. give me a simple workflow I can try to follow, and for this run make sure the required software is free

@claude

claude Bot commented Jul 24, 2026

Copy link
Copy Markdown

Claude finished @ronnie-guymon's task in 2m 22s —— View job


A free, end-to-end EBSD re-indexing workflow you can run today

Everything below is free and open source (BSD/GPL, pip-installable). No AZtecCrystal, no OIM Analysis, no MATLAB required. The one paid-adjacent tool (MTEX) is called out explicitly at the end so you can skip it.

The core idea: your raw patterns make indexing an offline, repeatable step. You re-run it with different settings as often as you like and score the result automatically — that's the loop CALIBER is meant to automate.


Step 0 — Check what you actually have

kikuchipy reads raw patterns from all the common vendor containers, so check your file extension first (supported formats):

Extension Vendor Notes
.h5oina Oxford pass processed=False to get unprocessed patterns
.ebsp Oxford uncompressed only; versions 4 and 6
.up1 / .up2 EDAX 8- or 16-bit; needs nav_shape if not stored
.h5 / .h5ebsd EDAX or Bruker h5ebsd

If you only have a .ang/.ctf (orientations, no patterns), stop — you can do Stage B clean-up but not re-indexing.

Step 1 — Install (all free)

pip install kikuchipy pyebsdindex orix matplotlib
  • kikuchipy — loading, background correction, indexing orchestration, refinement
  • PyEBSDIndex — the actual Hough/Radon indexing engine (US Naval Research Lab; very fast, optional GPU via pip install pyebsdindex[gpu])
  • orix — crystal orientations/symmetry + .ang export

Step 2 — Load, correct background, index

This is the whole first pass. Adapt the phase and the pattern-centre guess to your material/geometry.

import kikuchipy as kp
from orix.crystal_map import PhaseList
from orix import io

s = kp.load("patterns.h5oina", processed=False)   # or .ebsp / .up1 / .h5
s.remove_static_background()
s.remove_dynamic_background()

phase_list = PhaseList(names=["ni"], space_groups=[225],
                       structures=[...])           # your phase

det = kp.detectors.EBSDDetector(s.axes_manager.signal_shape[::-1],
                                sample_tilt=70)

indexer = det.get_indexer(
    phase_list,
    [[1, 1, 1], [2, 0, 0], [2, 2, 0], [3, 1, 1]],  # reflectors
    nBands=10, tSigma=2, rSigma=2,
)

# Calibrate the pattern centre on a small grid of patterns, not the whole map
s_grid, idx = s.extract_grid((5, 5), return_indices=True)
det = s_grid.hough_indexing_optimize_pc(
    pc0=[0.42, 0.22, 0.50], indexer=indexer,
    batch=True, method="PSO", search_limit=0.05,
)
indexer = det.get_indexer(phase_list, nBands=10, tSigma=2, rSigma=2)

xmap = s.hough_indexing(phase_list=phase_list, indexer=indexer, verbose=2)
io.save("run01.ang", xmap)

PC calibration is the step that most often makes or breaks a first run — a pattern-centre error maps almost linearly into orientation error (see docs/ebsd-parameter-selection.md §5). Do it before you blame anything else.

Step 3 — Score the run, then sweep

xmap.prop gives you the objective numbers to optimize against: fit (mean angular deviation, lower is better — cf. §9's MAD < ~1°), cm (confidence metric), pq (pattern quality), nmatch (bands matched).

score = {
    "indexed_frac": (xmap.fit < 1.4).mean(),
    "mean_fit":     xmap[xmap.is_indexed].fit.mean(),
}

Then loop the knobs that matter, keeping the best score:

  • nBands (7–12) — more bands over-determine the fit → fewer misindexes, slower (§7)
  • tSigma / rSigma — Hough (θ, ρ) smoothing, tuned to expected band width (§7)
  • background correction on/off, and a signal_mask to exclude dead detector corners
  • reflector list — adding/removing families changes indexing success a lot

This sweep is the "constantly tweak parameters" experience, just scripted instead of clicked — which is strictly better, because every run is reproducible and you get a number to rank them by.

Step 4 — Refine, and escalate only if you need to

Cheap refinement of the Hough solution:

xmap_ref = s.refine_orientation(xmap=xmap, detector=det, master_pattern=mp,
                                energy=20, method="minimize")

If Hough indexing plateaus at a poor success rate (deformed material, noisy or low-symmetry patterns), escalate to dictionary indexing — matching each pattern against simulated master patterns. Also free, but heavier: it needs a dynamical master pattern from EMsoft, which is a compile-from-source step. Don't start here; start with Hough.

from orix import sampling
R   = sampling.get_sample_fundamental(method="cubochoric", resolution=3, point_group=ph.point_group)
sim = mp.get_patterns(rotations=R, detector=det, energy=20, compute=True)
xmap = s.dictionary_indexing(sim, metric="ncc", keep_n=20, signal_mask=signal_mask)

Step 5 — Grains and statistics from the .ang

Your run01.ang now drops into any Stage-B tool. Free options only:

  • DREAM3D-NX — free GUI, open source. Reads .ang/.ctf, does clean-up, segmentation, grain statistics as a saved, re-runnable pipeline — so you can retune thresholds and replay in one click. This is the closest free equivalent to the commercial GUI experience you described.
  • orix — already installed; enough for orientation/misorientation/texture analysis directly on the CrystalMap in Python.
  • MTEX — the field standard for grain reconstruction (calcGrains, ~5° misorientation threshold). ⚠️ MTEX itself is free but requires MATLAB, which is not — skip it unless you already have a MATLAB license.

Realistic expectations for a first attempt

  1. First indexing run will probably look bad. That's the PC, not your sample.
  2. Get PC optimization converging on a 5×5 grid before indexing the full map.
  3. Only then sweep nBands / tSigma / rSigma / background correction.
  4. Export .ang, reconstruct grains, and check the grain count against §8's statistics requirement — if you have fewer than a few hundred grains, your grain-size mean isn't trustworthy regardless of how good the indexing is.

Say the word and I'll commit this as a runnable scripts/reindex_sweep.py plus a companion doc; I've kept it comment-only for now since the PR is currently docs-only.

Sources: kikuchipy Hough indexing · kikuchipy pattern matching · kikuchipy load/save · PyEBSDIndex · orix · EMsoft DI · DREAM3D-NX · MTEX grain reconstruction


  • Read existing docs/ebsd-parameter-selection.md
  • Verify kikuchipy / PyEBSDIndex / orix APIs against official docs
  • Confirm supported raw-pattern file formats
  • Restrict every recommendation to free software (MTEX/MATLAB caveat flagged)
  • Write step-by-step workflow with runnable code
  • Post answer

No code was committed — this was a question, and the workflow is comment-only until you want it as a script.
· branch claude/issue-2-20260724-1601

@ronnie-guymon

Copy link
Copy Markdown
Collaborator Author

Using a Jupyter Lab notebook allows me to see the entire scanned area, as well as the pattern for that area. I took these steps from https://kikuchipy.org/en/stable/tutorials/load_save_data.html. Apparently the widget is supposed to allow me to click anywhere on the scanned area to select which pattern I want to see, but I can only see the default (0,0) pattern unless I use s.inav[x,y].plot(). So the widget pretty much acts the same as if I did %matplotlib inline. The tool bars work for the widget, like zooming in or moving the map/pattern around, but actually clicking on the map doesn't navigate to a new pattern like it's supposed to. I tried %matplotlib qt as well which is supposed to work be interactive too, but it gave the exact same results as widget just with the extra step of opening two new windows, on for the scanned area and one for the pattern. Chat thinks that there might be some incompatibility with me using Python 3.14. @claude any ideas on how I could get this to be interactive? I'm doing all this in VS code btw

%matplotlib widget
import os
from pathlib import Path
import tempfile

import dask.array as da
import imageio.v3 as iio
import numpy as np
import matplotlib.pyplot as plt

import hyperspy.api as hs
import kikuchipy as kp

data_path = Path(r"C:\Users\rongu\Box\Baird_S_EBSD\Ronnie Guymon\AlSi10Mg EBSD\AlSi10Mg Part 2\Area 2\map20260707104751409.up2")
s = kp.load(data_path)
s.plot()

@claude

claude Bot commented Jul 24, 2026

Copy link
Copy Markdown

Claude finished @ronnie-guymon's task in 1m 28s —— View job


Why clicking on the map doesn't navigate — and how to fix it

TL;DR: plain clicking has never been the navigation gesture in HyperSpy 2.x. Use Shift + click on the navigator to jump to a position, or drag the square marker, or use Ctrl + arrow keys. Your setup is almost certainly working fine already.

The interaction model (this is the actual fix)

Since HyperSpy 2.0, the navigator interactions are (visualization docs):

  • Shift + left-click anywhere on the scanned-area map → jumps the pointer there and updates the pattern. This is the "click anywhere to select a pattern" behavior you were expecting — it just requires the modifier key.
  • Click-and-drag the square marker (the small square sitting at (0,0) on your map) → drags the pointer around continuously.
  • Ctrl + arrow keys (with the navigator figure focused) → step pixel-by-pixel. Plain arrow keys were deliberately removed in 2.0 because matplotlib binds left/right arrows to zoom history. PageUp/PageDown change the step size, +/- change the pointer size.

One extra gotcha: if a toolbar tool (zoom or pan) is active, it captures mouse drags, so deselect it before trying to drag the marker.

The symptom you describe — toolbar zoom/pan works, s.inav[x, y].plot() works, but clicks don't navigate — is exactly what a correctly working setup looks like when plain click is used instead of Shift+click. That also explains why %matplotlib qt behaved identically: same interaction model, just in separate windows.

On the Python 3.14 theory

Chat's guess is reasonable but likely wrong here: HyperSpy 2.4.0 (released 2026-01-26) added explicit Python 3.14 support (changelog), and 2.4.0 also added ipympl fixes for newer ipykernel. Worth a quick check that you're actually on it:

import hyperspy, kikuchipy, ipympl
print(hyperspy.__version__, kikuchipy.__version__, ipympl.__version__)

If hyperspy.__version__ is ≥ 2.4.0, Python 3.14 is a non-issue. If it's older, pip install -U hyperspy kikuchipy ipympl inside your environment.

VS Code specifics (only if Shift+click still doesn't work)

  • %matplotlib widget in VS Code requires ipympl installed in the same environment as the notebook kernel (pip install ipympl), and VS Code sometimes needs a kernel restart + window reload after installing it before mouse events reach Python.
  • Since your toolbar pan/zoom already works, the ipympl event pipeline is functioning — another sign the issue is the gesture, not the backend.
  • If you ever want the two panes side-by-side instead of stacked in the notebook, HyperSpy supports a horizontal layout with ipympl: s.plot() then hs.preferences.gui() → Plot, or pass plot_style="horizontal".

Suggested sanity check

%matplotlib widget
import kikuchipy as kp
s = kp.load(data_path)
s.plot()
# Now: hold Shift and click anywhere on the scanned-area map

If that jumps the pattern view, you're done — and this modifier-click detail is worth remembering for CALIBER docs later, since everyone hits it the first time.

Sources: HyperSpy visualization docs · HyperSpy changelog (2.4.0: Python 3.14 support; 2.0: Shift+click navigation) · HyperSpy install guide


  • Understand the reported behavior (widget/qt backends show only the (0,0) pattern; clicks don't navigate)
  • Verify HyperSpy's supported Python versions (3.14 supported since HyperSpy 2.4.0, Jan 2026)
  • Check how HyperSpy navigation actually works (Shift+click / drag marker / Ctrl+arrows — plain click is not a navigation gesture)
  • Check ipympl/VS Code considerations (ipympl must be in the kernel env; toolbar working means events are flowing)
  • Post concrete troubleshooting steps

No code changes — this was a question.

@sgbaird

sgbaird commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

@ronnie-guymon @claude see vertical-cloud-lab/byu-vcl#95 (comment)

@claude, look around thereabouts for additional context.

kikuchipy probably makes sense to use. But, I haven't done a lot of this so it's very much about testing out tools and finding the right ones. If we have real data, that means we can do a lot of empirical testing of different methods and settings, too.

@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown

Claude finished @sgbaird's task in 1m 39s —— View job


Context from byu-vcl PR 95 — and why it strengthens the kikuchipy pick

Todo

  • Read trigger comment and load tools
  • Fetch the referenced comment and surrounding discussion in vertical-cloud-lab/byu-vcl PR 95
  • Connect that context to this PR's EBSD post-processing discussion
  • Post synthesis and recommendation

What I found "thereabouts"

The linked comment (byu-vcl#95, comment 4272328728) is the open-source EDS stack recommendation: HyperSpy + eXSpy as the core, with sigma/SIGMA2 for phase clustering and pyEDS for map denoising. The surrounding thread ("Quantitative compositional analysis") adds a lot of directly relevant history:

  • Hardware/software: Apreo SEM with EDAX TEAM — so your raw patterns are .up1/.up2 (matching the file @ronnie-guymon already loaded in this PR), and EDAX conventions apply throughout.
  • Known empirical baselines from the sessions with Paul: elemental Al phase gave CI ≈ 0.1, the custom AlSi10Mg phase gave CI ≈ 0.94; raising beam current 1.6 → 6.4 nA moved the CI > 0.1 fraction from ~40% to ~77%.
  • Open hypotheses: eutectic Si (diamond-cubic) causing indexing noise; whether to add Si as a second phase; which cleanup filters (CI standardization → dilation) are defensible; PXRD as the source of lattice parameters for future novel Al alloys.
  • The standing decision (comment 4878877592): always save raw patterns so reindexing with open-source packages is possible — which is exactly the workflow this PR's discussion converged on.

Why this context reinforces kikuchipy

  1. One ecosystem for both modalities. eXSpy (EDS) and kikuchipy (EBSD) are both HyperSpy extensions — same hs.load/s.plot() idioms, same environment, same lazy/dask handling of big maps. The learning curve Ronnie is already climbing (widget navigation, inav, etc.) pays off twice.
  2. Native EDAX support. kikuchipy reads .up1/.up2 directly, and its Hough engine (PyEBSDIndex, from NRL) uses EDAX-compatible conventions — so offline results are comparable against TEAM's on-scope numbers.
  3. Scriptable = empirical testing. Since you have real data with known on-scope outcomes, every open microstructure question in the byu-vcl thread becomes a reproducible offline experiment instead of scope time.

Concrete empirical tests your existing data already supports

These use the Area 2 .up2 (and any future saved-pattern scans), each with a measurable outcome to compare against the TEAM baselines:

  1. Baseline reproduction — Hough-index the raw patterns with default settings; does the CI/fit distribution roughly match TEAM's report (~77% CI > 0.1)? This validates the offline pipeline before any tuning.
  2. Phase-list experiment — index three ways: elemental Al, custom AlSi10Mg lattice parameters, and two-phase Al + diamond-cubic Si. This directly tests both the CI 0.1 → 0.94 observation and Ronnie's eutectic-Si hypothesis, offline, for free.
  3. Parameter sweepsnBands, tSigma/rSigma, binning, PC optimization (the knobs in docs/ebsd-parameter-selection.md §5, §7), scored by indexed fraction + mean fit.
  4. Cleanup sensitivity — sweep CI thresholds and dilation passes (§9) and plot how grain count/size statistics move; this quantifies how much cleanup you can apply before biasing the grain characterization sgbaird flagged as a concern.
  5. Escalation test — dictionary indexing on the patterns Hough fails on, to see whether the residual noise is pattern-quality-limited or indexing-method-limited.

That set is basically CALIBER's proof-of-concept: same data, varied method/settings, objective score. Happy to commit test No. 1 and No. 2 as a runnable script against the Area 2 file when you want to start — that's the natural first commit beyond docs.


· branch claude/issue-2-20260724-1601

@sgbaird

sgbaird commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

That's why the "public share link" is nice. Claude can access that directly. Alternatively, it can probably use what comes via globus too

@ronnie-guymon ronnie-guymon mentioned this pull request Jul 27, 2026
@ronnie-guymon

Copy link
Copy Markdown
Collaborator Author

@claude help me understand what all I can do using kikuchipy, and at what point I would want to move on to MTEX. For me, everything is being compared to TEAM software. So what can kikuchipy do that is comparable to TEAM, and what does MTEX do that is comparable? Now that I have my patterns uploaded and I'm able to navigate around thanks to your shift+click advice, how do I go through with detecting patterns, seeing the CI and fit angle of what patterns are being detected, etc?

@claude

claude Bot commented Jul 27, 2026

Copy link
Copy Markdown

Claude finished @ronnie-guymon's task in 2m 5s —— View job


kikuchipy vs MTEX vs TEAM — who does what, and your next concrete steps

Todo

  • Read trigger request and repo context
  • Verify kikuchipy Hough-indexing outputs (CI/fit equivalents) against official docs
  • Map kikuchipy vs MTEX vs TEAM capabilities
  • Write concrete next-steps workflow (indexing + inspecting CI/fit maps)
  • Post answer

The mental model

TEAM does three jobs in one program: it acquires patterns, indexes them live (band detection → CI, fit, IQ), and gives you basic maps (IPF, IQ, phase). Offline, that splits into two tools:

  • kikuchipy replaces TEAM's indexing engine — pattern in, orientation + quality metrics out. It ends at "every pixel has an orientation, a CI-equivalent, and a fit angle."
  • MTEX replaces TEAM's (really OIM Analysis's) microstructure analysis — orientations in, grains/texture/statistics out. It never touches raw patterns.

The .ang file is the handoff between them. So the answer to "when do I move to MTEX" is: the moment you're satisfied with the indexing metrics and want grains, grain sizes, or texture. You'll bounce back to kikuchipy only if grain reconstruction reveals the indexing wasn't good enough after all.

Feature-by-feature vs TEAM

TEAM feature kikuchipy equivalent MTEX equivalent
Band detection (Hough) s.hough_indexing() (PyEBSDIndex engine, EDAX-style conventions)
CI xmap.cm — confidence metric, computed with the same vote-based scheme as EDAX's CI reads ci from .ang; filter with ebsd(ebsd.ci > 0.1)
Fit angle xmap.fit — mean angular deviation between detected and theoretical bands reads fit from .ang
IQ xmap.pq (Hough pattern quality); also s.get_image_quality() reads iq; can plot IQ maps
Bands matched xmap.nmatch
PC calibration s.hough_indexing_optimize_pc() — and re-runnable, unlike TEAM
Phase setup PhaseList (your custom AlSi10Mg lattice params go here) crystalSymmetry per phase
IPF / phase maps via orix (IPFColorKeyTSL) plot(ebsd, ebsd.orientations) — richer
Grain reconstruction, grain size not in kikuchipy calcGrains(...,'angle',5*degree) — the field standard
Cleanup (CI standardization, dilation) threshold-only fill, smooth, grain-based filtering
Texture / ODF / pole figures not in kikuchipy (orix has basics) first-class — this is MTEX's home turf
KAM / GND not built in ebsd.KAM, ebsd.calcGND
What TEAM can't do at all dictionary indexing, refinement against simulated patterns, unlimited re-indexing parent-grain reconstruction

MTEX caveat as before: MTEX is free but runs on MATLAB — as BYU folks you almost certainly have a campus MATLAB license, so this is likely a non-issue. (Free alternatives if not: DREAM3D-NX GUI, or orix for basic orientation analysis in the same notebook.)

Your next steps: index the Area 2 map and see CI/fit

You're at s = kp.load(...up2) + navigation. Here's the continuation (Hough indexing tutorial):

# pip install pyebsdindex  (kikuchipy calls it under the hood)
from diffpy.structure import Atom, Lattice, Structure
from orix.crystal_map import Phase, PhaseList
from orix import io, plot
import matplotlib.pyplot as plt

s.remove_static_background()
s.remove_dynamic_background()

# Your custom AlSi10Mg phase — use the lattice parameter from your TEAM phase (~4.05 Å fcc)
phase_list = PhaseList(Phase(
    name="alsi10mg", space_group=225,
    structure=Structure(atoms=[Atom("Al", [0, 0, 0])],
                        lattice=Lattice(4.05, 4.05, 4.05, 90, 90, 90)),
))

sig_shape = s.axes_manager.signal_shape[::-1]
det = kp.detectors.EBSDDetector(sig_shape, sample_tilt=70)
indexer = det.get_indexer(phase_list, nBands=10, tSigma=2, rSigma=2)

# Calibrate PC on a small grid first — pc0 guess can come from TEAM's calibration
s_grid, _ = s.extract_grid((5, 5), return_indices=True)
det = s_grid.hough_indexing_optimize_pc(
    pc0=[0.5, 0.5, 0.6], indexer=indexer, batch=True, method="PSO", search_limit=0.1)
indexer = det.get_indexer(phase_list, nBands=10, tSigma=2, rSigma=2)

xmap = s.hough_indexing(phase_list=phase_list, indexer=indexer, verbose=2)

Seeing CI and fit — the TEAM-style quality maps:

fig, axes = plt.subplots(2, 2, figsize=(10, 8))
for ax, metric in zip(axes.ravel(), ["cm", "fit", "pq", "nmatch"]):
    im = ax.imshow(xmap.get_map_data(metric))
    fig.colorbar(im, ax=ax, label=metric)

print("CI > 0.1 fraction:", (xmap.cm > 0.1).mean())   # compare to TEAM's ~77%
print("mean fit (deg):", xmap.fit.mean())              # want < ~1°, per docs §9

That (xmap.cm > 0.1).mean() line is your direct comparison to the TEAM baselines from the byu-vcl thread — it validates the offline pipeline against what you saw on-scope.

IPF map (TEAM's orientation map):

ckey = plot.IPFColorKeyTSL(xmap.phases[0].point_group)
xmap.plot(ckey.orientation2color(xmap.orientations), overlay="cm")

Handoff to MTEX once the metrics look right:

io.save("area2_run01.ang", xmap)
ebsd = EBSD.load('area2_run01.ang', 'convertEuler2SpatialReferenceFrame', 'setting 2');
ebsd = ebsd(ebsd.ci > 0.1);
[grains, ebsd.grainId] = calcGrains(ebsd('indexed'), 'angle', 5*degree);
grains = grains(grains.grainSize >= 10);   % the ≥10-points-per-grain rule, docs §4
plot(grains, grains.meanOrientation)

Practical notes

  1. PC first. If cm is uniformly low and fit high everywhere, it's almost always the pattern centre, not the phase or the sample. Get the 5×5 optimization converging before tweaking anything else.
  2. kikuchipy tools TEAM has no equivalent for, worth trying on this dataset: s.get_image_quality() and virtual BSE imaging before indexing (a "what does my data look like" check), geometrical simulations overlaid on a pattern to verify the phase/PC visually, and s.refine_orientation() / dictionary indexing if Hough plateaus.
  3. Iterate in kikuchipy, analyze in MTEX. Sweeping nBands/tSigma/rSigma and re-running takes seconds; each run gives a new .ang you can drop into the same MTEX script. That loop — score by cm/fit, then check grain-count stability in MTEX — is the CALIBER proof-of-concept in miniature.

Sources: kikuchipy Hough indexing · kikuchipy PC optimization · PyEBSDIndex · orix · MTEX grain reconstruction · MTEX EBSD import · docs/ebsd-parameter-selection.md

No code changes — this was a question.
· branch claude/issue-2-20260724-1601

@sgbaird

sgbaird commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

That's why the "public share link" is nice. Claude can access that directly. Alternatively, it can probably use what comes via globus too

@ronnie-guymon could you either get a public share/download link to the globus transferred items or copy the patterns over to this folder example https://byu.box.com/s/hz48nsx7ciln1jifm3u0yo5vqpge2lma (within our byu-vcl-shared box folder, which you should have access to)? That way claude can do some testing and analysis of this.

@ronnie-guymon

Copy link
Copy Markdown
Collaborator Author

That's why the "public share link" is nice. Claude can access that directly. Alternatively, it can probably use what comes via globus too

@ronnie-guymon could you either get a public share/download link to the globus transferred items or copy the patterns over to this folder example https://byu.box.com/s/hz48nsx7ciln1jifm3u0yo5vqpge2lma (within our byu-vcl-shared box folder, which you should have access to)? That way claude can do some testing and analysis of this.

I had to combine the 3 pattern zip files into one zip file since it didn't like me using files ending with ".zip.001" and just wanted ".zip". The combined .zip file, as well as a the folder containing the files from the zip file, are now uploaded. I did both to see how well claude would handle the zip file. In the future, I will probably take the saved patterns and combine them into one file, or at least rename the files in a way that ends in .zip before uploading through globus so we can just give claude the public share/download link. @claude how well do you handle zip files? and could you combine zip files together in a way that would be compliant with how kikuchipy wants them named? try some testing of what you mentioned above with kikuchipy using https://byu.app.box.com/folder/403652824587?s=hz48nsx7ciln1jifm3u0yo5vqpge2lma

@claude

claude Bot commented Jul 27, 2026

Copy link
Copy Markdown

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


Zip handling + a real kikuchipy test on your Area 2 patterns

I could reach the Box link directly, so I ran an actual test end-to-end. Short version: zip handling is a non-issue, and kikuchipy indexed your real AlSi10Mg patterns with 99% CI > 0.1 on the slice I pulled.


1. How I handle zips (I never downloaded the 2 GB)

Box serves the file with HTTP range support, so I read the zip's central directory from the tail and pulled only the bytes I needed — no 2 GB download. The archive AlSi10Mg Part 2.zip (2.03 GiB) contains one folder:

File Size What it is
Area 2/map20260707104751409.up2 2.15 GB the raw EBSPs (EDAX 16-bit patterns)
Area 2/map20260707104751409.osc 18.7 MB OIM scan/orientation container
Area 2/map20260707104751409.dat 9.4 MB EDAX binary data
Area 2/map20260707104751409.par 1.1 KB EDAX parameter blob

Because the .up2 is stored uncompressed inside the zip, I range-read just the header + first 25 map rows (4,900 patterns, ~120 MB) instead of the whole thing. So: I handle zips fine, including reading/extracting selectively from a huge remote one. You don't need to pre-extract for me — a link to the .zip (or the raw .up2) both work.

2. Combining zips + "kikuchipy-compliant naming" — clearing up the confusion

The naming issue you hit isn't a kikuchipy thing at all:

  • .zip.001, .zip.002, .zip.003 are split-archive volumes, not three separate zips. They're one archive cut into pieces (7-Zip's "split to volumes"). You don't extract .001 on its own; you reassemble first. On Windows: copy /b "name.zip.001"+"name.zip.002"+"name.zip.003" "name.zip" (or just open the .001 in 7-Zip and it stitches them automatically). What you did — merging into one .zip — was the right instinct; you just did manually what 7-Zip does for you.
  • kikuchipy never reads zips and has no zip naming rule. It reads the extracted EDAX files. The only naming that matters is that after unzipping, the pattern file and its companions share one basename in one folder: map20260707104751409.up2 alongside ….osc / ….ang. Then you point kp.load() at the .up2. The zip's name is irrelevant.
  • For future sharing, don't wrap it in a zip on kikuchipy's account — either share the raw .up2 directly, or (better) export/convert to a single HDF5 (.h5/.oh5), which bundles patterns + metadata in one file and is kikuchipy's preferred container. A public Globus/Box link to that is the cleanest hand-off.

3. I ran your mentioned-above kikuchipy workflow — real numbers

Installed the free stack (kikuchipy 0.13, PyEBSDIndex 0.3.10, orix 0.14) and indexed the 4,900-pattern slice. Two useful discoveries about your file first:

  • The .up2 header already stores the map geometry: version 3, 111×111 px patterns, 196 × 446 square grid (87,416 pts), 2.0 µm step. So kp.load("...up2") works without a nav_shape argument — kikuchipy reads it from the header.
  • EDAX .up2 carries no static-background frame, so remove_static_background() fails unless you supply one. Fix: use the map's mean pattern as the flat-field (one line, below).

Results — Hough indexing, 4,900 real patterns, PC auto-optimized:

Metric (kikuchipy → TEAM equivalent) Value
cm (≈ CI) mean / median 0.62 / 0.67
fraction CI > 0.1 99.2%
fit (mean angular deviation), median 0.53°
fraction fit < 1.4° 99.5%
points indexed 4,876 / 4,900
speed (CPU, no GPU) ~433 patterns/s

That 99.2% CI > 0.1 is above the ~77% TEAM baseline from the byu-vcl thread — with the caveats that this is only the top 25 of 446 rows (~6% of the map) and the pattern centre was fit on that same slice, so full-map numbers will differ. But it confirms the offline pipeline reproduces (and here beats) the on-scope result on your actual data.

The exact recipe that worked on your file (the two gotchas folded in):

import numpy as np, kikuchipy as kp
from diffpy.structure import Atom, Lattice, Structure
from orix.crystal_map import Phase, PhaseList

s = kp.load(r"...\Area 2\map20260707104751409.up2")   # nav_shape read from header (196x446)

s.static_background = s.data.mean(axis=(0, 1)).astype(s.data.dtype)  # up2 has none
s.remove_static_background(operation="subtract")
s.remove_dynamic_background()

pl = PhaseList(Phase(name="alsi10mg", space_group=225,
    structure=Structure(atoms=[Atom("Al", [0, 0, 0])],
                        lattice=Lattice(4.05, 4.05, 4.05, 90, 90, 90))))

sig = s.axes_manager.signal_shape[::-1]
det = kp.detectors.EBSDDetector(sig, sample_tilt=70.0)
indexer = det.get_indexer(pl, nBands=9, tSigma=2, rSigma=2)

s_grid, _ = s.extract_grid((5, 5), return_indices=True)
det = s_grid.hough_indexing_optimize_pc(pc0=[0.5, 0.5, 0.6], indexer=indexer,
                                        batch=False, method="PSO", search_limit=0.2)
indexer = det.get_indexer(pl, nBands=9, tSigma=2, rSigma=2)

xmap = s.hough_indexing(phase_list=pl, indexer=indexer)
print((xmap.cm > 0.1).mean(), np.median(xmap.fit))   # 0.99, ~0.5 deg on my slice

Note batch=False in the PC step — batch=True currently trips a reshape bug in 0.13 when the extracted grid isn't perfectly rectangular.

Two things worth doing next

  1. Run the full map, not a slice, and add Si as a second phase (diamond-cubic) to test the eutectic-Si indexing-noise hypothesis from byu-vcl PR 95 — that's the phase-list experiment that directly probes the CI 0.1 → 0.94 story.
  2. This is exactly the loop CALIBER should wrap: load → bg-correct → sweep {PC, nBands, tSigma/rSigma, phase list} → score by cm/fit. I can commit a runnable scripts/reindex_sweep.py doing this (parameterized so it runs on any .up2) plus a short doc capturing the verified .up2 spec above — say the word and I'll add it to this PR.

I didn't commit anything (PR is docs-only and the 2 GB data doesn't belong in git). No secrets touched; I accessed the Box link anonymously via its public share token.

Todo: ✅ access Box · ✅ enumerate zip remotely · ✅ extract small files + pattern slice · ✅ install free stack · ✅ Hough-index real patterns (CI/fit) · ✅ answer zip/naming questions
· branch claude/issue-2-20260724-1601

@sgbaird

sgbaird commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

@claude download the full 2 GB and process in-full, then report back to @ronnie-guymon

@claude

claude Bot commented Jul 27, 2026

Copy link
Copy Markdown

Claude finished @sgbaird's task in 2m 53s —— View job


Processing the full 2 GB Area 2 dataset…

Todo

  • Install free stack (kikuchipy 0.13.0 + PyEBSDIndex 0.3.10 + orix 0.14.3)
  • Download the full .up2 (2.15 GB) from the Box share — complete, byte-exact
  • Verify header/geometry (up2 v3, 196×446 map, 111×111 px patterns, 2.0 µm step ✓)
  • Background-correct + optimize pattern centre (running)
  • Hough-index the full 87,416-pattern map
  • Generate CI / fit / IQ / IPF maps and full-map statistics
  • Commit plots and report back to @ronnie-guymon

@ronnie-guymon

Copy link
Copy Markdown
Collaborator Author

@claude also, with the same portion of the patterns you processed in the first run, try it three more times. One time do just an Al phase instead of AlSi10Mg, one with AlSi10Mg and Si phase, and one with Al and Si phase. Building off of this, let's say that the phase makes a difference. Is it possible to build your own phases in kikuchipy? We can figure out how useful that would be after seeing the results of this test.

@claude

claude Bot commented Jul 27, 2026

Copy link
Copy Markdown

Claude finished @ronnie-guymon's task in 12m 27s —— View job


Phase-list experiment on the same 4,900-pattern slice — result: the phase list changed nothing, and that's the most useful finding

Todo

  • Install free stack (kikuchipy 0.13.0 + PyEBSDIndex 0.3.10 + orix 0.14.3)
  • Re-fetch the same slice (first 25 rows, 4,900 patterns, byte-exact) from the Box share
  • Run 1: AlSi10Mg only (baseline re-run)
  • Run 2: Al only
  • Run 3: AlSi10Mg + Si
  • Run 4: Al + Si
  • Bonus diagnostics: Si-only control + custom per-phase reflector lists
  • Answer: can you build your own phases in kikuchipy?
  • Report results

The four runs you asked for

Same slice, same background correction, same pattern centre (optimized once at PC = (0.528, 0.337, 0.741) and reused, so the phase list is the only variable):

Run CI > 0.1 median CI median fit indexed phase split
AlSi10Mg (baseline) 99.3% 0.683 0.476° 4,882/4,900
Al only 99.3% 0.683 0.476° 4,882/4,900
AlSi10Mg + Si 99.3% 0.683 0.476° 4,882/4,900 100% AlSi10Mg, 0% Si
Al + Si 99.3% 0.683 0.476° 4,882/4,900 100% Al, 0% Si

Every number is identical to full float precision — not "similar," identical. So I ran two diagnostics to find out why.

Diagnostic 1 — Si-only control: also identical

Indexing with only Si (diamond cubic, a = 5.4307 Å) gave the exact same CI (0.683) and fit (0.476°), just with every pixel labeled "si." The reason: kikuchipy's get_indexer gave both phases PyEBSDIndex's identical default cubic band families {002, 111, 022, 113} — and for cubic crystals, the angles between bands depend only on the (hkl) indices, not on the lattice parameter. Hough indexing solves orientation purely from those band angles. So Al at 4.0496 Å, "AlSi10Mg" at 4.05 Å, and Si at 5.4307 Å all produce the same triplet-angle library and the same solution.

Diagnostic 2 — physically correct reflector lists don't rescue it

I rebuilt the two-phase indexer with proper per-phase reflectors (fcc Al: {111, 200, 220, 311}; diamond Si, where {200} is structure-factor-forbidden: {111, 220, 311, 004}). Now the map "split" — 70% Al / 29% Si, with equally high CI on both (0.68 vs 0.70). That 29% Si is not real; it's a coin flip. The {004} and {002} poles are parallel, so even these two libraries are angularly identical, and phase assignment comes down to numerical noise. (It also ran 9× slower: 51 vs 451 patterns/s, since every pattern is voted against both libraries.)

What this means

  1. For grain characterization of your Al matrix, the phase list is a non-issue. Any fcc/cubic phase definition gives you the same orientations, same CI, same fit. Your 99.3% CI > 0.1 baseline stands regardless.
  2. Hough indexing fundamentally cannot see the eutectic Si in this alloy — both phases are cubic (Laue group m-3m), and band angles carry no lattice-parameter information for cubic. Discriminating Al from Si requires information Hough throws away: band widths and intensities. That's exactly what dictionary indexing / pattern matching uses (each phase gets its own simulated master pattern from EMsoft, and Si's patterns genuinely differ), or you fuse EDS chemistry with EBSD (what TEAM/OIM calls ChI-Scan). So the Si question from the byu-vcl thread is answerable offline — but by DI, not by adding Si to a Hough phase list.
  3. This reframes the TEAM observation (elemental Al CI ≈ 0.1 vs custom AlSi10Mg CI ≈ 0.94). Our test shows lattice parameters alone can't cause that for cubic phases — so the difference almost certainly came from something else in the material file: the enabled reflector families, number of bands, or angle tolerances. That's now a concrete, testable hypothesis rather than a mystery: in kikuchipy we can sweep the reflector list (the diagnostic-2 knob) and watch CI move.

Can you build your own phases in kikuchipy? Yes — we've been doing it all along

Every phase in these tests was custom-built. The recipe is three free building blocks (diffpy.structure + orix):

from diffpy.structure import Atom, Lattice, Structure
from orix.crystal_map import Phase

si = Phase(
    name="si", space_group=227,
    structure=Structure(
        atoms=[Atom("Si", [0, 0, 0]), Atom("Si", [0.25, 0.25, 0.25])],  # full basis
        lattice=Lattice(5.4307, 5.4307, 5.4307, 90, 90, 90),
    ),
)

Any space group, any lattice parameters (e.g. straight from a PXRD refinement of a novel alloy), full atomic basis with occupancies. And crucially, det.get_indexer(phase_list, reflectors=[...]) accepts a custom band-family list per phase — the closest kikuchipy analogue to editing a TEAM material file's reflector table.

When custom phases actually pay off, given today's result:

  • Hough indexing: only when candidate phases differ in Laue group (e.g. monoclinic β-Al₅FeSi intermetallics vs cubic Al) or when tuning reflector families for CI — not for cubic-vs-cubic discrimination. Note Mg₂Si is also cubic (a ≈ 6.35 Å), so it's angle-invisible too.
  • Dictionary indexing: fully — a custom phase feeds an EMsoft master-pattern simulation, and there Al vs Si genuinely separate. This is the natural next experiment if the eutectic-Si question matters.

Suggested next step

The decisive Si test is a small dictionary-indexing run on this same slice with Al and Si master patterns (EMsoft). Slower than Hough but runs offline on the patterns we already have. Happy to set that up, and/or commit the experiment script as scripts/phase_list_experiment.py — say the word (kept comment-only for now since the PR is docs-only).

No code committed. Data accessed anonymously via the public Box share token; nothing echoed from secrets.
· branch claude/issue-2-20260724-1601

@ronnie-guymon

Copy link
Copy Markdown
Collaborator Author

@claude I'm pretty sure we ruled out the phase being a defining factor of greatly increasing the CI, it was increasing the beam current by about 4x if I remember right that gave better patterns, but I'll have to double check. Just wanted to double check that. What would the next step be in actually getting the grain map? is that where MTEX comes in?

Also it looks like the job Sterling asked for didn't work correctly. Figure out what happened and try indexing the entire 2GB pattern file again.

@claude download the full 2 GB and process in-full, then report back to @ronnie-guymon

@claude

claude Bot commented Jul 27, 2026

Copy link
Copy Markdown

Claude finished @ronnie-guymon's task in 18m 53s —— View job


Full 2 GB map indexed successfully + answers on beam current and grain maps

Done. The full 87,416-pattern Area 2 map is indexed, and the plots/.ang are committed to example/area2-results/. Answers to your three questions below.


1. Why Sterling's job didn't deliver — and it's fixed now

The previous run (30292360697) succeeded as a workflow (exit 0) but ended before the compute finished. Timeline from its log: the Claude step ran only ~3 minutes (18:07:52 → 18:10:58), posted the "Processing…" comment with the download/PC steps still marked running, then the turn ended and the runner immediately hit Cleaning up orphan processes — killing the half-started indexing. Nothing crashed; the job just stopped while the long task was still in flight, so no results were ever produced.

This run avoided that by doing the whole pipeline as a single blocking foreground step (download → background-correct → PC-optimize → index → plot → export), so the runner can't be torn down mid-compute. Total wall-clock ~7 min.

2. Beam current vs phase — you're right, and our own data backs you up

Your memory is correct: the ~4× beam-current increase (1.6 → 6.4 nA) is what drove CI up (that's the ~40% → ~77% CI > 0.1 jump from the byu-vcl thread), not the phase definition. The phase-list experiment from the last run corroborates this from the other direction: swapping Al ↔ AlSi10Mg ↔ Si gave bit-for-bit identical CI and fit, because for cubic phases the Hough band angles carry no lattice-parameter information. So phase choice can't move CI here, but beam current changes the actual pattern signal-to-noise, which is exactly what CI measures. Both observations point the same way. (Worth double-checking your notes as you said, but nothing here contradicts the beam-current explanation.)

3. Full-map results

Custom AlSi10Mg fcc phase (a = 4.05 Å), mean-pattern static + dynamic background, PC auto-optimized to (0.518, 0.336, 0.753), nBands=9:

Metric Full map (87,416 pts)
CI > 0.1 99.5%
CI (mean / median) 0.65 / 0.69
fit (median / mean) 0.49° / 0.54°
fit < 1.4° 99.7%
points indexed 87,163 / 87,416 (99.7%)
speed (CPU, no GPU) ~446 patterns/s (~3.3 min)

The full-map 99.5% CI > 0.1 confirms the slice result held up across the whole scan — and it's well above the ~77% on-scope TEAM baseline, validating the offline pipeline on your real data.

Quality maps (CI, fit, IQ-equivalent, bands matched):

IPF-Z orientation map — the elongated, directional grains are the classic AM melt-pool solidification texture:

CI distribution:

Committed to example/area2-results/: the three PNGs, stats.json, and area2_full.ang.gz (the orientation map, gzipped — gunzip it and it drops straight into MTEX). Note: the kikuchipy .ang writer zeroes the CI/IQ columns, so the real CI/IQ live in the plots; fit and orientations are intact in the .ang.

4. Getting the grain map — yes, this is where MTEX comes in

You now have area2_full.ang — a per-pixel orientation map. Grains don't exist yet; they're reconstructed from that map by grouping neighboring pixels whose misorientation is below a threshold (~5°). kikuchipy/PyEBSDIndex don't do grain reconstruction — that's exactly the Stage B handoff. Three routes, in order of how close they are to TEAM/OIM:

MTEX (MATLAB) — the field standard, closest to OIM Analysis:

ebsd = EBSD.load('area2_full.ang','convertEuler2SpatialReferenceFrame','setting 2');
ebsd = ebsd(ebsd.fit < 1.4*degree);                      % CI col is 0 here, so gate on fit
[grains, ebsd.grainId] = calcGrains(ebsd('indexed'),'angle',5*degree);
grains = grains(grains.grainSize >= 10);                 % ≥10 px/grain, docs §4
plot(grains, grains.meanOrientation)                     % grain map
mean(grains.equivalentRadius)*2                          % mean grain size

MTEX is free but needs MATLAB — as BYU folks you almost certainly have a campus license, so this is likely your path. It gives grain size/shape, ODF/texture, KAM/GND, boundary character — everything past indexing.

Free alternatives if you'd rather not touch MATLAB:

  • DREAM3D-NX — free GUI, reads .ang directly, does segmentation + grain statistics as a re-runnable pipeline. Closest free equivalent to the interactive TEAM/OIM experience.
  • orix (already in your stack) — xmap → grain-ish analysis in pure Python; lighter than MTEX but keeps everything in the notebook.

The natural loop: iterate indexing in kikuchipy (seconds per re-index), reconstruct grains in MTEX/DREAM3D, check grain-count stability — that back-and-forth is the CALIBER proof-of-concept in miniature.

Suggested next step

Reconstruct grains from this .ang in MTEX and report the grain-size distribution — that closes the loop from raw patterns to a grain map. I can also add Si as a diamond-cubic second phase via dictionary indexing (the only method that can actually see the eutectic Si, since Hough can't distinguish cubic phases) if that question resurfaces. Say the word and I'll script either.


Todo: ✅ diagnosed prior job (ended mid-compute, runner torn down) · ✅ downloaded full 2.18 GB · ✅ indexed all 87,416 patterns (99.5% CI>0.1) · ✅ CI/fit/IQ/IPF plots + stats + .ang committed · ✅ answered beam-current + MTEX grain-map questions
· branch claude/issue-2-20260724-1601

Full 87,416-pattern map indexed with kikuchipy + PyEBSDIndex:
99.5% CI>0.1, median fit 0.49 deg. Includes CI/fit/IQ/bands quality
maps, IPF-Z orientation map, CI histogram, stats, and gzipped .ang.

Co-authored-by: Ronnie Guymon <244881888+ronnie-guymon@users.noreply.github.com>
@ronnie-guymon

Copy link
Copy Markdown
Collaborator Author

@claude help me understand dictionary indexing better, the benefits it has, and run a test with dictionary indexing and show me the script with everything you did.

@claude

claude Bot commented Jul 28, 2026

Copy link
Copy Markdown

Claude finished @ronnie-guymon's task in 32m 19s —— View job


Dictionary indexing: what it is, why it matters, and a real test on your Area 2 patterns

Everything ran end-to-end on the same 4,900-pattern slice (first 25 rows) used in the earlier experiments. The script and all outputs are committed: scripts/dictionary_indexing_test.py and example/area2-results/dictionary-indexing/. Headline numbers: DI + refinement agrees with Hough to 0.78° median, it produces a physically meaningful match score (NCC) where Hough only gives votes, and — the big one — it cleanly discriminates Al from Si (99.1% of pixels, unlike Hough where the two phases were bit-for-bit identical).


1. What dictionary indexing actually is

Hough indexing reduces each pattern to ~9 detected band positions and solves orientation from the angles between them. Everything else in the pattern — band widths, intensities, the fine structure between bands — is thrown away.

Dictionary indexing keeps all of it. The recipe:

  1. Simulate the full diffraction physics for your phase once, producing a "master pattern" — every possible diffraction direction on a sphere (this comes from EMsoft; we downloaded precomputed Al and Si master patterns from Zenodo, free, via kp.data.ebsd_master_pattern("al")).
  2. Generate a dictionary: project that master pattern onto your detector geometry for a dense grid of orientations (we used 2.5° spacing → 52,607 orientations covering all of cubic orientation space).
  3. Match: compute the normalized cross-correlation (NCC) between each experimental pattern and every dictionary pattern; the best-scoring one wins.
  4. Refine: polish the winning orientation with a local optimizer so accuracy isn't limited by the 2.5° grid.

The benefits, concretely:

  • Noise robustness — it uses every pixel, not just detectable band peaks, so it works on patterns too degraded for Hough.
  • A real quality metric — NCC measures "how much does this pattern look like the physics says it should," which is more informative than the Hough voting-based CI. It also drops naturally out of any candidate orientation, which is what makes refinement and pseudosymmetry checks possible.
  • Phase discrimination beyond band angles — band widths and intensities differ between phases even when band angles don't. This is exactly what the earlier phase-list experiment showed Hough can't do for cubic Al vs cubic Si.
  • The costs: you need a master pattern per phase, and it's ~10–100× slower than Hough (here: 48 s vs 11 s for the map, plus 87 s refinement — still fine offline).

2. What the test showed on your data

Your patterns are extremely noisy — and that made this a genuinely informative test. Look at the leftmost panel: to the eye there are almost no bands. Hough survives this because the Radon transform integrates intensity along whole lines. For DI we first applied neighbour-pattern averaging (s.average_neighbour_patterns() — averages each pattern with its map neighbours; safe because your grains span many pixels), which pulls the bands out of the noise:

That rightmost panel is the refined best-match simulation — DI's answer is literally "here is the pattern the physics predicts; it correlates at NCC 0.39 with what you measured."

DI vs Hough agreement (stats.json):

Metric Value
Median misorientation, DI-refined vs Hough 0.78°
Fraction within 2° / 5° 79% / 83%
Median NCC: dictionary hit → after refinement 0.221 → 0.285
Where Hough CI < 0.1 (36 px): median disagreement 41° — DI gives a different answer exactly where Hough admits it's guessing
Speed (4 CPU cores): Al dictionary projection / DI / refinement 13 s / 48 s / 87 s

The two methods independently reconstruct the same microstructure — mutual validation from completely different physics:

The ~17% disagreeing pixels are scattered speckle, concentrated at low-CI/boundary points (compare bottom-left vs bottom-right):

The Al-vs-Si phase test — DI succeeds where Hough couldn't. Remember the earlier experiment: Hough gave identical results for Al, AlSi10Mg, and Si phases, because cubic band angles carry no lattice information. We indexed the same slice against an Al dictionary and a Si dictionary (both refined, same settings):

  • Al wins at 99.1% of pixels, median NCC margin +0.073 (Al 0.285 vs Si 0.209) — a clear, quantitative phase call that Hough is structurally incapable of making. The map is Al matrix, as expected; any real eutectic-Si signal is below the 2 µm step size, but the capability is now demonstrated on your data.

The refined orientation map is committed as area2_slice_di_refined.ang.gz — gunzip and it drops into MTEX like the Hough one.

3. The script

Full runnable version (with the data download built in): scripts/dictionary_indexing_test.py. The essential pipeline:

import kikuchipy as kp
from orix import sampling
from orix.quaternion import symmetry

# patterns loaded + static/dynamic background removed as before, then:
s.average_neighbour_patterns()                      # the key step for noisy data

mp = kp.data.ebsd_master_pattern("al", allow_download=True,
                                 projection="lambert", energy=20, hemisphere="upper")

R = sampling.get_sample_fundamental(method="cubochoric", resolution=2.5,
                                    point_group=symmetry.Oh)       # 52,607 orientations
sim = mp.get_patterns(rotations=R, detector=det, energy=20, compute=True)

signal_mask = ~kp.filters.Window("circular", det.shape).astype(bool)
xmap_di = s.dictionary_indexing(sim, metric="ncc", keep_n=5, signal_mask=signal_mask)

xmap_ref = s.refine_orientation(xmap=xmap_best, detector=det, master_pattern=mp,
                                energy=20, signal_mask=signal_mask,
                                method="minimize", trust_region=[2, 2, 2])

4. Hard-won practical notes (worth remembering for CALIBER)

  1. A coarse dictionary silently fails. These sharp 111×111 patterns decorrelate within ~1–2° of misorientation, so a 6° dictionary found nothing but noise matches. 2.5° + refinement is the working recipe. (Binning the patterns to 55×55 made it worse — it raised the noise-correlation ceiling above the true-match signal.)
  2. Neighbour averaging is what made DI viable at all on this fast-camera data — before it, best NCC ≈ 0.09; after, 0.29.
  3. kikuchipy 0.13 gotchas encountered and worked around in the script: refine_orientation chokes on a keep_n=1 DI map (rebuild a clean CrystalMap with the best rotation first), and orix's rotations.reshape(...).flatten() does not round-trip in numpy order — use the already-flat xmap.rotations for any per-pixel comparison (this one cost me an hour of debugging phantom "disagreement").
  4. Energy was assumed 20 kV (the .par file doesn't record it) — worth confirming from your session notes; Al master patterns exist for 10–20 kV.

When to reach for DI in your workflow: Hough at 99.3% CI > 0.1 is clearly sufficient for routine grain mapping here. DI earns its cost when you need the low-CI pixels resolved (boundaries, deformed regions), when you want phase discrimination between cubic phases (the eutectic-Si question — which would need a finer-step scan to resolve spatially), or when you want a physics-based quality score for CALIBER's parameter-optimization loop.


Todo: ✅ explain DI + benefits · ✅ install stack · ✅ fetch slice + Al/Si master patterns · ✅ Hough baseline (99.3% CI>0.1) · ✅ full DI + refinement + Si phase test · ✅ commit script + results (4a760d6)
· branch claude/issue-2-20260724-1601

@claude

claude Bot commented Aug 11, 2026

Copy link
Copy Markdown

Claude finished @ronnie-guymon's task in 2m 53s —— View job


Your staged Hough→DI idea is sound — here are the holes and how to close them

First, validation: the architecture you sketched (cheap Hough first, escalate to DI only when needed) is not just reasonable — it's essentially what the field calls hybrid indexing, and kikuchipy ships a tutorial implementing exactly it: Hough-index everything, flag badly-indexed points by quality metrics, dictionary-index only those points, refine, merge. So you independently arrived at the community's answer. That said, there are five real holes in the version you described, and each one changes the design in a concrete way.

Todo

  • Read trigger comment and gather context (prior benchmark numbers from Area 2 runs)
  • Verify the hybrid-indexing reference (the field's name for this exact architecture)
  • Identify holes in the proposed loop (escalation trigger, metric choice, sampling design, DI assumptions)
  • Expand into a concrete staged architecture with decision rules
  • Post answer

Hole 1 — A CI plateau tells you Hough stalled, not why it stalled

Your trigger ("5 runs, CI fraction only moved 0.40→0.43 → switch to DI") treats every plateau the same. But a stalled CI has at least four distinct causes, and only one of them is fixed by DI:

Symptom (spatial pattern of the low-CI pixels) Likely cause Right escalation
Low CI and high fit uniformly everywhere Pattern-center / geometry error Re-optimize PC. DI would fail identically — it projects the dictionary through the same detector geometry, so a wrong PC breaks DI too, and you'd pay 10–100× the compute to discover it.
Low CI concentrated at grain boundaries Overlapping patterns — physics, not parameters Neighbour averaging or DI on those pixels; or accept it and let grain reconstruction handle it.
Low CI in patches/speckle with plausible CI elsewhere Misindexing (wrong reflector list, too few bands, pseudosymmetry) Reflector-list / nBands sweep — still Hough territory.
Low CI randomly scattered, with low IQ everywhere SNR-limited patterns (the beam-current ceiling) Averaging, then DI on the residue. This is the only row where DI is the real answer.

So the loop needs a diagnosis step between plateau and escalation: compute where the low-CI pixels live (uniform vs boundary-correlated vs patchy) and branch on that, not just on the plateau. This is exactly the "different functions depending on what outcomes still need improvement" part of your idea — the table above is the dispatch logic, and every input to it (CI map, fit map, IQ map, a quick boundary mask) is already computed.

Hole 2 — CI can't score the DI stage, so your loop's objective breaks at the handoff

"Move to DI and see if CI improves" is ill-posed: DI doesn't produce a CI. It produces NCC (pattern-to-simulation correlation), a different quantity on a different scale — our Area 2 runs gave median CI 0.69 from Hough and median NCC 0.285 from refined DI on the same pixels, and those numbers are not comparable. Two fixes, use both:

  1. Adopt a method-independent score. NCC-against-simulation works for both methods: you can score a Hough solution by simulating its orientation's pattern and correlating (that's what refine_orientation reports). That gives one currency across the whole ladder.
  2. Let the microstructure metrics be the referee. Speckle-grain fraction, orientation precision (intra-grain spread), and boundary integrity are computed from the orientation map and don't care which indexer produced it. These are the metrics that should decide "did DI actually help."

Related: on good data the CI > 0.1 fraction saturates (ours sits at 99.5%) and stops providing gradient for the loop. Use continuous versions (median CI, mean fit, median NCC) as the optimization signal and keep the threshold fractions only as pass/fail gates.

Hole 3 — "Map 15% first" needs a sampling design, and contiguity is the constraint

Two failure modes with a naive 15%:

  • A single contiguous block (like the first-25-rows slice we've been testing) can be unrepresentative — different texture region, missed second phase, and crucially the PC is then calibrated at one corner of a map whose true PC drifts as the beam scans. Fine for our small map; risky as a general policy.
  • Scattered random pixels are representative but destroy the metrics you specifically want: speckle, boundary integrity, and orientation precision all require contiguous neighborhoods to compute.

The fix is distributed tiles: several contiguous patches (say 4–6 tiles of ~30×50 pixels) spread across the map — corners plus center. Each tile supports the grain-adjacent metrics internally, the union approximates the global distributions, and per-tile PC fits reveal whether PC drift across the map is significant (if it is, that's its own finding: fit a PC plane, not a point). You likely need less than 15%, too — 5% in tiles was enough for our slice results (99.3%) to predict the full map (99.5%) almost exactly.

One honest caveat from our timings: for Hough, sampling barely matters — the full 87k-pattern map indexes in ~3.3 min, the slice in 11 s. Sampling is really protecting the DI tier (about 14 min per full map at 2.5°, hours at finer spacings) and the many-iteration sweep, which is where your instinct is right.

Hole 4 — DI is not an automatic rescue; neighbour averaging is the missing rung

Your ladder goes Hough → DI, but our own data shows a step in between. On the raw Area 2 patterns, DI failed (best NCC ≈ 0.09, pure noise matches) while Hough sailed through — the Radon transform integrates along whole lines, so it's more noise-tolerant than naive per-pixel correlation. DI only became viable after average_neighbour_patterns() (NCC 0.09 → 0.29). So the real escalation ladder is:

Hough sweep → PC re-optimization → averaging + Hough → averaging + DI + refinement (residue only)

…with the caveat we've flagged before: averaging blurs boundary pixels, so once it's on, the boundary-integrity and speckle metrics must stay in the gate to catch the damage. Also note DI has its own (smaller) parameter loop — dictionary resolution, energy, signal mask — so "move to DI and go from there" is a new inner loop, not a terminal state.

Hole 5 — Don't switch to DI wholesale; escalate only the failing pixels

This is the biggest cost win and it's exactly what the hybrid-indexing tutorial does. After your best Hough pass, typically 95%+ of pixels are already well-indexed — re-doing them with DI wastes nearly all the DI compute to reproduce answers you already trust. Instead: threshold on CI/fit, run DI only on the flagged residue, refine both populations, merge. If 5% of an 87k map fails, that's ~4,400 patterns — DI at 2.5° takes well under a minute instead of ~14, which means you can afford finer dictionaries (1.5–2°) on the residue, where they actually matter.

On the beam-current point — agreed, with one design consequence

You're right that re-acquisition (vent, reload, re-navigate) is the most expensive iteration loop imaginable, and right to push everything possible offline. The consequence to design in: offline processing cannot create information the detector never captured. There is an acquisition-quality floor below which even averaging + DI won't reach your targets — and when the loop terminates in that state, that's not a failure of the loop, it's the measurement that feeds your future SEM-parameter section: "this material at this current/dwell cannot support the requested characterization; the minimum acceptable acquisition is X." Two practical additions:

  1. A pre-flight gate during the session: before committing to a 2 GB full-map save, Hough-index a few hundred patterns with defaults (seconds). If median IQ/CI is below the empirically-known rescuable floor, fix the beam current then, while the sample is loaded. This preserves your one-session goal — it just spends 30 extra seconds of session time to guarantee the offline loop has enough signal to work with.
  2. Log the terminal state of every offline run (which tier it exited at, final metric vector) — that dataset is literally the training data for CALIBER's acquisition-parameter recommendations later.

The assembled loop (phases 1–4, as you scoped)

Tier Action Cost (Area 2 scale) Exit condition
0 Pre-flight: header check, PC prior, quick default Hough on tiles seconds metrics computable; SNR above rescuable floor
1 Hough sweep (nBands, tSigma/rSigma, background, reflectors) on tiles ~11 s/eval → dozens of evals cheap targets met, or improvement < ε over last k evals
2 Diagnose plateau (Hole-1 table) → PC re-opt if uniform; reflector fix if patchy ~1 min symptom cleared or confirmed SNR-limited
3 Neighbour averaging + re-run best Tier-1 params ~1 min CI/NCC up and boundary metrics not degraded
4 DI + refinement on the residue only, finer dictionary as budget allows ~1–5 min residue vs 14 min+ full merged map meets full gate, or floor declared → SEM-section signal

On "5 iterations": make it a convergence rule rather than a fixed count. Hough evaluations are so cheap relative to DI (one DI + refinement run ≈ 150 s ≈ 14 Hough sweeps on the slice) that the economics say exhaust Hough thoroughly — a plateau detector ("best-so-far improved < 2% over the last 8 evals") spends compute exactly as long as it's earning, and it's also the natural seam to swap in Bayesian optimization later without changing the architecture.

Net assessment: the skeleton is right and matches field practice; the five fixes are (1) diagnose before escalating, (2) use a cross-method score, (3) sample in distributed tiles, (4) insert the averaging rung, (5) escalate the residue, not the map. With those, phases 1–4 form a defensible base for grain reconstruction — and the terminal-failure case doubles as the empirical bridge to the SEM-parameter section you're planning.

Happy to turn the tier table into a runnable scripts/staged_indexing_loop.py against the Area 2 data (Tiers 0–3 end-to-end, Tier 4 on the residue) — say the word.

No code changes — this was a design discussion.
· branch claude/issue-2-20260724-1601

@ronnie-guymon

Copy link
Copy Markdown
Collaborator Author

@claude we would also want to include Bayesian optimization in order to determine what parameters to try next for a specific outcome.

@claude

claude Bot commented Aug 11, 2026

Copy link
Copy Markdown

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


Bayesian optimization as CALIBER's parameter-selection brain — how it slots in

Agreed, and the design from the last two comments was quietly shaped for exactly this: score_indexing() already returns the objective values, the parameter tables define the search space, and the staged tiers define the budget structure. BO replaces one specific line — the itertools.product grid — with a model that chooses the next parameter set based on everything tried so far. Below: why it's the right fit, which library, working code, and how it strengthens (rather than replaces) the Hough→DI switching idea from your previous comment.

Todo

  • Gather context (prior feedback-loop design, parameter stages, scoring functions)
  • Verify current Bayesian optimization library APIs (Ax 1.x, Honegumi, BayBE)
  • Map BO onto CALIBER's loop: search space, objectives, constraints, tier-switching
  • Post answer with concrete code

1. Why BO beats a grid here

A grid over just Tier 1 (nBands × tSigma × rSigma = 27 points) is affordable on the 4,900-pattern slice (11 s each). But the real space — add dynamic-background std, PC search_limit, averaging on/off, reflector-list choice, and eventually DI dictionary resolution — explodes combinatorially, and each DI evaluation costs minutes, not seconds. BO is built for exactly this regime:

  • Expensive, noisy evaluations, small budgets. A Gaussian-process surrogate models "score as a function of parameters" and an acquisition function picks the next trial to balance exploring uncertain regions vs exploiting promising ones. Typical budgets are 20–50 trials — versus thousands for a fine grid.
  • Mixed parameter types. Integer (nBands), continuous (tSigma, rSigma, std), and categorical (averaging on/off, reflector set) all coexist naturally.
  • "For a specific outcome" is literally the API. You declare which outcome is the objective and which are constraints, and the same loop code serves any goal — maximize CI fraction subject to fit < 1.0°, or minimize fit subject to speckle < 5%, etc.

2. Library: Ax — with a Honegumi assist

Ax (Meta, open source, BoTorch/PyTorch under the hood) is the standard Python BO platform and the one I'd wire in: pip install ax-platform. It natively handles mixed spaces, multi-objective Pareto optimization, outcome constraints, and noisy objectives, and it logs every trial for reproducibility. Two notes specific to your group:

  • Honegumi — Sterling's own interactive template generator for Ax — is the fastest way to scaffold variants of this loop (toggle multi-objective, constraints, fully Bayesian, etc.) without memorizing the API. Given the PI wrote the tool, this is presumably the house style.
  • Ax 1.x renamed the API (Client, configure_experiment, configure_optimization — the older AxClient snippets you'll find in tutorials still work but are deprecated). Code below uses the current 1.x API, verified against the quickstart.

Alternatives, for completeness: BayBE (Merck, chemistry-flavored), Optuna (great TPE-based generalist, weaker multi-objective GP support). Nothing about the loop locks you in — the score dict is library-agnostic.

3. The wiring — CALIBER's Tier-1 loop with Ax choosing parameters

Everything except the Ax calls is code already validated on Area 2 in this PR (scripts/dictionary_indexing_test.py):

from ax.api.client import Client
from ax.api.configs import RangeParameterConfig, ChoiceParameterConfig

client = Client()
client.configure_experiment(
    name="area2_hough_tier1",
    parameters=[
        RangeParameterConfig(name="nBands", bounds=(7, 12), parameter_type="int"),
        RangeParameterConfig(name="tSigma", bounds=(1.0, 3.0), parameter_type="float"),
        RangeParameterConfig(name="rSigma", bounds=(1.0, 3.0), parameter_type="float"),
        RangeParameterConfig(name="bg_std", bounds=(5.0, 15.0), parameter_type="float"),
        ChoiceParameterConfig(name="nbr_avg", values=[False, True], parameter_type="bool"),
    ],
)

# "For a specific outcome": objective + guardrails on the others
client.configure_optimization(
    objective="ci_frac_01",                      # maximize CI>0.1 fraction...
    outcome_constraints=[
        "mean_fit_deg <= 1.0",                   # ...but only where fit stays acceptable
        "speckle_frac <= 0.05",                  # ...and averaging can't game CI by blurring boundaries
    ],
)

def evaluate(p):                                  # one trial = one re-index of the slice
    s2 = s_raw.deepcopy()
    s2.static_background = s2.data.mean(axis=(0, 1)).astype(s2.data.dtype)
    s2.remove_static_background(operation="subtract")
    s2.remove_dynamic_background(std=p["bg_std"])
    if p["nbr_avg"]:
        s2.average_neighbour_patterns()
    indexer = det.get_indexer(phase_list, nBands=p["nBands"],
                              tSigma=p["tSigma"], rSigma=p["rSigma"])
    xmap = s2.hough_indexing(phase_list=phase_list, indexer=indexer, verbose=0)
    return {**score_indexing(xmap), **quick_speckle_metric(xmap)}   # from the previous comment

for _ in range(30):                               # 30 trials ≈ 6–10 min on the slice
    for trial_index, params in client.get_next_trials(max_trials=1).items():
        client.complete_trial(trial_index=trial_index, raw_data=evaluate(params))

best_params, best_metrics, _, _ = client.get_best_parameterization()

When you genuinely care about two outcomes at once and want the trade-off curve instead of a single winner, switch one line: client.configure_optimization(objective="ci_frac_01, -mean_fit_deg") — Ax then maps the Pareto frontier, which is exactly the "CI vs boundary integrity" tension we flagged as the thing a naive CI-maximizer would abuse.

4. How BO upgrades your Hough→DI switching rule

Your previous comment proposed: index ~15% of patterns, run 5 Hough feedback iterations, and switch to DI if CI improves by less than ~0.03. BO gives you a more principled version of each piece:

  1. The "not improving anymore" test comes from the model, not a fixed count. After each trial the GP knows both its best observed value and its predicted headroom — acquisition values (expected improvement) shrinking toward zero is the statistically grounded version of "0.40 → 0.43 over 5 runs, give up." Ax ships this as an improvement-based global stopping strategy, so "exhausted Tier 1, escalate to DI" becomes a model-triggered event rather than a magic number. In practice you'd still cap trials (say 30) as a backstop.
  2. The 15% slice is a fidelity level, and your instinct is the standard practice. Optimize on the cheap slice, then re-score only the top 2–3 candidates on the full map before accepting (slice: 11 s, full map: 3.3 min — the two agreed to within 0.2 percentage points of CI fraction in our Area 2 runs, so the slice is a faithful proxy). One caveat worth designing in: make the slice spatially representative (e.g. every Nth row, or a few dispersed blocks) rather than the top 25 rows we've been using for convenience — a contiguous strip can over-represent a few large grains.
  3. DI gets its own, smaller BO experiment. Different search space (dictionary resolution, keep_n, signal mask radius, refinement trust region), much pricier evaluations — so a separate Client with maybe 10–15 trials, seeded with the Hough tier's winning background/averaging settings. Keeping the tiers as separate experiments preserves your staged-escalation cost structure; BO just runs the search inside each stage.
  4. One imaging session per sample is the right constraint, and BO respects it. Everything above is offline knob-turning on saved patterns. Later, when the "SEM parameter section" of CALIBER exists, the same Ax machinery extends to acquisition parameters (beam current, exposure, step size) — that's the classic materials-acceleration use case — but with each "trial" costing a microscope session, which is precisely why squeezing everything possible out of post-processing first is the correct architecture.

5. Practical notes before v1

  • Trial noise: CI fraction measured on a slice is a noisy estimate of the full-map value. GPs handle noisy observations natively; just don't make the slice so small that noise swamps the parameter effects (your 15% ≈ 13k patterns is plenty; our 4,900 slice was already stable).
  • Log everything: each Ax trial (parameters + all metrics, not just the objective) saved to JSON is CALIBER's provenance record — and re-scoring a past dataset against new criteria becomes free, since the trials are already evaluated.
  • Start single-objective + constraints, graduate to Pareto. Constrained single-objective is easier to reason about and to explain to users; the Pareto version is a one-line change when you want the full trade-off picture.

Suggested next step

Commit a runnable scripts/bo_parameter_loop.py that runs the Tier-1 Ax loop above against the Area 2 slice end-to-end (30 trials, JSON trial log, comparison against the 27-point grid baseline so we can see BO's sample-efficiency win on real data). Say the word and I'll wire it up.

Sources: Ax · Ax 1.0 quickstart (Client API) · Ax multi-objective tutorial · ax-platform on PyPI · Honegumi · Ax GitHub

No code changes — this was a design question; the BO script is comment-only until requested.
· branch claude/issue-2-20260724-1601

@ronnie-guymon

Copy link
Copy Markdown
Collaborator Author

@claude that feedback is super helpful.

More thoughts/ideas based on the feedback:

  • When when trying to map 5-15% that will be representative of the entire map, would it be best to do a sort of grid, such as 25 tiles spaced evenly around the map? I don't think there needs to be any optimization as to where the samples are taken from since it is mostly just to sample many different portions of the image, but I could be wrong.
  • switching to DI only on the failed pixels makes a lot of sense. Question, when you say run DI at 2.5°, what does that mean exactly? Does that just get the actual pattern within 2.5% of the physics-generated guess?
  • It would make sense to use NCC for both Hough and DI, since then we could compare the Hough NCC of the "failed" pixels to the DI NCC and see how big of an improvement there was between Hough and DI. What would an NCC threshold be that is comparable to a CI >0.1 percentage?
  • Using Team, is there a way to index just a few hundred patterns that I could use during the imaging session to verify I am using the most optimal beam current?
  • In the Hole 1 discussion, there is a part about low resolution/CI on grain boundaries. How would the software distinguish between low resolution of grain boundaries vs some arbitrary spot? Is that something that we would have to manually determine by looking at the IQ/CI map and see if tit is poor at the grain boundaries?

After you mentioned this is basically what the hybrid indexing already does, I'm having trouble seeing what novelty CALIBER brings, at least for EBSD. It seems like these systems already use a feedback loop when indexing. Would some sort of BO/Honegumi incorporation add more novelty? To what point does existing software already do what I am proposing?

@claude

claude Bot commented Aug 11, 2026

Copy link
Copy Markdown

Claude finished @ronnie-guymon's task in 3m 56s —— View job


Answers to the five questions + an honest novelty assessment (with new prior art found)

Todo

  • Read trigger comment and prior discussion context
  • Q1: tile sampling design (25-tile grid?)
  • Q2: what "DI at 2.5°" actually means
  • Q3: NCC threshold comparable to CI > 0.1
  • Q4: quick beam-current check in TEAM during a session
  • Q5: automated boundary-vs-arbitrary low-CI diagnosis
  • Q6: CALIBER novelty vs existing software — including a prior-art search
  • Post answer

1. Tile grid: yes, evenly spaced — with one twist worth adding

Your instinct is right: no placement optimization is needed, just coverage. Three design notes:

  • Size tiles for grains, then pick the count. Each tile must be a contiguous patch big enough to hold several whole grains, or the boundary/speckle/precision metrics inside it are meaningless. On Area 2 (grains 5–30 µm at 2 µm step = roughly 3–15 pixels across), a tile of about 30×30 px works. Note that 25 tiles × 900 px is about 26% of your 87k-pixel map — so at a 10% budget it's more like 9–12 tiles of that size. Fewer, larger tiles beat many tiny ones whenever grains are large relative to the step.
  • The one real risk of a perfectly regular grid: aliasing. LPBF microstructure is itself periodic — hatch spacing and layer bands repeat at fixed intervals (order 100–200 µm). A regular tile lattice can accidentally synchronize with that pattern so every tile lands on the same part of the melt-pool geometry. The standard cheap fix is a jittered grid: keep the even grid cells, but randomly offset each tile's position within its cell. Even coverage, no alignment, still zero "optimization."
  • Validate once per dataset class. Since a full Hough pass costs only ~3 min, run it once and check that the tile-union metrics reproduce the full-map metrics (our 25-row slice predicted the full map to within 0.2 percentage points). After that you can trust tiles for the expensive sweeps.

2. "DI at 2.5°" — it's the spacing of the dictionary grid, not a match tolerance

Not 2.5% and not a search radius around a guess. The dictionary is built by taking a uniform grid over all possible crystal orientations (the cubic fundamental zone) with neighboring grid points about 2.5° of misorientation apart — that's the 52,607 simulated patterns. Every experimental pattern is compared against all of them and the highest-NCC entry wins.

The consequence of the spacing: the true orientation of your pixel almost never sits exactly on a grid point, so the raw DI answer can be off by up to roughly half the grid spacing (about 1.2–1.7°). That's why refinement follows — a continuous local optimizer starts from the winning grid point and polishes the orientation until the simulated pattern best matches the measured one, removing most of the grid error. Map-search analogy: pins every 2.5 km, find the nearest pin, then walk from the pin to the exact address. Finer spacing = more pins = better starting points (mainly for very noisy pixels) at proportionally more compute — that's the whole trade in the earlier timing table.

3. NCC threshold equivalent to CI > 0.1: there isn't a universal one — derive it per dataset

This is the key subtlety: NCC's absolute scale shifts with pattern noise, masking, resolution, and especially neighbour averaging. Our own data shows it: raw Area 2 patterns topped out near NCC 0.09 (indistinguishable from chance), while after averaging the refined median was 0.285. A hardcoded "NCC > X" transplanted between datasets is meaningless — but that's fine, because the threshold can be calibrated automatically, which fits the CALIBER loop perfectly:

  1. Noise-floor calibration (the principled one). Correlate the same experimental patterns against a few hundred random orientations → that gives the distribution of NCC you get by pure chance on this dataset. Set the threshold at, say, 3 standard deviations above that noise floor. Anything above it is a real match. On the averaged Area 2 slice this lands around 0.15–0.20, playing the role CI = 0.1 plays for Hough — but the loop should recompute it per run rather than trust that number.
  2. Percentile matching (the pragmatic cross-check). Since every Hough solution can be scored by NCC too (simulate the pattern at the Hough orientation and correlate — this is exactly what refine_orientation reports), compute both CI and NCC per pixel on the same Hough map, and read off the NCC value that sits at the same percentile as CI = 0.1. That gives you the dataset-specific exchange rate between the two scales — and yes, it then lets you quantify Hough-NCC vs DI-NCC on the failed-pixel population, exactly the before/after comparison you described.

4. Quick beam-current check in TEAM/APEX: yes — a small test scan is all it takes

No special feature needed; both TEAM and APEX let you drag the mapping region down to a small box and/or coarsen the step, so a few-hundred-point scan runs in well under a minute, with live CI/IQ statistics as it collects (APEX also has an explicit Quick scan mode among SD/HD/Manual). Practical session protocol:

  1. Pick a small, representative region (avoid the map area itself if you're dose-sensitive).
  2. At each candidate beam current, run a ~20×20-point test scan using the same exposure/binning you'll use for the real map — CI depends on the current×exposure combination, not current alone.
  3. Record CI > 0.1 fraction and mean IQ per condition; choose the current where the curve saturates (your own 1.6 → 6.4 nA jump from ~40% to ~77% is exactly this curve, measured the slow way).
  4. Bonus: enable save-patterns on the test scan. Those few hundred raw patterns are enough to run the offline kikuchipy scorer during the session — which is the "pre-flight gate" from the earlier design: confirmation before the 2 GB commit that the offline loop will have enough signal to work with.

5. Boundary-correlated vs arbitrary low CI: fully automatic, no eyeballing needed

You don't have to look at the map — the diagnosis is a few lines of array math, because after the first Hough pass you already hold an orientation map:

  1. Build a boundary mask: pixels whose misorientation to any neighbor exceeds ~5° (plus a 1-pixel dilation to cover the "near-boundary" zone).
  2. Compute an enrichment ratio: E = P(low CI | boundary zone) ÷ P(low CI | anywhere). If E ≈ 1, the low-CI pixels don't care about boundaries — arbitrary/scattered. If E is large (2–3+), they're boundary-correlated → the overlapping-patterns row of the Hole-1 table.
  3. The other two rows fall out the same way: uniform low CI + elevated fit everywhere → pattern-center diagnosis; patchy → run connected-component labeling on the low-CI pixels and look at the blob-size distribution (a few large blobs = misindexed patches; all singletons = noise speckle).

One honest caveat: the boundary mask comes from the very map you're diagnosing, so it's imperfect where indexing is worst — but for a coarse dispatch decision ("which escalation branch?") a rough segmentation is entirely sufficient. This is what makes the Hole-1 table an automated dispatcher rather than a manual checklist; the human only reviews the verdict.

6. The novelty question — the honest answer, including prior art I just found

First, the uncomfortable part, because you should scope claims correctly: while checking this I found a January 2026 paper (arXiv 2601.09627, ferroelectric polarization mapping via EBSD reindexing) that already uses Gaussian-process Bayesian optimization to auto-tune kikuchipy pattern-processing parameters (dynamic background, adaptive histogram equalization, FFT filters), maximizing NCC. So "apply BO to EBSD processing knobs" by itself is no longer a novel claim — though it's also encouraging: it validates that this direction is real and publishable, and it appeared months ago, meaning the field is just opening.

Now the layered answer to "what do existing systems already do":

Layer Existing software The gap
Indexing engines (Hough, DI, refinement) Solved: PyEBSDIndex, kikuchipy, EMsoft, MapSweeper None — CALIBER should build on these, not compete
Re-indexing stored patterns with new settings Solved: kikuchipy scripting, MapSweeper, OIM reprocessing The loop is a human clicking
Hybrid Hough→DI escalation Exists as a tutorial recipe: one pass, hand-picked thresholds, human judgment at every step No automation, no diagnosis, no stopping rule
BO over processing parameters One 2026 paper, single-objective (NCC), processing knobs only Narrow objective; no indexing/reconstruction knobs; not a tool
Closed-loop, diagnosis-driven, microstructure-aware optimization Nobody This is CALIBER

So the important reframe: "a feedback loop when indexing" does not exist in current systems — the feedback loop is the operator. Hybrid indexing is a recipe a person executes once; MapSweeper is a person turning dials. What no software does, and what your last several comments have effectively specified, is:

  1. Diagnosis-driven escalation — the Hole-1 dispatcher deciding why the plateau happened and branching accordingly (PC re-fit vs reflector fix vs averaging vs residual DI), with model-based stopping instead of magic iteration counts.
  2. Microstructure-aware objectives. All existing auto-tuning (including the 2026 paper) optimizes pattern-level scores. Optimizing against — and guarding — grain-level outcomes (grain-count stability across thresholds, speckle fraction, boundary integrity, % cleaned) is unclaimed territory, and it's the part end users actually care about. It's also the technically hard part: we showed a naive CI-maximizer gets gamed by neighbour averaging at the expense of boundaries; single-objective prior art has exactly that blind spot.
  3. Multi-objective trade-off exposure (Pareto: CI vs boundary integrity vs cleanup fraction) rather than one scalar — this is where the Ax/Honegumi machinery earns its place, and given Honegumi is the house tool, it's a natural differentiator rather than the novelty claim itself.
  4. The acquisition bridge. Logging every run's terminal state ("this SNR could not be rescued past X") accumulates the empirical dataset that powers the future SEM-parameter recommendations — turning failed post-processing runs into acquisition advice. Nothing in the field closes that outer loop; it's the same shape as self-driving-lab work, where the components all existed and the orchestration was the contribution.

My suggestion: before writing this into a proposal or paper outline, run an Edison literature search specifically on closed-loop/automated EBSD parameter optimization to map the prior-art boundary properly (the arXiv paper above was found with a quick web search; a systematic sweep may find more, or confirm the gap). Say the word and I'll send it.

Sources: kikuchipy hybrid indexing tutorial · arXiv 2601.09627 — BO-tuned pattern processing for EBSD reindexing · kikuchipy Hough indexing · EDAX APEX for EBSD · TEAM EBSD manual (v4.5, PDF) · prior Area 2 numbers from stats.json and dictionary-indexing/stats.json

No code changes — this was a design discussion.
· branch claude/issue-2-20260724-1601

@ronnie-guymon

Copy link
Copy Markdown
Collaborator Author

@claude

  1. If we are sticking to metals manufactured via LPBF, we could probably just pick one size for the tile and stick with that for all samples. If we want to do any metal, like you were saying it would probably be good to do a rough first pass, and from there we can discover what the average grain size is and change the tile sizes accordingly. The jittered grid is a good idea

  2. So would it work such that a failed pixel is determined by whether or not it is 3 stds above the noise floor? And once, say, only 5-10% of the pixels are failed, we move to DI?

  3. Good idea to save the patterns for the small portion being indexed when testing beam current

Run the exact Edison search you suggested. I'm not understanding how the paper using BO doesn't use a feedback loop, doesn't BO use the previous data to choose the next best point? Which is a feedback loop?

And you mentioned optimizing against grain level outcomes. Does that not come from the pattern level changes? Or are there other parameters for the grain-level software that can be messed wtih? I'm a lot more familiar with the pattern-level changes.

@claude

claude Bot commented Aug 11, 2026

Copy link
Copy Markdown

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


Edison prior-art survey results + answers on the noise-floor rule, the BO "feedback loop" question, and grain-level parameters

Todo

  • Read trigger comment and gather context
  • Install edison-client and submit the closed-loop EBSD optimization literature search
  • Wait for Edison task to complete (single blocking foreground wait, ~12 min to success)
  • Fetch results and commit all artifacts (3d238c0)
  • Answer Q1 (tile sizing), Q3 (noise-floor failed-pixel rule + DI trigger), BO-feedback-loop clarification, grain-level vs pattern-level parameters
  • Post final synthesis

1. Tile sizing — agreed, and the adaptive version is nearly free

Your split is right: for a fixed material class (LPBF Al alloys) one calibrated tile size will hold; for arbitrary metals, size tiles from a rough first pass. The nice part is the rough pass costs almost nothing — a full-map default-settings Hough pass is ~3 min on Area 2, and even a handful of tiles indexed with defaults gives a usable grain-scale estimate. So "measure grain size → set tile size" just becomes part of the Tier-0 pre-flight that was already in the design, not a new stage. Rule of thumb to encode: tile edge ≥ ~5× the mean grain diameter (in pixels), so each tile holds several whole grains and the boundary/speckle metrics computed inside it are meaningful.

3. The noise-floor rule — you have it right, with two directional clarifications

Direction of the test: a pixel passes when its NCC is ≥ (noise floor + 3σ); it fails when its best match is statistically indistinguishable from correlating against random orientations. So yes — failed = not above the noise floor.

Direction of the DI trigger: one subtle flip from how you phrased it. You don't wait until the failed fraction shrinks to 5–10% before moving to DI — the failed fraction is whatever it is once Hough is exhausted (plateau detected + Hole-1 diagnosis run). At that point:

  • Small residue (≲10%) → the cheap case: DI only those pixels, minutes of compute, done.
  • Large residue (say 30–50%) with diagnosis = SNR-limited → you still escalate (there's nothing left for Hough to give), but now DI costs approach full-map DI, and if DI also can't lift the residue above its own noise floor, that's the "acquisition floor declared" exit — the signal that feeds the future SEM-parameter section.

So the failed fraction sets the cost of the DI tier, not the decision to run it; the decision comes from the plateau + diagnosis. One practical note: during the Hough tiers the working definition of "failed" is CI/fit (Hough's native metrics, free per pixel); the NCC noise-floor definition takes over once you're scoring in the shared NCC currency. The percentile-matching calibration from the last comment is what keeps those two definitions consistent so pixels don't flip categories at the handoff.

4. Saved test-scan patterns — noted, it's in the design

Nothing to add — enabling save-patterns on the beam-current test scans gives the offline scorer real data during the session and doubles as the pre-flight gate.

The Edison search — five-area prior-art verdict

The exact search ran successfully (Edison LITERATURE/PaperQA, task 2a641486-1ec5-49ad-b3e8-b2836932d961, 18 contexts, all peer-reviewed). Full answer, references, and complete trajectory committed: answer.md · references.txt · task_full.json.

Area Verdict Closest prior art
1. Optimizer-driven tuning of pattern-processing/indexing knobs Open (see caveat) Polonsky 2019 sweeps post-indexing segmentation tolerance, not indexing knobs
2. Metric-driven iterative re-indexing of stored raw patterns Open Stinville 2022 archives raw .up2 and notes reindexability — enabling, not a loop
3. Grain-level objectives Partially claimed Polonsky 2019: 100 reconstructions sweeping 0.1–10° threshold, scored by grain count + retained CI-good voxel fraction, elbow at 2.0°; Staněk 2020 threshold-sensitivity study
4. Hybrid escalation with automated criteria Partially anticipated; Hough-specific form open Shen 2019 (Acta Mat.): CNN first pass + confidence, full dictionary search only on low-confidence pixels — proposed, not demonstrated
5. Autonomous workflow + feedback to SEM acquisition Open at the system level Burnett 2016, Sparks 2024, Winiarski 2021 automate sub-workflows; none closes post-processing → acquisition

Edison's closing assessment is worth quoting because it is nearly a CALIBER spec: the unexplored combination is "repeated reindexing of archived raw patterns under optimizer-selected settings; joint pattern-, pixel-, and grain-level objectives with explicit cleanup/topology constraints; confidence- and grain-context-based escalation from Hough to dictionary matching; and translation of the optimized result into acquisition recommendations." No publication combines those elements.

One reconciliation caveat: Edison's corpus did not surface the January 2026 arXiv paper (2601.09627) I found by web search, which does apply GP-based BO to kikuchipy pattern-processing parameters — likely because it's an arXiv preprint too recent for the indexed corpus. So treat Area 1 as "open in the peer-reviewed literature, with one very recent preprint doing the narrow single-objective version." Areas 2–5 verdicts stand: that paper tunes processing knobs against NCC only — no indexing-method escalation, no grain-level objectives, no acquisition bridge.

"Doesn't BO use previous data to pick the next point? Isn't that a feedback loop?"

You're completely right, and my earlier wording was sloppy. BO is a feedback loop by definition — evaluate parameters → update the surrogate model → pick the next trial from everything seen so far. The arXiv paper absolutely closes a loop, and so does CALIBER. The distinction I was actually drawing is what's inside the loop and what it optimizes:

The arXiv 2601.09627 loop The CALIBER loop
Parameters searched Pattern-processing only (background, filters) Processing + PC + Hough + DI + reconstruction
Objective Single pattern-level score (NCC) Grain-level objectives + constraints (speckle, boundary integrity, % cleaned) alongside CI/fit/NCC
Structure One flat BO search Diagnosis-driven tiered escalation with BO inside each tier
Output Best processing settings Best settings plus the "rescuable or not" verdict that feeds acquisition advice

So the accurate novelty claim is not "we add a feedback loop" — loops exist — it's "we change what the loop measures and decides." The Edison survey confirms the fuller version is unclaimed in the peer-reviewed literature.

"Don't grain-level outcomes come from pattern-level changes? Or are there grain-level parameters too?"

Both, and this is exactly the right question. Two separate mechanisms:

  1. Grain-level outcomes are downstream of pattern-level parameters. Better indexing → fewer misindexed pixels → fewer speckle grains, cleaner boundaries. You're familiar with these knobs, and they do most of the work. The point of scoring at the grain level even while tuning at the pattern level is guardrails: it catches the failure mode where a pattern-level objective looks great while the microstructure quietly degrades (neighbour averaging inflating CI while blurring boundaries is our documented example).
  2. The grain-reconstruction software has its own independent parameter set — the Stage 5 table from my earlier parameters comment: the misorientation threshold defining a boundary (2–7.5°), the minimum-points-per-grain cutoff, the pre-segmentation quality filter (which CI/fit level excludes a pixel), and cleanup passes (dilation/fill count). These change grain count, size statistics, and texture with the indexing untouched — Staněk 2020 (found by Edison) shows one dataset going from over a million "grains" to ~75k purely by changing noise-removal and minimum-size settings. Polonsky 2019 is the proof this layer alone is worth optimizing: 100 reconstructions sweeping just the threshold, scored by grain count + retained-data fraction.

So the full CALIBER search space is two coupled layers — pattern-level knobs (change what orientations you get) and grain-level knobs (change how orientations become grains) — with grain-level metrics as the shared objective across both. Nobody has published the joint optimization; Polonsky covers a one-parameter sweep of the second layer only.


Committed this session (3d238c0): the complete Edison trajectory under outputs/edison-closed-loop-ebsd/ (answer, 15-entry reference list with DOIs, task id + exact query, full provenance JSON). Key sources: Polonsky 2019 · Shen 2019 · Staněk 2020 · Stinville 2022 · Burnett 2016 · Sparks 2024 · Winiarski 2021 · arXiv 2601.09627
· branch claude/issue-2-20260724-1601

Co-authored-by: Ronnie Guymon <244881888+ronnie-guymon@users.noreply.github.com>
@ronnie-guymon

Copy link
Copy Markdown
Collaborator Author

Texture as I understand it: seeing if a certain grain orientation is preferred. If yes, then there will be anisotropy in the properties.

@ronnie-guymon

Copy link
Copy Markdown
Collaborator Author

@claude using the full map from #8 (comment), use DREAM3DMX to do grain reconstruction. Change different parameters by a large amount so I can understand and see the effect changing the parameters has on the grain map. Show me the script you use, and explain how each changed parameter changed the outcome.

If these can be changed in DREAM3DMX, change them:

2. the misorientation threshold defining a boundary (2–7.5°), the minimum-points-per-grain cutoff, the pre-segmentation quality filter (which CI/fit level excludes a pixel), and cleanup passes (dilation/fill count). These change grain count, size statistics, and texture with the indexing untouched — Staněk 2020 (found by Edison) shows one dataset going from over a million "grains" to ~75k purely by changing noise-removal and minimum-size settings. Polonsky 2019 is the proof this layer alone is worth optimizing: 100 reconstructions sweeping just the threshold, scored by grain count + retained-data fraction.

Even if changing a parameter will result in a worse outcome, do it anyways to show me what it looks like. In fact, I would like you to make the quality worse and then try to make it better, again showing me the code used and explaining what it does.

@claude

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown

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


DREAM3D-NX grain reconstruction on the full Area 2 map — 14 reconstructions, 4 parameter sweeps, worse-then-better demo

All done and committed (fdb3910). The script is scripts/dream3dnx_grain_reconstruction.py, all figures and numbers are in example/area2-results/grain-reconstruction/. The single most important takeaway: the same indexing data produced anywhere from 2 to 34,548 "grains" depending only on reconstruction parameters — this layer changes the answer as much as the indexing layer does.

Todo

  • Gather context (locate area2_full.ang.gz, check its columns)
  • Install DREAM3D-NX Python (simplnx)
  • Build baseline pipeline (read .ang → quality mask → quats → segment → min-size → cleanup)
  • Sweep misorientation threshold (1°, 2°, 5°, 15°)
  • Sweep minimum-points-per-grain cutoff (1, 10, 100, 400 px)
  • Sweep pre-segmentation quality filter (none, fit < 1.4°, fit < 0.5°)
  • Sweep cleanup passes (erode ×2, ×10, fill)
  • "Make it worse, then better" demonstration
  • Commit script + result images (fdb3910)
  • Post explanation of each parameter's effect

Setup and the script

DREAM3D-NX's Python bindings aren't on PyPI (pip install simplnx fails); the working install is the conda package dream3dnx from the BlueQuartz channel:

conda create -y -n nx -c bluequartzsoftware -c conda-forge python=3.12 dream3dnx numpy matplotlib-base
conda run -n nx python scripts/dream3dnx_grain_reconstruction.py

The pipeline in the script (full version here) is six DREAM3D-NX filters, mirroring what OIM Analysis does internally:

import simplnx as nx
import orientationanalysis as oa

# 1. Read the .ang from the kikuchipy Hough run (gunzip area2_full.ang.gz first)
oa.ReadAngDataFilter.execute(data_structure=ds, input_file="area2_full.ang",
                             output_image_geometry_path=GEOM)

# 2. Pre-segmentation quality filter -> boolean Mask
#    (kikuchipy's .ang writer zeroed CI/IQ, so we gate on the fit column, in degrees)
t = nx.ArrayThreshold(); t.array_path = FIT; t.comparison = LessThan; t.value = fit_max
nx.MultiThresholdObjectsFilter.execute(..., array_thresholds_object=ts)

# 3. Euler angles -> quaternions (what the segmenter compares)
oa.ConvertOrientationsFilter.execute(..., input_representation_index=0,  # Euler
                                     output_representation_index=2)      # Quaternion

# 4. THE grain reconstruction: flood-fill; a boundary = neighbor misorientation > tol
oa.EBSDSegmentFeaturesFilter.execute(..., misorientation_tolerance=5.0, use_mask=True)

# 5. Minimum-points-per-grain cutoff
nx.ComputeFeatureSizesFilter.execute(...)                    # counts px per grain
nx.RequireMinimumSizeFeaturesFilter.execute(..., min_allowed_features_size=10)

# 6. Cleanup: grow grains into unassigned (id 0) pixels
nx.ErodeDilateBadDataFilter.execute(..., operation_index=1,  # 1 = erode bad data
                                    num_iterations=2)
nx.FillBadDataFilter.execute(..., min_allowed_defect_size=1000)

Each run reports: raw grain count (straight out of segmentation), kept count, retained pixel fraction, % of pixels rewritten by cleanup, number-weighted and area-weighted mean equivalent diameter, speckle fraction, largest grain. All 14 runs: stats.json.

Sweep A — misorientation tolerance (what counts as a boundary)

tol raw grains kept (≥10 px) retained largest grain AW mean
34,274 1,426 92.5% 306 px 19.2 µm
14,711 1,930 97.7% 513 px 18.1 µm
5° (baseline) 10,885 1,905 98.7% 544 px 19.0 µm
15° 8,761 1,583 99.0% 1,391 px 23.9 µm

Two neighboring pixels join the same grain if their crystal misorientation is below the tolerance. Lower it toward the orientation noise floor (Hough precision is roughly 0.5°) and real grains shatter: at 1° the raw count triples to 34,274 because intra-grain orientation noise now counts as "boundaries." Raise it past real boundary angles and grains merge: at 15° the largest grain balloons from 544 to 1,391 px as genuinely distinct neighbors (misoriented 5–15°) fuse.

The subtle and important part: the kept counts (1,426 / 1,930 / 1,905 / 1,583) are non-monotonic and look deceptively similar, because the min-size cull deletes most of the 1° fragments. The raw count is the honest signal — which is exactly why Polonsky 2019's stability curve is plotted against raw segmentation output. In your future feedback loop, track raw count vs tolerance and look for the plateau (here roughly 2–5°, consistent with the Edison recommendation for AM material).

Sweep B — minimum-points-per-grain cutoff

min size kept grains retained mean diam (number-wt) speckle frac
1 px (off) 10,885 99.7% 4.7 µm 82%
10 px (baseline) 1,905 98.7% 14.2 µm 0%
100 px 109 63.5% 49.7 µm 0%
400 px 2 5.0% 105 µm 0%

With the cutoff off, 82% of "grains" are sub-10-px speckle — mostly misindexing noise — and the number-weighted mean grain size crashes to 4.7 µm while the area-weighted mean stays at 14.7 µm. That divergence between the two means is the fingerprint of a speckle-contaminated map, and it's the same mechanism as Staněk 2020's million-grains-to-75k example. At 100 px the opposite failure: real small grains are deleted, only 109 survive, and "mean grain size" inflates to 50 µm. At 400 px the map is reduced to 2 absurd grains.

Two DREAM3D-NX behaviors discovered here worth knowing:

  • The filter doesn't leave holes — it merges. Culled grains' pixels are iteratively reassigned to neighboring surviving grains, which is why baseline retention stays at 98.7% (and why the baseline's largest grain measures 544 px when the largest raw grain is 493 px — it absorbed culled neighbors). Aggressive cutoffs therefore don't just remove small grains, they transfer their area into big ones, biasing size statistics upward twice.
  • Cutoffs above the largest raw grain (493 px here) refuse to run: Error: The minimum size is larger than the largest Feature. All Features would be removed — and the map passes through unchanged. A CALIBER loop needs to catch that error, not just read the output.

Sweep C — pre-segmentation quality filter

filter kept grains retained mean diam
none 1,907 100% 14.3 µm
fit < 1.4° (baseline) 1,905 98.7% 14.2 µm
fit < 0.5° 955 26.3% 10.5 µm

This filter excludes untrusted pixels before segmentation (the DREAM3D equivalent of TEAM's CI filter; here it gates on fit since the CI column was zeroed on export). Because your indexing is good — 99.7% of pixels have fit < 1.4° — the mild filter barely differs from no filter at all. But fit < 0.5° sits at the median of the fit distribution (0.49°), so it discards half the map: grains become moth-eaten fragments, the count halves, and mean size shrinks 26%. Lesson for the feedback loop: quality thresholds must be set relative to the dataset's metric distribution, not as absolute constants — a threshold that's mild on one map is destructive on another.

Sweep D — cleanup passes (this is the "fabricated data" knob)

Starting from the crippled fit < 0.5° map (49% of pixels are holes) to make the effect visible:

cleanup retained px rewritten by cleanup mean diam
none 26.3% 0% 10.5 µm
erode bad data ×2 62.0% 35.7% 16.5 µm
erode bad data ×10 97.9% 71.5% 20.5 µm
fill defects < 1000 px 29.2% 2.8% 11.0 µm

Each erode iteration grows every grain one pixel into adjacent unassigned space. At ×10 the map looks "complete" (98% retained) — but 72% of its pixels are invented, and look at the morphology: the recovered grains are round blobs, while the real microstructure (baseline panel in the figures above) has the elongated, directional melt-pool grains. The cleanup didn't recover the microstructure; it manufactured a plausible-looking wrong one, doubling the apparent mean grain size along the way. FillBadData barely acted (2.8%) because it only fills enclosed defects below the size cutoff, and these holes form one huge connected network — a nice illustration that the two cleanup filters answer different geometric questions. This is why "% of pixels altered by cleanup" belongs in CALIBER's objective as a penalty term, and why the standards require reporting it.

Worse, then better

  • Worst (tol 1°, no min size, no quality filter, no cleanup): 34,548 grains, 96% of them speckle, "mean grain size" 3.0 µm. Every number is garbage — from the same 99.5%-CI indexing run we validated earlier.
  • Recovered (tol 5°, min 10 px, fit < 1.4°, erode ×2): 1,905 grains, 99.9% retained with only 1.2% of pixels rewritten, mean 14.3 µm, area-weighted 19.1 µm, elongated AM morphology intact.

Same orientations in, ~18× difference in reported grain count — the entire swing is reconstruction parameters. That's the empirical version of the point from the earlier design discussions: Stage 5 has its own parameter layer worth optimizing (and guarding), independent of indexing quality.

What this means for CALIBER's loop

Concrete, computable signals this experiment validated: raw-grain-count-vs-tolerance plateau (stability), number-weighted vs area-weighted mean divergence (speckle detector), retained fraction after filtering (threshold sanity), and % pixels rewritten by cleanup (fabrication penalty). All four came straight out of stats.json with no human eyeballing — they're ready to be objective/constraint terms in the Ax loop.

Natural next step: wire these grain-level metrics into the staged feedback loop as the guardrail objectives alongside CI/fit — say the word and I'll extend the script into that combined loop.


· branch claude/issue-2-20260724-1601

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

Copy link
Copy Markdown
Collaborator Author

@claude a few questions:

It looks like there are hundreds of grains, not the thousands you said we would need in order to find the texture. Would we need to scan a larger sample area if we wat to understand the texture?

For sweep A, why are there progressively fewer and fewer holes? Why are there holes at all?

For Sweep C, would we want to keep the fit cutoff at <1.5°? or would 2° work (Mike has said we want to be at least under 2°, ideally less than 1°)? This would also help with the questions from Sweep D, as there would be much fewer grains to reconstruct if we filtered at <1.5° instead of <0.5°, kind of like was done with the "worse, then better" portion? If only 1.2% of pixels had to be rewritten with a fit filter as low as <1.4°, then I guess that works. What we could do, and lmk if this is unnecessary, is start at fit <0.5°, see the percentage of pixels that were rewritten, and then increase the fit cutoff by a few tenths of a degree until only say 5% (or some other optimal percentage) of pixels are rewritten? This way we could reach a good balance between keeping the best quality pixels without having to rewrite a large quantity of grains.

@claude

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown

Claude encountered an error after 3m 25s —— View job


I'll analyze this and get back to you.

@sgbaird

sgbaird commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Super cool. Love what you're doing here, Ronnie.

@claude pick back up on #8 (comment)

@claude

claude Bot commented Aug 13, 2026

Copy link
Copy Markdown

Claude finished @sgbaird's task in 3m 42s —— View job


Answers: texture statistics, the Sweep A holes, and choosing the fit cutoff

(Picking up the errored run — the previous job died before answering.)

Todo

  • Gather context (re-read scripts/dream3dnx_grain_reconstruction.py + stats.json from the sweep run)
  • Q1: grain count needed for texture — larger scan area?
  • Q2: why Sweep A has holes, and why they shrink as tolerance rises
  • Q3: fit cutoff choice (1.4° vs 2°) + evaluate the adaptive "% rewritten" scheme (computed the real fit distribution from area2_full.ang)
  • Post answers

1. Texture: you have ~1,900 grains, and the fix is a coarser step, not more scan time

First a small correction to the premise: the baseline reconstruction kept 1,905 grains (stats.json), not hundreds — the 109-grain number was the deliberately-broken 100 px cutoff run. So you're at roughly 2k.

Is 2k enough? The statistical unit for texture is the grain, not the pixel — the 87k pixels don't help, because pixels inside one grain are copies of the same orientation, so your effective sample size is ~1,900 independent orientations. What that buys you:

  • Qualitative texture — yes, comfortably. Detecting whether a preferred orientation exists (your understanding of texture is exactly right, including the anisotropy consequence), seeing a strong ⟨100⟩ solidification fiber in a pole figure: ~1,900 grains is plenty. Strong textures need few grains to show up.
  • Quantitative texture — marginal. For trustworthy ODF intensity values and texture index (numbers you'd put in a paper or compare across builds), the community rules of thumb run from a few thousand grains for moderate textures up to ~10,000 for weak ones. At 1,900, your pole figure shape is real but the intensity numbers carry noticeable statistical noise.

Now the important part: you don't need a bigger sample or longer scan — you need a coarser step over a larger area. Texture needs exactly one orientation per grain; it does not need the ≥10 pixels per grain that size statistics need. At your 2 µm step you're spending ~50 pixels on each grain to answer a one-pixel question. Triple the step to 6 µm and the same scan time covers 9× the area ≈ 9× the grains (~17k). Alternatives: stitch several maps, or just combine the orientation data from multiple areas when computing the ODF (MTEX merges datasets trivially).

This is worth pausing on for CALIBER, because it's the cleanest demonstration yet that the optimal parameter set depends on the objective: grain-size statistics want fine steps (points-per-grain floor), texture wants coarse steps (max grain count per unit time). Same sample, same instrument, opposite step-size recommendations — exactly the kind of trade-off the tool should surface rather than hide.

2. Sweep A holes: two sources, and one of them scales with the cull

Holes = black pixels = pixels that ended the pipeline assigned to no grain (FeatureId 0). They come from two places in the script:

  1. The quality mask (constant across Sweep A, ~0.35% of the map). Pixels with fit ≥ 1.4° — including the 0.29% of points Hough couldn't index at all, which carry a fit-= 180° sentinel value in the .ang — are excluded before segmentation. The flood-fill never enters them, so they stay unassigned. This is the hole floor: the min_1px run (no culling at all) retains 99.65%, i.e. exactly the masked fraction.
  2. Orphans from the minimum-size cull (this is the part that varies). When a <10 px grain is deleted, DREAM3D-NX doesn't leave a hole on purpose — it reassigns the pixels to a neighboring surviving grain. But when an entire neighborhood of tiny fragments is culled at once, or a fragment only touches masked pixels, there is no surviving neighbor to inherit the pixels, and they drop to 0.

Why the holes shrink as tolerance rises is then just arithmetic on the cull:

tol raw grains grains culled holes
34,274 ~32,850 7.5%
14,711 ~12,780 2.3%
10,885 ~8,980 1.3%
15° 8,761 ~7,180 1.0%

At 1°, the tolerance sits near the Hough orientation noise floor (~0.5°), so intra-grain noise gets segmented as "boundaries," the map shatters into 34k fragments, ~96% of them get culled, and whole shattered regions have no surviving grain nearby to absorb them → 7.5% holes. As the tolerance rises past the noise, fragments stop forming, the cull touches less area, and holes converge toward the 0.35% mask floor.

Useful side effect: post-cull hole fraction is itself a free diagnostic. A spike in holes as you lower the tolerance is the signature that you've crossed below the orientation noise floor — one more automatic signal for the feedback loop, complementing the raw-grain-count plateau.

3. Fit cutoff: on this map 1.4° and 2° are the same choice — and your adaptive scheme is right, with one simplification

(Small note: Sweep C used fit < 1.4°, not 1.5°.) I computed the actual fit distribution from the committed area2_full.ang.gz (87,416 points):

cutoff % of map excluded
0.5° 48.8%
0.7° 21.2%
1.0° 4.8%
1.4° 0.35%
2.0° 0.29%

Three conclusions fall straight out:

  • 1.4° vs 2° is a non-decision here: they differ by 0.06% of pixels. Everything between ~1.3° and the 180° sentinels is nearly empty, so both cutoffs mean the same thing — "exclude only the garbage." Either satisfies Mike's <2° guidance; keep 1.4°.
  • Your proposed walk-up scheme lands at 1.0°. Start at 0.5°, raise in tenths until ~5% is excluded → the table says you stop at exactly 1.0° (4.8%). So the scheme is coherent — and it's the right principle: dataset-relative thresholds instead of absolute constants, which is precisely what Sweep C showed (0.5° was destructive here only because it sits at this map's median).
  • But you don't need the iterative loop. The excluded fraction at any cutoff is just the fit histogram, which you get from one pass over the data with zero reconstructions. So the implementation is a percentile lookup: pick a target retention (say 95%), set fit_max = np.percentile(fit, 95). Your version converges to the same answer; this one costs nothing. (Minor bookkeeping: "% excluded by the filter" and "% rewritten by cleanup" are only approximately equal — cleanup also has to rewrite the cull orphans from question 2, which is why the baseline showed 1.2% rewritten against 0.35% masked.)

Two guardrails before adopting "exclude 5%" as the policy, though:

  1. The worst-fit pixels are not randomly placed — they concentrate at grain boundaries (overlapping patterns). So the 4.8% you'd exclude at 1.0° and then let cleanup rewrite is disproportionately boundary pixels, and rewriting boundaries with grown-in data is the one place fabrication actually distorts grain statistics. The permissive cutoff (1.4°, → 1.2% rewritten) leaves boundaries measured rather than invented. Given how empty the 1.0–1.4° band is on good data, I'd default to the permissive end and let the boundary-enrichment check (from the Hole-1 dispatcher discussion) veto stricter settings, rather than targeting a fixed 5%.
  2. Keep the filter cutoff and Mike's quality target as separate things. "<2°, ideally <1°" is a target for the indexing itself — something the Tier 1/2 loop tries to achieve for the whole-map median (yours: 0.49°, well inside "ideally <1°"). The filter cutoff is an outlier gate relative to whatever distribution the indexing achieved. If a future map came back with median fit 1.8°, hard-coding a 1° filter would silently discard most of the map — the right response there is to fix indexing upstream (PC, Hough parameters, DI escalation), not to filter harder. Distribution-relative filtering fails gracefully in exactly that case; a constant doesn't.

So: not unnecessary at all — your scheme, restated as "set the cutoff from the fit-distribution percentile, subject to a boundary-enrichment veto," is the right general policy for CALIBER and drops into the loop as two lines of numpy.


No code changes — these were questions about the existing sweep results.
· branch claude/issue-2-20260724-1601

@sgbaird

sgbaird commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

@ronnie-guymon how do you feel about the "DI + refinement on the residue only, finer dictionary as budget allows", from what I understand, the highest quality analysis only being run on a subset of patterns? Claude seems to keep suggesting this, but it didn't seem like running everything with DI + refinement would be computationally intractable or even that burdensome, which gets me wondering how much we need to push back on Claude or if we're not actually losing out on anything. Just seems to me like we should be using the highest quality analysis on everything, not just a subset.

@claude see above. Keep response concise

@claude

claude Bot commented Aug 13, 2026

Copy link
Copy Markdown

Claude finished @sgbaird's task in 44s —— View job


Residue-only DI was a loop-economics choice, not a quality ceiling — full-map DI is fine as a final pass

  • Gather context (timing/memory numbers from prior runs on this branch)
  • Answer concisely

You're right that full-map DI + refinement is tractable. Measured on Area 2 (87k patterns, 4 CPU cores, 2.5° dictionary): DI ≈ 14 min + refinement ≈ 26 min ≈ 40 min total, ~2.6 GB dictionary in RAM. That's an easy overnight-or-lunch job, and a GPU or finer-but-residue-only combos make it faster still. Nothing intractable.

The residue-only suggestion was about where in the workflow the cost sits, not whether it's payable once:

  1. Inside the optimization loop, every parameter change means re-evaluating. Hough is ~11 s per slice evaluation; DI is minutes. A BO loop doing 30–100 evaluations at 40 min each is 1–3 days per dataset, vs under an hour with Hough in the loop. Residue-only DI is what keeps the iterative part cheap.
  2. As the final production pass, running DI + refinement on everything is completely reasonable — and simpler (no threshold/merge logic). No pushback needed there.

Two quality caveats before calling DI "the highest quality analysis on everything," though — from our own runs:

  • On well-indexed pixels, DI adds nothing measurable. Refined DI and Hough agreed to 0.78° median; where Hough was confident, the two converge. DI's real advantage is confined to the low-CI residue — so full-map DI mostly re-derives answers Hough already had. Also, refinement (the precision step) isn't DI-exclusive: you can refine_orientation Hough solutions directly and get the same noise-floor benefit map-wide for ~26 min without any dictionary.
  • On this dataset DI only worked after neighbour averaging (raw patterns: best NCC ≈ 0.09, pure noise). Averaging blurs boundary-adjacent patterns — so "DI on everything" here is really "DI on spatially smoothed patterns," which can be worse at grain boundaries than Hough on raw patterns. Highest-cost ≠ highest-quality per pixel; it depends on which pixels.

Suggested policy: Hough (+ refinement) drives the parameter loop; final deliverable map = full-map refinement, with DI on everything if budget allows or on the residue if not — then verify the grain-level metrics didn't move. If full-map DI and the hybrid map give the same grain statistics (my expectation), that's empirical license to use the cheap path routinely; easy experiment to run on Area 2 if you want the numbers.
· branch claude/issue-2-20260724-1601

@ronnie-guymon

Copy link
Copy Markdown
Collaborator Author

Notes from meeting Xavier:
Get some Sobol samples to test within the sample space for the 4 above mentioned parameters, run experiments by changing those 4 parameters, create the grain maps, compute the objective (total variance, fill number or percentage of pixels that were regrown, any other objective we decide on), look at the computed maps, and see if the is a correlation between out objectives and the quality of the grain maps to see if the objective is even a good measure of the grain map quality. If none of those work, we could consider adding or changing parameters. Set up the function so that BO can be used and create a workflow to test and incorporate Honegumi once we have confirmed our objective will actually produce our desired outcome of a high-quality grain map (less Christmas lights,

@claude I don't need any feedback, just keep this in mind.

@claude

claude Bot commented Aug 13, 2026

Copy link
Copy Markdown

Claude finished @ronnie-guymon's task in 45s —— View job


Noted — plan recorded, no feedback given per your request

  • Read the meeting notes from Xavier
  • Record the plan so future sessions on this repo start from it

Captured for future work, in order:

  1. Sobol samples over the 4 grain-reconstruction parameters (misorientation threshold, min-points-per-grain, quality/fit filter, cleanup passes).
  2. Run the reconstructions at those points and generate the grain maps.
  3. Compute candidate objectives (total variance, fill count / % of pixels regrown, others as decided).
  4. Visually check whether the objectives correlate with grain-map quality ("less Christmas lights") — validate the objective as a proxy before optimizing it; if none correlate, revisit parameters/objectives.
  5. Only then: set up the function for BO and build the Honegumi-based workflow.

I'll treat this as the standing plan for the next implementation step on this branch (starting point: example/area2-results/area2_full.ang.gz + the metrics already emitted by scripts/dream3dnx_grain_reconstruction.py). No code changes made.

--- · branch claude/issue-2-20260724-1601

@ronnie-guymon

ronnie-guymon commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

@ronnie-guymon how do you feel about the "DI + refinement on the residue only, finer dictionary as budget allows", from what I understand, the highest quality analysis only being run on a subset of patterns? Claude seems to keep suggesting this, but it didn't seem like running everything with DI + refinement would be computationally intractable or even that burdensome, which gets me wondering how much we need to push back on Claude or if we're not actually losing out on anything. Just seems to me like we should be using the highest quality analysis on everything, not just a subset.

@claude see above. Keep response concise

@sgbaird yeah as Claude said, I think doing Hough indexing will be ok as long as the patterns are intense enough it can read the Kikuchi patterns. If the lines aren't intense enough the we can move to DI. I can keep this in mind though and see the difference it would make to do all DI right off the bat.

@claude

claude Bot commented Aug 13, 2026

Copy link
Copy Markdown

Claude finished @ronnie-guymon's task in 37s —— View job


Agreed — with one caveat on "if the lines aren't intense enough, move to DI"

  • Read trigger comment (agreement with the Hough-first policy; no new request)

Your summary matches the plan: Hough drives the loop while band intensity supports it, escalate when it can't. Two brief refinements from our Area 2 runs worth keeping in mind:

  1. DI isn't automatically the rescue for faint bands. On the raw Area 2 patterns, Hough worked while plain DI failed (best NCC ≈ 0.09 — the Radon transform integrates along whole lines, so it tolerates noise better than per-pixel correlation). DI only became viable after neighbour-pattern averaging. So the escalation ladder stays: Hough sweep → PC re-check → averaging → DI on what's left.
  2. "Intense enough" doesn't need a human judgment call — the noise-floor NCC test and the plateau-plus-diagnosis dispatcher already discussed give the loop an automatic version of that decision, per pixel rather than per map.

No code changes — nothing was requested.

--- · branch claude/issue-2-20260724-1601

@ronnie-guymon

ronnie-guymon commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator Author

@claude in response to your commentary here #8 (comment):

I think you and I are saying the same thing for the texture. I said we need to scan a larger area of the sample, is that not also the same thing you're saying just with the addition that when we do scan the larger area, we use a coarser step size? In terms of workflow, that means when doing the imaging session, we should make sure that we have enough time to get both a smaller area for the microstructure analysis, and a larger scan for our texture analysis. We will probably have different functions for the two forms of analysis as well, or possibly even different objectives for each o f them. Something to keep in mind

We will probably just stay around the 5° misorientation tolereance, I don't see any reason to change from that. If we do decide to change that, we can use the hole fraction as our metric to decide whether or not a threshold has been reached.

I will finish commenting/questioning on the last half later

@claude

claude Bot commented Aug 14, 2026

Copy link
Copy Markdown

Claude finished @ronnie-guymon's task in 52s —— View job


Confirmed — same page on texture; two notes filed for the workflow

  • Read trigger comment (texture clarification + 5° tolerance decision)
  • Confirm agreement and add refinements
  • Post reply

Texture: yes, we're saying the same thing

Larger scan area is the substance of it; the coarser step is just what makes the larger area affordable in one session. Scan time scales roughly with area ÷ step², so a bigger area at the same 2 µm step costs quadratically more time — while tripling the step to 6 µm buys about 9× the area (≈ 9× the grains) in the same scan time. So your workflow conclusion is exactly right:

  • Scan 1 — microstructure: small area, fine step (the ≥10 points-per-grain floor drives step size), the full objective vector we've built (CI/fit + speckle, boundary integrity, % cleaned, grain-count stability).
  • Scan 2 — texture: large area, coarse step (one orientation per grain is all texture needs), and a genuinely simpler objective — indexed fraction and total grain count are what matter; the boundary/speckle guardrails largely drop out because you're not measuring sizes or shapes.

So yes: two scans per imaging session, and two objective functions in CALIBER keyed to which deliverable the scan serves. Two practical notes for the session plan: (1) coarsening the step doesn't degrade per-pattern quality — dwell/current still set CI, so the beam-current pre-flight applies to both scans; (2) keep save-patterns on for both, per the standing raw-patterns policy — the texture scan's patterns are cheap insurance and reindexable like everything else.

5° tolerance: agreed as the default, one asymmetry to remember about the hole-fraction metric

5° sits on the stability plateau from Sweep A (2–5°) and is the field convention — no reason to move for grain characterization. The one nuance if it's ever revisited: hole fraction only guards the low side. It spikes when the tolerance drops below the orientation noise floor (map shatters → mass culling → orphaned pixels), but merging at too-high tolerance produces no holes at all — that failure shows up in the raw grain count and the largest-grain size instead (Sweep A: largest grain 544 px at 5° → 1,391 px at 15°, holes nearly unchanged). So the pair of signals is: hole fraction for "too low," raw-count/largest-grain drift for "too high."

One flag for later, not now: the Edison synthesis recommended treating 2–5° as a sweep in AM material because cellular/subgrain boundaries are physically real in that range. Fixed 5° is the right call while the outcome is grain characterization — just note that it deliberately merges subgrain structure, so if KAM/subgrain analysis ever becomes a CALIBER deliverable, the tolerance becomes an output again rather than a constant.

Noted on the rest — I'll pick up your remaining questions on the last half whenever you post them.

No code changes — this was a design confirmation.

--- · branch claude/issue-2-20260724-1601

@ronnie-guymon

Copy link
Copy Markdown
Collaborator Author

@claude I think my knowledge on the subject of EBSD is still lacking. Find papers that will help me directly understand the subjects we have been talking about, especially when it comes to how parameters will alter the outcome. I want to understand the grain orientation as well and the implications of it. Provide any other papers you thin would be helpful, such as the ones you cited that are similar to CALIBER, and provide a reasoning for why they are chosen and their subject.

Also claude, Xavier thought that measuring variance would be the best outcome to start with measuring and trying to reduce and see if that causes a decrease in the failed pixels. Is this a good place to start, or are there any other outcomes we could use and possibly have a dual-outcome function. This would be done through probably minimum points per cutoff, fit filter, and regrowth in the function, as well as any other parameters we might need to change.

@sgbaird I feel like most of what I have been learning on this subject is from what claude has summarized, and after Xavier's comment yesterday about how he has been best able to gain understanding from reading other papers, I feel like that might be the best thing for me to do right now. I don't really understand what the parameters do or what specific outcomes I should be looking for, nor the implications of those outcomes. Is there a better way you can think of for me to go about CALIBER? I feel like before I can try and optimize parameters to get better outcomes, I need to have an understanding of why I even want those outcomes and why they are useful. Am I trying to get too deep of an understanding for the scope of this project? I just want to have the understanding we were talking about yesterday during our meeting.

@claude

claude Bot commented Aug 20, 2026

Copy link
Copy Markdown

Claude finished @ronnie-guymon's task in 4m 51s —— View job


A reading curriculum for the EBSD topics in this thread + assessment of variance as the starting objective

Todo

  • Gather context (committed Edison reference lists, grain-reconstruction stats)
  • Validate links/DOIs for every recommended paper (all confirmed via web search)
  • Build the curated reading list with reasoning per paper
  • Assess variance as the starting objective; propose the dual-outcome pairing
  • Brief note on the learning-approach question

Part 1 — The reading list

Ordered as a curriculum, not by importance — each tier assumes the one before it. OA = open access (readable without library login; for the rest, BYU library access will get you in). Reasoning for each pick is the second sentence.

Tier 1 — Foundations (read these first):

  1. Britton et al. 2016, "Tutorial: Crystal orientations and EBSD — Or which way is up?" Mater. Charact. 117:113 — DOI · free PDF (OA). This is the answer to "I want to understand grain orientation": what an orientation actually is, Euler angles, reference frames, and why conventions matter (it's the paper behind MTEX's 'convertEuler2SpatialReferenceFrame' incantation). Written explicitly as a tutorial.
  2. Humphreys 2001, "Grain and subgrain characterisation by electron backscatter diffraction" J. Mater. Sci. 36:3833 — DOI. The classic review of exactly what CALIBER measures: how step size, orientation noise, misorientation thresholds, and cleanup determine the grain sizes and boundary fractions you report. Most of our docs §4/§8 reasoning traces back to this paper.
  3. Wilkinson & Britton 2012, "Strains, planes, and EBSD in materials science" Materials Today 15:366 — DOI (OA). The big-picture "why do we want these outcomes" paper: what EBSD can tell you (texture, boundaries, strain) and why anyone cares. Short and readable; good for motivation before the technical tiers.

Tier 2 — How parameters alter the outcome (the core of your question):

  1. Randle 2009, "Electron backscatter diffraction: Strategies for reliable data acquisition and processing" Mater. Charact. 60:913 — DOI. A practitioner's walkthrough of the acquisition and processing choices (step size, cleanup, sampling) and the biases each one introduces — essentially the paper version of our parameter-selection doc.
  2. Wright & Nowell 2006, "EBSD Image Quality Mapping" Microsc. Microanal. 12:72 — DOI. What IQ actually measures and why its contrast mixes phase, strain, topography, and boundaries — important because IQ is one of your candidate feedback signals and it is not a single physical quantity.
  3. Wright et al. 2015, "Introduction and comparison of new EBSD post-processing methodologies" Ultramicroscopy 159:81 — DOI · free PDF from EDAX. By the authors of TEAM/OIM's own algorithms: neighbor-pattern averaging (NPAR — the same trick that rescued our DI run) and re-indexing, with honest discussion of the boundary-blurring trade-off we keep flagging.
  4. Bachmann, Hielscher & Schaeben 2011, "Grain detection from 2d and 3d EBSD data — specification of the MTEX algorithm" Ultramicroscopy 111:1720 — DOI. What the misorientation threshold means algorithmically during grain reconstruction — the theory behind Sweep A. Read with our sweep figures open.
  5. Staněk et al. 2020, "Comparison of segmentation of 2d and 3d EBSD measurements" Metallic Materials 58:301 — DOI (OA). The over-a-million-grains-to-75k example: proof that cleanup and minimum-size settings alone can change the answer by an order of magnitude. Our Sweep B is the same experiment on your data.

Tier 3 — Indexing methods:

  1. Jackson, Pascal & De Graef 2019, "Dictionary Indexing of Electron Back-Scatter Diffraction Patterns: a Hands-On Tutorial" Integr. Mater. Manuf. Innov. 8:226 — DOI. The definitive practical explanation of DI — what the dictionary is, what the orientation-sampling resolution means (your "2.5°" question), and every step with worked datasets. Directly explains what scripts/dictionary_indexing_test.py does.

Tier 4 — Your material: grain orientation and its implications in LPBF AlSi10Mg:

  1. Thijs et al. 2013, "Fine-structured aluminium products with controllable texture by selective laser melting of pre-alloyed AlSi10Mg powder" Acta Mater. 61:1809 — DOI. The landmark paper (1,700+ citations) on your exact material: why LPBF AlSi10Mg develops the ⟨100⟩ solidification texture and elongated grains you see in your Area 2 IPF map, how scan strategy controls it, and why that orientation preference produces anisotropic properties — the "implications of grain orientation" in concrete, mechanical terms.
  2. The two open-access AlSi10Mg microstructure papers already cited in the eutectic-Si discussion — heat-treatment/Si-network study (PMC) and the cell-vs-grain hierarchy paper (Sci. Rep.) — for the two-length-scale picture (cells vs grains) that explains what your 2 µm map can and cannot see.

Tier 5 — CALIBER-adjacent (the prior-art papers, and why each matters to the project):

  1. arXiv 2601.09627 (2026) (OA) — the closest existing work: GP-based Bayesian optimization of kikuchipy pattern-processing parameters against NCC. Read it to see the single-objective version of CALIBER — and to see the gap (no grain-level objectives, no escalation, no acquisition feedback) that defines your contribution.
  2. Shen et al. 2019 Acta Mater. 170:118 — DOI — confidence-based escalation (cheap indexer first, expensive search only on low-confidence pixels): the published ancestor of the Hough→DI residue strategy.
  3. Polonsky et al. 2019 Integr. Mater. Manuf. Innov. 8:37 — DOI — 100 reconstructions sweeping the misorientation threshold, scored by grain count + retained-data fraction. This is the single most CALIBER-like published experiment and the model for the Sobol campaign in Xavier's plan.
  4. Stinville et al. 2022 Sci. DataDOI (OA) — why archiving raw patterns for reindexing matters; the data-management philosophy this whole PR runs on.

The full Edison-generated bibliographies (with more, e.g. Wanni 2024 on texture representation) are committed at outputs/edison-closed-loop-ebsd/references.txt and outputs/edison-mtex-alternatives/references.txt.

Suggested order: 1 → 3 → 10 (one sitting each — orientation, motivation, your material), then 2 and 4 carefully (they're the parameter→outcome core), then 6–8 alongside the committed sweep figures, then 9 and the Tier-5 set when you're designing rather than learning.


Part 2 — Is variance the right starting objective?

Short answer: variance is a reasonable candidate, but only as one half of a pair — on its own it has a degenerate minimum that the optimizer will find immediately. And one causality correction first.

The causality correction. "Reduce variance and see if that causes a decrease in the failed pixels" has the arrow backwards for the Stage-5 knobs you listed. Failed pixels (excluded/unassigned) are directly set by two of the three parameters in the function: the fit filter decides how many pixels are excluded, and regrowth decides how many holes get filled back in. So variance and failed-pixel count won't be cause and effect — they're two symptoms co-driven by the same knobs. That's fine (Xavier's plan is to check whether objectives correlate with map quality, not to establish causation), but it means the loop should treat "failed pixels" as a second measured outcome, not as the downstream effect of variance.

Why variance alone is gameable — with numbers from our own sweeps (stats.json):

  • Grain-size variance is a plausible Christmas-lights detector: a speckle-contaminated map has thousands of 1–2 px grains next to a few hundred real ones, so the size distribution splits and variance (relative to the mean) blows up. In the no-cutoff run, the number-weighted mean crashed to 4.7 µm while the area-weighted mean stayed at 14.7 µm — that divergence is variance in another costume.
  • But the minimum-variance map is not the best map — it's a degenerate one. Crank the min-size cutoff and variance collapses trivially: our 400 px run kept 2 grains (near-zero variance, 5% of the map retained). Crank regrowth and holes vanish trivially: erode ×10 gave 98% retention with 72% of pixels fabricated. Both look excellent on (low variance, few failed pixels) and both are garbage.

The fix is exactly the dual-outcome function you asked about. Pair a quality objective with a fabrication/coverage penalty so the degenerate solutions become losing moves:

  • Objective A (minimize): coefficient of variation of grain size — CV = std/mean of equivalent diameters. Prefer CV over raw variance: it's scale-free, so it doesn't change meaning when mean grain size shifts between samples. (The NW-vs-AW mean divergence is an equivalent alternative.)
  • Objective B (minimize): cleanup_altered_frac — % of pixels regrown. This is the anti-gaming term.
  • Constraints (not objectives): retained_frac ≥ ~0.95 and n_grains ≥ a few hundred — these kill the 2-grain solution outright rather than trading against it.

That structure drops straight into Honegumi/Ax as a two-objective (Pareto) problem with outcome constraints.

Two measurement details that will matter:

  1. Compute variance on the raw segmentation (pre-cull), or at a fixed mild cull. If min-size cutoff is one of the swept parameters, post-cull variance is partly circular — the knob directly truncates the small-grain tail of the very distribution you're scoring. Polonsky 2019 scores raw segmentation output for the same reason.
  2. Don't test one objective at a time. Every reconstruction already yields the whole candidate panel for free (CV, NW/AW divergence, speckle fraction, hole fraction, % regrown, raw grain count, retained fraction — all in stats.json, CV is a one-line addition). Run the Sobol campaign once, compute all candidates per run, then rank-correlate each against your visual quality ranking. Xavier's validation step then picks the survivors from one campaign instead of one campaign per candidate. Worth adding to the panel: mean grain orientation spread (GOS) — a variance measured in orientation space rather than size space, sensitive to noise the size metrics miss.

So: start where Xavier suggested, but as CV of grain size + % regrown as a pair, computed alongside the rest of the panel in a single Sobol campaign. Happy to extend scripts/dream3dnx_grain_reconstruction.py into that campaign (scipy Sobol sampling over the 4 parameters, full metric panel + a thumbnail grid for the visual ranking) — say the word.


Part 3 — On the learning-approach question (addressed to Sterling, but two cents)

You are not going too deep. Objective design is the intellectual core of CALIBER — an optimizer pointed at a metric nobody understands produces maps nobody trusts, and Part 2 above is a live example of why (variance has a failure mode you can only see if you know what a good map looks like). The calibration I'd suggest: aim for working understanding, not theory — for each parameter, can you (a) predict the direction of its effect, and (b) name the failure mode it guards against? The reading list is scoped to that altitude: you don't need dynamical diffraction physics (treat EMsoft as a black box), but Tiers 1–2 you want cold. One study tip that fits how this branch has evolved: read Humphreys and Wright 2015 with the committed sweep figures open — the papers explain the mechanisms, and the sweeps show the same mechanisms on your own Area 2 data, which is a faster way to make it stick than either alone.


No code changes — this was a literature/design question. All paper links above were validated this session.
· branch claude/issue-2-20260724-1601

@ronnie-guymon

Copy link
Copy Markdown
Collaborator Author

@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 Author

@claude+opus you there?

@claude

claude Bot commented Aug 21, 2026

Copy link
Copy Markdown

Claude finished @ronnie-guymon's task in 32s —— View job


Here — and the two errored runs didn't lose anything

  • Confirm session is live and check branch state

Both of the failed runs (32505468129, 1 s; 31621260719, 3 m 25 s) died in the harness before doing any work — no partial commits, no half-finished state. Branch claude/issue-2-20260724-1601 is intact at fdb3910 with everything from prior sessions committed (indexing results, DI test, master-pattern comparison, both Edison trajectories, the DREAM3D-NX sweeps). The 3 m 25 s run's question — texture statistics, the Sweep A holes, the fit cutoff — was already answered in the Aug 13 pick-up comment, so nothing there is outstanding.

Where the thread stands: the last substantive item was your Aug 20 question, answered with the reading curriculum and the assessment of variance as the starting objective (short version: use CV of grain size + % pixels regrown as a pair, with retained_frac and n_grains as constraints rather than objectives — variance alone has a degenerate minimum that a 2-grain map wins).

Standing next step, ready when you want it — Xavier's plan from the Aug 13 meeting notes:

  1. Sobol samples over the 4 reconstruction parameters (misorientation tolerance, min points/grain, fit filter, cleanup passes)
  2. Run the reconstructions on area2_full.ang.gz, emit the full metric panel per run (CV, NW-vs-AW mean divergence, speckle fraction, hole fraction, % regrown, raw grain count, retained fraction, GOS) rather than one candidate at a time
  3. Thumbnail grid of the maps so you can rank them visually and check which metrics actually correlate with "less Christmas lights"
  4. Only then wire Ax/Honegumi

That's an extension of scripts/dream3dnx_grain_reconstruction.py — say the word and I'll build it. Or if you'd rather keep going on the reading-list side or the last half of your Aug 12 questions, I'm here for that too.

No code changes — this was a check-in.
· branch claude/issue-2-20260724-1601

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