Skip to content

Fix calibration bugs and clean up solvers, UDFs, UI and packaging - #69

Merged
uellue merged 34 commits into
LiberTEM:mainfrom
uellue:cleanup
Oct 14, 2026
Merged

uellue merged 34 commits into
LiberTEM:mainfrom
uellue:cleanup

Conversation

@uellue

@uellue uellue commented Oct 14, 2026

Copy link
Copy Markdown
Member

Changes

  • Fix tests that didn't check what they claimed, e.g. the overfocus fit and invert_focus()
  • Model4DSTEM.derive() uses dataclasses.replace(), normalize_types() names the field in errors
  • Solvers share their least-squares setup, validate their input and are documented
  • UDFs calculate their transformation matrices per task
  • One helper for the affine maps of the Numba kernels
  • Calibration UI split into smaller methods, its calculations moved into tested functions
  • CalibratedDataset moved to dataset.py, still importable from ui
  • jeol_dm4 reads each DM4 file once and is tested with fake tags
  • Seeded random test inputs, underfocus in random models, non-square shapes in correction tests
  • Apptainer image and pinned requirements updated to Python 3.14, CI tests the pins again
  • Coverage configuration moved to pyproject.toml, setup.cfg and test_requirements.txt removed
  • CLAUDE.md: model code stays SymPy-compatible, numeric versions via lambdify()

Behavior changes

  • semiconv is the slope at the source everywhere, back-projection and simulation used it as an angle (see Model4DSTEM docstring)
  • solve_coords_points() returns a flip factor of exactly ±1, also when starting with the wrong sign
  • solve_camera_length() returns the specimen-to-detector distance for any overfocus, diffraction_angle is renamed to twotheta
  • solve_tilt_descan_error_points() requires at least three points, solve_coords_points() at least one
  • make_overfocus_loss_function() raises ValueError for a starting model in focus
  • The UI passes fix points to the solvers as float64 instead of float32
  • Model4DSTEM.derive() only accepts keyword arguments and keeps fields of subclasses
  • Model4DSTEM.normalize_types() rejects strings
  • lambdify(args=...) works
  • UDFs no longer accept back_mat, corr_mat and ref_model, and accept models with JAX or NumPy scalars
  • UDFs log a warning for a singular matrix and raise ValueError for scans that aren't 2D
  • jeol_dm4 functions take DM4 tags instead of an open file, CalibratedJEOLDM4.dm4 is removed
  • CalibratedJEOLDM4.microscope_info() ignores the thumbnail
  • The UI instructions name the plots by their titles, and the sections are reordered to match
  • optax moved from the common to the test extra

Review notes

  • f22dd3a adds a type dispatch for cos/sin and c2e741c replaces it with lambdify() at the call sites, so the net change is in the tests
  • The UI refactor commits were checked with a script that compares data sources, figures, widgets, callbacks and layout over 111 interaction steps; it isn't part of this PR
  • The Apptainer image wasn't built; the pinned requirements pass the test suite
  • Branch lambdify-model builds on this branch

🤖 Generated with Claude Code

uellue and others added 30 commits October 12, 2026 14:36
The test checked scan_rotation, which the fit doesn't change.

- Fix docstring of make_overfocus_loss_function, which named scan_rotation
- Raise ValueError for a starting model in focus instead of dividing by zero

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…_focus

The center ray of the inverted model was traced with the original model.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Float models got SymPy numbers from adjust_*() and invert_focus(), and
jax.grad() through adjust_*() failed.

- Add util/backend.py, used by rotate(), generated code and runtime derivations
- Regenerate _generated.py
- Add docstring to invert_focus()

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…x.jit()

The derived compensation is the identity for an unchanged flip factor anyway.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
normalize_types() maps float() over the pytree.

- Fields of subclasses are kept instead of being reset to their defaults
- derive() only accepts keyword arguments
- Specifying both flip_y and flip_factor raises ValueError instead of AssertionError

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No change in results since the components scale offsets with Ray._one.
The new test checks that trace() is homogeneous in _one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The UI downcast the point tables. The solvers now convert their input points to float64.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s one

The flip factor came back as e.g. 0.99, and a start with the wrong sign
could converge to the wrong orientation.

- Return the residual without the flip penalty term
- Raise RuntimeError if the better fit didn't converge
- Add docstring and a round-trip test

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ra_length()

It traced tilted rays with zero overfocus, so the fitted camera length
included the overfocus.

Behavior changes:
- The parameter diffraction_angle is renamed to twotheta
- The result is the specimen-to-detector distance for any overfocus

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… errors

