ptyrad.analysis#

User-facing analysis of PtyRAD model.hdf5 outputs.

The main entry point is Analyzer. The module-level extract_* functions in ptyrad.analysis.extract are re-exported here so callers can work with a raw dict from ptyrad.io.load.load_ptyrad() without instantiating the class — useful when only a sub-dict is on hand or when processing many files in a streaming loop.

The geometry helpers (get_probe_center_positions(), get_scanned_fov_bbox(), apply_fov()) are public on purpose: they encode the top-left-vs-center crop_pos convention that is the most error-prone part of the saved file format.

class ptyrad.analysis.Analyzer(source)[source]#

Bases: object

Wrapper around a loaded PtyRAD model.hdf5 reconstruction.

Parameters:

source (str | os.PathLike | dict) – Either a path to a .hdf5 / .h5 reconstruction output (the file is loaded via ptyrad.io.load.load_ptyrad()) or an already-loaded dict. When constructed from a dict the analyzer cannot retrieve HDF5 root attributes (provenance); that path returns None from provenance with a one-time UserWarning.

Notes

Construction is cheap: only the HDF5 datasets/groups are loaded into a nested dict (provenance is deferred, the PtychoModel is not built until build_model() is called).

The class is a thin delegator: every get_* method is a one-line forward to the corresponding pure function in ptyrad.analysis.extract, so users who prefer raw dicts can skip the class entirely and call the functions directly.

build_model(device='cuda', overrides=None)[source]#

Rebuild a PtychoModel from the saved params and weights.

Runs ptyrad.init.initializer.Initializer against data['params']['init_params'] to construct the init_variables dict, instantiates the model, copies the saved optimizable_tensors (obja, objp, probe, probe_pos_shifts, obj_tilts, slice_thickness) into the fresh model, and finally restores the iteration histories (loss_iters, dz_iters, lr_iters, avg_tilt_iters, convergence_iters) so the rebuilt model is plottable via plot_summary() / plot_forward_pass() and suitable for forward-pass diagnostics.

The model and its init_variables are cached on self after a successful build.

Parameters:
  • device (str) – Torch device string (e.g. 'cpu', 'cuda', 'cuda:0').

  • overrides (Mapping[str, Any] | None) –

    Dict deep-merged into params before initialization. The common use is to redirect the measurement file path when the original measurement data has moved:

    a.build_model(
        overrides={'init_params': {'meas_params': {'path': '/new/path.raw'}}}
    )
    

Raises:

FileNotFoundError – If init_params['meas_params']['path'] does not resolve. The error names the missing path explicitly rather than failing deep in the dataloader.

property data: dict#
property dk: float#
property dx: float#
forward(indices, *, return_raw=False, enable_grad=False)[source]#

Run a forward pass on the rebuilt model.

Thin wrapper around self.model.forward(indices, return_raw=...). Requires build_model() to have been called first; indices is coerced to np.ndarray before being passed through.

By default this runs under torch.no_grad() because analysis and plotting calls usually do not need autograd graphs. Set enable_grad=True to record gradients for offline custom losses or refinement experiments.

Parameters:
  • return_raw (bool)

  • enable_grad (bool)

get_fov_bbox()[source]#

Inclusive scanned-FOV bbox over probe-center positions.

Returns (y_min, y_max, x_min, x_max) matching the slicing of the saved *_crop TIFFs.

Return type:

tuple[int, int, int, int]

get_keys()[source]#

Flat dotted-key listing of the loaded data dict.

Return type:

list[str]

get_loss_curves()[source]#

Loss-history fields; see extract.extract_loss_curves().

Return type:

dict

get_object(fov='full', *, as_torch=False, device='cpu')[source]#

Complex object obja * exp(1j * objp); see extract.extract_object().

Parameters:
  • fov (Literal['full', 'crop'])

  • as_torch (bool)

  • device (str)

get_object_amplitude(fov='full', *, as_torch=False, device='cpu')[source]#

Object amplitude obja; see extract.extract_object_amplitude().

Parameters:
  • fov (Literal['full', 'crop'])

  • as_torch (bool)

  • device (str)

