TomoJAX

tomojax.corrections

Corrections: from detector counts to line integrals, and on line integrals.

tomojax.Frames.corrected turns a scan's counts into line integrals: any steps on counts, then (I - D) / (F - D) with its dark and flat fields (each view's flat interpolated between the flat sets around it), any steps on transmission, -log, then any steps on line integrals. tomojax.Scan.corrected runs line-integral steps on a scan. What was done is recorded in tomojax.Scan.corrections and saved with the scan.

A step is a frozen dataclass of its settings with the domain it acts on; see Step.

NameSummary
BeamHardeningLinearise single-material beam hardening.
CorrectedCorrected line integrals with records and input-view indices.
CorrectionOne correction applied to a scan: what it was, its settings and what it found.
PaganinRetrieve single-material phase from propagation contrast.
RadialDistortionUnwarp transmission with a calibrated backward radial polynomial.
RejectViewsDrop views whose median line integral jumps from their neighbours'.
RingsCorrect ring artefacts from each detector column's own response.
StepA correction: the data it acts on, and what it does to a batch of it.
StripesCorrect detector stripes that reconstruct as rings.
ZingersReplace bright specks (zingers) with the median of the pixels around them.
correct_framesConvert detector counts to line integrals and record each correction.
correct_projectionsCorrect a line-integral stack and record the steps applied.
corrected_row_sourceRead corrected detector-row intervals without a materialized projection stack.
remove_phase_rampSubtract each image's phase plane fitted only to its known air pixels.
tube_spectrumThe detected spectrum of a tungsten-target X-ray tube.
unwrap_phaseIntegrate wrapped phase differences independently on each detector image.

BeamHardening

class BeamHardening(
    rates: tuple[float, ...] = (1.0,),
    weights: tuple[float, ...] = (1.0,),
    energy_kev: float | None = None,
)
Defined in src/tomojax/corrections/_hardening.py:20

Linearise single-material beam hardening.

A polychromatic beam's line integral through one material is q = g(p) = -ln sum_j w_j exp(-k_j p), where p is the material's line integral and the mixture (k_j, w_j) is the detected spectrum seen through the material's attenuation. Every such curve is increasing and concave and becomes linear at large p, and every curve with those properties that a single material can make has this form; this step replaces each q by p = g^-1(q).

rates are the k_j and weights the w_j (scaled to sum to one). With energy_kev=None the rates are scaled so that g'(0) = 1: thin paths keep their attenuation, and the corrected line integrals are in the units of the unhardened beam's mean attenuation. With energy_kev the rates are the material's attenuation at each component's energy divided by its attenuation at energy_kev, and the corrected line integrals are that energy's.

The coefficients come from from_polynomial (an explicit polynomial linearisation) or from_spectrum (the source spectrum, filters and detector). The curve is only as good as the stated tube settings and material: the scan is not used to check it. BeamHardening() leaves line integrals unchanged.

Parameters

  • rates (tuple of float, optional): Positive attenuation of each spectral component relative to the reference.
  • weights (tuple of float, optional): Nonnegative detected share of each component; scaled to sum to one.
  • energy_kev (float or None, optional): Reference energy whose attenuation the corrected line integrals give; None uses the unhardened beam's mean attenuation (unit initial slope).

Raises

  • ValueError: If rates and weights differ in length, a rate is not positive, a weight is negative or all weights are zero, or a value is not finite.

Examples

import numpy as np
from tomojax.corrections import BeamHardening
step = BeamHardening(rates=(0.5, 2.0), weights=(0.5, 0.5))
step.rates
# (0.4, 1.6)
round(float(step.inverse(step.curve(3.0))), 9)
# 3.0

BeamHardening.apply

apply(batch: jax.Array) -> jax.Array

Replace measured line integrals by the material's.

Parameters

  • batch (jax.Array): Float32 line integrals of shape (views, rows, columns).

Returns

  • jax.Array: Linearised line integrals, retaining the input shape.

BeamHardening.curve

curve(p: np.ndarray | float) -> np.ndarray

Measured line integrals g(p) of material line integrals p.

Parameters

  • p (array-like or float): Material line integrals in this step's units.

