Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
b5f4382
docs: 0.3.0 release plan — fitting, EELS/EDS, EBSD, atom mapping
CSSFrancis Jul 27, 2026
5a158d0
feat(data): synthetic EELS, EDS and EBSD datasets
CSSFrancis Jul 27, 2026
d069b8e
feat(toolbars): requires_package gate + domain extras (0.3.0 Wave 0)
CSSFrancis Jul 27, 2026
72e2f0c
feat(fitting): batched GPU model fitting (0.3.0 Wave 1.1-1.3)
CSSFrancis Jul 27, 2026
3e7db36
perf(fitting): analytic Jacobians + seeded propagation (#54)
CSSFrancis Jul 27, 2026
1216596
feat(ebsd): GPU dictionary indexing + pattern preprocessing (#70, #71)
CSSFrancis Jul 27, 2026
1b14532
feat(ebsd): batched orientation refinement (#72)
CSSFrancis Jul 27, 2026
b156867
feat(ebsd): CrystalMap, IPF colours, similarity map, phase merging (#73)
CSSFrancis Jul 27, 2026
a3e5ae5
feat(fitting): 2-D model support (#59)
CSSFrancis Jul 27, 2026
56d04b1
feat(atoms): atom finding, GPU refinement, property maps (#75, #77, #79)
CSSFrancis Jul 27, 2026
5d6d396
fix(fitting): Polynomial and Offset broke for any batch bigger than one
CSSFrancis Jul 27, 2026
689e1a9
feat(spectroscopy): composition -> auto-populated model (#62)
CSSFrancis Jul 27, 2026
3ba7bb0
fix(fitting): ModelSpec could not rebuild real EELS or EDS models
CSSFrancis Jul 27, 2026
3105585
feat(spectroscopy): tabulated EELS edges, so EELS gets the GPU path t…
CSSFrancis Jul 27, 2026
7202f3a
feat(spectroscopy): quantification -> per-element composition maps (#66)
CSSFrancis Jul 27, 2026
de09ae7
feat(atoms): dumbbell lattices, with JOINT pair refinement (#78)
CSSFrancis Jul 27, 2026
955a2f7
feat(fit): the Fit wizard — build a model, fit the scan, commit maps …
CSSFrancis Jul 27, 2026
cc7d68a
feat(fit): drag handles on the plot + a persistent model (#57)
CSSFrancis Jul 27, 2026
a2b9bde
feat(fit): Fit-spectrum button, handles move in place, composition + …
CSSFrancis Jul 27, 2026
682f5b7
fix(fit): drags now move the curve — follow anyplotlib's example prop…
CSSFrancis Jul 27, 2026
9b360f6
fix(fit): "Fit spectrum" fitted the navigation MEAN, not the displaye…
CSSFrancis Jul 27, 2026
dd5f392
feat(fit): adaptive fitting, per-position memory, smoother drags, own…
CSSFrancis Jul 27, 2026
49f9210
fix(fit): batch the per-frame plot pushes; show fit coverage
CSSFrancis Jul 27, 2026
674b87c
feat(fit): control points on every component; build each spec once
CSSFrancis Jul 27, 2026
a4ac566
chore(deps): lock the eels / ebsd / atoms extras
CSSFrancis Jul 27, 2026
a85538e
fix(fit): a whole-scan run reported "0 fitted" and left the handles b…
CSSFrancis Jul 27, 2026
3f0e7c9
chore(fit): drop an import left unused by the prototype cache
CSSFrancis Jul 27, 2026
17e0153
fix(fit): two components of a kind started ON each other and could no…
CSSFrancis Jul 27, 2026
86ad340
fix(fit): after a fit, every drag was undone by the caret itself
CSSFrancis Jul 27, 2026
5816af3
fix(fit): two components of a kind swapped identity from pixel to pixel
CSSFrancis Jul 27, 2026
a979648
feat(fit): Fit all Spectra — plots the maps, stores every position, r…
CSSFrancis Jul 27, 2026
80214f4
refactor(fit): hold the per-position parameters where HyperSpy alread…
CSSFrancis Jul 27, 2026
ac5a587
fix(fit): the per-position store was TRANSPOSED — every position show…
CSSFrancis Jul 27, 2026
ff27765
perf(fit): the model lagged a whole drag behind the navigator
CSSFrancis Jul 27, 2026
5e91a97
fix(actions): scope Remove Background and the Fit maps to their caret…
CSSFrancis Jul 27, 2026
55329c9
fix(background): the preview never followed the band, and could blow …
CSSFrancis Jul 27, 2026
087e06a
feat(fit): a "From <elements>" button — build the model from the comp…
CSSFrancis Jul 27, 2026
a6be65c
feat(eels): batch EELSCLEdge properly; delete TabulatedShape
CSSFrancis Jul 27, 2026
b670c7f
feat(ebsd): the indexing wizard — the same workflow, with bands inste…
CSSFrancis Jul 28, 2026
723ba5e
fix(ebsd): index the lazy scan chunk-by-chunk, never the whole thing
CSSFrancis Jul 28, 2026
f2d8886
refactor(ebsd): chunk through hyperspy rechunk, not by hand
CSSFrancis Jul 28, 2026
7508362
Merge pull request #82 from CSSFrancis/feat/0.3.0-wave0-wave1
CSSFrancis Jul 28, 2026
8358d83
Merge pull request #84 from CSSFrancis/feat/0.3.0-wave2-spectroscopy
CSSFrancis Jul 28, 2026
3bd73f6
Merge pull request #86 from CSSFrancis/feat/0.3.0-fit-wizard
CSSFrancis Jul 28, 2026
27c9ee8
Merge pull request #85 from CSSFrancis/feat/0.3.0-wave4-atoms
CSSFrancis Jul 28, 2026
e7c4180
Merge remote-tracking branch 'origin/feat/0.3.0' into feat/0.3.0-wave…
CSSFrancis Jul 28, 2026
bd61f6e
Merge pull request #83 from CSSFrancis/feat/0.3.0-wave3-ebsd
CSSFrancis Jul 28, 2026
acc0bdd
Merge remote-tracking branch 'origin/main' into feat/0.3.0
CSSFrancis Jul 28, 2026
98f03a1
feat(examples): the Examples menu, from em-database
CSSFrancis Jul 28, 2026
b9ce4a2
feat(examples): show the camera on the hover card
CSSFrancis Jul 28, 2026
21d7454
ci: the matrix was timing out on a coverage report nobody reads
CSSFrancis Jul 29, 2026
56c40ff
perf(tests): stop sleeping half a second per test to reap a cluster t…
CSSFrancis Jul 29, 2026
736f77e
test(dask): pin both halves of the shutdown reap contract
CSSFrancis Jul 29, 2026
3370c5d
fix(tests): two races the suite's own slowness was hiding
CSSFrancis Jul 29, 2026
b7bab96
fix(e2e): the welcome tour was eating pointer events on every CI runner
CSSFrancis Jul 29, 2026
067e9f4
test(e2e): only mint a scratch settings dir when the spec has not bro…
CSSFrancis Jul 29, 2026
d3b34ad
docs(ci): correct the coverage cost — it is not why the matrix timed out
CSSFrancis Jul 29, 2026
477be25
fix(session): shutdown never stopped the trees' in-flight compute
CSSFrancis Jul 29, 2026
87ddf24
docs(test): note how the real-cluster reap was verified
CSSFrancis Jul 29, 2026
dd342a0
fix: distinct names for the two Fit maps windows, and tour/shell isol…
CSSFrancis Jul 29, 2026
3edf9ae
perf(tests): wait for the selector debounce instead of sleeping 0.8s …
CSSFrancis Jul 29, 2026
89ecec9
fix(tests): settle the load before console-preview tests drive the se…
CSSFrancis Jul 29, 2026
d61587c
fix(e2e): the two "flaky" fit specs were measuring a stale overlay
CSSFrancis Jul 29, 2026
e96ddc8
fix(tests): pin the widget's answer too, so the cursor cannot be reve…
CSSFrancis Jul 29, 2026
cdea03b
fix(e2e): confirm the panel MOVED before judging the model
CSSFrancis Jul 29, 2026
b38ef3a
fix(e2e): wait for the MODEL to follow, not just the spectrum
CSSFrancis Jul 29, 2026
c7ae61e
fix(e2e): confirm the navigator LANDED before reading the caret
CSSFrancis Jul 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 84 additions & 13 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ jobs:
test:
name: ${{ matrix.os }}-py${{ matrix.python-version }}
runs-on: ${{ matrix.os }}
timeout-minutes: 30
# 30 was too tight once 0.3.0 grew the suite to 2372 tests: legs landed at
# 28m30s and half the matrix was CANCELLED mid-run at exactly 30m, which
# reads as a test failure but is a clock. The run is much shorter now (see
# the coverage note below); this is headroom, and a timeout never reached
# costs nothing.
timeout-minutes: 40
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -58,15 +63,74 @@ jobs:

# The single canonical Qt-free suite (testpaths=spyde/tests/migrated).
# GPU tests skip themselves without CUDA/MPS. No display server required.
#
# Coverage on ONE leg only: the upload below runs solely on
# directelectron/spyde, so on every fork the other 11 legs wrote a
# coverage.xml nobody reads. Cost is +45% on a Windows dev box (measured
# 171s -> 248s over the same 25 files) but only ~15% on a hosted Linux
# runner (12.5m with vs ~10.8m without, same commit) — so this is tidiness
# and ~1.5m a leg, NOT the reason the matrix used to time out. That was
# DaskManager.shutdown() sleeping 0.5s per Session teardown; see
# test_dask_shutdown_fast.py.
- name: Run tests
run: uv run pytest spyde/tests/migrated --cov=spyde --cov-report=xml
run: uv run pytest spyde/tests/migrated ${{ (matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12') && '--cov=spyde --cov-report=xml' || '' }}

- name: Upload coverage to Codecov
if: ${{ github.repository_owner == 'directelectron' }}
if: ${{ github.repository_owner == 'directelectron'
&& matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12' }}
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}

# ── Domain extras (0.3.0) ──────────────────────────────────────────────────
# The `test` job above runs with NO optional extras, which is the important
# half of the contract: SpyDE must work for a user who only does 4D-STEM, and
# the `requires_package:` gate must hide the EELS/EBSD/atoms actions rather
# than raising ImportError. This job is the other half — with exspy,
# kikuchipy and atomap installed the gated code actually executes, so a broken
# EELS/EBSD/atom path fails here instead of silently never running.
#
# One OS / one Python: the extras are pure-Python scientific packages and the
# cross-platform surface is already covered by `test`. Not part of the main
# matrix because that would quadruple install time for every leg.
test-extras:
name: extras (ubuntu, py3.12)
runs-on: ubuntu-latest
timeout-minutes: 40
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up uv
uses: astral-sh/setup-uv@v6
with:
version: "0.10.11"
enable-cache: true

- name: Set up Python
run: uv python install 3.12

# NOT --frozen: the domain extras are not in uv.lock (they are optional
# and resolve differently per platform), so the lock cannot satisfy them.
- name: Install with all domain extras
run: uv sync --extra tests --extra all --python 3.12

- name: Versions
run: |
uv run python -V
# importlib.metadata, not `pkg.__version__` — atomap imports fine but
# exposes no `__version__`, which failed this job before a single test
# ran. The point of the step is "the extra installed and imports", so
# ask the installed distribution, which every package answers.
uv run python -c "import importlib.metadata as m; \
import exspy, kikuchipy, atomap; \
print('\n'.join(f'{p} {m.version(p)}' \
for p in ('exspy', 'kikuchipy', 'atomap')))"

- name: Run tests (extras present)
run: uv run pytest spyde/tests/migrated

# ── Renderer/main TypeScript typecheck ─────────────────────────────────────
# Pure TS compile (tsc --noEmit) of the Electron main + renderer. No Python /
# display server needed, so it's a fast standalone job that gates the PR.
Expand Down Expand Up @@ -138,24 +202,24 @@ jobs:
# Best-effort for now (continue-on-error): the e2e launches a real Electron app
# + Dask LocalCluster and has not yet been validated green on a hosted runner.
# Drop continue-on-error once it's confirmed stable in CI.
# Sharded ×3: the suite is deliberately serial WITHIN a run (one Electron +
# Sharded ×4: the suite is deliberately serial WITHIN a run (one Electron +
# Dask cluster at a time — see playwright.config.ts), so wall-clock scales
# only by splitting the spec files across independent runners. Each shard is
# still fully serial, so the cluster-contention flakiness that forced
# workers:1 cannot recur across shards (separate VMs). Playwright balances
# shards by FILE COUNT, not duration — ×2 measured 22.7m vs 10.0m — so ×3
# keeps the worst shard under ~15m even while the known-failing specs pay
# retry + app-reboot tax (2026-07-11: 16 failing tests account for ~23 of
# the 33 spec-minutes; fixing them is the real speedup).
# shards by FILE COUNT, not duration — ×2 measured 22.7m vs 10.0m, and ×3
# still ran 8.1m / 14.5m / >30m (shard 1 was CANCELLED at the timeout on
# test 76 of 115, so it produced no report at all). ×4 plus the domain
# extras below brings the worst shard back under the budget.
e2e:
name: electron-e2e (ubuntu, shard ${{ matrix.shard }}/3)
name: electron-e2e (ubuntu, shard ${{ matrix.shard }}/4)
runs-on: ubuntu-latest
timeout-minutes: 30
timeout-minutes: 45
continue-on-error: true
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3]
shard: [1, 2, 3, 4]
steps:
- uses: actions/checkout@v4
with:
Expand All @@ -175,8 +239,15 @@ jobs:
version: "0.10.11"
enable-cache: true

# WITH the domain extras. Without them the `requires_package:` gate hides
# every EELS/EBSD/atoms action, so the 0.3.0 specs that click those
# buttons cannot pass — 9 of them failed for that reason alone, and with
# retries:1 each one paid a second full app boot. Installing the extras
# makes them testable AND takes ~10 minutes of dead retry off shard 1.
# Not --frozen: the extras resolve per-platform, so the lock can't satisfy
# them (same reason as the `extras` job above).
- name: Pre-sync the Python backend env
run: uv sync --frozen --extra tests
run: uv sync --extra tests --extra all

- name: Install Electron dependencies
working-directory: electron
Expand All @@ -192,7 +263,7 @@ jobs:
# downloads + screenshot writes) that is meant to be opt-in.
- name: Run Electron e2e
working-directory: electron
run: xvfb-run --auto-servernum npm run test:build -- --project=electron --shard=${{ matrix.shard }}/3
run: xvfb-run --auto-servernum npm run test:build -- --project=electron --shard=${{ matrix.shard }}/4

- name: Upload Playwright report
if: ${{ always() }}
Expand Down
Loading
Loading