get_object_phase(fov='full', *, as_torch=False, device='cpu')[source]#

Object phase objp; see extract.extract_object_phase().

Parameters:
  • fov (Literal['full', 'crop'])

  • as_torch (bool)

  • device (str)

get_probe(space='real', *, as_torch=False, device='cpu')[source]#

Complex probe modes; see extract.extract_probe().

Parameters:
  • space (Literal['real', 'fourier'])

  • as_torch (bool)

  • device (str)

get_probe_positions(fov='full', *, target='probe', units='px', include_sub_px_shifts=True, as_torch=False, device='cpu')[source]#

Probe-center or crop-window positions; see extract.extract_probe_positions().

include_sub_px_shifts controls whether the optimized probe_pos_shifts offsets are included. target='probe' returns probe centers; target='crop' returns crop-window top-left positions, which can be negative when combined with fov='crop'.

Power users who hold only a sub-dict (e.g. just optimizable_tensors from a streaming loader) should call the standalone extract.extract_probe_positions() with explicit model_attrs.

Parameters:
  • fov (Literal['full', 'crop'])

  • target (Literal['probe', 'crop'])

  • units (Literal['px', 'pixel', 'Ang'])

  • include_sub_px_shifts (bool)

  • as_torch (bool)

  • device (str)

property is_multislice: bool#
property lambd: float#
property model#

The cached PtychoModel instance built via build_model(), or None.

property model_attrs: dict#
property niter: int | None#
property nslice: int#
property omode: int#
property opt_tensors: dict#
property params: dict#
property path: str | None#
plot_dashboard(**kw)[source]#

Convergence dashboard via ptyrad.plotting.plot_convergence_dashboard().

Pulls loss_iters, lr_iters, dz_iters, avg_tilt_iters, convergence_iters straight from the loaded dict — no rebuilt model required.

plot_forward_pass(indices, *, dp_power=0.5, **kw)[source]#

Forward-pass diagnostic via ptyrad.plotting.plot_forward_pass().

Requires a rebuilt model — call build_model() first.

Parameters:

dp_power (float)

plot_loss(**kw)[source]#

Plot the loss curve via ptyrad.plotting.plot_loss_curves().

plot_positions(fov='full', *, target='probe', **kw)[source]#

Scatter scan positions via ptyrad.plotting.plot_scan_positions().

See get_probe_positions() for the fov / target semantics.

Parameters:
  • fov (Literal['full', 'crop'])

  • target (Literal['probe', 'crop'])

plot_probe(space='real', amp_or_phase='amplitude', **kw)[source]#

Plot probe modes via ptyrad.plotting.plot_probe_modes().

space and amp_or_phase map directly to that function’s real_or_fourier and amp_or_phase arguments.

Parameters:
  • space (Literal['real', 'fourier'])

  • amp_or_phase (Literal['amplitude', 'phase'])

plot_slice_thickness(**kw)[source]#

Plot slice-thickness evolution via ptyrad.plotting.plot_slice_thickness().

plot_summary(indices, **kw)[source]#

Summary figure via ptyrad.plotting.plot_summary().

Requires a rebuilt model — call build_model() first. output_path is read from kw (default '.').

plot_tilts(**kw)[source]#

Plot per-position object tilts via ptyrad.plotting.plot_obj_tilts().

property pmode: int#
property probe_shape: tuple[int, int]#
property provenance: dict | None#

Lazy-loaded provenance dict; None if unavailable.

Reads the provenance_json root HDF5 attr via extract.extract_provenance() on first access and caches the result. Returns None (with a one-time UserWarning) when the Analyzer was constructed from an in-memory dict, since root attrs are not preserved by ptyrad.io.load.load_ptyrad().

ptyrad.analysis.apply_fov(arr, bbox)[source]#

Crop the last two dims of arr to an inclusive bbox.

bbox is (y_min, y_max, x_min, x_max) and both bounds are inclusive. Returns arr unchanged when bbox is None.

Parameters:
  • arr (ndarray)

  • bbox (tuple[int, int, int, int] | None)