Returns

  • numpy.ndarray: The polychromatic line integrals, in float64.

BeamHardening.inverse

inverse(q: np.ndarray | float) -> np.ndarray

Material line integrals g^-1(q) of measured q, in float64 on the host.

Parameters

  • q (array-like or float): Measured line integrals.

Returns

  • numpy.ndarray: The linearised line integrals that apply approximates in float32.

BeamHardening.from_polynomial

@classmethod
from_polynomial(coefficients: Sequence[float], *, upto: float) -> BeamHardening

The physical curve closest to a polynomial linearisation p = sum_n c_n q^n.

The coefficients specify a polynomial in the measured line integral. This fits the mixture whose inverse best matches it for 0 <= q <= upto (each line integral weighted by its Poisson precision), then rescales it to unit initial slope; a polynomial that is not the inverse of a physical curve (one with zero initial slope, say) is replaced by the closest one that is.

Parameters

  • coefficients (sequence of float): (c0, c1, c2, ...), lowest power first.
  • upto (float): Largest measured line integral the curve must cover, such as the scan's maximum.

Returns

  • BeamHardening: The fitted linearisation.

Raises

  • ValueError: If upto is not positive or the polynomial gives no positive line integral.

BeamHardening.from_spectrum

@classmethod
from_spectrum(
    energies: Sequence[float] | np.ndarray,
    weights: Sequence[float] | np.ndarray,
    material: str,
    *,
    energy_kev: float | None = None,
) -> BeamHardening

The curve of one material seen by a known detected spectrum.

Each energy bin is a component whose rate is the material's attenuation there (from xraylib), relative to its attenuation at energy_kev or, with None, to the spectrum's mean. tube_spectrum models a tube's spectrum from its voltage, filters and scintillator; a measured or simulated spectrum works the same way.

Parameters

  • energies (array-like): Bin energies in keV.
  • weights (array-like): Detected signal per bin (for a scintillator, absorbed energy).
  • material (str): Chemical formula ("CuO") or NIST material name of the sample.
  • energy_kev (float or None, optional): Energy whose attenuation the corrected line integrals give; None gives unit initial slope.

Returns

  • BeamHardening: The linearisation for that material and spectrum.

Raises

  • ValueError: If the spectrum is empty or the material unknown.
  • ImportError: If xraylib is not installed.

Corrected

class Corrected()
Defined in src/tomojax/corrections/_engine.py:195

Corrected line integrals with records and input-view indices.

Parameters

  • projections (numpy.ndarray): Float32 corrected line integrals of shape (views, rows, columns).
  • records (tuple of Correction): Records in correction order.
  • kept (numpy.ndarray): Increasing indices of retained views in the original input stack.

Correction

class Correction(
    name: str,
    settings: dict[str, Json] = dict(),
    found: dict[str, Json] = dict(),
)
Defined in src/tomojax/corrections/_records.py:11

One correction applied to a scan: what it was, its settings and what it found.

settings are the step's own (a flat field's frame and set counts, a stripe filter's width); found holds what the data showed while it ran (pixels with no light, non-finite values set to zero). Both are JSON, so the record is saved with the scan and read back with it.

Parameters

  • name (str): Correction name, such as "flat_dark" or "stripes".
  • settings (dict, optional): JSON-compatible settings used by the correction. Defaults to an empty dict.
  • found (dict, optional): JSON-compatible findings recorded while correcting the data. Defaults to an empty dict.

Correction.to_dict

to_dict() -> dict[str, Json]

Serialize the correction record to a dictionary.

Returns

  • dict: Name, settings and findings, with copies of the two outer dictionaries.

Correction.from_dict

@classmethod
from_dict(value: dict[str, Any]) -> Correction

Restore a correction record from a dictionary.

Parameters

  • value (dict): Record with a required name and optional settings and found.

Returns

  • Correction: Restored record; missing settings and findings become empty dictionaries.

Raises

  • KeyError: If the name is absent.

Paganin

class Paganin(delta_beta: float, distance: float, energy_kev: float, pixel_size: float)
Defined in src/tomojax/corrections/_steps.py:496

Retrieve single-material phase from propagation contrast.