float() accepted strings like "1.5", and its errors didn't say which parameter
couldn't be converted. NaN and infinity are kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Failures with random models couldn't be reproduced. The seed depends on the
test ID. The pixel tolerance was at the level of float64 rounding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…solvers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… arbitrary values

Behavior changes:
- solve_tilt_descan_error_points() raises ValueError for fewer than 3 points
- solve_coords_points() raises ValueError without points

The UI test now adds three distinct descan points; it fitted from two before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- solve_hit_specimen() returns the residual in YX order and the slope as floats
- Add a round-trip test for solve_hit_specimen()

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…it__

The matrices were recalculated in __init__ when the model changed, which
relied on how LiberTEM re-creates UDFs.

Behavior changes:
- The UDFs no longer accept back_mat, corr_mat and ref_model
- Models with JAX or NumPy scalars work instead of raising TypeError
- A singular matrix, e.g. the back-projection in focus, logs a warning
- Cached matrices are read-only

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Raise ValueError for scans that aren't 2D
- Sum the results of CorrectedPickUDF without stacking all chunks
- Remove empty docstrings and the ineffective where="device"

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ites

Model functions stay SymPy-compatible. Numeric NumPy or JAX versions are
created with lambdify() once where they are needed, see CLAUDE.md.

- Remove util/backend.py and regenerate _generated.py
- Test adjust_*() and invert_focus() through lambdified NumPy and JAX functions

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pins were for Python 3.11 and lacked the extras of the package, e.g. JAX
and optimistix.

- Re-enable the CI job that tests the pinned requirements

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Remove setup.cfg with the unused flake8 configuration
- Remove test_requirements.txt, which duplicated part of the test extra
- Coverage no longer warns that include is ignored

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n versions

optax is only used in a test. The project requires Python 3.14.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Files were opened repeatedly and never closed.

Behavior changes:
- The model functions take the tags of a DM4 file instead of an open file, see read_tags()
- CalibratedJEOLDM4.dm4 is removed, use CalibratedJEOLDM4.tags
- microscope_info() only includes tags of the 4D STEM data, not of the thumbnail

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The same dot product was written out seven times.

- Remove the project_tilt_*() and project_det_*() helpers, use affine_column()
- Run test_project_frame_forward() without JIT for coverage, too

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 133 reflection of the reference is outside the default reciprocal radius.

- Test that the angles scale with the electron wavelength at 300 kV

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…shapes

Square shapes and centers couldn't detect mix-ups of y and x.

- test_correct_smallnumba is a parameter set of test_correct_fixed_manualref

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The kernels used it as an angle, the transformation matrices and the beam
radius as a slope. See the Model4DSTEM docstring.

Behavior changes:
- Back-projection and simulation limit the source slope to semiconv instead of tan(semiconv)
- The UI still shows the convergence semi-angle and converts it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ets and events

No change in behavior.

- Add helpers for figures, cursors, diffraction rings and fix point tables
- Remove redundant column checks in the table click handlers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No change in behavior.

- Replace commented-out code with a note on why it doesn't work

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
uellue and others added 4 commits October 14, 2026 08:07
No change in behavior.

- Correlation pattern, expected beam shifts and regression of the descan refinement
- Formatting of the model and descan error tables

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…stants

No change in the displayed text, since Panel dedents Markdown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he GUI

ui.py imports it from there, so it is still available from microscope_calibration.ui.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ordingly

Behavior changes:
- The instructions name the plots by their titles and fix a typo
- The corrected plots follow the uncorrected ones in the first section, the scale bar
  section follows the plot it refers to
- Remove a double space from the title of the corrected frame

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@codecov

codecov Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.04520% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.80%. Comparing base (f8f8d1b) to head (7daa7ae).

Files with missing lines Patch % Lines
src/microscope_calibration/util/jeol_dm4.py 91.04% 5 Missing and 1 partial ⚠️
src/microscope_calibration/udf/stem_overfocus.py 90.00% 3 Missing ⚠️
src/microscope_calibration/ui.py 97.67% 2 Missing and 1 partial ⚠️
src/microscope_calibration/util/optimize.py 96.87% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #69      +/-   ##
==========================================
+ Coverage   87.94%   93.80%   +5.86%     
==========================================
  Files          13       14       +1     
  Lines        2015     2002      -13     
  Branches      171      173       +2     
==========================================
+ Hits         1772     1878     +106     
+ Misses        203       83     -120     
- Partials       40       41       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@uellue

uellue commented Oct 14, 2026

Copy link
Copy Markdown
Member Author

Example notebooks seem to work!

@uellue
uellue merged commit cc33db7 into LiberTEM:main Oct 14, 2026
8 checks passed
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.

1 participant