Skip to content

Releases: physwkim/tomoxide

v0.10.2

Choose a tag to compare

@physwkim physwkim released this 31 Oct 23:27

Fixed

  • lprec on CUDA indexes past 2^31 elements on Windows. Its kernels
    computed offsets in long, which is 32 bits under MSVC, so a chunk of
    more than 2^31 floats (8 GiB) wrapped and read and wrote the wrong
    memory. They use long long now, as every other kernel already did.

v0.10.1

Choose a tag to compare

@physwkim physwkim released this 31 Oct 16:11

Fixed

  • The cuda feature builds with MSVC on Windows. build.rs passed
    gcc's -fPIC to cl.exe, linked a stdc++ MSVC does not have, and looked
    for the CUDA libraries only in lib64. It now builds the kernels against
    the CRT Rust links (/MD, or /MT with crt-static), leaves the C++
    runtime to cl.exe's objects, and also searches %CUDA_PATH%\lib\x64.
    stripe.cu read the Titarenko sinogram through a GNU statement
    expression, which nvcc rejects under cl.exe; it is a device function now.
    CI builds and links it on Windows with CUDA 13.0.

v0.10.0

Choose a tag to compare

@physwkim physwkim released this 30 Oct 14:59

Changed

  • Breaking: TiltViews hands over the band-middle layer (layer, at
    layer_z = recon::center::middle_slice) instead of the z_peak slice.
    Across a thick band the argmax wanders (z 843 at 16° against 387–467 at
    18–24° on one scan), so the montage compared different planes; the band
    middle is one layer at every tilt. PreppedStack::default_slice reads the
    same rule, and the GUI tilt montage shows that layer.
  • Breaking: cuda::reconstruct_lamino_streaming takes a
    cancel: Option<&CancelToken>
    before on_tile, checked between the
    chunks of every stage and every Fbp/Linerec round; the device path drains
    its in-flight work before it returns Error::Cancelled. lamino_tilt_scan
    hands its token through, so a cancelled Fourierrec tilt stops before its
    first tile where it ran on for the whole volume.

Fixed

  • tomoxide --version names lightweight release tags. The release tags
    from v0.7.0 on are lightweight, which git describe skips without
    --tags, so a v0.9.0 build read v0.6.0-126-gf7ba8ec7fcb1. Tagging the
    built commit now reruns the build script, which kept the old name.
  • cuda::center_probe and lamino_tilt_probe stop at the next tile. They
    transposed the stack to sinogram order before their first cancel check, and
    lamino_filter_to_host then filtered every tile unchecked, so a step1000
    bin-2 sweep could not stop for up to 8 s. Each tile now reads its rows in
    place and checks the token.

v0.9.0

Choose a tag to compare

@physwkim physwkim released this 29 Oct 13:17

Added

  • PreppedStack::reconstruct and select. reconstruct runs the phase,
    stripe and CUDA laminography tail of reconstruct_lamino_streaming on the
    normalized stack the driver already holds, so a preview does not reload the
    file; select keeps a subset of the projections (an even/odd half).
  • Config::algorithm, recon_params and prep — the one reading of a
    recipe's algorithm, reconstruction parameters and stripe/phase prep.
    tomoxide recon lays its flags over the config and calls them, and the GUI
    builds its preview from them, so a preview runs what the saved recipe would.
  • SampleBand::at: the band carried into another tilt's volume through the
    detector rows, which lamino_tilt_scan already did privately. A caller that
    read the band at one tilt can place it at another without a scan.
  • tomoxide --version names the commit it was built from
    (git describe --always --dirty --abbrev=12), so a job log identifies the
    build. A copy built outside tomoxide's own checkout prints the version alone.

Changed

  • Breaking: lamino_tilt_scan and PreppedStack::tilt_scan hand on_tilt a
    TiltViews
    (the z_peak slice and the xz plane through y = ny / 2 over
    the whole depth) instead of the slice alone. The xz plane is how the layers
    stack, which the eye compares across tilts and which the streamed volume was
    dropped before a caller could read.
  • Breaking: the align sweeps take a cancel: Option<&CancelToken>:
    PreppedStack::center_sweep and tilt_scan, cuda::center_probe,
    center_probe_sweep and lamino_tilt_probe stop with Error::Cancelled at
    the next probe, angle-chunk, tilt or tile boundary. lamino_tilt_scan takes
    its tilts and band as one TiltCandidates. The GUI cancels both the centre
    sweep and the tilt scan with a per-job token.
  • CUDA Fourierrec laminography streams its output in z-tiles of ≤256 MiB,
    each transposed from p00 as it is emitted, where it built the whole
    volume beside p00 and emitted it as one tile. Host peak drops by one
    volume; tiles still arrive only once the reconstruction is done.