Filters each view's transmission by 1 / (1 + z lambda (delta/beta) |k|^2 / (4 pi)) before the log, so the line integrals are of the material's attenuation with the edge fringes folded back in. delta_beta is the material's delta / beta, distance the effective propagation distance (m: the sample-to-detector distance, divided by the magnification in a cone beam), energy_kev the X-ray energy and pixel_size the detector pixel at the sample (m; the pixel divided by the magnification in a cone beam). Views are padded with repeated edges against wrap-around, by at least 16 decay lengths of the filter's point response (and up to half the view, at most 128 pixels), and to an efficient FFT length.

Parameters

  • delta_beta (float): Positive ratio of the material's refractive-index decrement to absorption.
  • distance (float): Positive effective propagation distance at the sample, in metres.
  • energy_kev (float): Positive X-ray energy in keV.
  • pixel_size (float): Positive detector-pixel size at the sample, in metres.

Raises

  • ValueError: If any parameter is nonfinite or nonpositive.

Paganin.workspace_pixels

workspace_pixels(rows: int, cols: int) -> int

Estimate additional workspace pixels per view.

Parameters

  • rows (int): Detector rows in the batch.
  • cols (int): Detector columns in the batch.

Returns

  • int: Padded real arrays and complex half-spectra for phase filtering.

Paganin.apply

apply(batch: jax.Array) -> jax.Array

Retrieve phase from a batch of detector images.

Parameters

  • batch (jax.Array): Float32 transmission of shape (views, rows, columns). Rows and columns form each detector image.

Returns

  • jax.Array: Corrected transmission, retaining the input shape.

RadialDistortion

class RadialDistortion(*, center: tuple[float, float], coefficients: tuple[float, ...])
Defined in src/tomojax/corrections/_steps.py:409

Unwarp transmission with a calibrated backward radial polynomial.

center is (column, row) in zero-based pixel coordinates of the frames (not the rotation axis), and coefficients are (c0, c1, ...): an undistorted pixel at radius r from the centre samples the measured image at radius r * (c0 + c1 r + ...), the convention of Algotom and Discorpy ((1.0,) is the identity). Bilinear interpolation acts on the normalised transmission before the log, repeating the nearest edge pixel beyond the detector; the shape is kept and no Jacobian factor is applied.

The calibration must be in the coordinates of the frames corrected: after a crop, subtract its first column and row from center; after mean binning by f, use (center - (f - 1) / 2) / f and c_k * f**k. Correct before cropping when the map needs pixels outside the crop.

Parameters

  • center (tuple of float): Distortion centre (column, row) in zero-based frame pixels.
  • coefficients (tuple of float): Backward radial-polynomial coefficients (c0, c1, ...) in frame-pixel coordinates; (1.0,) is the identity.

Raises

  • ValueError: If center is not a finite pair or coefficients are empty or nonfinite.

RadialDistortion.workspace_pixels

workspace_pixels(rows: int, cols: int) -> int

Estimate additional workspace pixels per view.

Parameters

  • rows (int): Detector rows in the batch.
  • cols (int): Detector columns in the batch.

Returns

  • int: Two detector-coordinate arrays for resampling.

RadialDistortion.apply

apply(batch: jax.Array) -> jax.Array

Unwarp a batch of detector images.

Parameters

  • batch (jax.Array): Float32 transmission of shape (views, rows, columns). Rows and columns form each detector image.

Returns

  • jax.Array: Corrected transmission, retaining the input shape.

RejectViews

class RejectViews(z: float = 6.0, neighbours: int = 5, min_jump: float = 0.05)
Defined in src/tomojax/corrections/_steps.py:237

Drop views whose median line integral jumps from their neighbours'.

A view taken with the beam off, a shutter closed or the sample out of the beam differs as a whole from the views either side of it, while the object's own change with angle is smooth. Each view's median is compared with the median of the neighbours views around it (in the order recorded); a view is rejected when that difference is more than z robust standard deviations (1.4826 times the median absolute deviation) of all views' differences and more than min_jump of the views' typical median (so the small, smooth changes of a noise-free scan never count). Views with a nonfinite median are also rejected and do not participate in the neighbours or robust baseline. The scan's geometry keeps the same views; the record lists those rejected, including the nonfinite subset.

