Getting started with Ptypy

Installation

General installation instructions

Download and unpack PtyPy from the sources. For example, on Linux systems you can do:

$ wget https://github.com/ptycho/ptypy/archive/master.zip
$ unzip master.zip -d /tmp/; rm master.zip

or make a clone of the PtyPy github repository:

$ git clone https://github.com/ptycho/ptypy.git /tmp/ptypy-master

which will unpack the master branch of ptypy into /tmp/ptypy-master but you are of course free to place the software wherever it is convenient for you. In principle, next you only have to navigate to that directory and install with

$ pip install .

However, since PtyPy depends on a number of other packages, we recommend installing it in a virtual environment using the anaconda package manager. Conveniently, this installation route allows you to install PtyPy across platforms.

Essential install

Only three Python packages are essential for PtyPy to work:

Install the essential version like so

$ conda env create -f dependencies_core.yml
$ conda activate ptypy_core
(ptypy_core)$ pip install .

Install for GPU support with PyCUDA

Alternatively, we also support an accelerated version of PtyPy for CUDA-capable GPUs based on our own kernels and the PyCUDA package.

Install the dependencies for this version like so.

$ conda env create -f ptypy/accelerate/cuda_pycuda/dependencies.yml
$ conda activate ptypy_pycuda
(ptypy_pycuda)$ pip install .

We use Reikna to provide a filtered FFT, i.e. a FFT that is fused with a pointwise matrix multiplication.

Optional installation of filtered cufft

For optimal performance with both CuPy and PyCUDA engines, you can optionally install a version of the filtered FFT based on cufft and callbacks. Due to the nature of this extension it needs to be built for fixed array sizes externally and currently supports array sizes of 16, 32, 64, 128, 256, 512, 1024 and 2048.

$ conda activate ptypy_cupy
(ptypy_cupy)$ cd cufft
(ptypy_cupy)$ conda env update --file dependencies.yml --name ptypy_cupy
(ptypy_cupy)$ pip install .

Optional packages

Other very useful packages are

Quickstart with a minimal script

Note

This tutorial was generated from the python source [ptypy_root]/tutorial/minimal_script.py using ptypy/doc/script2rst.py. You are encouraged to modify the parameters and rerun the tutorial with:

$ python [ptypy_root]/tutorial/minimal_script.py

This tutorial explains the minimal settings to get a reconstruction runnig in PtyPy. A PtyPy script consists of two parts:

  • Creation of a parameter tree with parameters as listed in Parameter tree structure and

  • calling a Ptycho instance with this parameter tree, specifying a level that determines how much the Ptycho instance will do.

Preparing the parameter tree

We begin with opening an empty python file of arbitrary name in an editor of your choice, e.g.:

$ gedit minimal_script.py

Next we create an empty parameter tree. In PtyPy, parameters are managed by the Param class which is a convenience class subclassing Python’s dict type. It is designed in such a way that dictionary items can be accessed also as class attributes, making the scripts and code much more readable.

>>> from ptypy import utils as u
>>> p = u.Param()  # root level

We set the verbosity to a high level, in order to have information on the reconstruction process printed to the terminal. See verbose_level.

>>> p.verbose_level = "info"

We limit this reconstruction to single precision. The other choice is to use double precision.

>>> p.data_type = "single"

We give this reconstruction the name 'minimal' although it will automatically choose one from the file name of the script if we put in None. (But then the tutorial may not work on your computer as the chosen run name may differ from the one that this tutorial was created with)

>>> p.run = 'minimal'

Next, we set the home path. The Ptycho instance will use this path as base for any other relative file path (e.g io.autosave.path or io.rfile). Relative paths lack a leading “/” (or “C:\” on windows). Make sure to replace all “/” with “\” if you run the scripts on a Windows system.

>>> p.io = u.Param()
>>> p.io.home = "/tmp/ptypy/"

We want an intermediate result of the reconstruction to be dumped regularly every 20 reconstructions.

>>> p.io.autosave = u.Param()
>>> p.io.autosave.interval = 20

In this tutorial we switch off the threaded plotting client. (alternative one-liners would be p[‘io.autoplot.active’] = False or p.io[‘autoplot.active’] = False)

>>> p.io.autoplot = u.Param()
>>> p.io.autoplot.active = False

Since we do not want to plot anything, we don’t need the interaction server either.

>>> p.io.interaction = u.Param()
>>> p.io.interaction.active = False

Now we have to insert actual parameters associated with a ptychographic scan.

PtyPy is designed to support reconstruction from mutliple scans. Each individual scan is represented by a branch in scans. The parameters in these branches are those that differ from the defaults in the scan branch mentioned above. Obviously at least the data branch will differ from scan.data. In this tutorial we create a new scan parameter branch MF.

>>> p.scans = u.Param()
>>> p.scans.MF = u.Param()
>>> p.scans.MF.name = 'Vanilla'
>>> p.scans.MF.data = u.Param()