v0.8.0

Choose a tag to compare

@physwkim physwkim released this 29 Oct 02:57

Changed

  • tomoxide-gui now depends on rsplot / rsdm =0.5.5 (was =0.5.2).
    rsdm 0.5.5 builds against epics-rs 0.24 — the same version as the GUI's
    live-test epics-pva-rs dev-dependency — so the dependency tree unifies on
    one epics-pva-rs copy again (0.7.0 carried a 0.21 + 0.24 pair). rsplot moves
    in lockstep because rsdm 0.5.5 requires rsplot ^0.5.5; the jump also picks
    up rsplot 0.5.3's VolumeRaycaster rendering fixes (thickness cue,
    faint-voxel hue) for the XANES 3-D view.
  • Every tilt↔phi conversion goes through geometry::lamino_phi /
    lamino_tilt_deg.
    The CPU lamino path spelled it d/180·π, which differs
    by 1 ULP from d·π/180 at 298 of 1801 tenth-degree tilts; it now takes the
    latter, as the CLI, GUI and CUDA sites already did.

Added

  • tomoxide::align::PreppedStack — the one alignment driver. Loads and
    preps the stack once, then runs the rings estimate, the tilt-flow estimate,
    the centre probe sweep and the tilt scan on it (align::grid builds the
    sweep candidates). tomoxide align and the GUI each carried their own copy;
    both now drive this one.
  • recon::tilt_flow::estimate_tilt_flow — the laminography tilt with no
    reconstruction.
    Fits the vertical flow between projections a few steps apart
    to a plane whose slope is the tilt and whose zero is the axis column. On the
    261003 step scan (bin 2, 20° nominal) it reads 19.40 ± 1.46°, every pair
    matching the Python original it ports.
  • recon::center::register_shift, find_center_rings_from_mean,
    focus_by_z.
    register_shift is the phase-correlation registration
    find_center_pc runs, exposed for the 0°-vs-360° drift check.
    find_center_rings_from_mean runs the rings estimate on a mean projection
    already in hand or on a band of its rows; find_center_rings delegates to it.
    focus_by_z is the one bulk slice_focus scorer, which the tilt scan and
    the centre sweep now share.
  • Geometry::valid_fov — per slice, the radius every projection saw. Off
    the middle of a laminography volume a voxel leaves the detector for part of
    the orbit, and the slice there is no longer the sample's.
  • prep_band and PrepOptions::row_separable; Config::stripe_method /
    phase_method.
    prep_band preps a detector-row band as the whole stack is
    prepped, widening the read by the phase kernel's margin. row_separable says
    whether a prep may be run chunk by chunk. The two Config methods are the one
    method-name parser for the CLI and the GUI, so the GUI now accepts every case
    and the vo_all / vo_sort spellings the CLI did.

Fixed

  • find_center_rings and find_center_sift read half a pixel right. Both
    register an image against its left–right flip, which mirrors about column
    (n − 1)/2, but converted the shift about n/2. They now use (n − 1)/2,
    the axis find_center_pc and find_center_vo already use. A synthetic
    bullseye now reads its own column (was +0.50); the aligned pouch scan reads
    397.00 against a known 396 (was 397.50). tomocupy's SIFT formula n//2 − shift/2 carries the same half pixel on even widths and is not copied.
  • Fourier laminography (recon::lamino, CUDA Fourierrec) came out flipped
    in y
    against Linerec, FBP and the volume it was projected from. The USFFT
    divker kept tomocupy's n1−ty−1 row, which matches tomocupy's own y-flipped
    linerec; tomoxide's linerec is not flipped. The row is now ty, so
    lamino_project at zero tilt equals the parallel projection and Fourierrec
    volumes are the y-flip of earlier ones and of tomocupy's.
  • Fourier laminography filtered its projections with its own ramp, a plain
    linear |f| on a 2·detw edge pad, while tomocupy's LamFourierRec uses its
    FBP filter: the _wint quadrature ramp on 4·detw. Every backend now builds
    the lamino filter from make_fbp_filter (RampShape::Wint), and Pearson
    against tomocupy on the 261003 scan (parzen) rose from 0.9975 to 0.99996
    8×-binned and from 0.99933 to 0.999998 2×-binned. --filter none is now no
    filter, as for every other method, rather than the bare ramp.
  • tomoxide recon with phase retrieval took the row-chunked paths, the
    CUDA/wgpu streaming pipeline and the multi-GPU shard, which refuse phase
    retrieval because a 2D phase filter couples rows. A plan that is not
    row_separable now takes the whole-volume path, and recon_steps refuses it
    as it does chain and --lamino_angle.
  • --start_row bands truncated the Paganin kernel at their edges. The band
    was read as bare rows z0..z1 before phase retrieval (relative error 0.25 at
    the edge rows of a 400..600 band); it now preps through prep_band.