Parameters

  • z (float, optional): Positive robust-standard-deviation threshold. Default is 6.
  • neighbours (int, optional): Odd neighbourhood length of at least 3 views. Default is 5.
  • min_jump (float, optional): Nonnegative minimum jump as a fraction of the typical view median. Default is 0.05.

Raises

  • ValueError: If thresholds are nonfinite or outside their ranges, or the neighbourhood length is not odd and at least 3.

RejectViews.statistic

statistic(batch: jax.Array) -> jax.Array

Compute the median line integral of every view.

Parameters

  • batch (jax.Array): Float32 line integrals of shape (views, rows, columns).

Returns

  • jax.Array: Median of each detector image, with shape (views,).

RejectViews.select

select(statistics: np.ndarray, views: np.ndarray) -> tuple[np.ndarray, dict[str, Json]]

Select views with medians consistent with their neighbours.

Parameters

  • statistics (numpy.ndarray): Median line integral of every input view, with shape (views,).
  • views (numpy.ndarray): Original scan indices corresponding to these medians.

Returns

  • keep (numpy.ndarray): Boolean mask of retained views; nonfinite medians are rejected.
  • found (dict): Rejected original indices, robust scale and any nonfinite-view indices.

Rings

class Rings(geometry: object = None)
Defined in src/tomojax/corrections/_rings.py:519

Correct ring artefacts from each detector column's own response.

A column whose gain or response the flat field does not describe reports q + a + b (1 - exp(-q)) instead of the line integral q: a stripe in the sinogram, a ring in the volume, stronger where the sample absorbs more when b is not zero. The fitted response is inverted in two passes. This step does not interpolate detector columns: low angular variation alone cannot distinguish a defective pixel from object structure. Raw flat/dark calibration interpolates bracketed pixels without measured illumination; other defective measurements need independent calibration evidence and repair before this step.

After statistical regularisation, a constrained coefficient refit ensures that every observation has an inverse on a branch with response derivative at least one half. It minimises coefficient displacement in the reference regression metric subject to a conservative linear feasibility constraint; corrected line integrals are not clipped. This also modifies the parity prior where it would otherwise produce a noninvertible response.

On a stationary parallel-beam scan over one evenly sampled full turn, each view is compared with a local quadratic through 30 columns either side, narrowed toward 6 where the object changes faster. Independent neighbouring views provide the instrument for the response fit. Keeping these references unsorted preserves their spatial smoothness at the axis. The fine fits use cyclic angular neighbours and average a repeated closing phase. Large stripes are detected first using the sorted-column procedure of Vo, Atwood and Drakopoulos (2018, Opt. Express 26, 28396, algorithms 4 and 5); a last sorted fit over 100 columns either side addresses broad rings.

Opposed columns see the same lines over a full turn. Their fitted odd component therefore supplies a stationary detector-field spectrum; the even component is regularised locally in an undecimated multiscale frame, accounting for correlation between reflected neighbours. Flagged large and fine corrections use per-scale simultaneous significance; the final broad correction uses a Wiener gain. The variance and effective degrees are estimated, not a calibrated scan-wide confidence bound. DC power is inferred from the lowest measured odd-frequency band. Purely even detector errors and rotationally symmetric object structure remain ambiguous, especially at broad scales; this is not a no-change guarantee. The axis used, or none, is recorded.

Otherwise, each unsorted view is compared with a quadratic through six neighbouring columns on either side. Independent neighbouring views supply an instrument for the response fit, avoiding correlation between reference noise and regression error. Angular covariance and the unfiltered scene's spatial power regularise the local response contrast before inversion. Smooth object structure is retained rather than interpreted as calibration error. This is a statistical separation, not a no-change guarantee: fine stationary object structure and broad detector errors can be ambiguous.

Built-in angular geometries order the views by angle within each trajectory; single turns are unwrapped across the largest unsampled arc, explicit multi-turn angle labels are retained, and independent cone trajectories are kept separate even when their calibration segments are subdivided. Without such geometry, views must follow a continuous acquisition trajectory. At least two views per trajectory are needed. Output view order is preserved.