As data source we have choosen the ‘MoonFlowerScan’ test source. That will make PtyPy use the internal MoonFlowerScan class to generate data. This class is meant for testing, and it provides/simulates diffraction patterns without using the more complex generic SimScan class.

>>> p.scans.MF.data.name = 'MoonFlowerScan'

We set the diffraction frame shape to a small value (128x128px) and limit the number af diffraction patterns at 100. The MoonFlowerScan instance will balance the diffraction patterns accordingly.

>>> p.scans.MF.data.shape = 128
>>> p.scans.MF.data.num_frames = 100

We skip saving the “prepared” data file for now. The PtyPy data management is described in detail in Data management

>>> p.scans.MF.data.save = None

Needlees to say, we need to specify a reconstruction engine. We choose 40 iterations of difference map algorithm.

>>> p.engines = u.Param()
>>> p.engines.engine00 = u.Param()
>>> p.engines.engine00.name = 'DM'
>>> p.engines.engine00.numiter = 40
>>> p.engines.engine00.numiter_contiguous = 5

Running ptypy

We import the Ptycho class and pass the tree p at level 5. This level tells Ptycho to initialize everything and start the reconstruction using all reconstruction engines in p.engines immediately upon construction.

>>> from ptypy.core import Ptycho
>>> P = Ptycho(p, level=5)

From the terminal log, we note that there was an autosave every 20 iterations and the error reduced from iteration to iteration.

Utilies/Binaries for convenience

PtyPy provides a few utility scripts to make life easier for you, the user. They are located in [ptypy_root]/scripts. In case of an install with conda, these are copied to <conda_env_dir>/bin

Note

Due to the early stage of developmnet, these scripts may see substantial changes in further releases, i.e. the call signature may change.

Plotting from a reconstruction/dump file (*.ptyr)

ptypy.plot is an automatic plotting script that installs on Unix systems It has the syntax

$ ptypy.plot [-h] [-l LAYOUT] [-t IMFILE] ptyrfile

For our minimal example this translates to

$ ptypy.plot /tmp/ptypy/recons/minimal/minimal_DM.ptyr -t minimal.png

and the image looks like this (Fig. 1)

../_images/minimal_result.png

Fig. 1 Example plot made with ptypy.plot using the default layput

Inspecting a hdf5 compatible file

Sometimes we want to quickly inspect what is in a hdf5 file that was created by PtyPy. For such cases, we can use ptypy.inspect.

$ ptypy.inspect [-h] [-p PATH] [--report] [-d DEPTH] h5file

For example, a quick view at the top level can be realized with

$ ptypy.inspect /tmp/ptypy/recons/minimal/minimal_DM_0040.ptyr -d 1

which has the following the output:

* content [Param 5]:
    * obj [dict 1]:
    * pars [Param 9]:
    * positions [dict 1]:
    * probe [dict 1]:
    * runtime [Param 8]:
* header [dict 2]:
    * description [string = "Ptypy .h5 compatible storage format"]
    * kind [string = "minimal"]

If we are interested solely in the probe we could use

$ ptypy.inspect /tmp/ptypy/recons/minimal/minimal_DM.ptyr -d 1 -p content/probe

which has the following the output:

* SMF [dict 20]:
    * DataTooSmall [scalar = False]
    * ID [string = "SMF"]
    * _center [array = [64. 64.]]
    * _energy [scalar = 7.2]
    * _origin [array = [-3.59918584e-06 -3.59918584e-06]]
    * _pool [dict 0]:
    * _psize [array = [5.62372787e-08 5.62372787e-08]]
    * _record [scalar = (b'SMF',)]
    * _recs [dict 0]:
    * data [1x128x128 complex64 array]
    * distributed [scalar = False]
    * fill_value [scalar = 0.0]
    * grids [tuple = 2x[1x128x128 float64 array]]
    * layermap [list = [0]]
    * model_initialized [scalar = True]
    * nlayers [scalar = 1]
    * numID [scalar = 1]
    * padding [scalar = 0]
    * padonly [scalar = False]
    * shape [tuple = (1, 128, 128)]

We omitted the result for the complete file to save some space but you are encouraged to try:

$ ptypy.inspect /tmp/ptypy/recons/minimal/minimal_DM.ptyr

Create a new template for a reconstruction script

[WIP] Due to wildcards and links in the parameter tree, this section will be reworked.

Run a plotclient in a separate process

PtyPy supports a Client/Server approach. That means that the reconstruction process runs on a remote server (cluster) while we can monitor the progress on a local machine.

In this case, we need to start a plotting Client on a separate machine). You can implement your own plotting client but you may find it convenient to use the plotting utility ptypy.plotclient:

$ ptypy.plotclient [-h] [-l LAYOUT] [--dump] [--movie] [-i INTERVAL] [-d DIRECTORY]

Note

None of the options are currently implemented. The plotting client receives all information from the server it connects to. Work in progress ..

More script templates

Besides the script from which section Quickstart with a minimal script was generated, there is a trinity of similar scripts at your disposal that you can temper with.