Return type:

ndarray

ptyrad.analysis.extract_keys(data, delimiter='.')[source]#

Return a flat dotted-key listing of a loaded data dict.

Thin wrapper over ptyrad.io.hierarchy.list_nested_keys() that accepts a nested dict (the form returned by load_ptyrad) and yields keys like 'optimizable_tensors.probe', 'model_attributes.crop_pos'.

Parameters:
  • data (Mapping[str, Any])

  • delimiter (str)

Return type:

list[str]

ptyrad.analysis.extract_loss_curves(data)[source]#

Pull the loss-history fields from a loaded data dict.

Returns a dict with these keys (missing fields become None):

  • loss_iters(niter, 2) float64 ndarray of (iter_number, total_loss) pairs.

  • batch_lossesdict[str, list[float]] of per-loss batch values from the most recent iteration.

  • avg_lossesdict[str, float] of averaged batch losses.

  • niter — final iteration number (int).

Parameters:

data (Mapping[str, Any])

Return type:

dict[str, Any]

ptyrad.analysis.extract_object(data_or_opt, *, fov='full', model_attrs=None, probe_shape=None, as_torch=False, device='cpu')[source]#

Return the complex object obja * exp(1j * objp).

Shape (omode, Nz, Ny, Nx), complex64. The 4D shape is preserved even for single-slice runs (Nz == 1) so downstream code can branch on Nz directly; squeeze yourself if you need 2D.

fov='crop' clips to the scanned-FOV bbox (see get_scanned_fov_bbox()); it requires crop_pos from model_attrs. probe_shape is auto-inferred from opt_tensors['probe'] and only needs to be passed when callers have stripped the probe out of opt_tensors.

Parameters:
  • data_or_opt (Mapping[str, Any])

  • fov (Literal['full', 'crop'])

  • model_attrs (Mapping[str, Any] | None)

  • probe_shape (tuple[int, int] | None)

  • as_torch (bool)

  • device (str)

ptyrad.analysis.extract_object_amplitude(data_or_opt, *, fov='full', model_attrs=None, probe_shape=None, as_torch=False, device='cpu')[source]#

Return the saved object amplitude obja (no recomposition).

Shape (omode, Nz, Ny, Nx), float32. fov behaves the same as extract_object().

Parameters:
  • data_or_opt (Mapping[str, Any])

  • fov (Literal['full', 'crop'])

  • model_attrs (Mapping[str, Any] | None)

  • probe_shape (tuple[int, int] | None)

  • as_torch (bool)

  • device (str)

ptyrad.analysis.extract_object_phase(data_or_opt, *, fov='full', model_attrs=None, probe_shape=None, as_torch=False, device='cpu')[source]#

Return the saved object phase objp (no recomposition).

Shape (omode, Nz, Ny, Nx), float32. fov behaves the same as extract_object(). Sign convention follows PtychoModel: the complex object is obja * exp(1j * objp); no sign flip is applied here.

Parameters:
  • data_or_opt (Mapping[str, Any])

  • fov (Literal['full', 'crop'])

  • model_attrs (Mapping[str, Any] | None)

  • probe_shape (tuple[int, int] | None)

  • as_torch (bool)

  • device (str)

ptyrad.analysis.extract_probe(data_or_opt, *, space='real', as_torch=False, device='cpu')[source]#

Return the probe modes. Shape (pmode, Ny, Nx), complex64.

space='real' returns the stored real-space complex wavefunction (the form held in optimizable_tensors['probe'] after the view_as_complex post-process at save time).

space='fourier' returns fftshift(fft2(ifftshift(probe), norm='ortho')) along the last two axes. The fftshift sandwich matches ptyrad.plotting.plot_probe_modes() exactly so amplitudes and phases line up between getters and plotters; in particular the pre-fftshift to the corner avoids the checkerboard-phase artifact that a plain fft2 would produce.

Parameters:
  • data_or_opt (Mapping[str, Any])

  • space (Literal['real', 'fourier'])

  • as_torch (bool)

  • device (str)

ptyrad.analysis.extract_probe_positions(data_or_opt, model_attrs=None, *, fov='full', target='probe', units='px', include_sub_px_shifts=True, as_torch=False, device='cpu')[source]#

Return positions, shape (N, 2) as [y, x], float32.

Parameters:
  • data_or_opt (Mapping[str, Any]) – Full data dict from ptyrad.io.load.load_ptyrad(), or the optimizable_tensors sub-dict. model_attrs must be supplied when the latter is passed.

  • model_attrs (Mapping[str, Any] | None) – Optional explicit model_attributes sub-dict. Must contain crop_pos (always) and dx (only when units='Ang').

  • fov (Literal['full', 'crop']) – 'full' returns positions in the full saved-object coordinate frame. 'crop' subtracts the probe-center scanned-FOV bbox top-left (see get_scanned_fov_bbox()) so that, regardless of target, the returned positions live in the same coordinate frame as the array returned by extract_object(fov='crop'). Because that cropped FOV is anchored on probe centers, target='crop' can produce negative local coordinates near the top/left edge.

  • target (Literal['probe', 'crop']) – 'probe' (default) returns probe-center positions (crop_pos + (Hp // 2, Wp // 2)). 'crop' returns the top-left crop-window positions used by PtychoModel.get_obj_patches(). The two differ by exactly (Hp // 2, Wp // 2) and that offset survives fov='crop' because the same bbox is subtracted in both cases.

  • units (Literal['px', 'pixel', 'Ang']) – 'px' / 'pixel' returns object-space pixels. 'Ang' multiplies by model_attrs['dx'] to return Ångströms.

  • include_sub_px_shifts (bool) – When True (default), add opt_tensors['probe_pos_shifts'] if present. These are the optimized sub-pixel offsets relative to the integer crop_pos grid. Older saves without the key silently fall back to zero shifts.

  • as_torch (bool)

  • device (str)

ptyrad.analysis.extract_provenance(path)[source]#

Read the provenance_json root attribute from an HDF5 file.

The provenance JSON is stored as a root-level HDF5 attribute, not as a dataset, so ptyrad.io.load.load_ptyrad() silently drops it. This helper opens the file directly via ptyrad.io.provenance.load_provenance_from_h5() and returns the parsed dict (with keys like 'probe', 'obj', 'pos', 'tilt'). Returns None when the attribute is missing or the JSON parse fails.

Parameters:

path (str | PathLike)

Return type:

dict | None

ptyrad.analysis.get_probe_center_positions(crop_pos, probe_shape, probe_pos_shifts=None)[source]#

Convert top-left crop_pos to probe-center positions.

The crop_pos stored in model.hdf5 is the top-left corner of the probe window in object pixels — that is the form consumed by PtychoModel.get_obj_patches(), not the visual probe center. The probe center sits (Hp // 2, Wp // 2) further in.

Parameters:
  • crop_pos (array-like, shape (N, 2), int) – Top-left [y, x] of each probe window in object pixels.

  • probe_shape (tuple) – Probe shape; only the last two dims (Hp, Wp) are used.

  • probe_pos_shifts (array-like, shape (N, 2), optional) – Optimized sub-pixel offsets; added to the center positions when given.

Returns:

Shape (N, 2), float32, [y, x] order, in object pixels.

Return type:

np.ndarray

ptyrad.analysis.get_scanned_fov_bbox(crop_pos, probe_shape)[source]#

Inclusive (y_min, y_max, x_min, x_max) of probe-center positions.

The bbox is computed from the integer probe-center positions (crop_pos + probe_shape // 2) without probe_pos_shifts — matching the bbox math in ptyrad.io.save.save_results() so the result lines up exactly with the saved *_crop TIFFs (sliced as [y_min:y_max + 1, x_min:x_max + 1]).

Parameters:
  • crop_pos (ndarray)

  • probe_shape (tuple[int, int])

Return type:

tuple[int, int, int, int]

Modules

analyzer

User-facing wrapper around a PtyRAD model.hdf5 output.

extract

Pure extraction functions for PtyRAD model.hdf5 outputs.