Parameters

  • geometry (object, optional): The scan's geometry, with the detector the data were recorded on. Default is None.

Raises

  • ValueError: If a geometry trajectory contains fewer than two views.

Examples

import tomojax as tj
from tomojax.corrections import Rings
grid = tj.Grid(64, 64, 1, 1.0, 1.0, 1.0)
turn = tj.ParallelGeometry(
    grid, tj.Detector(64, 1, 1.0, 1.0), [i * 2.0 for i in range(180)]
)
Rings(turn).mirror_axis
# 31.5
Rings().mirror_axis is None
# True

Rings.mirror_axis

@property
mirror_axis: float | None

The rotation-axis column the mirror stages use.

Returns

  • float or None: Detector column on the half-pixel grid, or None when the geometry is not a stationary parallel-beam full turn.

Rings.record

record() -> Correction

Build this step's record: the mirror axis used, or None.

Returns

  • Correction: Step name and settings, with no findings yet.

Rings.apply

apply(batch: jax.Array) -> jax.Array

Correct the rings of a batch of detector rows.

Parameters

  • batch (jax.Array): Float32 line integrals of shape (views, rows, columns), every view of each row.

Returns

  • jax.Array: Corrected line integrals of the same shape.

Raises

  • ValueError: If the geometry's detector is not as wide as the data, the view count differs, or fewer than two views are supplied.

Step

class Step()
Defined in src/tomojax/corrections/_steps.py:30

A correction: the data it acts on, and what it does to a batch of it.

Subclasses are frozen dataclasses of their settings with a domain and an apply taking and returning a (views, rows, columns) float32 JAX array (every view of a slab of rows when whole_rows). A step that selects_views has instead a statistic of each view in a batch and a select of the views to keep from every view's statistic.

Step.apply

apply(batch: jax.Array) -> jax.Array

Apply this correction to a batch of detector images.

Parameters

  • batch (jax.Array): Float32 data of shape (views, rows, columns) in this step's domain. Whole-row steps require every view of the selected row slab.

Returns

  • jax.Array: Corrected data with the same shape and domain.

Raises

  • NotImplementedError: If the subclass does not implement a batch correction.

Step.workspace_pixels

workspace_pixels(rows: int, cols: int) -> int

Estimate device pixels per view needed in addition to the input.

Parameters

  • rows (int): Detector rows in the batch.
  • cols (int): Detector columns in the batch.

Returns

  • int: Workspace estimate used to size batches; zero in the base class.

Step.statistic

statistic(batch: jax.Array) -> jax.Array

Summarize each view for a view-selecting correction.

Parameters

  • batch (jax.Array): Float32 data of shape (views, rows, columns) in this step's domain.

Returns

  • jax.Array: Per-view statistics with shape (views, ...).

Raises

  • NotImplementedError: If the subclass does not implement view statistics.

Step.select

select(statistics: np.ndarray, views: np.ndarray) -> tuple[np.ndarray, dict[str, Json]]

Select views from their statistics and record what was found.

Parameters

  • statistics (numpy.ndarray): Statistics for every input view, with shape (views, ...).
  • views (numpy.ndarray): View indices in the scan the correction steps started from.

Returns

  • keep (numpy.ndarray): Boolean mask of the views to keep.
  • found (dict): JSON-compatible findings to include in the correction record.

Raises

  • NotImplementedError: If the subclass does not implement view selection.

Step.record

record() -> Correction

Build this step's record from its name and settings.

Returns

  • Correction: Step name and JSON-compatible settings, with no findings yet.

Stripes

class Stripes(width: int = 9, *, varying: bool = False)
Defined in src/tomojax/corrections/_steps.py:143

Correct detector stripes that reconstruct as rings.

A pixel whose gain or offset the flat field does not describe adds the same absorption to every view, a stripe in the sinogram and a ring in the volume. Each detector row's values are sorted over views, column by column (as in Vo, Atwood and Drakopoulos, 2018), so equal ranks of neighbouring columns compare like with like; a pixel's offset is the median over ranks of its sorted values minus their median over width columns. Only that constant is subtracted, preserving differences between views; widen width for wider stripes. A feature symmetric about the rotation axis is also stationary in the sinogram, and one narrower than width can be altered. An even width averages the two windows centred half a pixel either side, so reversing the detector does not change the correction.

