diff --git a/.github/pages/index.html b/.github/pages/index.html new file mode 100644 index 000000000..ce7572f9f --- /dev/null +++ b/.github/pages/index.html @@ -0,0 +1,5 @@ + + + + + diff --git a/.github/pages/switcher.py b/.github/pages/switcher.py new file mode 100644 index 000000000..bf8fb2646 --- /dev/null +++ b/.github/pages/switcher.py @@ -0,0 +1,41 @@ +"""Create/modify switcher.json to allow docs to switch between different versions.""" + +import json, os +from argparse import ArgumentParser +from pathlib import Path + + +def get_versions(root: str) -> list[str]: + """Generate a list of versions.""" + versions = sorted([ f.name for f in os.scandir(root) if f.is_dir() ]) + print(f"Sorted versions: {versions}") + return versions + + +def write_json(path: Path, repository: str, versions: list[str]): + """Write the JSON switcher to path.""" + org, repo_name = repository.split("/") + struct = [ + {"version": version, "url": f"https://{org}.github.io/{repo_name}/{version}/"} + for version in versions + ] + text = json.dumps(struct, indent=2) + print(f"JSON switcher:\n{text}") + path.write_text(text, encoding="utf-8") + + +def main(args=None): + """Parse args and write switcher.""" + parser = ArgumentParser(description="Make a versions.json file") + parser.add_argument("root", type=Path, help="Path to root directory with all versions of docs") + parser.add_argument("repository", help="The GitHub org and repository name: ORG/REPO") + parser.add_argument("output", type=Path, help="Path of write switcher.json to") + args = parser.parse_args(args) + + # Write the versions file + versions = get_versions(args.root) + write_json(args.output, args.repository, versions) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/.github/workflows/_build_docs.yaml b/.github/workflows/_build_docs.yaml new file mode 100644 index 000000000..f6a32df3d --- /dev/null +++ b/.github/workflows/_build_docs.yaml @@ -0,0 +1,52 @@ +on: + workflow_call: + inputs: + tag: + type: string + description: A tag for the docs artifact + required: true + +jobs: + build_new_docs: + runs-on: ubuntu-latest + steps: + - name: Checkout PtyPy Code + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.13' + check-latest: true + + - name: Setup MPI + uses: mpi4py/setup-mpi@v1 + with: + mpi: mpich + + - name: Install Sphinx + run: pip install sphinx myst_parser + + - name: Install PtyPy + run: pip install .[full] + + - name: Install docs dependencies + run: pip install -r docs/requirements.txt + + - name: Set Path to Sphinx Build + run: echo "SPHINXBUILD=`which sphinx-build`" >> $GITHUB_ENV + + - name: Build Sphinx Documentation + working-directory: docs + run: make html + + - name: Rename Build Directory + run: | + mkdir artifacts + mv docs/_build/html artifacts/${{ inputs.tag }} + + - name: Upload Docs Artifact + uses: actions/upload-artifact@v4.4.3 + with: + name: docs-${{ inputs.tag }} + path: artifacts diff --git a/.github/workflows/_build_legacy_docs.yaml b/.github/workflows/_build_legacy_docs.yaml new file mode 100644 index 000000000..a3a638be4 --- /dev/null +++ b/.github/workflows/_build_legacy_docs.yaml @@ -0,0 +1,61 @@ +on: + workflow_call: + inputs: + tag: + type: string + description: A tag for the docs artifact + required: true + +jobs: + build_legacy_docs: + runs-on: ubuntu-latest + steps: + - name: Checkout PtyPy Code + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.13' + check-latest: true + + - name: Setup MPI + uses: mpi4py/setup-mpi@v1 + with: + mpi: mpich + + - name: Install Sphinx + run: pip install sphinx + + - name: Install PtyPy + run: pip install .[full] + + - name: Prepare Tutorials + working-directory: doc + run: python script2rst.py + + - name: Prepare Templates + working-directory: doc + run: python tmp2rst.py + + - name: Prepare Parameters + working-directory: doc + run: python parameters2rst.py + + - name: Set Path to Sphinx Build + run: echo "SPHINXBUILD=`which sphinx-build`" >> $GITHUB_ENV + + - name: Build Sphinx Documentation + working-directory: doc + run: make html + + - name: Rename Build Directory + run: | + mkdir artifacts + mv doc/build/html artifacts/${{ inputs.tag }} + + - name: Upload Docs Artifact + uses: actions/upload-artifact@v4.4.3 + with: + name: docs-${{ inputs.tag }} + path: artifacts diff --git a/.github/workflows/_github_pages.yaml b/.github/workflows/_github_pages.yaml new file mode 100644 index 000000000..5b71d1654 --- /dev/null +++ b/.github/workflows/_github_pages.yaml @@ -0,0 +1,35 @@ +on: + workflow_call: + +jobs: + publish_pages: + runs-on: ubuntu-latest + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Setup Pages + uses: actions/configure-pages@v5.0.0 + + - name: Download All Docs Artifact + uses: actions/download-artifact@v4.1.8 + with: + pattern: docs-* + merge-multiple: true + path: ./ + + - name: Fix File Permissions for Pages + run: | + chmod -R +rX . + + - name: Upload Merged Artifact to Pages + uses: actions/upload-pages-artifact@v3.0.1 + with: + path: ./ + + - name: Publish Docs to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4.0.5 diff --git a/.github/workflows/_switcher.yaml b/.github/workflows/_switcher.yaml new file mode 100644 index 000000000..44b3fcbc5 --- /dev/null +++ b/.github/workflows/_switcher.yaml @@ -0,0 +1,32 @@ +on: + workflow_call: + +jobs: + version_switcher: + runs-on: ubuntu-latest + steps: + - name: Checkout PtyPy Code + uses: actions/checkout@v4 + + - name: Upload index.html as Artifact + uses: actions/upload-artifact@v4.4.3 + with: + name: docs-index-html + path: .github/pages/index.html + + - name: Download All Docs Artifact + uses: actions/download-artifact@v4.1.8 + with: + pattern: docs-* + merge-multiple: true + path: ./doc_versions + + - name: Create Switcher File + run: python .github/pages/switcher.py ./doc_versions ${{ github.repository }} .github/pages/switcher.json + + - name: Upload switcher.json as Artifact + uses: actions/upload-artifact@v4.4.3 + with: + name: docs-switcher-json + path: .github/pages/switcher.json + \ No newline at end of file diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml new file mode 100644 index 000000000..7cc2ea589 --- /dev/null +++ b/.github/workflows/docs.yaml @@ -0,0 +1,30 @@ +name: Documentation + +on: + push: + branches: + - master + - dev + release: + type: [published] + +jobs: + legacy_docs: + uses: ./.github/workflows/_build_legacy_docs.yaml + with: + tag: legacy + + new_docs: + uses: ./.github/workflows/_build_docs.yaml + with: + tag: ${{ github.ref_name }} + + switcher: + uses: ./.github/workflows/_switcher.yaml + needs: + - legacy_docs + - new_docs + + github_pages: + needs: switcher + uses: ./.github/workflows/_github_pages.yaml diff --git a/.github/workflows/test.yml b/.github/workflows/tests.yaml similarity index 97% rename from .github/workflows/test.yml rename to .github/workflows/tests.yaml index b37bc8c30..0e6bf2ed1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/tests.yaml @@ -6,11 +6,16 @@ on: push: branches: - master + paths-ignore: + - "docs/**" + - ".github/workflows/**" pull_request: branches: - master - dev - hotfixes + paths-ignore: + - "docs/**" # Also trigger on page_build, as well as release created events page_build: release: diff --git a/.gitignore b/.gitignore index 3bebd583d..8e328a81a 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,5 @@ ghostdriver* .ipynb_checkpoints .clang-format pip-wheel-metadata/ +.venv/ +docs/_build diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 000000000..7b78447c4 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,3 @@ +source/reference/generated/ +source/parameters/generated/ +_build/ \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 000000000..92dd33a1a --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 000000000..954237b9b --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,35 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=. +set BUILDDIR=_build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.https://www.sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 000000000..e47016789 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1 @@ +pydata-sphinx-theme diff --git a/docs/source/_param_generator.py b/docs/source/_param_generator.py new file mode 100644 index 000000000..d876018cb --- /dev/null +++ b/docs/source/_param_generator.py @@ -0,0 +1,143 @@ +import os +from ptypy import defaults_tree +from pathlib import Path + +def write_desc_recursive(prst, tree): + for path, desc in tree.children.items(): + print(path) + types = desc.type + default = desc.default + lowlim, uplim = desc.limits + is_wildcard = (desc.name == '*') + + if is_wildcard: + path = path.replace('*', desc.parent.name[:-1] + '_00') + + if path == '': + continue + + if desc.children or desc.is_symlink: + if desc.parent is desc.root: + prst.write('\n' + path + '\n') + prst.write('=' * len(path) + '\n\n') + if desc.parent.parent is desc.root: + prst.write('\n' + path + '\n') + prst.write('-' * len(path) + '\n\n') + + prst.write('.. py:data:: ' + path) + + if desc.is_symlink: + tp = 'Param' + else: + tp = ', '.join([str(t) for t in types]) + prst.write(' (' + tp + ')') + prst.write('\n\n') + + if is_wildcard: + prst.write(' *Wildcard*: multiple entries with arbitrary names are accepted.\n\n') + + # prst.write(' '+desc.help+'\n\n') + prst.write(' ' + desc.help.replace('', '\n').replace('\n', '\n ') + '\n\n') + prst.write(' ' + desc.doc.replace('', '\n').replace('\n', '\n ') + '\n\n') + + if desc.children: + print('recursion ' + path) + prst.write('\n') + write_desc_recursive(prst, desc) + elif desc.is_symlink: + print('following symlink ' + path) + prst.write('\n') + write_desc_recursive(prst, desc.type[0]) + else: + prst.write(' *default* = ``' + repr(default)) + if lowlim is not None and uplim is not None: + prst.write(' (>' + str(lowlim) + ', <' + str(uplim) + ')``\n') + elif lowlim is not None and uplim is None: + prst.write(' (>' + str(lowlim) + ')``\n') + elif lowlim is None and uplim is not None: + prst.write(' (<' + str(uplim) + ')``\n') + else: + prst.write('``\n') + + prst.write('\n') + + +def write_desc_tree(prst, tree): + for path, desc in tree.descendants: + + types = desc.type + default = desc.default + lowlim, uplim = desc.limits + is_wildcard = (desc.name == '*') + + if is_wildcard: + path = path.replace('*', desc.parent.name[:-1] + '_00') + + if path == '': + continue + if desc.children and desc.parent is desc.root: + prst.write('\n' + path + '\n') + prst.write('=' * len(path) + '\n\n') + if desc.children and desc.parent.parent is desc.root: + prst.write('\n' + path + '\n') + prst.write('-' * len(path) + '\n\n') + + prst.write('.. py:data:: ' + path) + + if desc.is_symlink: + tp = 'Param' + else: + tp = ', '.join([str(t) for t in types]) + prst.write(' (' + tp + ')') + prst.write('\n\n') + + if is_wildcard: + prst.write(' *Wildcard*: multiple entries with arbitrary names are accepted.\n\n') + + # prst.write(' '+desc.help+'\n\n') + prst.write(' ' + desc.help.replace('', '\n').replace('\n', '\n ') + '\n\n') + prst.write(' ' + desc.doc.replace('', '\n').replace('\n', '\n ') + '\n\n') + + if desc.is_symlink: + prst.write(' *default* = ' + ':py:data:`' + desc.type[0].path + '`\n') + else: + prst.write(' *default* = ``' + repr(default)) + if lowlim is not None and uplim is not None: + prst.write(' (>' + str(lowlim) + ', <' + str(uplim) + ')``\n') + elif lowlim is not None and uplim is None: + prst.write(' (>' + str(lowlim) + ')``\n') + elif lowlim is None and uplim is not None: + prst.write(' (<' + str(uplim) + ')``\n') + else: + prst.write('``\n') + + prst.write('\n') + +def generate_parameters_rst(root=None, outdir="./parameters/generated/", outfile="params.rst", title=None): + if root is not None: + try: + tree = defaults_tree[root] + except KeyError: + print("Cannot access defaults tree with root at {root}") + return + else: + tree = defaults_tree + + # Create ouput directory if needed + if not os.path.exists(outdir): + os.makedirs(outdir) + + # Title + if title is None: + title = root + + # Make header + title_underline = len(title)*"=" + + header = f"""{title}\n{title_underline}\n""" + + # Write rst file with parameter tree + outpath = Path(outdir, outfile).resolve() + with open(outpath,'w') as prst: + prst.write(header) + write_desc_tree(prst, tree) diff --git a/docs/source/_static/banner.html b/docs/source/_static/banner.html new file mode 100644 index 000000000..de23007f9 --- /dev/null +++ b/docs/source/_static/banner.html @@ -0,0 +1,14 @@ +
+PtyPy is a community project. If you'd like to contribute or participate in one of our workshops, check out our website. +
+
+
+
+