v0.7.0

Choose a tag to compare

@physwkim physwkim released this 17 Jul 23:26

Changed

  • tomoxide-gui's live-test dev-dependency epics-pva-rs is now 0.24
    (was 0.21). The in-process test server and rsdm's internal pvAccess client
    (^0.21 at the pinned rsdm) are coupled only by a TCP address, so the two
    copies coexist and talk wire protocol — verified by the live source tests.
  • tomoxide-gui now depends on rsplot / rsdm =0.5.2 (was =0.5.0).
    0.5.1 adds the VolumeRaycaster widget the XANES 3-D chemical-map view uses
    and 0.5.2 hardens it; the GUI builds against crates.io again, with the
    local-checkout [patch.crates-io] block commented out.

Added

  • recon::center::judge_sweep — a focus sweep's verdict, not just its
    argmax.
    Returns SweepVerdict::{Resolved, Railed, Ambiguous, Flat}, and only
    Resolved carries a value (SweepVerdict::resolved()); best() exists to
    show the winner on a plot or montage, never to adopt it. Every caller — align,
    the GUI's centre and tilt steps — now refuses rather than reports a number the
    curve did not establish, which matters because a bad axis silently poisons the
    tilt search that consumes it.
    The verdict is one uniform test — does this lobe own at least 20 % of the
    curve's height? — asked of the winner and of every other lobe. It replaces an
    edge check, which measurement showed is blind to both ways a real sweep fails.
    On the aligned pouch scan (known axis 396, tilt 44°), swept ±40 px: on one
    probed slice the curve carries three lobes and the highest is 417 — 21 px
    wrong, interior, beating the truth by 0.34 %, so no edge check can fire
    (Ambiguous, naming 396 as the strongest rival at 55 % prominence). On
    another the curve instead rises across the whole window and turns over one
    sample short of the edge at 435 — an interior maximum with no rival to
    contradict it, and still nothing but the range running out (Railed). Both are
    the same fact: the winner owns no lobe of its own. Narrowed to ±8 px around a
    prior, the same curve resolves 395.75 at 83 %. (These sweeps predate the
    z-centring fix below, which found the probe's slice index offset by
    (rh − nz)/2 from the volume z it claimed — the curve shapes and their
    verdicts, not the planes' names, are what these measurements establish.)
  • tomoxide align — the alignment workflow as a subcommand, plus the two
    library pieces it stands on. recon::center::find_center_rings is the ring
    estimator from docs/LAMINOGRAPHY_ALIGNMENT.md §1: the 360° mean projection is
    a bullseye centred on the rotation axis, registered against its own mirror. It
    reports a prominence — (peak − median)/MAD of the registration profile —
    and flags the scan when the rings never closed, which is the acquisition-time
    misalignment no reconstruction geometry repairs (measured: aligned scan 397.50
    at prominence 21.0 → trustworthy; misaligned scan 281.72 at 2.22 → flagged, and
    align stops there unless --force). cuda::center_probe / center_probe_sweep
    then refine it: cfunc_linerec's backprojection_try reconstructs one slice at
    N candidate centres in one launch off a single filtering. A probe slice
    equals the reconstruction exactly only when the shift is a whole number of
    columns (measured 2e-7…9e-7 of peak) and differs by ~1.6 % at half a column, so
    a naive sub-pixel sweep ranks integer offsets artificially sharp;
    center_probe_sweep removes that by construction, issuing one probe per
    fractional lattice, and never producing the biased slices at all. End to end on
    the aligned pouch scan, swept ±8 px around the axis read off the rings:
    --center 395.00 against a known 396. Around a prior is the whole contract —
    the same sweep widened to ±40 px grows three lobes and picks 417, and that is
    what judge_sweep above now refuses rather than returns.
    The two axes are searched by different machinery, and the asymmetry is physics,
    not an optimisation left undone. A fixed slice is sound for the centre — an
    in-plane shift does not move the in-focus layer, so one slice can rank the
    candidates — and unsound for the tilt, whose response is broad
    (~2 % per degree) while the in-focus layer moves in z with it (z_peak 800 →
    1120 as tilt went 40° → 58°), so a fixed slice scores the wrong plane by more
    than the tilt signal is worth. Measured: a fixed-slice tilt sweep returns 48° at
    one slice and rails to the top of any range at another. Hence
    recon::center::lamino_tilt_scan (--tilt_width / --tilt_step): one full
    reconstruction per candidate — that needs every (tilt, z) pair, which is
    exactly the work lamino_tilt_probe skips, so the probe buys nothing there and
    remains for the case its saving is real, a plane that is already known — scored
    by the max slice_focus over its slices, optionally inside the sample's z
    band
    (recon::center::SampleBand, --focus_z LO:HI) — the reference
    workflow's practice, and the guard wherever structured noise competes with
    the sample. A band fixed in z would miss the layer (it moves: the sample's
    spike sits at z 837 / 890 / 956 at tilts 40° / 44° / 48°), so the band is
    stated once, at the tilt it was read from, and carried into each candidate's
    volume through the detector rows — which belong to the data and do not move
    (v − nz/2 = cos(tilt)·(z − rh/2); the mapping reproduces those measured
    spikes to ~1 px, and that measurement is pinned as a unit test). End to end
    on the aligned pouch scan at 44°: the unbanded tilt curve over 36…52° is
    unimodal with its interior peak on the known 44° and a 3.0× span, every
    candidate's z_peak on the sample layer, the three measured spikes among
    them. The scan streams the volume, so its memory cost is one
    rh-tile regardless of depth, and it keeps what the reconstruction already
    computed: the winning in-band slice (§3 confirms by eye) and focus_by_z, the
    focus of every slice — the profile the next band is read off, so it is never
    truncated to the current one.
  • recon::center::pick_interior_max — the argmax of a sweep, plus whether the
    sweep earned the right to call it an optimum. A maximum on the first or last
    candidate is the range running out, and there is no way to tell from inside the
    range whether the true peak is at the edge or beyond it. Both align and the
    GUI refuse to adopt a railed pick; measured worth: it caught a real tilt scan
    railing at 47° on the pouch scan, and a centre sweep flat to 1.8 % and railing
    when pointed at an empty slice.
  • recon::center::slice_focus and mean_projection — the two measurements
    the alignment method is written in, as library functions rather than copies in
    each consumer. slice_focus is §2's score (mean |∇|² inside a 0.92-FOV disk;
    the disk matters — reconstruction geometry decides where the sampling leaves the
    detector, so scoring the full frame scores a boundary that moves with the
    parameter being scored). mean_projection is the bullseye find_center_rings
    measures, exposed because §1 makes looking at it step one and treats
    eye-vs-correlation disagreement as the misalignment flag — which needs the
    image, not only the number.
  • tomoxide-gui: laminography alignment on the Center screen (Beam:
    Parallel | Laminography). The toggle is not a display option: under a
    laminographic tilt the axis leans along the beam, so 0°/180° is not a mirror of
    the object and find_center_vo / _pc / _sift all lose the symmetry they
    assume (mirror registration scattered 395…607 against a known 396). It picks the
    estimator family valid for the beam. The three steps are the doc's, and each is
    a picture the CLI can only summarise as a number: 1 Rings shows the mean
    projection itself, so closed rings versus arcs that never close is a thing you
    see rather than a prominence you trust; 2 Centre is the probe sweep as a
    montage plus a focus curve with click-to-pick; 3 Tilt streams one result per
    full reconstruction — the in-focus slice into a montage, focus-vs-tilt, and
    focus-by-slice for the selected candidate — with a cancel, since it is minutes
    per candidate. Railed picks are shown as such and never adopted.
  • docs/LAMINOGRAPHY_ALIGNMENT.md — how to find the laminography rotation
    center and tilt.
    A field-tested recipe, validated against a scan whose center
    was already known independently. Leads with reading the center off the raw data
    before reconstructing anything: the mean of all projections over 360° is a
    bullseye of rings centred on the rotation axis, so the center column is
    readable by eye (automated flip-registration measured 397.5 vs a known 396),
    and rings that open into arcs instead of closing diagnose a scan that was
    mis-aligned at acquisition. Also documents the reconstruction sweep that
    follows (full projections; focus scored as the max over z, optionally confined
    to a sample band carried per tilt) and records the three methods that failed
    validation — 0°/180° mirror registration (the tilted axis breaks the mirror
    symmetry), 1-D column-profile symmetry (truncation destroys it), and
    sub-sampled projections with a fixed z band.
  • Fourier laminography (LamFourierRec) now honours --filter. The
    Fourier/USFFT lamino path applied a plain |f| ramp and ignored the selected
    apodisation, unlike tomocupy's LamFourierRec (which filters with
    args.fbp_filter, default parzen) and unlike tomoxide's own fbp/linerec lamino
    paths. The apodisation window is now folded into the projection ramp on all
    three paths (CPU golden, CUDA host, CUDA device) from a single window
    definition shared with make_fbp_filter. The default is parzen (tomocupy
    parity); pass --filter ramp for the previous plain-ramp behaviour.
  • Multi-GPU angle-split SIRT for laminography. Laminographic SIRT can now run
    across multiple GPUs by partitioning the projection angles into contiguous
    per-device chun...
