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.
| Name | Summary |
|---|---|
BeamHardening | Linearise single-material beam hardening. |
Corrected | Corrected line integrals with records and input-view indices. |
Correction | One correction applied to a scan: what it was, its settings and what it found. |
Paganin | Retrieve single-material phase from propagation contrast. |
RadialDistortion | Unwarp transmission with a calibrated backward radial polynomial. |
RejectViews | Drop views whose median line integral jumps from their neighbours'. |
Rings | Correct ring artefacts from each detector column's own response. |
Step | A correction: the data it acts on, and what it does to a batch of it. |
Stripes | Correct detector stripes that reconstruct as rings. |
Zingers | Replace bright specks (zingers) with the median of the pixels around them. |
correct_frames | Convert detector counts to line integrals and record each correction. |
correct_projections | Correct a line-integral stack and record the steps applied. |
corrected_row_source | Read corrected detector-row intervals without a materialized projection stack. |
remove_phase_ramp | Subtract each image's phase plane fitted only to its known air pixels. |
tube_spectrum | The detected spectrum of a tungsten-target X-ray tube. |
unwrap_phase | Integrate 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,
)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.ArrayReplace 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.ndarrayMeasured 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.ndarrayMaterial 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) -> BeamHardeningThe 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,
) -> BeamHardeningThe 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()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(),
)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]) -> CorrectionRestore 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)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) -> intEstimate 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.ArrayRetrieve 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, ...])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) -> intEstimate 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.ArrayUnwarp 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)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.ArrayCompute 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)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 | NoneThe 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() -> CorrectionBuild 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.ArrayCorrect 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()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.ArrayApply 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) -> intEstimate 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.ArraySummarize 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() -> CorrectionBuild 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)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.ArrayRemove 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)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.ArrayReplace 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,
) -> CorrectedConvert 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,
) -> CorrectedCorrect 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]]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.ArraySubtract 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]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.ArrayIntegrate 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)