varying=True instead detects pixels with an anomalous view-to-view response and fits a gain and offset to their neighbouring sorted values. It leaves unselected pixels unchanged, including stationary centred features, but does not remove ordinary constant-offset stripes there. Detection compares adjacent-view differences with neighbouring columns; it needs sufficiently sampled, noisy views, and sharp moving edges can cause false detections. Selected unresponsive pixels receive only the neighbouring mean: their missing angular information is not recovered. It cannot distinguish a real feature from a defect at a selected pixel.

Parameters

  • width (int, optional): Neighbourhood width in detector columns, at least 3. Default is 9.
  • varying (bool, optional): Fit gain and offset at anomalous pixels instead of subtracting constant offsets at all pixels. Default is False.

Raises

  • ValueError: If width is not a whole number of columns at least 3.

Examples

from tomojax.corrections import Stripes
correction = Stripes(width=9)
correction.domain
# 'line integrals'

Stripes.apply

apply(batch: jax.Array) -> jax.Array

Remove stripes from a batch of detector images.

Parameters

  • batch (jax.Array): Float32 line integrals of shape (views, rows, columns). Supply every view of each row for stripe estimation.

Returns

  • jax.Array: Corrected line integrals, retaining the input shape.

Zingers

class Zingers(threshold: float = 0.1, size: int = 3)
Defined in src/tomojax/corrections/_steps.py:347

Replace bright specks (zingers) with the median of the pixels around them.

A pixel is a zinger when its transmission exceeds the median of the size x size pixels around it by more than threshold (a fraction of the open beam). Zingers, from scattered or direct hits on the detector, are far brighter than that; a sharp corner of a strongly absorbing object can be too, so raise threshold if one is touched.

Parameters

  • threshold (float, optional): Positive excess over the neighbourhood median, as a fraction of the open beam. Default is 0.1.
  • size (int, optional): Odd side length of the square neighbourhood, at least 3 pixels. Default is 3.

Raises

  • ValueError: If threshold is not finite and positive, or size is not odd and at least 3.

Zingers.apply

apply(batch: jax.Array) -> jax.Array

Replace bright specks in a batch of detector images.

Parameters

  • batch (jax.Array): Float32 transmission of shape (views, rows, columns). Rows and columns form each detector image.

Returns

  • jax.Array: Corrected transmission, retaining the input shape.

correct_frames

correct_frames(
    counts: Any,
    *,
    flats: Any,
    flat_positions: np.ndarray | None,
    darks: Any,
    white_level: float | None,
    steps: Sequence[Step],
    epsilon: float,
    view_positions: np.ndarray | None = None,
    exposure_times: np.ndarray | float | None = None,
    flat_exposure_times: np.ndarray | float | None = None,
    batch_views: int | None = None,
    binning: int = 1,
) -> Corrected
Defined in src/tomojax/corrections/_engine.py:39

Convert detector counts to line integrals and record each correction.

flats are flat frames and flat_positions the number of views recorded before each (frames at one position are one set; None, one set); white_level stands in for flats on scanners that record it instead. darks default to zero. view_positions places each view among the flats (by default view i at i + 1/2). binning averages blocks of binning x binning detector pixels of the corrected line integrals.

Parameters

  • counts (array-like): Detector counts with shape (views, rows, columns). May be a lazy source.
  • flats (array-like or None): Open-beam frames with shape (frames, rows, columns), or None when white_level supplies the open-beam count.
  • flat_positions (numpy.ndarray or None): Number of views recorded before each flat frame. Frames at the same position form one set; None puts all frames in one set.
  • darks (array-like or None): Dark frames with shape (frames, rows, columns); None uses zero dark.
  • white_level (float or None): Positive open-beam count used when flats is None.
  • steps (sequence of Step): Corrections, run by domain and in the supplied order within each domain.
  • epsilon (float): Positive transmission floor before the logarithm.
  • view_positions (numpy.ndarray or None, optional): Position of each view among the flats. Defaults to i + 0.5 for view i.
  • 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. Supply both exposure arrays to compare dark-subtracted count rates rather than integrated counts.
  • batch_views (int or None, optional): Positive view-batch size. None selects a memory-bounded size automatically.
  • binning (int, optional): Positive detector-block side length for mean binning of the corrected line integrals. Default is 1; incomplete edge blocks are dropped.