Read more

v0.6.0

Choose a tag to compare

@physwkim physwkim released this 06 Jul 01:44

Changed

  • tomoxide-gui now depends on rsplot 0.5.0 from crates.io (was the
    siplot git dependency pinned to a rev). The plotting crate and its GitHub
    repo were renamed siplot → rsplot (and the sibling EPICS engine
    sidm → rsdm) and published to crates.io; the GUI switches to
    rsplot = "=0.5.0" with a commented-out [patch.crates-io] for local dev.
    Mostly a use siplot → use rsplot identifier rename. The one behavioural
    gap: the local siplot carried an unpushed commit making a Plot2D
    crosshair read out the pixel value under the cursor ("x, y, value"); rsplot
    0.5.0 instead exposes the value via the higher-level ImageView
    (value_changed, silx PositionInfo "Data"). The Data sinogram inspector and
    the Tune single-slice preview were migrated Plot2D → ImageView to keep the
    value readout (the preview's ColormapDialog is replaced by ImageView's
    interactive colorbar). GUI build/clippy/35 tests green on CPU and CUDA. The
    Data sinogram inspector and Tune preview ImageView layout was fixed and
    verified on-screen: the ImageView position-info bar is emptied and the value
    readout is pinned to the bottom so the image fills exactly the available
    height, keeping the sinogram's resizable bottom panel stable (an earlier
    version let it grow into / collapse away from the projection browser).

