midas-dct-tt
Differentiable forward + inverse for diffraction contrast tomography (DCT) and
topotomography (TT), built on midas-dfxm and midas-diffract.
PRIVATE / UNRELEASED.
packages/midas_dct_tt/is excluded in.git/info/exclude(local-only, so the ignore rule itself cannot leak the name), andrelease.shrefuses to run while it is. Nothing about this package — the name included — should reachorigin, PyPI, a talk, or a proposal untilRELEASE_CHECKLIST.mdhas been worked through deliberately.
What it is
DCT and TT are not separate physics. They are the same forward operator as DFXM with the objective removed and the scan convention changed:
| acceptance | projection | rotation axis | projections/grain | |
|---|---|---|---|---|
| DFXM | divergence + bandwidth + objective NA | magnified, inclined | goniometer | n/a (real-space image) |
| TT | divergence + bandwidth | parallel (M = 1) | parallel to G | 10²–10³, freely chosen |
| DCT | divergence + bandwidth | parallel (M = 1) | lab vertical | 10–60, crystallography-fixed |
So this package is a scan-convention layer plus a projection/reconstruction layer
over existing MIDAS primitives — not a new physics stack. The reuse map is in
implementation_plan.md §5, and the two reuse claims that matter (na = 0 for
the acceptance, M = 1 for the projection) are pinned by
tests/test_dfxm_contract.py so an upstream
release cannot break them quietly.
The one result to know first
With the tomographic rotation axis parallel to G — the TT alignment — the Bragg condition gives, exactly and for every θ:
angle(k_h, a_hat) = 90° − θ
Parallel-beam tomography needs the projection direction perpendicular to the rotation axis. TT is off by exactly θ, so TT is laminography with a missing cone of half-angle θ, not tomography. Consequences: low-θ (high-energy, low-index) reflections reconstruct better; Friedel-paired settings flip the cone and recover part of it; a reconstructed sphere elongates along the axis.
midas_dct_tt.missing_cone_half_angle_deg carries the full statement; the
identity is verified from vectors, independently of the θ that produced them, in
tests/test_geometry.py.
Install (editable, from the repo)
pip install -e packages/midas_dct_tt --no-deps # siblings already editable
Quickstart
import torch
from midas_dct_tt import sphere_grain, attach_uniform_field, tt_alignment
# A planted grain: soft voxel occupancy chi(r) + a perfect-crystal F(r).
grain = attach_uniform_field(sphere_grain(radius_um=5.0, spacing_um=0.5))
# Align its (111) so G lies on the tomographic rotation axis.
G0 = grain.field.reference_G((1, 1, 1)) # sample frame, |G| = 2*pi/d
al = tt_alignment(G0, wavelength_A=0.172979) # ~71.7 keV, 1-ID/HEXM
print(f"theta = {float(al.theta_deg):.3f} deg")
print(f"missing cone = {float(al.missing_cone_deg()):.3f} deg half-angle")
print(f"k_h to axis = {float(al.axis_beam_angle_deg()):.3f} deg (= 90 - theta)")
# The TT scan: G is invariant, so k_h never moves.
for psi in (0.0, 90.0, 180.0, 270.0):
lab_positions = grain.positions_lab(al.sample_rotation(psi))
Phase 1 — a topograph, and a full sinogram:
from midas_dct_tt import PlaneDetector, psi_scan, topograph_image, topograph_stack, tt_resolution
det = PlaneDetector(pixel_um=0.5, shape=(96, 96), distance_um=5000.0)
# Ideal grain: the pixel reads path length in micrometers (centre = 2R).
img = topograph_image(grain, al, psi_deg=0.0, detector=det)
# With acceptance and deformation: intensity AND position respond to F(r).
res = tt_resolution(al) # DFXM's resolution at na = 0
sino = topograph_stack(grain, al, psi_scan(180), detector=det,
hkl=(1, 1, 1), resolution=res) # (180, 96, 96)
DCT instead — solve the Bragg flashes, then render frames:
from midas_dct_tt import bragg_flashes, dct_frames, dct_omega_scan
flashes = bragg_flashes(G0, wavelength_A=0.172979) # 2 per 360 deg, or [] if blind
frames = dct_frames([grain], [flashes], wavelength_A=0.172979,
detector=PlaneDetector(pixel_um=1.0, shape=(192, 192), distance_um=500.0),
omega_centres=dct_omega_scan(720))
Phase 2 — reconstruct the grain shape from the sinogram:
from midas_dct_tt import forward_operator, sirt, reconstruct_differentiable, dice
A = forward_operator(grain.positions, al, psi_scan(36), det,
voxel_volume_um3=grain.voxel_volume_um3)
sino = A(grain.occupancy) # linear in chi: no sigmoid, no acceptance
chi_sirt = sirt(sino, A, grain.n_voxels, n_iter=60) # classical baseline
chi_grad, info = reconstruct_differentiable(sino, A, grain.n_voxels, steps=300)
print(dice(chi_sirt, grain.occupancy), dice(chi_grad, grain.occupancy))
Conventions
Inherited unchanged from midas_dfxm so a grain, a field, and a reflection mean
the same thing in both packages:
- lab x along the incident beam, z vertical up,
y = z × x; k_in = (2π/λ) x̂, and reciprocal vectors carry the 2π convention (|G| = 2π/d);v_lab = R @ v_sample;- micrometers, degrees, Ångström (wavelength and lattice only).
Two conventions are hazards rather than choices, and both are named constants with loud docstrings rather than literals:
- DCT ω sign. The 1-ID aero stage is clockwise, so recorded ω must be
negated (
DCT_OMEGA_SIGN_AERO). A wrong sign here does not degrade any residual — it mirrors the reconstruction, undetectably. - Soft-boundary volume bias. Occupancy is
sigmoid(−sdf/w), which over-counts volume on a convex surface byA·(2H)·w²·π²/6. Measured, ∝ w², and it inverts beloww ≈ 0.25 × spacingwhere the grid stops resolving the ramp. Do not quote a grain volume from a soft occupancy without correcting or statingw. Seelogits_from_signed_distance.
Status
Phases 0-3 complete — conventions/geometry/containers; the forward model (TT topographs, DCT frames, acceptance, extinction, projector); shape reconstruction (SIRT + differentiable, Friedel pairing, missing-cone study); and the deformation inverse with a pre-registered identifiability study. 343 tests, all three device backends verified: CPU + MPS on macOS, CUDA on an H200 (sentosa, torch 2.11/cu128, PYTHONPATH overlay on the shared MIDAS env).
Validated against two independent implementations:
skimage.transform.radonfor the projector — 4e-12 relative at 0°/90°, ~0.2% RMS elsewhere (its interpolation blur, which we don't incur).pymicrofor the DCT scan geometry — our Bragg-flash angles matchOrientation.dct_omega_anglesto 1.1e-13 degrees across 3 orientations × 3 reflections, with no fitted constant.
plus an internal NumPy oracle to 1e-13. A sphere projects to its chord, 2R, and
projected mass is conserved exactly.
Phase 3 refuted this package's original novelty claim. Exact finite-strain inversion is correct — its difference from the linearised model is exactly the omitted
O(|H|²)term, verified to better than 1% — but the difference reaches only 2.2% at |H| = 5% and needs |H| ≈ 24% to reach 10%, below the measurement floor throughout. Separately, a fixed TT setting goes dark at |H| ≈ 2e-3 (rotation), 13× before the linearisation matters. Pre-registered indev/paper/PREREGISTER.md; full result indev/paper/RESULTS_phase3.md.What survives: a validated differentiable forward model, a clean identifiability result (one reflection constrains 3 of 9 components of F; three non-coplanar give all 9; collinear add nothing), and the finding that TT's spatial resolution resolves a strain sign that intensity alone cannot.
Three results that change how you should use this package:
- A Friedel partner is a lab-frame mirror, not an inversion. The two
lab-frame
Gvectors share(x, y)and have oppositez(cos = +0.79). Pair in the sample frame — a lab-frame antiparallel test returns zero pairs and fails silently.BraggFlashcarriesG_samplefor exactly this. - Do not measure the missing cone by reconstructing a sphere. It detects
nothing (Dice 1.0000, zero elongation, at any θ) because same-grid noiseless
data is an inverse crime. Measure the operator's Fourier response instead —
tests/test_missing_cone.pyshows ~10× suppression inside the cone with the width tracking θ.
And two from Phase 1 about reflection selection:
- Low θ trades tomographic coverage against strain contrast. Both scale as cot θ: fcc (111) at 71.7 keV gives a 2.4° missing cone but σ_par/|Q| = 6.4e-3; at 17 keV, 10.0° and 1.5e-3. Neither choice dominates, so reflection selection wants Fisher-information design rather than a rule of thumb.
- TT strain contrast is divergence-limited, not bandwidth-limited at HEXM energies (the cot²θ·div² term beats 4ε² by ~2000×). A narrower monochromator will not sharpen it; collimation will.
Tests
KMP_DUPLICATE_LIB_OK=TRUE python -m pytest tests/ -q
Markers: unit, contract (sibling API surfaces), device (CPU/CUDA/MPS),
autograd (gradcheck), slow (deselected by default; run with -m slow).
tests/test_radon_crosscheck.py needs scikit-image and skips without it.
tests/test_pymicro_crosscheck.py uses a frozen reference table rather than
importing pymicro, which pins numpy<2; regenerate with
examples/pymicro_reference.py.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file midas_dct_tt-0.1.0.tar.gz.
File metadata
- Download URL: midas_dct_tt-0.1.0.tar.gz
- Upload date:
- Size: 182.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e6a7603b121ba4af021518129bdd11b49eea382cc40b161214c7c5bacb63b1d1
|
|
| MD5 |
726aeb9c69c853657eded5d9ec3b62d4
|
|
| BLAKE2b-256 |
e3249e19d64bad10bd90de317792697f73169b1193cc089612ffb1048fda70e2
|
Provenance
The following attestation bundles were made for midas_dct_tt-0.1.0.tar.gz:
Publisher:
python-packages.yml on marinerhemant/MIDAS
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
midas_dct_tt-0.1.0.tar.gz -
Subject digest:
e6a7603b121ba4af021518129bdd11b49eea382cc40b161214c7c5bacb63b1d1 - Sigstore transparency entry: 2507230405
- Sigstore integration time:
-
Permalink:
marinerhemant/MIDAS@63f04bb83f8137672a238c3cc60451d89cd95b32 -
Branch / Tag:
refs/tags/midas-dct-tt-v0.1.0 - Owner: https://github.com/marinerhemant
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-packages.yml@63f04bb83f8137672a238c3cc60451d89cd95b32 -
Trigger Event:
release
-
Statement type:
File details
Details for the file midas_dct_tt-0.1.0-py3-none-any.whl.
File metadata
- Download URL: midas_dct_tt-0.1.0-py3-none-any.whl
- Upload date:
- Size: 116.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e36a99b8a2d1b3bfd0bd3934aaf5a5493bf7ca6d261fe0c980e2057c3de446f0
|
|
| MD5 |
d13c0f07c52e84cbb1c3d15e4fd5a37d
|
|
| BLAKE2b-256 |
d0b750382ca506cf799d5c91e4525c70867946a1723a13dd400ead0c35591e0b
|
Provenance
The following attestation bundles were made for midas_dct_tt-0.1.0-py3-none-any.whl:
Publisher:
python-packages.yml on marinerhemant/MIDAS
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
midas_dct_tt-0.1.0-py3-none-any.whl -
Subject digest:
e36a99b8a2d1b3bfd0bd3934aaf5a5493bf7ca6d261fe0c980e2057c3de446f0 - Sigstore transparency entry: 2507230482
- Sigstore integration time:
-
Permalink:
marinerhemant/MIDAS@63f04bb83f8137672a238c3cc60451d89cd95b32 -
Branch / Tag:
refs/tags/midas-dct-tt-v0.1.0 - Owner: https://github.com/marinerhemant
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
python-packages.yml@63f04bb83f8137672a238c3cc60451d89cd95b32 -
Trigger Event:
release
-
Statement type: