Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
e5511a0
minor change in quick start.md
Sep 23, 2026
3ef7dd5
Merge branch 'PtyLab:main' into initial_doc_changes
cliulucien Sep 23, 2026
f7dda89
Enhance documentations: API reference - ExperimentalData
Sep 23, 2026
8aa8fc2
Merge branch 'main' into initial_doc_changes
cliulucien Sep 23, 2026
9827070
Improve API documentation for Reconstruction.py.
Sep 24, 2026
e9316cf
Merge branch 'PtyLab:main' into initial_doc_changes
cliulucien Sep 24, 2026
4e3796c
Merge branch 'initial_doc_changes' of https://github.com/cliulucien/P…
Sep 24, 2026
2a55dcd
Minor change: move back FPMcalibration in API. Not sorted yet. I am n…
Sep 24, 2026
09291a4
Minor change: more comprehensive param description in ExperimentalDat…
Sep 25, 2026
e66eee4
API documentation: BaseEngine.py - finished. Fixed some small
Sep 29, 2026
eea4f04
Minor: add 'footnote' extension for reference render in mkdocs.
Sep 29, 2026
baf9113
API documentation: ePIE.py - finished.
Sep 29, 2026
cde8067
API documentation: mPIE.py
Sep 30, 2026
84d68bc
Merge branch 'remove_TV_seprate_engine' into initial_doc_changes
Sep 30, 2026
b0251df
API documetation - ePIE and mPIE finished.
Sep 30, 2026
719b5dd
Merge branch 'main' into initial_doc_changes
cliulucien Sep 30, 2026
a990ec6
Remove pcPIE.py but keep the same interface, functions already includ…
Sep 30, 2026
81d1127
delete pcPIE.py
Sep 30, 2026
7dfc428
Merge branch 'remove_redundant_engines' into initial_doc_changes
Sep 30, 2026
c327b54
modify GPU requirement
Sep 30, 2026
58abc7e
modified tool.uv requirement
Sep 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,039 changes: 936 additions & 103 deletions PtyLab/Engines/BaseEngine.py

Large diffs are not rendered by default.

14 changes: 11 additions & 3 deletions PtyLab/Engines/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,24 @@
"mqNewton",
"multiPIE",
"OPR",
"pcPIE",
"qNewton",
"zPIE",
]
from .e3PIE import e3PIE
from .ePIE import ePIE
from .mPIE import mPIE
from .mPIE import mPIE, pcPIE
from .mqNewton import mqNewton
from .multiPIE import multiPIE
from .OPR import OPR
from .pcPIE import pcPIE
from .qNewton import qNewton
from .zPIE import zPIE


import warnings

warnings.warn(
"`pcPIE` is deprecated. Use `mPIE` with "
"`params.positionCorrectionSwitch = True` instead.",
DeprecationWarning,
stacklevel=2,
)
224 changes: 204 additions & 20 deletions PtyLab/Engines/ePIE.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,77 @@