Added

  • CLI --progress_json (recon, recon_steps) — one flushed JSON line
    per completed output chunk on stdout
    ({"start":s,"end":e,"total":nz,"secs":t}; global slice range, full output
    slice count, wall-clock seconds since run start), implemented as a thin
    VolumeWriter tee. The multi-GPU shard orchestrator forwards the flag and
    lets children inherit stdout, so shard lines stream through with global
    ranges against one total. Machine progress for wrappers — the GUI Run
    screen tails these from its subprocess runs. Runtime-only (not a config
    key); progress lines are exactly the stdout lines starting with {.
  • Cooperative cancellation for the chunked drivers — new
    pipeline::CancelToken (Clone-able atomic flag) attachable via
    ReconSteps::with_cancel; all three drivers (run, run_streaming,
    run_streaming_pipelined_range) check it at chunk boundaries and stop with
    the new Error::Cancelled. Cancellation truncates: chunks already written
    stay on disk and the writer finalizes the partial output.
  • io::InMemoryWriter — a VolumeWriter that collects the reconstruction
    into a shared in-memory InMemoryVolume (the Arc<Mutex<…>> handle
    survives the pipelined driver consuming the writer) with an optional
    on_chunk progress callback. Backing store for GUI previews.
  • tomoxide::config (feature config) — the CLI's TOML Config moved
    into the library behind a default-off feature (optional serde/toml
    deps) so GUI recipes and CLI configs are one format. Gains three fields:
    lamino_angle, dtype, and output (base path; each writer adds its own
    suffix). The CLI gained the matching --output flag, --config now feeds
    all three, and the multi-GPU z-shard fan-out forwards the resolved output
    path to its children.
  • io::read_h5_frame — read one [ny, nx] frame of a 3-D HDF5 stack as
    f32 through the reader's full dtype dispatch (u8/i8/u16/i16/u32/i32/f32/
    f64). Real beamline stacks are usually uint16; the GUI projection browser
    reads through this (rsplot's own HDF5 loader handles 4/8-byte floats only).
  • io::read_h5_sizes — the (n, ny, nx) shape probe paired with
    read_h5_frame, so a volume browser can size its frame list without
    reading any data.
  • Vo 2018 single-method stripe variants through config/CLI — the
    long-implemented StripeMethod::VoSort/VoFilter/VoLarge/VoDead/
    VoFit are now selectable as --remove_stripe vo-sort | vo-filter | vo-large | vo-dead | vo-fit, with per-method config fields/flags
    (vo_sort_*, vo_filter_*, vo_large_*, vo_dead_*, vo_fit_*;
    median sizes use the fw_level convention 0 = tomopy auto) and
    multi-GPU shard forwarding.
  • tomoxide-gui M1 (offline preview loop) — new repo-internal but
    workspace-excluded crate (rsplot is edition-2024/rust-1.92; workspace
    membership would raise the repo's effective MSRV above 1.82) implementing
    docs/GUI.md M1: a single worker thread owns the Engine and all HDF5
    handles (!Send); Data (DXchange open + metadata, projection browser —
    frames load through io::read_h5_frame so uint16 beamline stacks render,
    with the colormap scaled to the stack's raw-count range — theta plot, raw
    sinogram inspector), Tune (single-slice preview through
    run_streaming_pipelined_range into io::InMemoryWriter, parameter panel
    with auto-recon, A/B pin compare), Center (Vo / entropy /
    phase-correlation / SIFT auto methods, ±0.5/±0.25 px tweak, hand-off to
    Tune), and recipe save/load (recipe file = CLI config TOML plus a [gui]
    table the CLI ignores). tomoxide-gui [FILE] [--mode <mode>] opens a
    dataset and/or picks the starting mode from the command line, and Tune
    fires the first preview of a fresh dataset by itself (the auto toggle
    still gates re-runs on parameter changes).
  • GUI Tune — λ sweep (L-curve regularization tuner). For algorithms whose
    reg_par[0] is a regularization strength λ (tv, grad, tikh,
    pml_*, ospml_*), a new worker Job::LambdaSweep reconstructs the preview
    slice once per λ across a log-spaced grid and scores each on the L-curve —
    data residual ‖A x − b‖₂ (the fidelity term, via sim::project) vs the
    reconstruction's isotropic TV seminorm (roughness). A floating window shows
    the per-λ montage over the L-curve; the max-distance-to-chord corner is the
    suggested λ, a click picks any point, and "Use selected λ" writes it to
    reg_par[0]. The guide is the L-curve corner, not a sharpness auto-pick:
    sharpness falls monotonically with λ and real data has no ground truth
    (docs/BENCHMARKS.md §10), so the choice stays the user's. Verified finite
    and λ-varying on both CPU and the CUDA device-resident 1-slice path.
  • GUI design document (docs/GUI.md) — design for a tomoxide-gui
    desktop application built on rsplot (egui + wgpu) and rsdm (EPICS PVA):
    offline workflow (dataset browsing, single-slice tune loop with A/B
    compare, center finding with a write_center sweep montage, subprocess
    full-volume runs, output browsing) plus a tomostream-style live streaming
    mode, with the prioritized list of library additions it requires.

Added

  • ext_pad — truncated-projection support extension for iterative
    methods.
    Real samples routinely overhang the field of view, so the
    projections don't end at zero; an iterative forward model whose support is
    the detector-width grid then dumps that inconsistency into a huge FOV-edge
    ring and background offset that swamp the (intact) interior — on real
    800-wide data a 10-iteration CGLS correlated only 0.56 with fbp full-frame
    while agreeing 0.99 in the interior. With ReconParams::ext_pad (CLI
    --ext_pad, config ext_pad, GUI Tune "extend FOV" — on by default in the
    GUI) the sinogram is edge-replicate extended by ncols/4 per side, the
    solve runs on the wider grid, and the central crop is returned; the wrapper
    sits above the backend dispatch so CPU/CUDA/wgpu see the identical extended
    problem. Real-data result: CGLS-10 full-frame correlation with fbp 0.56 →
    0.996, interior display contrast restored to fbp's level. Off by default in
    the library (tomopy-parity semantics unchanged; ~2.25× cost per iteration).

Changed

  • Analytic reconstructions now emit the physical μ — the shared
    make_fbp_filter base ramp is the physical |ω| inversion filter (peak
    0.5 at Nyquist) instead of tomopy/tomocupy's doubled ramp (peak 1), and
    recon::gridrec's ramp_scale drops its matching empirical ×2. Every
    analytic method (FBP, linerec, fourierrec, lprec, gridrec, on all backends)
    therefore reconstructs the attenuation μ per pixel-unit rather than 2×μ —
    the same scale the iterative solvers converge to, so fbp→iterative
    warm-starts are now scale-consistent. All analytic output amplitudes are
    halved
    ; downstream code that hard-coded the old scale (rescale windows,
    8/16-bit export ranges) must adjust. Cross-method and cross-backend ratios
    are unchanged (both sides halve). Pinned by tests/analytic_amplitude.rs (a
    unit disk reconstructs to core mean ≈ 1.0). This is a deliberate departure
    from both upstreams, whose absolute analytic amplitude is itself
    convention-dependent (tomopy gridrec ≈ 1.16×μ, tomocupy ≈ 4/π×μ).
  • Iterative reconstructions now converge to the physical μ — the
    forward/back-projector pair used by the iterative solvers is the plain
    line-integral Radon transform W and its pure adjoint Wᵀ on every
    backend (CPU, CUDA, wgpu); the π/nproj FBP angular-quadrature weight
    previously baked into both operators is now passed by the analytic FBP
    call sites only, where it belongs. A converged SIRT/CGLS/MLEM/… solve of
    W x = p therefore lands on the attenuation per pixel-unit (pinned by the
    new tests/iterative_amplitude.rs against an analytic disk sinogram)
    instead of (nproj/π)·μ — e.g. ≈ 143× smaller at 450 projections —
    matching ART/BART, which always solved the ungained ray equations. Iterate
    trajectories are unchanged up to that overall scale for the self-scaling
    methods (SIRT/CGLS/MLEM/OSEM/PML/OSPML); for grad/tikh/tv the fixed
    step and regularization now act on the physical scale, so hand-tuned
    reg_par values from before may need retuning. The grad/tv host and CUDA
    gain-compensation machinery (adj_scale/fwd_gain_inv) is deleted.
    Analytic (FBP/gridrec/…) outputs are unchanged.
  • GUI preview autoscale is percentile-robust — image colormaps scale to
    the 0.5–99.5 % range instead of the absolu...
Read more

v0.5.1

Choose a tag to compare

@physwkim physwkim released this 02 Jul 14:25

Changed

  • HDF5 reads are now rayon-parallel (rust-hdf5 0.3.0 with the parallel
    feature: parallel chunk decode — deflate inflate + chunk gather). Read sits on
    the critical path of the streaming reconstruction (each row-chunk re-decodes
    the whole projection-chunked dataset), so this is a large end-to-end win. On a
    1200×512×512 fbp streaming reconstruction (1 GPU): gzip-chunked input
    73.5 s → 4.3 s (17×), uncompressed-chunked 9.5 s → 3.1 s (3.1×);
    reconstruction output is byte-for-byte identical (the decode is deterministic).

v0.5.0

Choose a tag to compare

@physwkim physwkim released this 02 Jul 08:53

Headline: the wgpu (portable GPU) backend becomes device-resident end to end,
closing most of the gap to CUDA — analytic recon, streaming, SIRT and the
gridding recons now keep their data on the GPU across the pipeline, and the
scatter kernels use native f32 atomics (wgpu 30). The workspace is also now
self-contained (all dependencies resolve from crates.io) and gains
CI on every branch push across Linux, Windows and macOS.

Added

  • CGLS reconstruction (--algorithm cgls). Conjugate-gradient least squares
    (the standard algorithm; recurrence parity-checked against ASTRA's
    CglsAlgorithm but implemented independently — ASTRA is GPL-3.0, no ASTRA code
    is used): a Krylov solver of the same ‖Ax − b‖² normal equations as SIRT/GRAD
    but with the optimal step and conjugate directions, so it reaches a given
    residual in far fewer iterations (≈4–30× vs SIRT). Runs on every backend via
    the generic solver, plus a CUDA device-resident fast path (per-slice dot/axpy
    kernels; one upload / one download across all iterations). Parameter-free;
    supports warm-start chaining. No built-in regularization, so it needs early
    stopping on ill-posed data.
  • Per-stage iteration budgets in algorithm chains. An iterative stage in a
    --algorithm chain can carry a :iters suffix
    (e.g. --algorithm fbp,sirt:30,tv:10); stages without one fall back to
    --num_iter. Analytic stages reject the suffix. Lets a chain spend, say, 30
    SIRT iterations then 10 TV iterations in one run — previously every stage shared
    a single --num_iter.
  • Continuous integration (GitHub Actions). Runs on every branch push and
    every pull request: rustfmt, then a per-platform matrix — Linux/x86_64,
    Windows/x86_64, macOS/arm64 — each running clippy -D warnings, the test
    suite and doctests on the default (CPU) build plus a gpu-wgpu compile-check,
    and a Linux job that type-checks the CUDA FFI bindings without an nvcc toolkit.

Changed

  • wgpu backend upgraded to wgpu 30 (from 23). Enables SHADER_FLOAT32_ATOMIC
    (Vulkan VK_EXT_shader_atomic_float); devices without it fall back to the
    portable compare-exchange emulation automatically.
  • wgpu reconstruction is now device-resident. A fused analytic path keeps the
    filtered sinogram on-GPU (fbp/linerec/fourierrec); device-resident streaming
    reconstructs chunk-by-chunk (fbp/linerec/fourierrec and lprec, the latter
    caching its log-polar grids across chunks); SIRT keeps volume/sinogram resident
    across iterations; the FBP filter and the fourierrec/lprec gridding run on the
    GPU. The scatter kernels (forward projection, fourierrec gather/wrap, lprec
    gather) use native f32 atomicAdd, replacing the CAS-emulation penalty —
    forward projection ~6.6× and SIRT ~6.4× faster (same-build A/B). Net effect:
    fbp/linerec reach CUDA parity, SIRT and lprec beat CUDA on this hardware.
  • Shared host precompute sped up. lprec precomputes its FFTs on the CPU
    (rustfft) instead of a GPU round-trip (wgpu and CUDA), build_grids coordinate
    loops are parallelised with rayon, and the CPU minus_log prep is parallelised
    across the projection volume.
  • xraylib (the optional beam-hardening dependency) is now sourced from
    crates.io
    instead of an out-of-repo path dependency, so a fresh clone / CI
    resolves the workspace without a sibling checkout. Local development against a
    sibling checkout can override via [patch.crates-io].

Fixed

  • wgpu forward projection now applies the π/nproj adjoint gain, so the
    {A, Aᵀ} pair is matched — fixes forward_project output scale and the
    iterative solvers built on it (SIRT).
  • stripe TI block-size division uses checked_div (satisfies
    clippy::manual_checked_ops, new on stable Rust 1.96).
  • HDF5 test fixtures were swallowed by the repo-wide *.h5 gitignore and
    never committed; they are now tracked so the test suite runs on a fresh clone.

tomoxide v0.4.0

Choose a tag to compare

@physwkim physwkim released this 01 Jul 07:48

tomoxide v0.4.0

tomoxide is a Rust tomographic reconstruction toolkit combining the algorithmic
breadth of tomopy (CPU libtomo) with the GPU-accelerated streaming
reconstruction of tomocupy (CUDA), behind a single tri-backend abstraction:
CPU / CUDA / wgpu.

This release has two headline themes: the full iterative reconstruction suite
now runs on the GPU
(device-resident), and a cross-backend convention
unification
that makes CUDA analytic output match the CPU/wgpu (tomopy)
convention. The CLI also gains the full preprocessing / iterative / filter
composition surface and a live TOML config.

Behaviour changes — read before upgrading

  • CUDA analytic output changed orientation and amplitude. CUDA analytic
    reconstruction (fbp / linerec / fourierrec / lprec) now matches the
    CPU/wgpu (tomopy) convention: the tomocupy vertical (y-axis) flip is removed and
    the amplitude is tomopy scale (cuda/cpu ≈ 1), not tomocupy's. In 0.3.0 CUDA
    deliberately matched tomocupy; if you depended on that, this is a breaking
    change. Backends now agree up to a deterministic ~1.6% _wint-vs-linear ramp
    shape residual (Pearson ≈ 1.0).
  • sim::project output changed by π/nproj. The CPU forward projector is now
    a true adjoint of the back-projector at a single scale, matching the CUDA pair,
    so the iterative solvers stay well-posed across backends. The fixed-step
    grad/tv solvers gain-normalize the residual, so their behaviour is unchanged.
  • Laminography is excluded from the unification. The CUDA (tilted linerec) and
    CPU (USFFT) lamino paths are different algorithms and are not scale-comparable;
    each is validated against its own reference and both stay y-flipped. Do not
    warm-start one lamino backend from the other.

Highlights

  • The iterative suite runs on the GPU, device-resident. A CUDA forward
    projector (exact adjoint of the linerec back-projector) unlocks tomopy's
    iterative family on the device: sirt, mlem, osem, ospml, pml, grad,
    tikh, tv keep the volume and sinogram on the GPU across all iterations
    (one upload, one download) — 1.3–11.4× over a per-iteration CUDA loop and
    51–95× over CPU at 512². art/bart also run on CUDA (bit-identical to CPU).
  • Warm-start / algorithm chaining. Seed a solver from a prior volume via
    ReconParams.init, or from the CLI with --algorithm fbp,sirt — the analytic
    result warm-starts the iterative refinement so it converges in fewer iterations.
  • A composable CLI. recon/recon_steps now expose stripe removal, phase
    retrieval (with physics), filter choice, iterations, regularization, and every
    per-method parameter, and --config (a tomoxide init TOML) drives a run with
    flag > config > default precedence.
  • Cross-backend convention unification. CUDA analytic reconstruction matches
    CPU/wgpu in orientation and amplitude; pinned by
    tests/cuda_cpu_convention_parity.rs and documented in docs/ARCHITECTURE.md.

Install

[dependencies]
tomoxide = "0.4.0"

CLI:

cargo install tomoxide-cli

GPU features are opt-in (cuda, gpu-wgpu); see the README for the feature
matrix, the command-line reference, and the benchmark tables.

See CHANGELOG.md for the complete list of changes.