All-in-one

We encourage you to use the script [ptypy_root]/templates/ptypy_minimal_prep_and_run.py and modify the recipe part of the data parameter branch. Observe what changes in the reconstruction when scan parameters change.

 1"""
 2This script is a test for ptychographic reconstruction in the absence
 3of actual data. It uses the test Scan class
 4`ptypy.core.data.MoonFlowerScan` to provide "data".
 5"""
 6from ptypy.core import Ptycho
 7from ptypy import utils as u
 8
 9import tempfile
10tmpdir = tempfile.gettempdir()
11
12p = u.Param()
13
14# for verbose output
15p.verbose_level = "info"
16
17# set home path
18p.io = u.Param()
19p.io.home = "/".join([tmpdir, "ptypy"])
20
21# saving intermediate results
22p.io.autosave = u.Param(active=False)
23
24# opens plotting GUI if interaction set to active)
25p.io.autoplot = u.Param(active=True)
26p.io.interaction = u.Param(active=True)
27
28# max 200 frames (128x128px) of diffraction data
29p.scans = u.Param()
30p.scans.MF = u.Param()
31# now you have to specify which ScanModel to use with scans.XX.name,
32# just as you have to give 'name' for engines and PtyScan subclasses.
33p.scans.MF.name = 'BlockVanilla' # or 'BlockFull'
34p.scans.MF.data= u.Param()
35p.scans.MF.data.name = 'MoonFlowerScan'
36p.scans.MF.data.shape = 128
37p.scans.MF.data.num_frames = 200
38p.scans.MF.data.save = None
39
40# position distance in fraction of illumination frame
41p.scans.MF.data.density = 0.2
42# total number of photon in empty beam
43p.scans.MF.data.photons = 1e8
44# Gaussian FWHM of possible detector blurring
45p.scans.MF.data.psf = 0.
46
47# attach a reconstrucion engine
48p.engines = u.Param()
49p.engines.engine00 = u.Param()
50p.engines.engine00.name = 'DM'
51p.engines.engine00.numiter = 80
52
53# prepare and run
54if __name__ == "__main__":
55    P = Ptycho(p,level=5)

Creating a .ptyd data-file

We encourage you to use this script [ptypy_root]/templates/ptypy_make_sample_ptyd.py to create various different samples and see what happens if the data processing parameters are changed. If you have become curious, move forward to Data management and take a look at PtyPy’s data management. Check out the data parameter branch scan.data for detailed parameter descriptions.

 1"""
 2This script creates a sample *.ptyd data file using the built-in
 3test Scan `ptypy.core.data.MoonFlowerScan`
 4"""
 5import time
 6from ptypy import utils as u
 7from ptypy.core.data import MoonFlowerScan
 8# for verbose output
 9u.verbose.set_level("info")
10
11# create data parameter branch
12data = u.Param()
13data.dfile = 'sample.ptyd'
14data.num_frames = 200
15data.save = 'append'
16data.label = None
17data.auto_center = None
18data.rebin = None
19data.orientation = None
20
21# create PtyScan instance
22MF = MoonFlowerScan(data)
23
24MF.initialize()
25for i in range(2):
26    # autoprocess data
27    msg = MF.auto(200)
28    time.sleep(2)
29    # logs the out put of .auto() to terminal prompt
30    u.verbose.logger.info(u.verbose.report(msg), extra={'allprocesses': True})

Loading a data file to run a reconstruction

The script [ptypy_root]/templates/minimal_load_and_run.py should resembles the case of data analysis after the experiment has taken place. Take a challenging sample data from before and alter the reconstruction parameters and algorithms to find out if you can make the recontruction converge. Check out the engine parameter branch engine for detailed parameter descriptions.

 1"""
 2This script is a test for ptychographic reconstruction after an
 3experiment has been carried out and the data is available in ptypy's
 4data file format in the current directory as "sample.ptyd". Use together
 5with `ptypy_make_sample_ptyd.py`.
 6"""
 7from ptypy.core import Ptycho
 8from ptypy import utils as u
 9
10import tempfile
11tmpdir = tempfile.gettempdir()
12
13p = u.Param()
14p.verbose_level = "info"
15p.io = u.Param()
16p.io.home = "/".join([tmpdir, "ptypy"])
17
18p.scans = u.Param()
19p.scans.MF = u.Param()
20p.scans.MF.data= u.Param()
21p.scans.MF.name = 'BlockVanilla'
22p.scans.MF.data.name = 'PtydScan'
23p.scans.MF.data.source = 'file'
24p.scans.MF.data.dfile = 'sample.ptyd'
25
26p.engines = u.Param()
27p.engines.engine00 = u.Param()
28p.engines.engine00.name = 'DM'
29p.engines.engine00.numiter = 80
30p.engines.engine01 = u.Param()
31p.engines.engine01.name = 'ML'
32p.engines.engine01.numiter = 20
33
34if __name__ == "__main__":
35    P = Ptycho(p,level=5)