tomojax
TomoJAX: tomography, laminography and lab cone-beam CT reconstruction and alignment.
The workflow API takes a Scan (projections and their geometry) and
returns results that carry their geometry:
import tomojax as tj
scan = tj.load("scan.nxs") # or a Nikon .xtekct
recon = tj.reconstruct(scan) # Geometry-aware automatic reconstruction
recon = tj.reconstruct(scan, method="cgls", iterations=50)
result = tj.align(scan, mode="cor-then-pose") # result.scan carries the corrections
tj.save("recon.nxs", tj.reconstruct(result.scan))Build a scan from arrays with tj.Scan(projections, geometry) and the
geometry classes below; tj.project simulates projections for any geometry.
The subpackages (tomojax.recon, tomojax.alignment, tomojax.geometry,
tomojax.io) hold the building blocks these use. Names load on first use,
so import tomojax does not import JAX. Importing sets growing GPU allocation
defaults for future JAX backends; explicit allocator environment settings are
preserved. See docs/large-scans.md for process-wide memory configuration.
| Name | Summary |
|---|---|
load | Load line integrals from a dataset or recognised raw acquisition. |
load_frames | Load detector frames and acquisition metadata from supported scan formats. |
reconstruct | Reconstruct scan with method. |
align | Estimate scan's geometry corrections and reconstruct with them. |
project | Project a volume through the supplied geometry. |
backproject | Apply the exact transpose of the forward projector. |
save | Save a scan, reconstruction or alignment as a TomoJAX dataset. |
load_reconstruction | Load a reconstruction saved by save or tomojax recon. |
Scan | Projections and the geometry that produced them. |
Frames | Detector frames as recorded, with the geometry of each sample view. |
Reconstruction | A reconstructed volume, the scan it came from and how it was made. |
Alignment | The result of align. |
ConeBeam | Source and detector placement in the lab frame, shared by every view. |
ConeGeometry | Cone-beam scan of a sample rotating about +z, or a tilted axis. |
Detector | Regular detector plane metadata. |
Grid | Regular voxel grid metadata. |
LaminographyGeometry | Laminography geometry with tilted rotation axis (parallel-ray model). |
ParallelGeometry | Parallel-beam CT geometry with axis-aligned detector. |
RegionEstimate | The resized scan and the evidence/uncertainty behind its reconstruction grid. |
estimate_center | Estimate a parallel scan's rotation centre without reconstructing a volume. |
estimate_region | Select a reconstruction region from an already calibrated laminography scan. |
reconstruct_slabs | Correct raw frames and reconstruct bounded z slabs. |
load
load(path: str | PathLike[str], *, poses: bool = True) -> ScanLoad line integrals from a dataset or recognised raw acquisition.
A file of raw detector frames (an NXtomo image_key marking flats or
darks, or a Nikon or Zeiss acquisition) is corrected with the standard chain, as
load_frames(path).corrected(): dark subtraction, flat division and
-log; Scan.corrections records it. For other corrections, load
the frames with load_frames. The scan carries the per-view
corrections saved with it (by align or tomojax align) unless
poses is False. Frames that image_key marks as other than sample
views are left out.
Parameters
path(str or path-like): Dataset or recognised acquisition file or directory.poses(bool, optional): Apply the saved per-view pose corrections; default is True.
Returns
- Scan: Line-integral projections with geometry and correction records.
Raises
- FileNotFoundError: If the input does not exist.
- ValueError: If TIFFs need geometry, counts lack calibration, or metadata are invalid.
Examples
import tomojax as tj
scan = tj.load("scan.nxs")load_frames
load_frames(
path: str | PathLike[str],
*,
flats: np.ndarray | float | str | PathLike[str] | None = None,
darks: np.ndarray | float | str | PathLike[str] | None = None,
angles: Sequence[float] | np.ndarray | None = None,
detector: Detector | None = None,
grid: Grid | None = None,
geometry: ScanGeometry | None = None,
exposure_times: np.ndarray | float | None = None,
flat_exposure_times: np.ndarray | float | None = None,
data_path: str | None = None,
image_key_path: str | None = None,
angles_path: str | None = None,
) -> FramesLoad detector frames and acquisition metadata from supported scan formats.
An HDF5 file's image_key marks its flats (1) and darks (2), and its
sample, flat and dark frames are read only as they are corrected (including
a few sampled rows when an absent axial rotation centre needs estimating). The
frames, key and angles are found at their NXtomo paths or as the file's only datasets of
that name (data, image_key, rotation_angle), or their paired GDA imaging
paths; data_path, image_key_path and angles_path name them otherwise.
Nikon, SkyScan, Zeiss and ESRF acquisitions are recognized by content and
supply their own geometry and calibration. SkyScan logs require geometrically
and flat-field corrected TIFF exports; their storage bit depth is not a
measured white level, so supply flats= (frames or one level) explicitly.
Zeiss Xradia projection files keep their embedded reference images,
physical calibration and per-view image shifts in the geometry.
A GDA command mislabeled as mm is read in degrees with a warning; discovered
paths and this correction stay in source metadata.
Generic TIFF stacks without acquisition metadata need angles (degrees),
flats (frames or one level) and
their detector (including pixel sizes and centre). Missing detector
pixel pitch is never replaced by unit pixels. Axial acquisitions with recorded
pitch use the opposite-view correlation estimator for an absent rotation centre,
recording inferred provenance; failed estimates require detector=.
Missing dark fields require darks=; use darks=0 only for already dark-corrected counts.
flats and darks (arrays, HDF5 stacks, TIFF files or folders, Zeiss XRM
images, or one level for every pixel), angles, detector, grid and a
whole geometry (of every sample view; then TIFFs need no angles)
replace what the file holds. HDF5 calibration paths use the same automatic
dataset discovery as the input and stay lazy; every frame in their stack
contributes to the calibration mean.
Parameters
path(str or path-like): HDF5, NPZ, Nikon, SkyScan, Zeiss or ESRF acquisition, or generic TIFF stack.flats(array-like, float, str, path-like or None, optional): Replacement flat frames, calibration stack path or constant open-beam level.darks(array-like, float, str, path-like or None, optional): Replacement dark frames, calibration stack path or constant dark level.angles(array-like or None, optional): Sample-view angles in degrees, replacing file metadata.detector(Detector or None, optional): Replacement detector dimensions, pixel sizes and physical centre.grid(Grid or None, optional): Replacement reconstruction grid.geometry(ScanGeometry or None, optional): Complete sample-view geometry, overriding individual metadata.exposure_times(numpy.ndarray or float or None, optional): Sample exposure durations in seconds, replacing file metadata.flat_exposure_times(numpy.ndarray or float or None, optional): Flat exposure durations in seconds, replacing file metadata.data_path(str or None, optional): Explicit HDF5 frame dataset path; None discovers it.image_key_path(str or None, optional): Explicit HDF5 sample/flat/dark key path; None discovers it.angles_path(str or None, optional): Explicit HDF5 rotation-angle path; None discovers it.
Returns
- Frames: Raw sample and calibration frames with geometry; HDF5/Zeiss/TIFF reads stay lazy.
Raises
- FileNotFoundError: If the input does not exist.
- ValueError: If required angles are absent or metadata and frame shapes disagree.
- KeyError: If a required dataset cannot be found in the file.
Examples
import tomojax as tj
frames = tj.load_frames("raw_scan.nxs")
scan = frames.corrected(binning=4)reconstruct
reconstruct(
scan: Scan,
method: Method = 'auto',
*,
grid: Grid | None = None,
out: np.ndarray | None = None,
config: MethodConfig | None = None,
filter: str | None = None,
iterations: int | None = None,
tv_weight: float | None = None,
nonnegative: bool | None = None,
warm_start: bool | None = None,
seed: int | None = None,
devices: Device | Sequence[Device] | None = None,
) -> ReconstructionReconstruct scan with method.
auto (default) chooses Fourier inversion for unposed axial half-turn
scans, the guarded laminography workflow for oblique scans, matched CGLS
for segmented cone geometry, and FBP/FDK otherwise.
An explicitly unrecorded axial centre may be inferred; oblique auto
may infer setup and support on a coarse grid before native refinement.
An explicit grid fixes the region and final voxel sampling instead;
automatic setup and motion still run.
The scan and info["automatic"]
record those choices and limitations. Explicit methods bypass this policy.
fbp: Filtered backprojection: FDK for cone beams, with Parker weights for short scans and Wang's for offset detectors.filterisramp(default),shepp-loganorhann. Volumes too large for the device are reconstructed in slabs on the host.fourier: Fourier-slice inversion of parallel-beam half-turn scans, with nonuniform or repeated angles and translation-only poses; no cone beams or tilted axes. Input and output stay on the host;FourierConfigselects NumPy or CuPy CUDA.cgls: Least squares by conjugate gradients,iterations(default 50).fista: Least squares with total-variation weighttv_weight(default 0.005),iterations(default 50), optionallynonnegative.sirt: SIRT, OS-SART, bounds and support viaSIRTConfig.spdhg: The same objective asfistaby stochastic primal-dual updates:iterations(default 400) each update one block of views, in an order drawn withseed.
mlem is linear Poisson MLEM/OSEM, not transmission counts (MLEMConfig).
warm_start uses FBP/FDK for least squares; grid overrides the scan's output grid.
out is a writable float32 array or memmap of shape (grid.nx, grid.ny, grid.nz)` for `fbp`, `fourier`, and `cgls`, `sirt` and `mlem on
cone beams: the volume is made in host slabs straight into it and returned
as volume. It must not overlap the scan, and a later failure can leave
it partly written. Cone CGLS, SIRT and MLEM also use host slabs on their
own when their volumes would not fit on the device; with a memmapped
out, CGLS keeps its three work volumes in temporary files beside it.
config is from tomojax.recon; keywords replace its fields.
Mismatched config classes or unknown keywords raise ValueError.
devices (fbp, cgls and fista; one or several,
jax.devices() for all) shares the views among them: each projects its
own views and holds the whole volume, and their backprojections are
summed. The volume, on the first device, is the one-device result up to
the order of that sum. Host slabs use the first device only.
Reconstruction.info holds config and solver records (iterations,
termination, losses). Fourier's backend records the actual selected
implementation separately from the requested configuration.
Parameters
scan(Scan): Measured line integrals with their acquisition geometry.method(str, optional): One of auto, fbp, fourier, cgls, fista, spdhg, sirt or mlem.grid(Grid or None, optional): Output grid; None retains the scan's grid.out(numpy.ndarray or None, optional): Writable float32 output or memmap for fbp/fourier and cone cgls/sirt/mlem; must not overlap input.config(reconstruction config or None, optional): Method-specific settings; explicit keywords replace its fields.filter(str or None, optional): FBP filter: ramp, shepp-logan or hann; None uses the configuration.iterations(int or None, optional): Iteration count; None uses the method default or configuration.tv_weight(float or None, optional): Total-variation weight for fista/spdhg; None uses the configuration.nonnegative(bool or None, optional): Nonnegative constraint where supported; None uses the configuration.warm_start(bool or None, optional): Use FBP/FDK initialization for supported iterative methods.seed(int or None, optional): Random seed for stochastic view ordering where supported.devices(jax.Device or sequence of jax.Device or None, optional): Devices sharing views for supported methods; None uses the default device.
Returns
- Reconstruction: Volume with the actual scan geometry, method and solver records.
Raises
- ValueError: If the method, configuration or options are incompatible.
Examples
import tomojax as tj
scan = tj.load("scan.nxs")
recon = tj.reconstruct(scan, "cgls", iterations=50)align
align(
scan: Scan,
*,
mode: AlignmentMode | Literal['auto'] = 'auto',
quality: QualityTier = 'fast',
levels: Iterable[int] | None = None,
freeze: Iterable[str] = (),
grid: Grid | None = None,
checkpoint: str | PathLike[str] | None = None,
config: AlignConfig | ProjectionMatchingConfig | None = None,
devices: Device | Sequence[Device] | None = None,
) -> AlignmentEstimate scan's geometry corrections and reconstruct with them.
mode="auto" selects centre estimation for axial parallel scans and
guarded setup/support/motion selection for unposed laminography. Its
conditional outcome and limitations are in info["automatic"].
Centre estimation records its input/output physical detector coordinates;
its success resolves only the reconstruction-only calibration limitation.
Explicit mode is pose (per-view motion), cor (detector
centre, or a cone beam's axis offset and detector roll), cor-then-pose
or full (setup geometry, then motion, coarse to fine); see
tomojax.alignment. quality is fast or reference (slower,
more conservative). freeze keeps named parameters fixed (for example
("dy",)); levels sets the coarse-to-fine binning factors.
config (a tomojax.alignment.AlignConfig) holds expert solver
settings and replaces the quality profile's defaults.
ProjectionMatchingConfig instead selects native-texture, translation-only
joint refinement for unposed axial scans, without resampling the input.
It may fit noise or model mismatch as motion; inspect poses and image quality.
Its image uses Fourier. Checkpoints and level/quality overrides are unsupported.
A spectral_setup initializer does not reconstruct; reconstruct its returned scan.
grid overrides the scan's reconstruction grid, as reconstruct does.
In automatic laminography it fixes the region and final voxel sampling,
while setup and motion remain automatic; it is not inferred support.
checkpoint is a file the alignment saves its progress to after each
outer iteration. When that file holds a checkpoint of the same alignment
(the same projections and geometry, mode, levels, grid and settings, from
the same TomoJAX version), the alignment resumes from it, and a finished
one returns its result at once. Any other file there raises
ValueError saying how it differs, and is left as it is. A cone
beam's cor mode runs no outer iterations and writes none.
devices (one or several, jax.devices() for all) shares the views
among them, as reconstruct does, in the joint pose and volume
update of pose alignment and in cone-beam reconstruction steps; the
rest runs on the first. The devices change only the order of sums, so a
checkpoint made on some resumes on others.
A scan that already carries poses (an ASTRA import's, say, or an earlier
alignment's) is corrected on top of them, and multi-orbit
ConeSegments scans are aligned as one, so their
orbits come into register; only pose alignment takes either.
The returned Alignment.scan carries the corrections, and the grid,
so tomojax.reconstruct(result.scan) reconstructs with them;
Alignment.poses are the scan's poses, its own included.
Alignment.info holds the mode, levels and resolved config with
the solver's record (losses, gauge, any calibrated setup geometry).
Parameters
scan(Scan): Measured line integrals with their acquisition geometry.mode(str, optional): Alignment mode: auto, pose, cor, cor-then-pose or full.quality({'fast', 'reference'}): Quality profile; reference is slower and more conservative.levels(iterable of int or None, optional): Coarse-to-fine binning factors; None uses the mode's defaults.freeze(iterable of str, optional): Names of parameters to keep fixed, such as ("dy",).grid(Grid or None, optional): Alignment and reconstruction grid; None keeps the scan's grid.checkpoint(str, path-like or None, optional): Progress file; compatible existing checkpoints resume automatically.config(AlignConfig or None, optional): Expert solver settings, replacing the quality profile's defaults.devices(jax.Device or sequence of jax.Device or None, optional): Devices sharing supported pose and reconstruction updates.
Returns
- Alignment: Reconstruction, corrected scan, full pose table and solver records.
Raises
- ValueError: If options, geometry or an existing checkpoint are incompatible.
Examples
import tomojax as tj
scan = tj.load("scan.nxs")
result = tj.align(scan, mode="cor-then-pose")
recon = tj.reconstruct(result.scan)project
project(
geometry: Geometry,
volume: np.ndarray | jax.Array,
*,
grid: Grid | None = None,
devices: Device | Sequence[Device] | None = None,
) -> jax.ArrayProject a volume through the supplied geometry.
Parameters
geometry(Geometry): Per-view geometry defining the matched projector.volume(numpy.ndarray or jax.Array): Volume in (x, y, z) order.grid(Grid or None, optional): Voxel grid; None uses the geometry's grid.devices(jax.Device or sequence of jax.Device or None, optional): Devices sharing the views; None uses the current default device.
Returns
- jax.Array: Line-integral projections, shape (views, detector.nv, detector.nu).
Raises
- ValueError: If dimensions or geometry metadata do not match the operation.
Examples
import tomojax as tj
scan = tj.load("scan.nxs")
projections = tj.project(scan.geometry, tj.reconstruct(scan).volume)backproject
backproject(
geometry: Geometry,
projections: np.ndarray | jax.Array,
*,
grid: Grid | None = None,
devices: Device | Sequence[Device] | None = None,
) -> jax.ArrayApply the exact transpose of the forward projector.
Parameters
geometry(Geometry): Per-view geometry defining the matched projector.projections(numpy.ndarray or jax.Array): Line integrals in (view, v, u) order.grid(Grid or None, optional): Voxel grid; None uses the geometry's grid.devices(jax.Device or sequence of jax.Device or None, optional): Devices sharing the views; None uses the current default device.
Returns
- jax.Array: Adjoint volume, shape (grid.nx, grid.ny, grid.nz).
Raises
- ValueError: If dimensions or geometry metadata do not match the operation.
Examples
import tomojax as tj
scan = tj.load("scan.nxs")
volume = tj.backproject(scan.geometry, scan.projections)save
save(path: str | PathLike[str], item: Scan | Reconstruction | Alignment) -> NoneSave a scan, reconstruction or alignment as a TomoJAX dataset.
Reconstructions and alignments are saved with their scan (projections,
geometry and any pose corrections), so the file reloads with load
and load_reconstruction.
Parameters
path(str or path-like): Output dataset path; an existing file is overwritten.item(Scan, Reconstruction or Alignment): Scan or result to save with its geometry and metadata.
Returns
- None: Write the dataset to disk.
Raises
- TypeError: If item is not a scan, reconstruction or alignment.
- ValueError: If the output format is unsupported.
Examples
import tomojax as tj
scan = tj.load("scan.nxs")
tj.save("copy.nxs", scan)load_reconstruction
load_reconstruction(path: str | PathLike[str]) -> ReconstructionLoad a reconstruction saved by save or tomojax recon.
Parameters
path(str or path-like): Saved reconstruction dataset.
Returns
- Reconstruction: Volume, source scan and stored reconstruction settings.
Raises
- ValueError: If the dataset has no reconstructed volume.
- FileNotFoundError: If the input does not exist.
Examples
import tomojax as tj
recon = tj.load_reconstruction("recon.nxs")Scan
class Scan(
projections: np.ndarray | jax.Array,
geometry: ScanGeometry,
name: str = 'sample',
corrections: tuple[Correction, ...] = (),
source: ProjectionDataset | None = None,
)Projections and the geometry that produced them.
projections are (views, rows, columns) line integrals (absorption,
not intensities); geometry describes every view, and carries the
reconstruction grid and the detector. A scan loaded from a file or
returned by align may carry per-view pose corrections, which every
operation applies (see poses). corrections records how its
projections were made from detector frames (see tomojax.corrections),
and is saved with it.
Parameters
projections(numpy.ndarray or jax.Array): Line integrals, not intensities, shaped (views, rows, columns).geometry(ScanGeometry): Per-view geometry carrying the reconstruction grid and detector.name(str, optional): Sample name.corrections(tuple of Correction, optional): Record of corrections already applied to the projections.source(ProjectionDataset or None, optional): Source metadata retained for saving and provenance.
Raises
- ValueError: If projection dimensions disagree with the detector or view count.
Examples
import numpy as np
import tomojax as tj
grid = tj.Grid(8, 8, 1, 1.0, 1.0, 1.0)
detector = tj.Detector(8, 1, 1.0, 1.0)
geometry = tj.ParallelGeometry(grid, detector, [0.0, 90.0])
scan = tj.Scan(np.zeros((2, 1, 8), dtype=np.float32), geometry)Scan.corrected
corrected(*steps: Step, batch_views: int | None = None) -> ScanThe scan with line-integral steps applied (see tomojax.corrections).
Steps on counts or transmission need the frames: use load_frames
and Frames.corrected.
Parameters
*steps(Step): Line-integral correction steps, applied in order.batch_views(int or None, optional): Positive views per batch; None chooses automatically.
Returns
- Scan: Corrected projections and geometry, with appended correction records.
Raises
- ValueError: If a step needs raw counts or transmission, or settings are invalid.
Scan.cropped
cropped(rows: slice, cols: slice) -> ScanCrop a contiguous detector window and move the detector to match.
Parameters
rows(slice): Nonempty contiguous detector row window.cols(slice): Nonempty contiguous detector column window.
Returns
- Scan: Cropped projections with the detector centre moved to match.
Raises
- ValueError: If a window is empty or has a step other than one.
Scan.selected
selected(views: slice | Sequence[int] | np.ndarray) -> ScanSelect views and their geometry in acquisition order.
Parameters
views(slice, sequence of int or numpy.ndarray): Forward slice, increasing indices or nonempty boolean view mask.
Returns
- Scan: Selected projections and their geometry, in acquisition order.
Raises
- ValueError: If indices are empty, unordered, repeated or out of bounds.
Scan.grid
@property
grid: GridThe reconstruction grid.
Returns
- Grid: Reconstruction grid carried by the geometry.
Scan.detector
@property
detector: DetectorThe detector.
Returns
- Detector: Detector carried by the geometry.
Scan.angles
@property
angles: np.ndarrayRotation angle of each view, in degrees.
Returns
- numpy.ndarray: One rotation angle per view, in degrees.
Scan.poses
@property
poses: np.ndarray | NonePer-view pose corrections, (views, 6) as alpha, beta, phi, dx, dz, dy.
Rotations in radians, translations in the geometry's length unit, in the detector frame. None when the scan carries no corrections.
Returns
- numpy.ndarray or None: Pose corrections with shape (views, 6), or None when absent.
Scan.with_poses
with_poses(poses: np.ndarray | None) -> ScanThe scan with its per-view corrections replaced by poses.
The table has one row per view and six columns, in poses's
order, units and detector translation frame. It is relative to the
calibrated geometry, not to the scan's current corrections: applying
the same table twice does not double the motion. None removes
corrections while retaining the calibrated axis, detector and grid.
Use this to attach externally estimated or interpolated poses to a full scan after fitting selected views. Interpolate small corrections in acquisition order, not wrapped rotation angle; no interpolation or additional alignment is performed here.
Parameters
poses(numpy.ndarray or None): Replacement (views, 6) correction table; None removes all corrections.
Returns
- Scan: Scan with replacement poses and unchanged calibrated geometry.
Raises
- ValueError: If the pose table does not have shape (views, 6).
Scan.from_astra
@classmethod
from_astra(
projections: np.ndarray,
proj_geom: Mapping[str, Any],
vol_geom: Mapping[str, Any],
*,
name: str = 'sample',
) -> ScanA scan from ASTRA Toolbox data and geometries.
projections are in ASTRA's (rows, views, columns) layout;
proj_geom is a cone or cone_vec geometry and vol_geom a
3-D volume geometry, as astra.create_proj_geom and
astra.create_vol_geom make them. The scan's grid is the ASTRA
volume, and its volumes are ASTRA's (z, y, x) arrays transposed to
(x, y, z). The fitted circular orbit is the scan's geometry; any
departure from it (tilts, wobble, per-view corrections) becomes
poses. ASTRA turns the source and detector about the object
and TomoJAX the object, so angles run the other way.
Parameters
projections(numpy.ndarray): ASTRA line integrals in (rows, views, columns) order.proj_geom(mapping): ASTRA cone or cone_vec projection geometry.vol_geom(mapping): ASTRA three-dimensional volume geometry.name(str, optional): Sample name.
Returns
- Scan: Imported cone-beam scan, including departures from its fitted orbit as poses.
Raises
- ValueError: If ASTRA geometry or data shape is unsupported or inconsistent.
Scan.combine
@classmethod
combine(scans: Sequence[Scan], *, name: str | None = None) -> ScanOne scan of the same object from several cone-beam scans, view after view.
For scans that differ in their source and detector arrangement (a
multi-orbit scan, a tall sample scanned in stacked sections): each
becomes a ConeSegments segment. They share
scans[0]'s grid and need equal detector pixel counts.
Parameters
scans(sequence of Scan): Nonempty cone-beam scans with equal detector pixel counts.name(str or None, optional): Combined sample name; None keeps the first scan's name.
Returns
- Scan: Concatenated views on the first scan's reconstruction grid.
Raises
- ValueError: If scans are empty or their geometry and detector counts are incompatible.
Scan.binned
binned(factor: int) -> ScanThe scan with factor x factor detector pixels averaged into one.
Use it when the detector samples finer than the reconstruction grid (a
pixel's footprint at the rotation axis smaller than a voxel): projection
cost falls by factor**2 for little change in the volume. Pixels that
do not fill a block at the detector's edges are dropped, and the
detector centre moves to match.
Parameters
factor(int): Positive detector-block side length, in pixels.
Returns
- Scan: Block-averaged projections and detector geometry; the grid is unchanged.
Raises
- ValueError: If factor is less than one or leaves no complete detector block.
Scan.to_astra
to_astra() -> tuple[np.ndarray, dict[str, Any], dict[str, Any]]Export projections and cone-beam geometry for ASTRA.
The inverse of from_astra, for cone-beam scans; transpose a
volume (x, y, z) -> (z, y, x) to use it with ASTRA.
Returns
projections(numpy.ndarray): Line integrals in ASTRA (rows, views, columns) order.proj_geom(dict): ASTRA cone_vec projection geometry.vol_geom(dict): ASTRA three-dimensional volume geometry.
Raises
- ValueError: If the scan is not a supported cone-beam geometry.
Frames
class Frames(
counts: Any,
geometry: ScanGeometry,
flats: Any = None,
darks: Any = None,
flat_positions: np.ndarray | None = None,
white_level: float | None = None,
view_positions: np.ndarray | None = None,
exposure_times: np.ndarray | float | None = None,
flat_exposure_times: np.ndarray | float | None = None,
name: str = 'sample',
source: ProjectionDataset | None = None,
)Detector frames as recorded, with the geometry of each sample view.
counts are the sample frames, (views, rows, columns), in memory, a
memmap or read lazily from the file; flats the frames of the beam
alone, with flat_positions the number of views recorded before each
(flats taken before and after a scan are interpolated between); darks
the frames with the beam off, both in memory or lazy like counts.
Scanners that calibrate their flat field record a white_level instead.
view_positions places each view among the flats, in the units of
flat_positions (by default view i at i + 1/2; selected
keeps it). corrected makes the Scan of line integrals.
Made by load_frames.
Parameters
counts(array-like): Sample detector frames, shape (views, rows, columns); may be lazy.geometry(ScanGeometry): Geometry of every sample view, including the grid and detector.flats(array-like or None, optional): Open-beam calibration frames; may be lazy.darks(array-like or None, optional): Beam-off calibration frames; may be lazy.flat_positions(numpy.ndarray or None, optional): Number of sample views preceding each flat frame.white_level(float or None, optional): Calibrated open-beam count level, instead of flat frames.view_positions(numpy.ndarray or None, optional): Sample positions in flat-position units; default is i + 1/2.exposure_times(numpy.ndarray or float or None, optional): Sample exposure durations in seconds, one per view or a common duration.flat_exposure_times(numpy.ndarray or float or None, optional): Flat exposure durations in seconds, one per flat or a common duration.name(str, optional): Sample name.source(ProjectionDataset or None, optional): Original dataset metadata retained when loading from a file.
Raises
- ValueError: If frame shape or view-position count does not match the geometry.
Frames.views
@property
views: intThe number of sample views.
Returns
- int: Number of sample frames.
Frames.corrected
corrected(
*steps: Step,
epsilon: float = 1e-06,
batch_views: int | None = None,
binning: int = 1,
) -> ScanCorrect detector counts to line integrals with the requested steps.
Steps run by the data they act on: counts steps first, then the flat
and dark fields, transmission steps, the log, and line-integral steps,
each in the order given (see tomojax.corrections). epsilon
must be finite and positive; it bounds the flat field and transmission
below. Views are corrected on the device in batches of batch_views,
a positive integer. Automatic view batches target 32 MiB for simple
pipelines, or 256 MiB including additional workspace such as the padded
FFT arrays needed for Paganin phase retrieval.
binning averages blocks of binning x binning detector pixels of
the line integrals, as Scan.binned does, without holding the
unbinned stack on the host unless a step such as RejectViews or a
non-row step needs it. Pixels where the flat does not exceed the dark by
three standard errors of their means take no absorption; the flat-field
record counts them. Detector-derived output grids follow this resolution;
grids supplied by the file or caller remain unchanged.
Parameters
*steps(Step): Corrections applied in domain order, then in the order supplied.epsilon(float, optional): Positive finite lower bound for flat field and transmission.batch_views(int or None, optional): Positive views per device batch; None selects a workspace-based size.binning(int, optional): Positive factor for averaging detector blocks after correction.
Returns
- Scan: Corrected line integrals with matching geometry and correction records.
Raises
- ValueError: If calibration, correction settings or batch dimensions are invalid.
Frames.cropped
cropped(rows: slice, cols: slice) -> FramesThese frames' block of detector rows and cols, the detector moved to match.
A lazily read file reads only that block, of the flats and darks too. An automatically chosen axial-parallel output grid follows the window; an explicit grid, or a non-axial output box, is retained.
Parameters
rows(slice): Nonempty contiguous detector row window.cols(slice): Nonempty contiguous detector column window.
Returns
- Frames: Selected frames with matching geometry and calibration.
Raises
- ValueError: If the selection is empty, out of bounds or not ordered as required.
Frames.selected
selected(views: slice | Sequence[int] | np.ndarray) -> FramesThese sample views only, with their geometry.
Take a forward slice, increasing integer indices or a nonempty mask.
Each view keeps its place among the flats (view_positions), so
its flat is interpolated as before.
Parameters
views(slice, sequence of int or numpy.ndarray): Forward slice, increasing indices or nonempty boolean sample-view mask.
Returns
- Frames: Selected frames with matching geometry and calibration.
Raises
- ValueError: If the selection is empty, out of bounds or not ordered as required.
Reconstruction
class Reconstruction(
volume: np.ndarray | jax.Array,
scan: Scan,
method: str,
info: Mapping[str, object],
)A reconstructed volume, the scan it came from and how it was made.
volume is (nx, ny, nz) on grid, in the reciprocal of the
geometry's length unit (a NumPy or JAX array; np.asarray gives NumPy).
info records the method's settings.
Parameters
volume(numpy.ndarray or jax.Array): Volume in (x, y, z) order and reciprocal geometry-length units.scan(Scan): Input scan, including the output grid and any inferred geometry.method(str): Reconstruction method used.info(mapping): Resolved method settings and solver records.
Reconstruction.grid
@property
grid: GridThe grid the volume is on, the scan's.
Returns
- Grid: Reconstruction grid carried by the scan.
Alignment
class Alignment(
volume: np.ndarray | jax.Array,
scan: Scan,
poses: np.ndarray,
info: Mapping[str, object],
)The result of align.
volume is the reconstruction the alignment converged with. scan is
the input scan with the estimated geometry and per-view poses applied:
reconstruct it, align it again or save it. poses are its (views, 6)
poses (see Scan.poses), always a table, zero where nothing moved;
info is the solver's record.
Parameters
volume(numpy.ndarray or jax.Array): Reconstruction used by the alignment, in (x, y, z) order.scan(Scan): Scan with estimated geometry and per-view corrections applied.poses(numpy.ndarray): Complete (views, 6) correction table, zero where nothing moved.info(mapping): Solver record and resolved alignment settings.
ConeBeam
class ConeBeam(
source_to_axis: float,
source_to_detector: float,
detector_roll_deg: float = 0.0,
detector_pitch_deg: float = 0.0,
detector_yaw_deg: float = 0.0,
axis_offset: float = 0.0,
*,
source_height: float = 0.0,
)Source and detector placement in the lab frame, shared by every view.
The beam travels along +y. The source sits at
(0, -source_to_axis, source_height)
and the detector plane at y = source_to_detector - source_to_axis; the
rotation axis passes through (axis_offset, 0, 0), so the magnification
at the axis is source_to_detector / source_to_axis. axis_offset is
the lab-CT centre-of-rotation offset: the axis's lateral distance from the
line through the source and the unshifted detector centre, in physical
units. source_height is the calibrated lab-frame z position of the source
and unshifted detector centre, not an editable per-view object correction.
Detector.center offsets the detector relative to that centre. Roll,
pitch and yaw rotate the detector about its centre: roll about the beam
(y), pitch about detector u (x) and yaw about detector v (z), applied in
that order.
Parameters
source_to_axis(float): Source-to-axis distance in physical units; strictly positive.source_to_detector(float): Source-to-detector distance, greater than source_to_axis.detector_roll_deg(float, optional): Detector roll about the lab y axis, in degrees.detector_pitch_deg(float, optional): Detector pitch about the x axis, in degrees.detector_yaw_deg(float, optional): Detector yaw about the z axis, in degrees.axis_offset(float, optional): Lateral x displacement of the rotation axis, in physical units.source_height(float, optional): Lab-frame z position of the source and unshifted detector centre, in physical units.
Raises
- ValueError: If 0 < source_to_axis < source_to_detector does not hold.
ConeBeam.magnification
@property
magnification: floatMagnification of the rotation axis on the detector.
Returns
- float: Source-to-detector divided by source-to-axis distance.
ConeBeam.source
source() -> np.ndarraySource position in the lab frame.
Returns
- numpy.ndarray: Lab-frame source (x, y, z) position, in physical units.
ConeBeam.detector_frame
detector_frame(detector: Detector) -> tuple[np.ndarray, np.ndarray, np.ndarray]Lab position of the detector centre and its unit u and v directions.
Parameters
detector(Detector): Detector with its physical in-plane centre offset.
Returns
centre(numpy.ndarray): Lab-frame detector-centre position in physical units.u_dir(numpy.ndarray): Lab-frame unit direction of increasing detector columns.v_dir(numpy.ndarray): Lab-frame unit direction of increasing detector rows.
ConeBeam.to_dict
to_dict() -> dict[str, float]Return JSON-compatible metadata.
Returns
- dict: Source distances, axis offset and detector rotations, ready for JSON.
ConeGeometry
class ConeGeometry(
grid: Grid,
detector: Detector,
angles: Sequence[float],
beam: ConeBeam,
tilt_deg: float = 0.0,
tilt_about: str = 'x',
axis_unit: tuple[float, float, float] | None = None,
)Cone-beam scan of a sample rotating about +z, or a tilted axis.
Poses follow ParallelGeometry (tilt_deg == 0) or
LaminographyGeometry: pose_for_view returns world_from_object, and
the object's +z axis is the rotation axis, turned by angles[i] degrees
at view i. axis_unit, a lab-frame unit
vector, replaces the tilt for an arbitrary calibrated axis. The beam is
described by beam; projectors read it to trace rays from the source to
each pixel.
Parameters
grid(Grid): Reconstruction voxel grid.detector(Detector): Detector dimensions, pitches and physical centre.angles(sequence of float): View rotation angles in degrees, in acquisition order.beam(ConeBeam): Source and detector placement in the lab frame.tilt_deg(float, optional): Axis tilt away from +z, in degrees; defaults to zero.tilt_about({'x', 'z'}): Tilt convention used when axis_unit is absent.axis_unit(tuple of float or None, optional): Explicit lab-frame rotation axis, replacing the tilt settings.
ConeGeometry.pose_for_view
pose_for_view(i: int) -> PoseMatrixReturn the world-from-object pose for one view.
Parameters
i(int): Zero-based view index.
Returns
- tuple of tuple of float: Row-major 4 x 4 homogeneous world-from-object transform.
Raises
- IndexError: If the view index is outside the angle sequence.
ConeGeometry.poses
poses(*, angles: np.ndarray | None = None) -> np.ndarrayReturn every view's world-from-object pose as an (n, 4, 4) FP64 array.
angles, in degrees, replaces the geometry's own view angles.
Parameters
angles(numpy.ndarray or None, optional): Replacement view angles in degrees; None uses this geometry's angles.
Returns
- numpy.ndarray: World-from-object poses with shape (views, 4, 4), in float64.
ConeGeometry.rotation_axis
rotation_axis() -> np.ndarrayReturn the lab-frame unit rotation axis.
Returns
- numpy.ndarray: Three-component lab-frame unit rotation axis.
Raises
- ValueError: If the tilt convention is unsupported for a nonzero tilt.
ConeGeometry.geometry_metadata
geometry_metadata() -> dict[str, object]Return the metadata that reconstructs this geometry from a saved dataset.
Returns
- dict: Serializable beam and rotation-axis metadata.
ConeGeometry.rays_for_view
rays_for_view(i: int) -> RayPairReturn world-frame ray callbacks (source origin, unit direction) for inspection.
Parameters
i(int): Zero-based view index.
Returns
- tuple of callable: Origin and unit-direction callbacks taking detector pixel indices (u, v).
Detector
class Detector(
nu: int,
nv: int,
du: float,
dv: float,
center: Vec2 = (lambda: (0.0, 0.0))(),
)Regular detector plane metadata.
center is the detector centre's (u, v) offset in its plane, in the
same physical units as du and dv.
Parameters
nu,nv(int): Detector column and row counts.du,dv(float): Pixel pitches along u and v, in the geometry's physical length unit.center(tuple of float, optional): Detector-plane (u, v) centre offset in physical units; defaults to zero.
Raises
- TypeError: If counts are not integers or centre coordinates are not real numbers.
- ValueError: If center does not have two components.
Detector.to_dict
to_dict() -> DetectorDictReturn JSON-compatible detector metadata.
Returns
- dict: Pixel counts, pitches and centre under the file key det_center.
Detector.from_dict
@classmethod
from_dict(data: Mapping[str, Any]) -> DetectorReturn the detector that a to_dict mapping describes.
Parameters
data(mapping): Complete metadata written by to_dict, including det_center.
Returns
- Detector: Detector described by the metadata.
Raises
- KeyError: If pixel counts or pitches are missing.
- ValueError: If the centre is missing or has the wrong number of coordinates.
Grid
class Grid(
nx: int,
ny: int,
nz: int,
vx: float,
vy: float,
vz: float,
vol_origin: Vec3 | None = None,
vol_center: Vec3 | None = None,
)Regular voxel grid metadata.
TomoJAX treats vol_origin as the physical location of voxel (0, 0, 0)'s
centre. When vol_origin is omitted, the default centred convention places
voxel centres on coordinates (i - (n/2 - 0.5)) * v along each axis.
vol_center can override that default centre while still deriving the
voxel (0, 0, 0) location from the grid dimensions and voxel sizes. If
both are provided, vol_origin is the authoritative placement.
Parameters
nx,ny,nz(int): Voxel counts along x, y and z.vx,vy,vz(float): Voxel pitches in a consistent physical length unit.vol_origin(tuple of float or None, optional): Physical centre of voxel (0, 0, 0); takes precedence over vol_center.vol_center(tuple of float or None, optional): Physical grid centre when vol_origin is absent; defaults to (0, 0, 0).
Raises
- TypeError: If counts are not integers or placement coordinates are not real numbers.
- ValueError: If a placement vector does not have three components.
Grid.resampled
resampled(shape: tuple[int, int, int]) -> GridReturn a grid with new voxel counts and the same physical extent.
Voxel pitches change to preserve the outer cell edges and centre, not
the positions of the first and last voxel centres. Explicit origins
remain authoritative over vol_center. No volume data is resampled.
Parameters
shape(tuple of int): Positive voxel counts(nx, ny, nz)for the returned grid.
Returns
- Grid: Grid covering the same physical box at the requested resolution.
Raises
- ValueError: If
shapedoes not contain three positive counts. - TypeError: If a count is not an integer.
Examples
Grid(100, 80, 60, 0.2, 0.3, 0.4).resampled((50, 40, 30)).vx
# 0.4Grid.to_dict
to_dict() -> GridDictReturn JSON-compatible grid metadata.
Returns
- dict: Grid counts, voxel pitches and any explicit placement, ready for JSON.
LaminographyGeometry
class LaminographyGeometry(
grid: Grid,
detector: Detector,
angles: Sequence[float],
tilt_deg: float = 30.0,
tilt_about: str = 'x',
)Laminography geometry with tilted rotation axis (parallel-ray model).
- Beam direction is fixed along +y (as in ParallelGeometry).
- Rotation axis is tilted away from +z by
tilt_degwithin a chosen plane. Withtilt_about='x'the axis leans towards +y (beam-aligned laminography); withtilt_about='z'the axis leans towards +x. Each view rotates the object around this axis byangles[i], in degrees.
Frame convention:
- pose_for_view(i) returns T_world_from_obj for the sample frame, where the object's +z axis equals the nominal rotation axis. At theta=0, T is the fixed alignment S (generally not identity) that maps e_z -> axis.
Parameters
grid(Grid): Reconstruction voxel grid.detector(Detector): Detector dimensions, pitches and physical centre.angles(sequence of float): View rotation angles in degrees, in acquisition order.tilt_deg(float, optional): Axis tilt away from +z, in degrees; defaults to 30.tilt_about({'x', 'z'}): Tilt convention: x leans towards +y; z leans towards +x.
LaminographyGeometry.axis_unit_lab
@property
axis_unit_lab: np.ndarrayThe rotation axis in the lab frame, a unit vector.
Returns
- numpy.ndarray: Three-component unit rotation axis in lab coordinates.
Raises
- ValueError: If tilt_about is unsupported for a nonzero tilt.
LaminographyGeometry.pose_for_view
pose_for_view(i: int) -> PoseMatrixWorld-from-object pose in sample frame; rotation about object +z.
We align the object's +z axis to the laminography axis once via S, then rotate around +z by view angle theta: R = S @ R_z(theta). The returned 4x4 poses are suitable for the projector contract (world_from_object).
Parameters
i(int): Zero-based view index.
Returns
- tuple of tuple of float: Row-major 4 x 4 homogeneous world-from-object transform.
Raises
- IndexError: If the view index is outside the angle sequence.
LaminographyGeometry.rays_for_view
rays_for_view(i: int) -> RayPairReturn detector ray callbacks for inspection tools.
Parameters
i(int): Zero-based view index.
Returns
- tuple of callable: Origin and unit-direction callbacks taking detector pixel indices (u, v).
ParallelGeometry
class ParallelGeometry(grid: Grid, detector: Detector, angles: Sequence[float])Parallel-beam CT geometry with axis-aligned detector.
- World frame: detector lies in (x,z) plane; rays along +y.
anglesare the view angles, in degrees. Viewirotates the object around +z byphi = angles[i]; pose_for_view returns T_world_from_obj = Rz(phi). At phi=0, T = I.
Parameters
grid(Grid): Reconstruction voxel grid.detector(Detector): Detector dimensions, pitches and physical centre.angles(sequence of float): View rotation angles in degrees, in acquisition order.
ParallelGeometry.pose_for_view
pose_for_view(i: int) -> PoseMatrixReturn the world-from-object pose for one view.
Parameters
i(int): Zero-based view index.
Returns
- tuple of tuple of float: Row-major 4 x 4 homogeneous world-from-object transform.
Raises
- IndexError: If the view index is outside the angle sequence.
ParallelGeometry.rays_for_view
rays_for_view(i: int) -> RayPairReturn detector rays for one view.
Parameters
i(int): Zero-based view index.
Returns
- tuple of callable: Origin and unit-direction callbacks taking detector pixel indices (u, v).
RegionEstimate
class RegionEstimate(scan: Scan, info: Mapping[str, object])The resized scan and the evidence/uncertainty behind its reconstruction grid.
Projection samples, detector calibration and per-view poses are unchanged.
info distinguishes a conditionally selected region from a broad survey;
neither is a certificate of the object's physical support.
Parameters
scan(Scan): Scan with the proposed reconstruction grid; measurements are unchanged.info(mapping): Selection evidence, uncertainty, memory budget and limitations.
RegionEstimate.grid
@property
grid: GridThe proposed reconstruction grid, also carried by scan.
Returns
- Grid: Proposed reconstruction grid carried by the scan.
estimate_center
estimate_center(scan: Scan) -> ScanEstimate a parallel scan's rotation centre without reconstructing a volume.
Returns the scan with its absolute physical detector-u centre set, available as
result.detector.center[0]; no pixels are shifted. For a cropped or binned
preview, add result.detector.center[0] - preview.detector.center[0] to the
full scan's centre rather than copying the preview's. Needs corrected
projections of an unposed axial ParallelGeometry with usable
near-opposite views (within two degrees). The estimate uses row-wise
gradient correlations and can be ambiguous for repetitive structure. Source
metadata, when present, records pair offsets, conditional uncertainty,
alternative matches and row trends without changing the input. Physical centre and sample motion
are aliases; conditional matching intervals do not cover motion.
Parameters
scan(Scan): Unposed axial parallel projections with near-opposite views.
Returns
- Scan: Scan with the inferred absolute physical detector-u centre; pixels are unchanged.
Raises
- ValueError: If geometry is unsupported or no usable near-opposite views exist.
Examples
import tomojax as tj
scan = tj.estimate_center(tj.load("scan.nxs"))
recon = tj.reconstruct(scan)estimate_region
estimate_region(scan: Scan) -> RegionEstimateSelect a reconstruction region from an already calibrated laminography scan.
A broad FBP preview tests axial focus isolation. Sixty-step positive reconstructions on balanced fitting/validation views then compare candidate depths using ordinary and high-pass projection error. Disagreement or multiple strong focus peaks returns a broad detector-derived survey instead of inventing a thin object.
Detector-pitch isotropic voxels are preferred. The output may be coarser to budget
eight volume work arrays plus measured data on the working device; info reports
this explicitly. The region is a conditional reconstruction choice, not physical
support or an accuracy certificate. Inspect the reconstruction and independent views.
No measured projections, poses, detector calibration or solver defaults are changed.
Parameters
scan(Scan): Calibrated oblique laminography scan of corrected line integrals.
Returns
- RegionEstimate: Resized scan and evidence distinguishing a selected region from a broad survey.
Raises
- ValueError: If the geometry is not calibrated oblique laminography.
Examples
import tomojax as tj
scan = tj.load("laminography.nxs")
region = tj.estimate_region(scan)
recon = tj.reconstruct(region.scan, "cgls")reconstruct_slabs
reconstruct_slabs(
frames: Frames,
method: Literal['fbp', 'fourier', 'sirt', 'mlem'],
*,
grid: Grid | None = None,
corrections: Sequence[Step] = (),
config: FBPConfig | FourierConfig | SIRTConfig | MLEMConfig | None = None,
warm_start: bool | None = None,
epsilon: float = 1e-06,
) -> Iterator[Iterator[tuple[slice, Reconstruction]]]Correct raw frames and reconstruct bounded z slabs.
Use with reconstruct_slabs(frames, "fbp", ...) as slabs: and iterate
over (z_slice, reconstruction). The slice indexes the requested output
grid; each reconstruction retains its physical global grid, genuine corrected
source rows (including interpolation neighbors), and correction records.
Volumes are owned float32 host arrays. Results remain valid after advancing
or closing the iterator; collecting them retains their volume and projections.
This supports cone-beam FDK, unposed axial parallel beams and whole-row line-integral
corrections such as Stripes, not Paganin or view rejection. SIRT/OSEM also
require matching detector and voxel planes; warm_start=True uses FBP.
Method configurations retain the same meaning as in reconstruct.
CUDA consumes corrected rows without a corrected-projection host round trip.
Raw reads are prefetched within bounded storage and calibration is reused.
The context drains pending work on normal completion, early exit or failure.
Raw frames and calibration arrays must remain unchanged inside the context.
Cone FDK corrects host row bands in device-sized view batches and retains
the complete acquisition's weights while reconstructing each slab.
Parameters
frames(Frames): Raw detector frames for cone FDK or unposed axial parallel-beam geometry.method({'fbp', 'fourier', 'sirt', 'mlem'}): Slab reconstruction method.grid(Grid or None, optional): Output grid; None uses the frames' grid.corrections(sequence of Step, optional): Whole-row line-integral corrections applied before reconstruction.config(FBPConfig or FourierConfig or SIRTConfig or MLEMConfig or None, optional): Method-specific settings, as forreconstruct.warm_start(bool or None, optional): Start SIRT/OSEM from FBP; None uses the method's default.epsilon(float, optional): Positive finite lower bound for flat field and transmission.
Yields
- iterator of tuple of (slice, Reconstruction): Context-managed slab iterator; each z slice indexes the requested grid.
Raises
- ValueError: If geometry, method, corrections or configuration do not support slabs.
Examples
import tomojax as tj
frames = tj.load_frames("raw_scan.nxs")
with tj.reconstruct_slabs(frames, "fbp") as slabs:
for z_slice, recon in slabs:
print(z_slice, recon.volume.shape)