Returns

  • Corrected: Float32 host line integrals, correction records and kept input-view indices.

Raises

  • ValueError: If calibration, positions, batching, binning or correction domains are invalid.
  • TypeError: If a correction is not a Step.

Examples

import numpy as np
from tomojax.corrections import correct_frames
result = correct_frames(
    np.full((2, 3, 4), 50),
    flats=None,
    flat_positions=None,
    darks=None,
    white_level=100,
    steps=(),
    epsilon=1e-6,
)
result.projections.shape
# (2, 3, 4)

correct_projections

correct_projections(
    projections: Any,
    steps: Sequence[Step],
    *,
    batch_views: int | None = None,
) -> Corrected
Defined in src/tomojax/corrections/_engine.py:213

Correct a line-integral stack and record the steps applied.

Parameters

  • projections (array-like): Line integrals with shape (views, rows, columns); copied to float32.
  • steps (sequence of Step): Corrections acting on line integrals, in the order to run them.
  • batch_views (int or None, optional): Positive view-batch size. None selects a memory-bounded size automatically.

Returns

  • Corrected: Corrected host line integrals, correction records and kept input-view indices.

Raises

  • ValueError: If a step does not act on line integrals or batch_views is invalid.
  • TypeError: If a correction is not a Step.

Examples

import numpy as np
from tomojax.corrections import BeamHardening, correct_projections
result = correct_projections(np.ones((2, 3, 4)), [BeamHardening((1, 0.05))])
result.projections.shape
# (2, 3, 4)

corrected_row_source

corrected_row_source(
    counts: Any,
    *,
    flats: Any,
    flat_positions: np.ndarray | None,
    darks: Any,
    white_level: float | None,
    steps: Sequence[Step],
    epsilon: float = 1e-06,
    view_positions: np.ndarray | None = None,
    exposure_times: np.ndarray | float | None = None,
    flat_exposure_times: np.ndarray | float | None = None,
    host: bool = False,
) -> Iterator[Callable[[int, int], CorrectedRows | Corrected]]
Defined in src/tomojax/corrections/_rows.py:109

Read corrected detector-row intervals without a materialized projection stack.

Each request returns owned projections, independent of later reads and context closure. Only whole-row transmission and line-integral steps without view selection are supported; operations requiring neighboring rows must run on full frames. Raw inputs must be NumPy arrays or support lazy detector windows. The source and calibration arrays must remain unchanged until this context closes.

Parameters

  • counts (array-like): Detector counts with shape (views, rows, columns). May be a lazy source.
  • flats (array-like or None): Open-beam frames with shape (frames, rows, columns), or None when white_level supplies the open-beam count.
  • flat_positions (numpy.ndarray or None): Number of views recorded before each flat frame. Frames at the same position form one set; None puts all frames in one set.
  • darks (array-like or None): Dark frames with shape (frames, rows, columns); None uses zero dark.
  • white_level (float or None): Positive open-beam count used when flats is None.
  • steps (sequence of Step): Corrections, run by domain and in the supplied order within each domain.
  • epsilon (float, optional): Positive transmission floor before the logarithm. Default is 1e-6.
  • view_positions (numpy.ndarray or None, optional): Position of each view among the flats. Defaults to i + 0.5 for view i.
  • 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.
  • host (bool, optional): Return NumPy bands using bounded device view batches, instead of keeping the whole corrected band on the device. Default is False.

Yields

  • callable: Reader accepting a half-open row interval (first, stop) and returning corrected rows with their correction records. Use within a with block.

Raises

  • ValueError: If steps require full frames or select views, or calibration or positions are invalid.
  • TypeError: If counts lack detector-window access or a correction is not a Step.

remove_phase_ramp

remove_phase_ramp(phase: jax.Array, *, air_mask: jax.Array) -> jax.Array
Defined in src/tomojax/corrections/_phase.py:67

Subtract each image's phase plane fitted only to its known air pixels.