class ePIE(BaseEngine):
r"""
Extended Ptychographical Iterative Engine (ePIE).

ePIE jointly reconstructs the complex object and illumination probe by
iterating over overlapping scan positions.[^maiden2009] For each position $j$, the
exit surface wave is formed as

$$
\Psi_j = O_j P
$$

where $O_j$ is the object patch illuminated by the probe $P$.

After propagation to the detector plane and application of the measured
intensity constraint, the corrected exit wave $\Psi'_j$ is propagated
back to the object plane. The resulting exit-wave difference is

$$
\Delta\Psi_j = \Psi'_j - \Psi_j
$$

In the classical single-mode ePIE formulation, the object and probe are
updated according to

$$
O'_j = O_j + \beta_O \frac{P^*}{\max |P|^2}\Delta\Psi_j
$$

and

$$
P' = P + \beta_P \frac{O_j^*}{\max |O_j|^2}\Delta\Psi_j
$$

where $\beta_O$ and $\beta_P$ control the object and probe update step
sizes.

The PtyLab implementation generalizes these updates to its multidimensional
reconstruction representation by summing the corresponding contributions
over the relevant wavelength, mode, and slice dimensions.

The default ePIE settings are `betaObject = 0.25`,
`betaProbe = 0.25`, and `numIterations = 50`.

[^maiden2009]: A. M. Maiden and J. M. Rodenburg,
"An improved ptychographical phase retrieval algorithm for diffractive
imaging," Ultramicroscopy 109, 1256-1262 (2009).
https://doi.org/10.1016/j.ultramic.2009.05.012
"""
def __init__(
self,
reconstruction: Reconstruction,
experimentalData: ExperimentalData,
params: Params,
monitor: Monitor,
):
# This contains reconstruction parameters that are specific to the reconstruction
# but not necessarily to ePIE reconstruction
"""
Initialize the ePIE reconstruction engine.

Args:
reconstruction (Reconstruction):
Reconstruction state containing the current object, probe, and
geometry.
experimentalData (ExperimentalData):
Experimental diffraction data and acquisition parameters.
params (Params):
Shared reconstruction parameters and constraint settings.
monitor (Monitor):
Monitor used for reconstruction visualization and progress
reporting.
"""
super().__init__(reconstruction, experimentalData, params, monitor)
self.logger = logging.getLogger("ePIE")
self.logger.info("Sucesfully created ePIE ePIE_engine")
Expand All @@ -34,27 +96,93 @@ def __init__(

def initializeReconstructionParams(self):
"""
Set parameters that are specific to the ePIE settings.
:return:
Initialize ePIE-specific reconstruction parameters.

Defaults:
betaObject (float):
Object update step size. Default is 0.25.
betaProbe (float):
Probe update step size. Default is 0.25.
numIterations (int):
Number of reconstruction iterations. Default is 50.
"""
self.betaProbe = 0.25
self.betaObject = 0.25
self.numIterations = 50

def reconstruct(self, experimentalData: ExperimentalData = None):
"""Run the reconstruction to completion.
"""
Run the ePIE reconstruction to completion.

This method consumes the generator returned by `reconstruct_stepwise()`
until all reconstruction iterations and scan positions have been
processed.

Use :meth:`reconstruct_stepwise` instead if you want to interleave your
own work between scan positions.
Use `reconstruct_stepwise()` when custom operations need to be inserted
between individual scan-position updates.

Args:
experimentalData (ExperimentalData, optional):
Experimental dataset to use for the reconstruction. If provided,
it replaces the currently attached experimental data.
"""
for _ in self.reconstruct_stepwise(experimentalData):
pass

def reconstruct_stepwise(self, experimentalData: ExperimentalData = None):
"""Generator variant of :meth:`reconstruct`.
r"""
Run the ePIE reconstruction one scan-position update at a time.

For each reconstruction iteration, the scan positions are visited in the
order selected by `params.positionOrder`. At each position $j$, the
corresponding object patch is extracted and combined with the current
probe to form the exit surface wave:

$$
\Psi_j = O_j P
$$

The exit wave is propagated to the detector plane, constrained by the
measured diffraction intensity through `intensityProjection()`, and
propagated back to obtain an updated exit wave $\Psi'_j$.

The exit-wave correction is

$$
\Delta\Psi_j = \Psi'_j - \Psi_j
$$

Yields ``(iteration, positionLoop)`` after every scan position. Nothing
happens until the generator is consumed.
By default, the object is updated using the standard ePIE rule implemented
by `objectPatchUpdate()`.

If `params.objectTVregSwitch` is enabled, the TV-regularized update
`objectPatchUpdate_TV()` is used every `params.objectTVfreq` iterations.
The standard ePIE object update is retained and an additional TV
regularization term is added with strength controlled by
`params.objectTVregStepSize`.

The probe is updated using `probeUpdate()` after each object update.

If `params.OPRP` is enabled, position-dependent probe estimates are
retrieved from `reconstruction.probe_storage` before each scan-position
update and stored again after the probe update. Without OPRP, a shared
probe estimate is updated sequentially across all scan positions.

After all scan positions in an iteration have been processed,
`getErrorMetrics()` evaluates the reconstruction error and
`applyConstraints()` applies the enabled reconstruction constraints.

The method yields after every scan-position update, allowing custom code
to be interleaved with the reconstruction.

Args:
experimentalData (ExperimentalData, optional):
Experimental dataset to use for the reconstruction. If provided,
it replaces the currently attached experimental data.

Yields:
tuple:
`(iteration, positionLoop)` after each scan-position update.
"""
if experimentalData is not None:
self.reconstruction.data = experimentalData
Expand Down Expand Up @@ -136,11 +264,39 @@ def reconstruct_stepwise(self, experimentalData: ExperimentalData = None):
self.params.gpuFlag = 0

def objectPatchUpdate(self, objectPatch: np.ndarray, DELTA: np.ndarray):
"""
Todo add docstring
:param objectPatch:
:param DELTA:
:return:
r"""
Update the object patch using the ePIE object-update rule.

For the classical single-mode case, the probe weighting is

$$
W_P(x,y) = \frac{P^*(x,y)}{\max_{x,y}|P(x,y)|^2}
$$

and the object patch is updated according to

$$
O'_j = O_j + \beta_O W_P\Delta\Psi_j
$$

where $O_j$ is the current object patch, $\Delta\Psi_j$ is the
exit-wave correction obtained from the detector-plane intensity
constraint, and $\beta_O$ is `betaObject`.

In the multidimensional PtyLab representation, contributions from the
relevant wavelength, probe-mode, and slice dimensions are summed before
updating the object patch.

Args:
objectPatch (ndarray):
Current object patch at the active scan position.
DELTA (ndarray):
Exit-wave correction
`reconstruction.eswUpdate - reconstruction.esw`.

Returns:
ndarray:
Updated object patch.
"""
# find out which array module to use, numpy or cupy (or other...)
xp = getArrayModule(objectPatch)
Expand All @@ -153,11 +309,39 @@ def objectPatchUpdate(self, objectPatch: np.ndarray, DELTA: np.ndarray):
)

def probeUpdate(self, objectPatch: np.ndarray, DELTA: np.ndarray):
"""
Todo add docstring
:param objectPatch:
:param DELTA:
:return:
r"""
Update the probe using the ePIE probe-update rule.

For the classical single-mode case, the object weighting is

$$
W_O(x,y) = \frac{O_j^*(x,y)}{\max_{x,y}|O_j(x,y)|^2}
$$

and the probe is updated according to

$$
P' = P + \beta_P W_O\Delta\Psi_j
$$

where $O_j$ is the current object patch, $\Delta\Psi_j$ is the
exit-wave correction obtained from the detector-plane intensity
constraint, and $\beta_P$ is `betaProbe`.

In the multidimensional PtyLab representation, the implemented update
sums the corresponding correction over axes `(0, 1, 3)` while preserving
the probe-mode dimension.

Args:
objectPatch (ndarray):
Current object patch at the active scan position.
DELTA (ndarray):
Exit-wave correction
`reconstruction.eswUpdate - reconstruction.esw`.

Returns:
ndarray:
Updated probe.
"""
# find out which array module to use, numpy or cupy (or other...)
xp = getArrayModule(objectPatch)
Expand Down
Loading
Loading