Phase Focus Limited of Sheffield, UK has an international portfolio of patents and pending applications which relate to ptychography. A current list is available here.

+Phase Focus grants royalty free licences of its patent rights for non-commercial academic research use, for reconstruction of simulated data and for reconstruction of data obtained at synchrotrons at X-ray wavelengths. These licenses can be applied for online by clicking on this link.

+Phase Focus asserts that the software we have made available for download may be capable of being used in circumstances which may fall within the claims of one or more of the Phase Focus patents. Phase Focus advises that you apply for a licence from it before downloading any software from this website.

+ +
+
diff --git a/docs/source/_static/logo_100px.png b/docs/source/_static/logo_100px.png new file mode 100644 index 000000000..57059e0cd Binary files /dev/null and b/docs/source/_static/logo_100px.png differ diff --git a/docs/source/_static/ptypy.css b/docs/source/_static/ptypy.css new file mode 100644 index 000000000..7efd31d83 --- /dev/null +++ b/docs/source/_static/ptypy.css @@ -0,0 +1,18 @@ +div.disclaimer { + /* background-color: #2c5d8a; + color: #f9eed0; */ + text-align: left; +} + +/* div.disclaimer a{ + color: #d8b340; +} */ + +html[data-theme="light"] { + --pst-color-border: black; +} + +html[data-theme="dark"] { + --pst-color-border: white; +} + diff --git a/docs/source/_static/ptypyicon.ico b/docs/source/_static/ptypyicon.ico new file mode 100644 index 000000000..daa25d385 Binary files /dev/null and b/docs/source/_static/ptypyicon.ico differ diff --git a/docs/source/_templates/custom-class-template.rst b/docs/source/_templates/custom-class-template.rst new file mode 100644 index 000000000..e7ebd6703 --- /dev/null +++ b/docs/source/_templates/custom-class-template.rst @@ -0,0 +1,32 @@ +{{ fullname | escape | underline}} + +.. currentmodule:: {{ module }} + +.. autoclass:: {{ objname }} + :members: + :inherited-members: + :show-inheritance: + + {% block methods %} + .. automethod:: __init__ + + {% if methods %} + .. rubric:: {{ _('Methods') }} + + .. autosummary:: + {% for item in methods %} + ~{{ name }}.{{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block attributes %} + {% if attributes %} + .. rubric:: {{ _('Attributes') }} + + .. autosummary:: + {% for item in attributes %} + ~{{ name }}.{{ item }} + {%- endfor %} + {% endif %} + {% endblock %} diff --git a/docs/source/_templates/custom-module-template.rst b/docs/source/_templates/custom-module-template.rst new file mode 100644 index 000000000..a726085b9 --- /dev/null +++ b/docs/source/_templates/custom-module-template.rst @@ -0,0 +1,65 @@ +{{ fullname | escape | underline}} + +.. automodule:: {{ fullname }} + + {% block attributes %} + {% if attributes %} + .. rubric:: Module Attributes + + .. autosummary:: + {% for item in attributes %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block functions %} + {% if functions %} + .. rubric:: {{ _('Functions') }} + + .. autosummary:: + :toctree: + {% for item in functions %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block classes %} + {% if classes %} + .. rubric:: {{ _('Classes') }} + + .. autosummary:: + :toctree: + :template: custom-class-template.rst + {% for item in classes %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block exceptions %} + {% if exceptions %} + .. rubric:: {{ _('Exceptions') }} + + .. autosummary:: + :toctree: + {% for item in exceptions %} + {{ item }} + {%- endfor %} + {% endif %} + {% endblock %} + +{% block modules %} +{% if modules %} +.. rubric:: Modules + +.. autosummary:: + :toctree: + :template: custom-module-template.rst + :recursive: +{% for item in modules %} + {{ item }} +{%- endfor %} +{% endif %} +{% endblock %} diff --git a/docs/source/_userguide_generator.py b/docs/source/_userguide_generator.py new file mode 100644 index 000000000..7d62ce185 --- /dev/null +++ b/docs/source/_userguide_generator.py @@ -0,0 +1,252 @@ +import os +import numpy as np +import matplotlib.pyplot as plt +from ptypy import utils as u +from ptypy.core import geometry, illumination, Storage, Container, Ptycho + +# if output directory does not exist, create it +if not os.path.exists("./userguide/generated/"): + os.mkdir("./userguide/generated/") + + + +def create_test_image(outdir="./userguide/generated/", outfile="test.png"): + data = np.random.random((20,2)) + plt.figure() + plt.plot(data[:,0], data[:,1], c='r', marker='>') + plt.tight_layout() + plt.savefig(f'{outdir}{outfile}') + + +def create_all_init_probe_figures(outdir="./userguide/generated/"): + create_one_init_probe_figure(p=make_probe_example_01(), outdir=outdir, outfile="init_probe_example_01.png") + create_one_init_probe_figure(p=make_probe_example_02(), outdir=outdir, outfile="init_probe_example_02.png") + #create_one_init_probe_figure(storage=make_probe_example_11(), outdir=outdir, outfile="init_probe_example_11.png") + create_one_init_probe_figure(p=make_probe_example_21(), outdir=outdir, outfile="init_probe_example_21.png") + create_one_init_probe_figure(p=make_probe_example_22(), outdir=outdir, outfile="init_probe_example_22.png") + create_one_init_probe_figure(p=make_probe_example_23(), outdir=outdir, outfile="init_probe_example_23.png") + create_one_init_probe_figure(p=make_probe_example_31(), outdir=outdir, outfile="init_probe_example_31.png") + create_one_init_probe_figure(p=make_probe_example_32(), outdir=outdir, outfile="init_probe_example_32.png") + create_one_init_probe_figure(p=make_probe_example_33(), outdir=outdir, outfile="init_probe_example_33.png") + create_one_init_probe_figure(p=make_probe_example_41(), outdir=outdir, outfile="init_probe_example_41.png") + create_one_init_probe_figure(p=make_probe_example_42(), outdir=outdir, outfile="init_probe_example_42.png") + create_one_init_probe_figure(p=make_probe_example_43(), outdir=outdir, outfile="init_probe_example_43.png") + create_one_init_probe_figure(p=make_probe_example_44(), outdir=outdir, outfile="init_probe_example_44.png") + create_one_init_probe_figure(p=make_probe_example_51(), outdir=outdir, outfile="init_probe_example_51.png") + create_one_init_probe_figure(p=make_probe_example_52(), outdir=outdir, outfile="init_probe_example_52.png") + create_one_init_probe_figure(p=make_probe_example_53(), outdir=outdir, outfile="init_probe_example_53.png") + create_one_init_probe_figure(p=make_probe_example_54(), outdir=outdir, outfile="init_probe_example_54.png") + create_one_init_probe_figure(p=make_probe_example_61(), outdir=outdir, outfile="init_probe_example_61.png") + create_one_init_probe_figure(p=make_probe_example_62(), outdir=outdir, outfile="init_probe_example_62.png") + create_one_init_probe_figure(p=make_probe_example_63(), outdir=outdir, outfile="init_probe_example_63.png") + + + +def create_one_init_probe_figure(p=None, outdir="./userguide/generated/", outfile="init_probe_test.png"): + G, g = make_geometry() + s = Storage(Container(ID='probe'), shape=(1, g.shape, g.shape), psize=G.resolution) + illumination.init_storage(s, p, energy=g.energy, shape=(g.shape,g. shape)) + extent = [0, np.shape(s.data)[2] * s._psize[1] * 1.e6, 0, np.shape(s.data)[1] * s._psize[0] * 1.e6] + + plt.figure(figsize=(8,3), dpi=100) + + plt.subplot(1,2,1) + plt.title('initlial probe - amplitude') + plt.imshow(np.abs(s.data[0]), interpolation='None', cmap='Greys_r', extent=extent) + plt.colorbar() + plt.xlabel('um') + plt.ylabel('um') + plt.subplot(1,2,2) + plt.title('initlial probe - phase') + plt.imshow(np.angle(s.data[0]), interpolation='None', cmap='hsv', extent=extent, vmin=-np.pi, vmax=np.pi) + plt.colorbar() + plt.xlabel('um') + plt.ylabel('um') + plt.tight_layout() + plt.savefig(f'{outdir}{outfile}') + +def make_geometry(): + g = u.Param() + g.energy = 12.4 # photon energy in keV + g.distance = 3.16 # detector distance in m + g.psize = 75e-6 # detector pixel size in m + g.shape = 128 # size of the probe array + g.propagation = 'farfield' + G = geometry.Geo(owner=None, pars=g) + return G, g + +def make_probe_example_01(): + probe = np.zeros((1, 256,256), dtype=complex) + probe[0, 64:128, 64:128] = 1. * np.exp(1.j * -0.5 * np.pi) + probe[0, 128:192, 32:96] = 2. * np.exp(1.j * 0.5 * np.pi) + p = u.Param() + p.model = probe + p.aperture = u.Param() + p.aperture.form = 'rect' + p.aperture.size = 10e-6 + return p + +def make_probe_example_02(): + probe = np.zeros((1, 256,256), dtype=complex) + probe[0, 64:128, 64:128] = 1. * np.exp(1.j * -0.5 * np.pi) + probe[0, 128:192, 32:96] = 2. * np.exp(1.j * 0.5 * np.pi) + p = u.Param() + p.model = probe + p.aperture = u.Param() + return p + +def make_probe_example_11(): + # ToDo: implenet example with loading a probe from a previous reconstruction + pass + +def make_probe_example_21(): + p = u.Param() + p.model = None + p.aperture = u.Param() + p.aperture.size = 500e-9 # in meters + return p + +def make_probe_example_22(): + p = u.Param() + p.model = None + p.aperture = u.Param() + p.aperture.size = 2000e-9 # in meters + return p + +def make_probe_example_23(): + p = u.Param() + p.model = None + p.aperture = u.Param() + p.aperture.size = (2000e-9, 500e-9) + return p + +def make_probe_example_31(): + p = u.Param() + p.model = None + p.aperture = u.Param() + p.aperture.form = 'circ' # default + p.aperture.size = 2000e-9 + return p + +def make_probe_example_32(): + p = u.Param() + p.model = None + p.aperture = u.Param() + p.aperture.form = 'rect' + p.aperture.size = 2000e-9 + return p + +def make_probe_example_33(): + p = u.Param() + p.model = None + p.aperture = u.Param() + p.aperture.form = 'rect' + p.aperture.size = (500e-9, 2000e-9) + return p + +def make_probe_example_41(): + p = u.Param() + p.model = None + p.aperture = u.Param() + p.aperture.form = 'rect' + p.aperture.size = (500e-9, 2000e-9) + p.aperture.rotate = 0.15 * 3.1415 # angle in radians + return p + +def make_probe_example_42(): + p = u.Param() + p.model = None + p.aperture = u.Param() + p.aperture.form = 'rect' + p.aperture.size = 2000e-9 + p.aperture.central_stop = 0.20 + return p + +def make_probe_example_43(): + p = u.Param() + p.model = None + p.aperture = u.Param() + p.aperture.form = 'rect' + p.aperture.size = 2000e-9 + p.aperture.edge = 20 # in pixels + return p + +def make_probe_example_44(): + p = u.Param() + p.model = None + p.aperture = u.Param() + p.aperture.form = 'rect' + p.aperture.size = 2000e-9 + p.aperture.offset = (500e-9, 1000e-9) # in m + return p + +def make_probe_example_51(): + p = u.Param() + p.model = None + p.aperture = u.Param() + p.aperture.form = 'rect' + p.aperture.size = 2000e-9 + p.aperture.diffuser = (0.5 * 3.1415, 5) + return p + +def make_probe_example_52(): + p = u.Param() + p.model = None + p.aperture = u.Param() + p.aperture.form = 'rect' + p.aperture.size = 2000e-9 + p.aperture.diffuser = (1 * 3.1415, 2) + return p + +def make_probe_example_53(): + p = u.Param() + p.model = None + p.aperture = u.Param() + p.aperture.form = 'rect' + p.aperture.size = 2000e-9 + p.aperture.diffuser = (0 * 3.1415, 0 , 0.7, 5) + return p + +def make_probe_example_54(): + p = u.Param() + p.model = None + p.aperture = u.Param() + p.aperture.form = 'rect' + p.aperture.size = 2000e-9 + p.aperture.diffuser = (0.5 * 3.1415, 10 , 0.7, 5) + return p + +def make_probe_example_61(): + p = u.Param() + p.model = None + p.aperture = u.Param() + p.aperture.form = 'circ' + p.aperture.size = 1e-6 + p.propagation = u.Param() + p.propagation.parallel = 1e-3 # distance in m + return p + +def make_probe_example_62(): + p = u.Param() + p.model = None + p.aperture = u.Param() + p.aperture.form = 'rect' + p.aperture.size = 525e-6 # aperture diameter of the KB mirrors + p.propagation = u.Param() + p.propagation.focussed = 0.200 # focal length of the KB mirror(s) + p.propagation.parallel = 500e-6 # distance sample to focus + p.propagation.antialiasing = 1 + return p + +def make_probe_example_63(): + p = u.Param() + p.model = None + p.aperture = u.Param() + p.aperture.form = 'circ' + p.aperture.size = 100e-6 # aperture diameter of the FZP + p.aperture.central_stop = 25e-6 / p.aperture.size + p.propagation = u.Param() + p.propagation.focussed = 0.18 # focal length of FZP + #p.propagation.parallel = 100e-6 # distance sample to focus + p.propagation.antialiasing = 1 + return p diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 000000000..45c292a08 --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,158 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +import sys +from pathlib import Path + +sys.path.insert(0, str(Path('../..', 'ptypy').resolve())) +sys.path.insert(0, str(Path(__file__).parent.resolve())) +from _param_generator import generate_parameters_rst + +# Generate List of Parameters +#generate_parameters_rst("ptycho", outfile="ptycho.rst", title="Root/Ptycho (p)") +generate_parameters_rst("io", outfile="io.rst", title="Input/Output (p.io)") +generate_parameters_rst("scans", outfile="scans.rst", title="List of Scans (p.scans)") +generate_parameters_rst("scan", outfile="scan.rst", title="Scan Definition (p.scans.scan_00)") +generate_parameters_rst("scandata", outfile="scandata.rst", title="Scan Data Definition (p.scans.scan_00.data)") +generate_parameters_rst("engines", outfile="engines.rst", title="List of Engines (p.engines)") +generate_parameters_rst("engine", outfile="engine.rst", title="Engine Definition (p.engines.engine_00)") + +# Generate images for user guide +from _userguide_generator import create_test_image +create_test_image(outdir="./userguide/generated/", outfile="test.png") +from _userguide_generator import create_all_init_probe_figures +create_all_init_probe_figures(outdir="./userguide/generated/") + + + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +project = 'PtyPy' +copyright = '2024, Pierre Thibault, Bjoern Enders, Benedikt Daurer and others' + +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.autosummary', + 'sphinx.ext.doctest', + 'sphinx.ext.extlinks', + 'sphinx.ext.intersphinx', + 'sphinx.ext.mathjax', + 'sphinx.ext.napoleon', + 'sphinx.ext.todo', + 'myst_parser', +] + +templates_path = ['_templates'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +rst_epilog = """ +.. |ptypy| replace:: PtyPy +.. _ptypy: https://www.github.com/ptycho/ptypy +""" + +autosummary_generate = True +autodoc_mock_imports = ["cupy", "pycuda", "reikna", "hdf5plugin", "bitshuffle", "fabio", "swmr_tools"] + +todo_include_todos = True + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +html_theme = 'pydata_sphinx_theme' +html_static_path = ['_static'] +html_css_files = ["ptypy.css"] +html_logo = '_static/logo_100px.png' +html_favicon = '_static/ptypyicon.ico' +html_show_sourcelink = False +html_sidebars = { + 'overview': [] + } + +html_theme_options = { + "icon_links": [ + { + "name": "GitHub", + "url": "https://github.com/ptycho/ptypy", + "icon": "fab fa-github-square", + }, + { + "name": "ptypy.org", + "url": "https://ptypy.org/", + "icon": "fa-solid fa-link ", + }, + ], + "switcher": { + "json_url": "https://daurer.github.io/ptypy-new-docs/switcher.json", + "version_match": "master", + }, + "navbar_start": ["navbar-logo", "version-switcher"] +} + + +# -- Custom functions ---------------------------------------------------- + +def truncate_docstring(app, what, name, obj, options, lines): + """ + Remove the Default parameter entries. + """ + if not hasattr(obj, 'DEFAULT'): + return + if any(l.strip().startswith('Defaults:') for l in lines): + while True: + if lines.pop(-1).strip().startswith('Defaults:'): + break + + +def remove_mod_docstring(app, what, name, obj, options, lines): + from ptypy import utils as u + from ptypy import defaults_tree + u.verbose.report.headernewline='\n\n' + searchstr = ':py:data:' + + def get_refs(dct, pd, depth=2, indent=''): + if depth < 0: + return + + for k, value in dct.items(): + ref = ', see :py:data:`~%s`' % pd.children[k].entry_point if k in pd.children else '' + if hasattr(value, 'items'): + v = str(value.__class__.__name__) + elif str(value) == value: + v = '"%s"' % value + else: + v = str(value) + + lines.append(indent + '* *' + k + '* = ``' + v + '``' + ref) + + if hasattr(value, 'items'): + lines.append("") + get_refs(value, pd.children[k], depth=depth-1, indent=indent+' ') + lines.append("") + + if isinstance(obj, u.Param) or isinstance(obj, dict): + pd = None + + for l in lines: + start = l.find(searchstr) + if start > -1: + newstr = l[start:] + newstr = newstr.split('`')[1] + newstr = newstr.replace('~', '') + pd = defaults_tree.get(newstr) + break + + if pd is not None: + get_refs(obj, pd, depth=2, indent='') + + +def setup(app): + print("Custom setup") + app.connect('autodoc-process-docstring', remove_mod_docstring) + app.connect('autodoc-process-docstring', truncate_docstring) + pass diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 000000000..77fb61a11 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,34 @@ +Quicklinks +---------- + +* | Starting from a **clean slate**? + | Check out the :ref:`installation instructions ` + +* | You want to understand the **inner principles** of ptypy without + having to browse the source code? + | Have a look at the :ref:`tutorials about its special classes `. + +* | Only interested in |ptypy|'s **data file structure** and + **management**? + | Indulge yourself :ref:`here` for the structure and + :ref:`here` for the concepts. + +PtyPy Documentation Contents +============================ + +.. toctree:: + :maxdepth: 2 + + overview + installation + userguide/index + parameters/index + reference/index + + +Indices and Tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/source/overview.rst b/docs/source/overview.rst new file mode 100644 index 000000000..5acee4347 --- /dev/null +++ b/docs/source/overview.rst @@ -0,0 +1,91 @@ +Overview +======== + +|ptypy| [#Enders2016]_ is a +framework for scientific ptychography compiled by +P.Thibault, B. Enders, and others (see AUTHORS). + +It is the result of years of experience in the field of ptychography condensed +into a versatile python package. The package covers the whole path of +ptychographic analysis after the actual experiment is completed +- from data management to reconstruction to visualization. + +The main idea of ptypy is: *"Flexibility and Scalabality through abstraction"*. +Most often, you will find a class for every concept of ptychography in +|ptypy|. Using these or other more abstract base classes, new ideas +may be developed in a rapid manner without the cumbersome overhead of +:py:mod:`data` management +, memory access or :py:mod:`distributed ` computing. Additionally, |ptypy| +provides a rich set of :py:mod:`utilities ` and helper functions, +especially for :py:mod:`input/output ` + +Get started quickly :ref:`here ` or with one of the examples in the ``templates`` directory. + + +Highlights +---------- + +* **Difference Map** [#dm]_ algorithm engine with power bound constraint [#power]_. +* **Maximum Likelihood** [#ml]_ engine with preconditioners and regularizers. +* A few more engines (RAAR, sDR, ePIE, ...). + +* **Fully parallelized** using the Massage Passing Interface + (`MPI `_). + Simply execute your script with:: + + $ mpiexec/mpirun -n [nodes] python .py + +* **GPU acceleration** based on custom kernels, CuPy or PyCUDA/reikna. + See examples in ``templates/accelerate``, ``templates/engines/cupy`` and ``templates/engines/pycuda``. + +* A **client-server** approach for visualization and control based on + `ZeroMQ `_ . + The reconstruction may run on a remote hpc cluster while your desktop + computer displays the reconstruction progress. + + +* **Mixed-state** reconstructions of probe and object [#Thi2013]_ for + overcoming partial coherence or related phenomena. + +* **On-the-fly** reconstructions (while data is being acquired) using the + the :py:class:`PtyScan` class in the linking mode :ref:`linking mode` + + +Quicklinks +---------- +* | The complete :ref:`documentation `. + +* | Starting from a **clean slate**? + | Check out the :ref:`installation instructions ` + +* | You want to understand the **inner principles** of ptypy without + having to browse the source code? + | Have a look at the :ref:`tutorials about its special classes `. + +* | Only interested in |ptypy|'s **data file structure** and + **management**? + | Indulge yourself :ref:`here` for the structure and + :ref:`here` for the concepts. + + +.. rubric:: Footnotes + +.. [#Enders2016] B.Enders and P.Thibault, **Proc. R. Soc. A** 472, 20160640 (2016), `doi `__ + +.. [#Thi2013] P.Thibault and A.Menzel, **Nature** 494, 68 (2013), `doi `__ + +.. [#ml] P.Thibault and M.Guizar-Sicairos, **New J. of Phys. 14**, 6 (2012), `doi `__ + +.. [#dm] P.Thibault, M.Dierolf *et al.*, **Ultramicroscopy 109**, 4 (2009), `doi `__ + +.. [#power] K.Giewekemeyer *et al.*, **PNAS 108**, 2 (2007), `suppl. material `__, `doi `__ + + +.. + .. include:: ../README.rst + :start-line: 26 + + +.. note:: | Phase Focus Limited of Sheffield, UK has an international portfolio of patents and pending applications which relate to ptychography. A current list is available `here `_. + | Phase Focus grants royalty free licences of its patent rights for non-commercial academic research use, for reconstruction of simulated data and for reconstruction of data obtained at synchrotrons at X-ray wavelengths. These licenses can be applied for online by clicking on this `link `_. + | Phase Focus asserts that the software we have made available for download may be capable of being used in circumstances which may fall within the claims of one or more of the Phase Focus patents. Phase Focus advises that you apply for a licence from it before downloading any software from this website. diff --git a/docs/source/parameters/index.rst b/docs/source/parameters/index.rst new file mode 100644 index 000000000..332e79912 --- /dev/null +++ b/docs/source/parameters/index.rst @@ -0,0 +1,18 @@ +Parameters +========== + +.. todo:: Add short explanation of the parameter tree, possible with a simple graph. + +Parameter definitions +--------------------- + +.. toctree:: + :maxdepth: 1 + + generated/ptycho + generated/io + generated/scans + generated/scan + generated/scandata + generated/engines + generated/engine diff --git a/docs/source/reference/core.rst b/docs/source/reference/core.rst new file mode 100644 index 000000000..8843eaca9 --- /dev/null +++ b/docs/source/reference/core.rst @@ -0,0 +1,40 @@ +Core Functionalities +==================== + +Core Classes +------------ + +.. autosummary:: + :toctree: generated/ + :template: custom-module-template.rst + :recursive: + + ptypy.core.classes + ptypy.core.ptycho + +Data Management +--------------- + +.. autosummary:: + :toctree: generated/ + :template: custom-module-template.rst + :recursive: + + ptypy.core.data + ptypy.core.manager + ptypy.core.save_load + ptypy.core.paths + +Physics +------- + +.. autosummary:: + :toctree: generated/ + :template: custom-module-template.rst + :recursive: + + ptypy.core.geometry + ptypy.core.illumination + ptypy.core.sample + ptypy.core.xy + diff --git a/docs/source/reference/engines.rst b/docs/source/reference/engines.rst new file mode 100644 index 000000000..702c069c7 --- /dev/null +++ b/docs/source/reference/engines.rst @@ -0,0 +1,43 @@ +Reconstruction Engines +====================== + +Base Classes and Utilities +-------------------------- + +.. autosummary:: + :toctree: generated/ + :template: custom-module-template.rst + :recursive: + + ptypy.engines.base + ptypy.engines.utils + ptypy.engines.posref + +Core Engines +------------ + +.. autosummary:: + :toctree: generated/ + :template: custom-module-template.rst + :recursive: + + ptypy.engines.projectional + ptypy.engines.stochastic + ptypy.engines.ML + +Additional (custom) Engines +--------------------------- + +.. autosummary:: + :toctree: generated/ + :template: custom-module-template.rst + :recursive: + + ptypy.engines.Bragg3d_engines + ptypy.custom.DMOPR + ptypy.custom.MLOPR + ptypy.custom.DM_object_regul + ptypy.custom.WASP + ptypy.custom.ePIE_parallel + ptypy.custom.threepie + diff --git a/docs/source/reference/experiment.rst b/docs/source/reference/experiment.rst new file mode 100644 index 000000000..48b161231 --- /dev/null +++ b/docs/source/reference/experiment.rst @@ -0,0 +1,57 @@ +Data Loaders +============ + +Diamond Light Source Loaders +---------------------------- + +.. autosummary:: + :toctree: generated/ + :template: custom-module-template.rst + :recursive: + + ptypy.experiment.hdf5_loader + ptypy.experiment.swmr_loader + ptypy.experiment.diamond_nexus + ptypy.experiment.diamond_streaming + ptypy.experiment.epsic_loader + +Max IV Loaders +-------------- + +.. autosummary:: + :toctree: generated/ + :template: custom-module-template.rst + :recursive: + + ptypy.experiment.nanomax + ptypy.experiment.nanomax3d + ptypy.experiment.nanomax_streaming + +Other Synchrotron/FEL Loaders +----------------------------- + +.. autosummary:: + :toctree: generated/ + :template: custom-module-template.rst + :recursive: + + ptypy.experiment.ALS_5321 + ptypy.experiment.AMO_LCLS + ptypy.experiment.DiProI_FERMI + ptypy.experiment.ID16Anfp + ptypy.experiment.cSAXS + ptypy.experiment.spec + +Miscellaneous Loaders +--------------------- + +.. autosummary:: + :toctree: generated/ + :template: custom-module-template.rst + :recursive: + + ptypy.experiment.UCL + ptypy.experiment.optiklabor + ptypy.experiment.plugin + ptypy.experiment.savu + ptypy.experiment.spec diff --git a/docs/source/reference/index.rst b/docs/source/reference/index.rst new file mode 100644 index 000000000..7b26d79e1 --- /dev/null +++ b/docs/source/reference/index.rst @@ -0,0 +1,22 @@ +API Reference +============= + +Modules +------- + +.. toctree:: + :maxdepth: 2 + + core + engines + experiment + io + utils + +Module contents +--------------- + +.. automodule:: ptypy + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/source/reference/io.rst b/docs/source/reference/io.rst new file mode 100644 index 000000000..b4cf40678 --- /dev/null +++ b/docs/source/reference/io.rst @@ -0,0 +1,27 @@ +Input/Output +============ + +Read/Write with Files +--------------------- + +.. autosummary:: + :toctree: generated/ + :template: custom-module-template.rst + :recursive: + + ptypy.io.edfIO + ptypy.io.h5rw + ptypy.io.json_rw + ptypy.io.rawIO + ptypy.io.imageIO + ptypy.io.image_read + +Client/Server Interaction +------------------------- + +.. autosummary:: + :toctree: generated/ + :template: custom-module-template.rst + :recursive: + + ptypy.io.interaction diff --git a/docs/source/reference/utils.rst b/docs/source/reference/utils.rst new file mode 100644 index 000000000..51e066647 --- /dev/null +++ b/docs/source/reference/utils.rst @@ -0,0 +1,54 @@ +Utilities +========= + +General Utilities +----------------- + +.. autosummary:: + :toctree: generated/ + :template: custom-module-template.rst + :recursive: + + ptypy.utils.array_utils + ptypy.utils.math_utils + ptypy.utils.misc + ptypy.utils.parallel + ptypy.utils.parameters + ptypy.utils.plot_client + ptypy.utils.plot_utils + ptypy.utils.scripts + ptypy.utils.descriptor + ptypy.utils.verbose + +Debugging Utilities +------------------- + +.. autosummary:: + :toctree: generated/ + :template: custom-module-template.rst + :recursive: + + ptypy.debug.embedded_shell + ptypy.debug.ipython_kernel + +Simulation Utilities +-------------------- + +.. autosummary:: + :toctree: generated/ + :template: custom-module-template.rst + :recursive: + + ptypy.simulations.detector + ptypy.simulations.ptysim_utils + ptypy.simulations.simscan + +Resources +--------- + +.. autosummary:: + :toctree: generated/ + :template: custom-module-template.rst + :recursive: + + ptypy.resources diff --git a/docs/source/userguide/index.md b/docs/source/userguide/index.md new file mode 100644 index 000000000..c6aa9086d --- /dev/null +++ b/docs/source/userguide/index.md @@ -0,0 +1,11 @@ +# User guide + + +```{toctree} +--- +maxdepth: 1 +--- +setting_probe_init.md +``` + + diff --git a/docs/source/userguide/setting_probe_init.md b/docs/source/userguide/setting_probe_init.md new file mode 100644 index 000000000..34a967860 --- /dev/null +++ b/docs/source/userguide/setting_probe_init.md @@ -0,0 +1,366 @@ +# setting the initial probe + +Starting a ptychography reconstruction with a good or bad initial estimate of the probe(s) can change the convergence speed of the reconstruction or even influence if the reconstruction converges at all or fails. +The closer the initial estimate is to the real probing wavefront on the sample, the better. +Hence it is important to know what kind of beam is expected on the sample. + +* What size? +* What shape? +* What phase profile? + +Having knowledge about the optics used to focus the beam, their parameters and what kind of focus they create and where the sample was roughly positioned with respect to the focus are important things to consider when choosing how to initialize the probe estimate(s) for a ptychographic reconstruction. +This true for all reconstruction algorithms (engines). + +```ptypy``` allows to initialize the probe estimate prior to the first iteration in various ways. +One way is simply using an arbitrary numpy array of the right size that the user build by whatever python capabilities she/he has. +Results from previous reconstructions can also be loaded. +The initial probe estimate can also be made from basic geometric shapes that can also be modified in various ways. + +## init by any numpy array + +If you know some numpy, you are able to create any probe estimate you like. +Just create a three-dimensional array where the first dimension is simply as long as the number of probe modes (in the most simple case =1) and the other two dimensions match the size of the 2D probe array depending on the cropping and binning. +Then give that array you created into the parameter tree as 'illumination.model'. + +```python +import numpy as np +probe = np.zeros((1, 256,256), dtype=complex) +# make sure to init as as complex otherwise the imaginary part will be discarded +probe[0, 64:128, 64:128] = 1. * np.exp(1.j * -0.5 * np.pi) +probe[0, 128:192, 32:96] = 2. * np.exp(1.j * 0.5 * np.pi) + +p.scans.scan00.illumination = u.Param() +p.scans.scan00.illumination.model = probe +# here we just give the numpy array we made above +p.scans.scan00.illumination.aperture = u.Param() +p.scans.scan00.illumination.aperture.form = 'rect' +# this aperture is not optional +p.scans.scan00.illumination.aperture.size = 10e-6 +# either make it very large, or you will cut down the probe +``` + +![init probe from numpy array](generated/init_probe_example_01.png) + + +Warning: not defining the aperture, will still apply a default aperture (round and about a third of the array size) and this cut down the probe you made. So define a large enough aperture. + +```python +import numpy as np +probe = np.zeros((1, 256,256), dtype=complex) +probe[0, 64:128, 64:128] = 1. * np.exp(1.j * -0.5 * np.pi) +probe[0, 128:192, 32:96] = 2. * np.exp(1.j * 0.5 * np.pi) + +p.scans.scan00.illumination = u.Param() +p.scans.scan00.illumination.model = probe +p.scans.scan00.illumination.aperture = u.Param() +``` + +![init probe from numpy array](generated/init_probe_example_02.png) + +## init by loading a previous reconstructions + +By setting 'illumination.model' to 'recon' one can load the probe of a previous reconstruction by giving the the relative or absolute file path of a previous reconstruction (the .ptyr file). + +```python +p.scans.scan00.illumination = u.Param() +p.scans.scan00.illumination.model = 'recon' +p.scans.scan00.illumination.recon = u.Param() +p.scans.scan00.illumination.recon.rfile = '/data//rec_24_ML_1000.ptyr' +p.scans.scan00.illumination.aperture = u.Param() +p.scans.scan00.illumination.aperture.form = 'rect' +# same thing with the needed aperture +p.scans.scan00.illumination.aperture.size = 10e-6 +``` + +[//]: # (ToDo: put a real probe somewhere to download and create a figure from it) +[//]: # (![init probe from a previous reconstruction](generated/init_probe_example_11.png)) + +Warning: When loading a probe from a previous reconstruction, things like pixel size, photon energy ect are all ignored. +The probe is simply loaded as the numpy array and used that way, pixel by pixel. +If the probe you load has fewer pixels than the probe your reconstruction calls for, the missing pixels will be padded on with 0. +Likewise, the other way around, the too large input array will simply be cropped to the smaller size. + +Again, if no aperture is defined, the default aperture (circle with a third of the array size as a diameter) is applied and might cut down the probe that you loaded. + +## init by geometric base shapes (illumination.aperture) + +Besides making a probe yourself or loading a previous reconstruction, it is also possible to define inital probe estimate(s) using simple geometric shapes with the 'illumination.aperture' parameter in the parameter tree. + +### illumination.aperture.size + +One important parameter is the size of the initial probe estimate. In ptypy this parameter is given in meters. Here an example for a 500nm sized probe. + +```python +p.scans.scan00.illumination = u.Param() +p.scans.scan00.illumination.model = None +p.scans.scan00.illumination.aperture = u.Param() +p.scans.scan00.illumination.aperture.size = 500e-9 # in meters +``` + +![init probe from base shapes](generated/init_probe_example_21.png) + +As expected, a larger value in 'aperture.size' will result in a larger probe estimate: + +```python +p.scans.scan00.illumination = u.Param() +p.scans.scan00.illumination.model = None +p.scans.scan00.illumination.aperture = u.Param() +p.scans.scan00.illumination.aperture.size = 2000e-9 # in meters +``` + +![init probe from base shapes](generated/init_probe_example_22.png) + +When giving a two-element list/tuple the two values are applied to the vertical and horizontal (python like, y before x) direction respectively. +This allows to create asymmetric probes: + +```python +p.scans.scan00.illumination = u.Param() +p.scans.scan00.illumination.model = None +p.scans.scan00.illumination.aperture = u.Param() +p.scans.scan00.illumination.aperture.size = (2000e-9, 500e-9) +# python like y first, then x +``` + +![init probe from base shapes](generated/init_probe_example_23.png) + +### illumination.aperture.form + +Of course the shape can also be changed. +This can be achieved by changing the 'aperture.form' parameter. +As we have seen in the previous example, a simple circle is the default setting: + +```python +p.scans.scan00.illumination = u.Param() +p.scans.scan00.illumination.model = None +p.scans.scan00.illumination.aperture = u.Param() +p.scans.scan00.illumination.aperture.form = 'circ' # default +p.scans.scan00.illumination.aperture.size = 2000e-9 +``` +![init probe from base shapes](generated/init_probe_example_31.png) + +The other option is 'rect' for rectangle. +This can of course also be used to create squares: + +```python +p.scans.scan00.illumination = u.Param() +p.scans.scan00.illumination.model = None +p.scans.scan00.illumination.aperture = u.Param() +p.scans.scan00.illumination.aperture.form = 'rect' +p.scans.scan00.illumination.aperture.size = 2000e-9 +``` + +![init probe from base shapes](generated/init_probe_example_32.png) + +But it can also be used for rectangles when giving two different numbers on the 'aperture.size' parameter. + +```python +p.scans.scan00.illumination = u.Param() +p.scans.scan00.illumination.model = None +p.scans.scan00.illumination.aperture = u.Param() +p.scans.scan00.illumination.aperture.form = 'rect' +p.scans.scan00.illumination.aperture.size = (500e-9, 2000e-9) +``` + +![init probe from base shapes](generated/init_probe_example_33.png) + +### illumination.aperture.rotate + +Any probe estimate created from a basic aperture can also be rotated. +The rotation is set via 'aperture.rotate' and is given in radians. + +```python +p.scans.scan00.illumination = u.Param() +p.scans.scan00.illumination.model = None +p.scans.scan00.illumination.aperture = u.Param() +p.scans.scan00.illumination.aperture.form = 'rect' +p.scans.scan00.illumination.aperture.size = (500e-9, 2000e-9) +p.scans.scan00.illumination.aperture.rotate = 0.15 * 3.1415 # angle in radians +``` + +![init probe from base shapes](generated/init_probe_example_41.png) + +### illumination.aperture.central_stop + +To account for illuminations from Fresnel zone plates, it is also possible to add a central stop. +Basically an aperture inside the aperture. +It has the same shape as the defined aperture. +Only its size can be defined as a relative fraction of the aperture size. +This way one can cut out a center bit of the created aperture: + +```python +p.scans.scan00.illumination = u.Param() +p.scans.scan00.illumination.model = None +p.scans.scan00.illumination.aperture = u.Param() +p.scans.scan00.illumination.aperture.form = 'rect' +p.scans.scan00.illumination.aperture.size = 2000e-9 +p.scans.scan00.illumination.aperture.central_stop = 0.20 +# relative fraction of the aperture size +``` + +![init probe from base shapes](generated/init_probe_example_42.png) + +### illumination.aperture.edge + +The edges of the probe estimates created via apertures can be softened. +The extend of this soft edge is given in pixels in the 'aperture.edge' parameter. +By default is set to 2 pixels. +Larger values can be used to make large fuzzy edges: + +```python +p.scans.scan00.illumination = u.Param() +p.scans.scan00.illumination.model = None +p.scans.scan00.illumination.aperture = u.Param() +p.scans.scan00.illumination.aperture.form = 'rect' +p.scans.scan00.illumination.aperture.size = 2000e-9 +p.scans.scan00.illumination.aperture.edge = 20 # in pixels +``` + +![init probe from base shapes](generated/init_probe_example_43.png) + +### illumination.aperture.offset + +So far all examples have been centered in the probe array. +It is also possible to put place the aperture somewhere else using the 'aperture.offset' parameter. +It is also given as a tuple (y-offset, x-offset) in meters: + +```python +p.scans.scan00.illumination = u.Param() +p.scans.scan00.illumination.model = None +p.scans.scan00.illumination.aperture = u.Param() +p.scans.scan00.illumination.aperture.form = 'rect' +p.scans.scan00.illumination.aperture.size = 2000e-9 +p.scans.scan00.illumination.aperture.offset = (500e-9, 1000e-9) # in m +``` + +![init probe from base shapes](generated/init_probe_example_44.png) + +### illumination.aperture.diffuser + +Up to now all probe estimates created using apertures show a flat amplitude profile and flat phase profile. +The 'aperture.diffusor' allows to add noise to either the amplitude profile, phase profile or both. +By default it is set to None, which creates these flat profiles + +The 'aperture.diffusor' is a tuple. +Defining a tuple with two elements allows to define a variation of (only) the phase profile. +The first number is the amplitude (rms) of the phase variants in radians and the second parameter is the minimum feature size in pixels: + +```python +p.scans.scan00.illumination = u.Param() +p.scans.scan00.illumination.model = None +p.scans.scan00.illumination.aperture = u.Param() +p.scans.scan00.illumination.aperture.form = 'rect' +p.scans.scan00.illumination.aperture.size = 2000e-9 +p.scans.scan00.illumination.aperture.diffuser = (0.5 * 3.1415, 5) +# noise in phase (amplitude (rms), minimum feature size) in radian +``` + +![init probe from base shapes](generated/init_probe_example_51.png) + +Larger amplitudes of the phase profile variation will create larger differences between the mountains and the valleys in the phase profile. +The minimum feature size defines the extend of the noise spots created: + +```python +p.scans.scan00.illumination = u.Param() +p.scans.scan00.illumination.model = None +p.scans.scan00.illumination.aperture = u.Param() +p.scans.scan00.illumination.aperture.form = 'rect' +p.scans.scan00.illumination.aperture.size = 2000e-9 +p.scans.scan00.illumination.aperture.diffuser = (1 * 3.1415, 2) +``` + +![init probe from base shapes](generated/init_probe_example_52.png) + +Giving the 'aperture.diffusor' two more entries allows to also add noise to the amplitude as well using the same syntax. +Setting the first two entries to zero allows to vary the amplitude, but keeping a flat phase. + +```python +p.scans.scan00.illumination = u.Param() +p.scans.scan00.illumination.model = None +p.scans.scan00.illumination.aperture = u.Param() +p.scans.scan00.illumination.aperture.form = 'rect' +p.scans.scan00.illumination.aperture.size = 2000e-9 +p.scans.scan00.illumination.aperture.diffuser = (0 * 3.1415, 0 , 0.7, 5) +# (zero) noise in phase and amplitude (rms_ph,mfs_ph,rms_mod) +``` + +![init probe from base shapes](generated/init_probe_example_53.png) + +Of course noise can be added to both amplitude and phase separately with different strength and size. + +```python +p.scans.scan00.illumination = u.Param() +p.scans.scan00.illumination.model = None +p.scans.scan00.illumination.aperture = u.Param() +p.scans.scan00.illumination.aperture.form = 'rect' +p.scans.scan00.illumination.aperture.size = 2000e-9 +p.scans.scan00.illumination.aperture.diffuser = (0.5 * 3.1415, 10 , 0.7, 5) +# (rms_ph,mfs_ph,rms_mod,mfs_mod) +``` + +![init probe from base shapes](generated/init_probe_example_54.png) + + +## propagation +A very powerful feature is the capability to propagate a probe estimate. +This works for probes given as numpy arrays, for loaded probes and also for probes defined as apertures. +This feature allows to give the initial probe estimate the right phase curvature, to kick the reconstruction in the right way. + +### illumination.propagation.parallel + +The first option to propagate is the the 'parallel' propagation. +This nearfield propagataion is usful when describing the wavefront very close to the sample plane. +In the following example a 1um pinhole is illumanted by a large flat (phase and amplitude) beam and placed 1mm upstream of the sample: + +```python +p.scans.scan00.illumination = u.Param() +p.scans.scan00.illumination.model = probe +p.scans.scan00.illumination.aperture = u.Param() +p.scans.scan00.illumination.aperture.form = 'circ' +p.scans.scan00.illumination.aperture.size = 1e-6 +p.scans.scan00.illumination.propagation = u.Param() +p.scans.scan00.illumination.propagation.parallel = 1e-3 +``` + +![init probe from base shapes](generated/init_probe_example_61.png) + +### illumination.propagation.focussed + +The second option to propagate is the the 'focussed' propagation. +This farfield propagataion is usful when describing the wavefront far away from the sample plane. + +In the following example a pair of KB mirrors is illumanted by a large flat (phase and amplitude) beam. +The mirrors have an idential NA (aperture of 525um and focal length of 200mm). +The sample is however not placed in focus, but 500um downstream of the focus. + +```python +p.scans.scan00.illumination = u.Param() +p.scans.scan00.illumination.model = probe +p.scans.scan00.illumination.aperture = u.Param() +p.scans.scan00.illumination.aperture.size = 525e-6 # aperture diameter +p.scans.scan00.illumination.propagation = u.Param() +p.scans.scan00.illumination.propagation.focussed = 0.200 # focal length +p.scans.scan00.illumination.propagation.parallel = 500e-6 # dist: sample<->focus +p.scans.scan00.illumination.propagation.antialiasing = 1 +``` + +![init probe from base shapes](generated/init_probe_example_62.png) + +In the next example an Fresnel Zone Plane (FZP) is illumanted by a large flat (phase and amplitude) beam. +The FZP has a diameter of 100um and focal length of 18mm). +A central stop of 25um was used. +The sample is again not placed in focus, but 500um downstream of the focus. + +```python +p.scans.scan00.illumination = u.Param() +p.scans.scan00.illumination.model = None +p.scans.scan00.illumination.aperture = u.Param() +p.scans.scan00.illumination.aperture.form = 'circ' +p.scans.scan00.illumination.aperture.size = 100e-6 # aperture diameter of the FZP +p.scans.scan00.illumination.aperture.central_stop = 25e-6 / p.aperture.size +p.scans.scan00.illumination.propagation = u.Param() +p.scans.scan00.illumination.propagation.focussed = 0.18 # focal length of FZP +p.scans.scan00.illumination.propagation.parallel = 100e-6 # distance sample to focus +p.scans.scan00.illumination.propagation.antialiasing = 1 +``` + +![init probe from base shapes](generated/init_probe_example_63.png) \ No newline at end of file