phase has detector axes last, (..., rows, columns), in radians. air_mask is a boolean mask or nonnegative weights broadcastable to its shape. It must cover enough noncollinear air pixels to determine a plane. A common mask can describe air strips at both sides of the sample; per-view masks can follow a drifting sample. Unreliable border pixels must not be included. No sample pixels should contribute: this correction cannot distinguish the sample's own low-frequency signal from a phase ramp.

Returns a real JAX array of the same shape and units. For a reconstruction in pixel units, -phase * wavelength / (2*pi*pixel_size) then gives line integrals of the refractive-index decrement. Wavelength and pixel size must have the same units.

Parameters

  • phase (array-like): Real phase in radians with shape (..., rows, columns).
  • air_mask (array-like): Boolean air mask or nonnegative weights broadcastable to phase.shape. Must cover enough noncollinear air pixels to determine a plane.

Returns

  • jax.Array: Real phase with its fitted plane subtracted, in the input shape and units.

Examples

import jax.numpy as jnp
from tomojax.corrections import remove_phase_ramp
corrected = remove_phase_ramp(jnp.ones((4, 4)), air_mask=jnp.ones((4, 4)))
corrected.shape
# (4, 4)

tube_spectrum

tube_spectrum(
    kv: float,
    *,
    filters: Iterable[Layer] = (),
    detector: Layer | None = None,
    target_angle: float = 12.0,
    step_kev: float = 0.5,
) -> tuple[np.ndarray, np.ndarray]
Defined in src/tomojax/corrections/_spectrum.py:74

The detected spectrum of a tungsten-target X-ray tube.

Bremsstrahlung follows Kramers' thin-target law, with each photon absorbed on its way out of the target from the depth where the electron made it (Thomson-Whiddington slowing, Kanaya-Okayama range, leaving the target at target_angle degrees). Tungsten's K lines are not modelled; above 70 kV they add a few percent of the photons near 60 keV. The beam is then filtered by filters and detected by detector, a scintillator whose signal is the energy it absorbs; None counts each photon's full energy.

Parameters

  • kv (float): Tube voltage in kV.
  • filters (iterable of tuple, optional): Layers between source and detector, including the tube window, as (material, thickness_mm) or (material, thickness_mm, density_g_cm3). A material is a chemical formula ("Al", "CuO") or a NIST name ("Water, Liquid").
  • detector (tuple or None, optional): Scintillator (material, thickness_mm[, density]); None absorbs every photon.
  • target_angle (float, optional): Angle in degrees between the target surface and the beam. Default 12.
  • step_kev (float, optional): Energy bin width in keV. Default 0.5.

Returns

  • energies (numpy.ndarray): Bin centres in keV.
  • weights (numpy.ndarray): Detected signal in each bin, summing to one.

Raises

  • ValueError: If a setting is out of range or a material is unknown.
  • ImportError: If xraylib is not installed.

unwrap_phase

unwrap_phase(projections: jax.Array) -> jax.Array
Defined in src/tomojax/corrections/_phase.py:10

Integrate wrapped phase differences independently on each detector image.

Accepts complex transmission or wrapped phase in radians, with detector axes last: (..., rows, columns). The result is a real JAX array of the same shape, in radians, with zero mean on each image. Use remove_phase_ramp to set its phase origin from air.

Solves the least-squares fit to nearest-neighbour wrapped phase gradients with nonperiodic (Neumann) boundaries, using a discrete cosine transform. Smooth phase is recovered up to a constant when adjacent true phase differences are less than pi. With phase residues the gradients are inconsistent: this returns their least-squares integral, not an exact pointwise unwrapping. Missing or low-amplitude pixels should be cropped before this operation; they are not inferred from neighbouring views.

Parameters

  • projections (array-like): Complex transmission or wrapped phase in radians, with detector axes last: (..., rows, columns).

Returns

  • jax.Array: Real phase in radians with the input shape and zero mean on each image.

Examples

import jax.numpy as jnp
from tomojax.corrections import unwrap_phase
phase = unwrap_phase(jnp.ones((2, 4, 4), dtype=jnp.complex64))
phase.shape
# (2, 4, 4)

On this page