diff --git a/.gitignore b/.gitignore
index ff67362..5416535 100644
--- a/.gitignore
+++ b/.gitignore
@@ -57,7 +57,7 @@ cover/
# Sphinx documentation
docs/_build/
-docs/generated
+docs/ref/generated
# PyBuilder
.pybuilder/
diff --git a/docs/_img/python/focus_images.py b/docs/_img/python/focus_images.py
deleted file mode 100644
index cceac3c..0000000
--- a/docs/_img/python/focus_images.py
+++ /dev/null
@@ -1,60 +0,0 @@
-import matplotlib.pyplot as plt
-import numpy as np
-import lentil
-
-import matplotlib as mpl
-mpl.rcParams['figure.figsize'] = (4, 4)
-
-amp = np.zeros((256, 256))
-amp += lentil.circlemask((256, 256), 64, shift=(32, 8))
-amp -= lentil.circlemask((256, 256), 40, shift=(32, 8))
-amp[:, 0:128+8] = 0
-amp[32:225, 104-16:128-16] = 1
-amp[96:120, 128-16:144-8] = 1
-amp[201:225, 128-16:144-8] = 1
-
-focus = lentil.zernike(mask=np.ones((256, 256)), index=4)
-
-pupil_neg = lentil.Pupil(amplitude=amp, pixelscale=1/240, focal_length=10)
-pupil_neg.phase = -6e-6 * focus
-w_neg = lentil.Wavefront(650e-9)
-w_neg *= pupil_neg
-w_neg = lentil.propagate_dft(w_neg, pixelscale=5e-6, shape=200, oversample=2)
-
-pupil_pos = lentil.Pupil(amplitude=amp, pixelscale=1/240, focal_length=10)
-pupil_pos.phase = 6e-6 * focus
-w_pos = lentil.Wavefront(650e-9)
-w_pos *= pupil_pos
-w_pos = lentil.propagate_dft(w_pos, pixelscale=5e-6, shape=200, oversample=2)
-
-plt.subplot(1, 2, 1)
-plt.imshow(w_pos.intensity, origin='lower')
-plt.title('Image plane (+focus)')
-plt.xticks(np.linspace(0, 400, 5), labels=np.linspace(-1, 1, 5))
-plt.yticks(np.linspace(0, 400, 5), labels=np.linspace(-1, 1, 5))
-plt.xlabel('[m]')
-
-plt.subplot(1, 2, 2)
-plt.imshow(w_neg.intensity, origin='lower')
-plt.title('Image plane (-focus)')
-plt.xticks(np.linspace(0, 400, 5), labels=np.linspace(-1, 1, 5))
-plt.yticks(np.linspace(0, 400, 5), labels=np.linspace(-1, 1, 5))
-plt.xlabel('[m]')
-
-#fig, axs = plt.subplots(1, 2)
-#
-#ax1 = axs[0]
-#ax1.imshow(w_pos.intensity, origin='lower')
-#ax1.set_title('Image plane (+focus)')
-#ax1.set_xticks(np.linspace(0, 400, 5), labels=np.linspace(-1, 1, 5))
-#ax1.set_yticks(np.linspace(0, 400, 5), labels=np.linspace(-1, 1, 5))
-#ax1.set_xlabel('[mm]')
-#
-#ax2 = axs[1]
-#ax2.imshow(w_neg.intensity, origin='lower')
-##ax2.set_title('Image plane (-focus)')
-#ax2.set_xticks(np.linspace(0, 400, 5), labels=np.linspace(-1, 1, 5))
-#ax2.set_yticks(np.linspace(0, 400, 5), labels=np.linspace(-1, 1, 5))
-#ax2.set_xlabel('[mm]')
-
-plt.tight_layout()
diff --git a/docs/_img/python/tilt_images.py b/docs/_img/python/tilt_images.py
deleted file mode 100644
index 7a387af..0000000
--- a/docs/_img/python/tilt_images.py
+++ /dev/null
@@ -1,50 +0,0 @@
-import matplotlib.pyplot as plt
-import numpy as np
-import lentil
-
-import matplotlib as mpl
-mpl.rcParams['figure.figsize'] = (3.5, 3.5)
-
-amp = lentil.circle((256, 256), 120)
-x_tilt = 8e-6 * lentil.zernike(amp, 3) # +x tilt
-y_tilt = 8e-6 * lentil.zernike(amp, 2) # +y tilt
-
-py = lentil.Pupil(focal_length=10, pixelscale=1 / 240, amplitude=amp, phase=y_tilt)
-wy = lentil.Wavefront(650e-9)
-wy *= py
-wy = lentil.propagate_dft(wy, pixelscale=5e-6, shape=200, oversample=5)
-
-px = lentil.Pupil(focal_length=10, pixelscale=1 / 240, amplitude=amp, phase=x_tilt)
-wx = lentil.Wavefront(650e-9)
-wx *= px
-wx = lentil.propagate_dft(wx, pixelscale=5e-6, shape=200, oversample=5)
-
-plt.subplot(2, 2, 1)
-plt.imshow(x_tilt, origin='lower')
-plt.title('Pupil plane ($+R_x$)')
-plt.xticks(np.linspace(0, 256, 5), labels=np.linspace(-0.5, 0.5, 5))
-plt.yticks(np.linspace(0, 256, 5), labels=np.linspace(-0.5, 0.5, 5))
-plt.xlabel('[m]')
-
-plt.subplot(2, 2, 2)
-plt.imshow(y_tilt, origin='lower')
-plt.title('Pupil plane ($+R_y$)')
-plt.xticks(np.linspace(0, 256, 5), labels=np.linspace(-0.5, 0.5, 5))
-plt.yticks(np.linspace(0, 256, 5), labels=np.linspace(-0.5, 0.5, 5))
-plt.xlabel('[m]')
-
-plt.subplot(2, 2, 3)
-plt.imshow(wx.intensity ** 0.2, origin='lower')
-plt.title('Image plane ($+R_x$)')
-plt.xticks(np.linspace(0, 200 * 5, 5), labels=np.linspace(-1, 1, 5))
-plt.yticks(np.linspace(0, 200 * 5, 5), labels=np.linspace(-1, 1, 5))
-plt.xlabel('[mm]')
-
-plt.subplot(2, 2, 4)
-plt.imshow(wy.intensity ** 0.2, origin='lower')
-plt.title('Image plane ($+R_y$)')
-plt.xticks(np.linspace(0, 200 * 5, 5), labels=np.linspace(-1, 1, 5))
-plt.yticks(np.linspace(0, 200 * 5, 5), labels=np.linspace(-1, 1, 5))
-plt.xlabel('[mm]')
-
-plt.tight_layout()
diff --git a/docs/_static/css/lentil.css b/docs/_static/css/lentil.css
index 7c3af04..a456efd 100644
--- a/docs/_static/css/lentil.css
+++ b/docs/_static/css/lentil.css
@@ -31,15 +31,12 @@ a:hover code{
text-decoration-thickness: 1px;
}
-.sig-name {
- --pst-color-inline-code: #5E81AC;
+a.headerlink {
+ color: #ee9040;
}
-pre {
- background-color: #f8f8f8;
- border: 0px;
- box-shadow: none;
- padding: 20px;
+.sig-name {
+ --pst-color-inline-code: #5E81AC;
}
h1, h2, h3, h4, h5, h6 {
@@ -89,6 +86,11 @@ a.hederlink:hover {
color: #ee9040
}
+/* Disable API reference name highlighting */
+dt:target, span.highlighted {
+ background-color: transparent;
+}
+
:target:before{
content:"";
display:block;
diff --git a/docs/_templates/autosummary/attribute.rst b/docs/_templates/autosummary/attribute.rst
new file mode 100644
index 0000000..f1baeaf
--- /dev/null
+++ b/docs/_templates/autosummary/attribute.rst
@@ -0,0 +1,7 @@
+:orphan:
+
+{{ fullname | escape | underline}}
+
+.. currentmodule:: {{ module }}
+
+.. auto{{ objtype }}:: {{ fullname | replace("lentil.", "lentil::") }}
diff --git a/docs/_templates/autosummary/class.rst b/docs/_templates/autosummary/class.rst
index c13da30..5a887a9 100644
--- a/docs/_templates/autosummary/class.rst
+++ b/docs/_templates/autosummary/class.rst
@@ -1,26 +1,31 @@
-{% extends "!autosummary/class.rst" %}
+{{ fullname | escape | underline}}
-{% block methods %}
-{% if methods %}
- .. HACK -- the point here is that we don't want this to appear in the output, but the autosummary should still generate the pages.
- .. autosummary::
- :toctree:
- {% for item in all_methods %}
- {%- if not item.startswith('_') or item in ['__call__'] %}
- {{ name }}.{{ item }}
- {%- endif -%}
- {%- endfor %}
-{% endif %}
-{% endblock %}
+.. currentmodule:: {{ module }}
+
+.. autoclass:: {{ objname }}
-{% block attributes %}
{% if attributes %}
- .. HACK -- the point here is that we don't want this to appear in the output, but the autosummary should still generate the pages.
- .. autosummary::
- {% for item in all_attributes %}
- {%- if not item.startswith('_') %}
- {{ name }}.{{ item }}
- {%- endif -%}
- {%- endfor %}
+.. rubric:: {{ _('Attributes') }}
+
+.. autosummary::
+ :toctree:
+ {% for item in attributes %}
+ {%- if not item.startswith('_') %}
+ ~{{ name }}.{{ item }}
+ {%- endif -%}
+ {%- endfor %}
+
{% endif %}
-{% endblock %}
\ No newline at end of file
+
+{% if methods %}
+.. rubric:: {{ _('Methods') }}
+
+.. autosummary::
+ :toctree:
+ {% for item in methods %}
+ {%- if not item.startswith('_') or item in ['__call__'] %}
+ ~{{ name }}.{{ item }}
+ {%- endif -%}
+ {%- endfor %}
+
+{% endif %}
\ No newline at end of file
diff --git a/docs/_templates/autosummary/property.rst b/docs/_templates/autosummary/property.rst
new file mode 100644
index 0000000..de272bd
--- /dev/null
+++ b/docs/_templates/autosummary/property.rst
@@ -0,0 +1,12 @@
+:orphan:
+
+{{ fullname | escape | underline}}
+
+.. currentmodule:: {{ module }}
+
+.. autoattribute:: {{ fullname | replace("lentil.", "lentil::") }}
+
+{# Normally this line would read
+ .. auto{{ objtype }}:: {{ fullname | replace("lentil.", "lentil::") }}
+but we've explicitly called autoattribute so that properties are rendered
+in a way that is indistinguishable from attributes #}
\ No newline at end of file
diff --git a/docs/_templates/index.html b/docs/_templates/index.html
index 156134f..e15fbbd 100644
--- a/docs/_templates/index.html
+++ b/docs/_templates/index.html
@@ -63,7 +63,7 @@
Lentil: Fast optical propagation
-
+
![](_static/reference.svg)
Reference
diff --git a/docs/_templates/indexcontent.html b/docs/_templates/indexcontent.html
deleted file mode 100644
index 207d801..0000000
--- a/docs/_templates/indexcontent.html
+++ /dev/null
@@ -1,82 +0,0 @@
-{#
- Loosely inspired by the deprecated sphinx/themes/basic/defindex.html
- #}
- {%- extends "layout.html" %}
- {% set title = _('Overview') %}
- {% block body %}
-
Lentil documentation
-
Version: {{ release|e }}
- {% if last_updated %}
Date: {{ last_updated|e }}
{% endif %}
-
-
- Lentil is a Python library for modeling the imaging chain of an optical system.
- It was originally developed at NASA's Jet Propulsion Lab by the Wavefront Sensing and
- Control group (383E) to provide an easy to use framework for simulating point spread
- functions of segmented aperture telescopes.
-
-
-
-
Note
-
Lentil is still under active development and new features continue to be added.
- Until Lentil reaches version 1.0, the API is not guaranteed to be stable, but
- changes breaking backwards compatibility will be noted in the release notes.
-
-
-
Getting started
-
-
- Installation
- Package Overview
- An overview of Lentil features
- Quickstart
- A short introduction to using Lentil
- |
-
-
-
User guide
-
-
- Coordinate system
- Which way is up?
- Specifying planes
- How to model optical planes
- Representing wavefront error
- How to create useful wavefront error maps
- Describing Optical Systems
- How to use Planes to represent optical systems using Lentil
- Modeling Diffraction
- How Lentil numerically models diffraction propagation
- Computational Radiometry
- Working with radiometric data
- Image Sensors
- Tools for modeling focal plane arrays
- Model Patterns
- General solutions to common problems
- Optimizing Performance
- Making models run faster
- Using Lentil with MATLAB
- Tips and tricks for using Lentil and MATLAB together
- |
-
-
-
API reference
-
-
- Lentil API reference
- Automatically generated reference documentation
- |
-
-
-
-
Developer guide
-
-
- Lentil contributor guide
- How to contribute
- Technical notes
- Specialized documentation describing Lentil's technical inner workings
- Release notes
- |
-
-
- {% endblock %}
diff --git a/docs/conf.py b/docs/conf.py
index c764f0a..ca3131d 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -21,6 +21,7 @@
with open(os.path.normpath(os.path.join(path, '..', 'lentil', '__init__.py'))) as f:
version = release = re.search("__version__ = '(.*?)'", f.read()).group(1)
+today_fmt = '%B %d, %Y'
# -- General configuration ---------------------------------------------------
@@ -32,8 +33,8 @@
'sphinx_remove_toctrees',
'sphinx_copybutton',
'sphinx_design',
- 'matplotlib.sphinxext.plot_directive',
- 'numpydoc']
+ 'matplotlib.sphinxext.plot_directive']#,
+ #'numpydoc']
templates_path = ['_templates']
source_suffix = '.rst'
master_doc = 'docs'
@@ -53,6 +54,8 @@
},
"collapse_navigation": True,
"navbar_persistent": ["search-button"],
+ "pygment_light_style": "tango",
+ "pygment_dark_style": "nord",
"favicons": [
{
"rel": "icon",
@@ -93,20 +96,20 @@
html_css_files = ['css/lentil.css']
-pygments_style = 'default'
-
# if true, the current module name will be prepended to all description
# unit titles (such as .. function::).
add_module_names = True
+add_function_parentheses = True
+
autodoc_default_options = {
- 'member-order': 'bysource',
- 'undoc-members': None
-# 'exclude-members': '__init__, __weakref__, __dict__, __module__'
+ 'member-order': 'alphabetical',
+ 'exclude-members': '__init__, __weakref__, __dict__, __module__',
}
autosummary_generate = True
+
#remove_from_toctrees = ["generated/*"]
# -- Plot config -------------------------------------------------------------
@@ -138,16 +141,9 @@
plot_html_show_formats = False
plot_formats = [('png', dpi*2)]
plot_pre_code = """
+import lentil
+import matplotlib.pyplot as plt
import numpy as np
np.random.seed(12345)
"""
-
-
-
-#def fix_attributes(app, pagename, templatename, context, doctree):
-# if 'generated' in pagename:
-# context['body'] = context['body'].replace('Variables', 'Attributes')
-
-#def setup(app):
-# app.connect("html-page-context", fix_attributes)
\ No newline at end of file
diff --git a/docs/dev/tech_notes/dft_caching.rst b/docs/dev/tech_notes/dft_caching.rst
deleted file mode 100644
index 4fc4dfc..0000000
--- a/docs/dev/tech_notes/dft_caching.rst
+++ /dev/null
@@ -1,86 +0,0 @@
-*****************************
-Caching in ``fourier.dft2()``
-*****************************
-
-:func:`~lentil.fourier.dft2` uses the matrix triple product (MTP) approach to
-compute Fourier transforms with variable input and output plane sampling and
-sizes [1]_.
-
-The Fourier transform of an array :math:`f` is given by
-
-.. math::
-
- F = E_1 f E_2
-
-where the Fourier kernels :math:`E_1` and :math:`E_2` are constructed as
-
-.. math::
-
- E_1 = \exp{(-2\pi i \alpha_y Y V)}
-
- E_2 = \exp{(-2\pi i \alpha_x X U)}
-
-given vectors of input plane coordinates :math:`X` and :math:`Y`, vectors of output
-plane coordinates :math:`U` and :math:`V`, and sampling coefficients :math:`\alpha_x`
-and :math:`\alpha_y`.
-
-Fourier kernel caching
-----------------------
-If the input and output array sizes and sampling are held constant, :math:`E_1` and
-:math:`E_2` can be computed once and reused providing increased performance for all
-subsequent evaluations. This is achieved by employing Python's ``modeltools.lru_cache``
-decorator on a method which computes these matrices. The maximum cache size is 32
-entries.
-
-Caching with variable output plane shifts
------------------------------------------
-Repeated calls to :func:`~lentil.fourier.dft2` with a static :attr:`shift` term will
-hit the Fourier kernel cache described above. In the event that :attr:`shift` varies
-on repeated calls to :func:`~lentil.fourier.dft2` we can still achieve some performance
-gains by caching the :math:`X`, :math:`Y`, :math:`U`, and :math:`V` vectors.
-
-Implementation
---------------
-The MTP described above is imlemented in Lentil using a two stage LRU caching approach
-that caches the calculation of ``X``, ``Y``, ``U``, and ``V`` in all cases and caches
-the calculation of ``E1`` and ``E2`` for when ``shift`` does not vary.
-
-.. code:: python
-
- @functools.lru_cache(maxsize=32)
- def _dft2_coords(m, n, M, N):
- # Y and X are (r,c) coordinates in the (m x n) input plane f
- # V and U are (r,c) coordinates in the (M x N) ourput plane F
-
- X = np.arange(n) - np.floor(n/2.0)
- Y = np.arange(m) - np.floor(m/2.0)
- U = np.arange(N) - np.floor(N/2.0)
- V = np.arange(M) - np.floor(M/2.0)
-
- return X, Y, U, V
-
-
- @functools.lru_cache(maxsize=32)
- def _dft2_matrices(m, n, M, N, ax, ay, sx, sy):
- X, Y, U, V = _dft2_coords(m, n, M, N)
- E1 = np.exp(-2.0 * np.pi * 1j * ay * np.outer(Y-sy, V-sy)).T
- E2 = np.exp(-2.0 * np.pi * 1j * ax * np.outer(X-sx, U-sx))
- return E1, E2
-
-
- def dft2(f, alpha, npix=None, shift=(0, 0)):
-
- ...
-
- E1, E2 = _dft2_matrices(m, n, M, N, ax, ay, sx, sy)
- F = np.dot(E1.dot(f), E2)
-
- ...
-
- return F
-
-
-References
-----------
-
-.. [1] Jurling et. al., *Techniques for arbitrary sampling in two-dimensional Fourier transforms*.
\ No newline at end of file
diff --git a/docs/dev/tech_notes/index.rst b/docs/dev/tech_notes/index.rst
index 5a6dec3..d25ae27 100644
--- a/docs/dev/tech_notes/index.rst
+++ b/docs/dev/tech_notes/index.rst
@@ -8,5 +8,4 @@ Technical Notes
:maxdepth: 1
dft_sampling
- dft_caching
prop_algorithm
diff --git a/docs/docs.rst b/docs/docs.rst
index dc6713e..c6476f1 100644
--- a/docs/docs.rst
+++ b/docs/docs.rst
@@ -3,10 +3,10 @@
.. toctree::
:hidden:
- user/index
- examples/index
- reference
- dev/index
+ User Guide
+ Examples
+ API Reference [
+ Development
********************
Lentil Documentation
@@ -45,7 +45,7 @@ functions of segmented aperture telescopes.
.. button-ref:: user/index
:expand:
- :color: muted
+ :color: info
:click-parent:
To the user guide
@@ -59,7 +59,7 @@ functions of segmented aperture telescopes.
.. button-ref:: examples/index
:expand:
- :color: light
+ :color: info
:click-parent:
To the examples
@@ -73,9 +73,9 @@ functions of segmented aperture telescopes.
+++
- .. button-ref:: reference
+ .. button-ref:: ref/index
:expand:
- :color: dark
+ :color: info
:click-parent:
To the reference guide
diff --git a/docs/examples/index.rst b/docs/examples/index.rst
index 90a3788..0bc04e9 100644
--- a/docs/examples/index.rst
+++ b/docs/examples/index.rst
@@ -9,7 +9,6 @@ General
.. toctree::
:maxdepth: 1
- tutorial
general/simple
general/large
general/attributes
diff --git a/docs/examples/tutorial.rst b/docs/examples/tutorial.rst
deleted file mode 100644
index 0e9e75d..0000000
--- a/docs/examples/tutorial.rst
+++ /dev/null
@@ -1,86 +0,0 @@
-.. _tutorial:
-
-************************
-Getting started tutorial
-************************
-
-In this short example, we'll walk through the steps to develop a very simple Lentil
-model of an imaging system with a single pupil. We'll then propagate light through
-the imaging system to an image plane and view the resulting point spread function
-(PSF). The imaging system has a 1m diameter primary mirror, a secondary mirror
-obscuration of 0.33m centered over the primary, a focal length of 10m, and a focal
-plane with 5um pixels.
-
-First, we'll import Lentil and matplotlib:
-
-.. code-block:: pycon
-
- >>> import matplotlib.pyplot as plt
- >>> import lentil
-
-Now we can define the system amplitude and plot it:
-
-.. code-block:: pycon
-
- >>> amplitude = lentil.circle(shape=(256, 256), radius=128) -
- ... lentil.circle(shape=(256, 256), radius=128/3)
- >>> plt.imshow(amplitude)
-
-.. image:: /_static/img/getting_started_amp.png
- :width: 350px
-
-We'll create some wavefront error constructed from a combination of Zernikes:
-
-.. code-block:: pycon
-
- >>> coeffs = [0, 0, 0, 300e-9, 50e-9, -100e-9, 50e-9]
- >>> opd = lentil.zernike_compose(mask=amplitude, coeffs=coeffs)
- >>> plt.imshow(opd)
-
-.. image:: /_static/img/getting_started_opd.png
- :width: 350px
-
-Next we'll define the system's pupil plane. Note that the
-:attr:`~lentil.Pupil.pixelscale` attribute represents the physical sampling of each
-pixel in the pupil (in meters/pixel). Because our amplitude has a diameter of 256 pixels
-and the system diameter was specified as 1m, the pixelscale is 1/256.
-
-.. code-block:: pycon
-
- >>> pupil = lentil.Pupil(amplitude=amplitude, phase=opd, focal_length=10,
- ... pixelscale=1/256)
-
-We'll create a monochromatic :class:`~lentil.Wavefront` with wavelength of 650nm and
-propagate it "through" the pupil plane. This operation is represented by multiplying the
-wavefront by the pupil:
-
-propagate the wavefront through the pupil plane, and finally on to an image plane with
-5um pixels. We'll also oversample the image plane by a factor of 10.
-
-.. code-block:: pycon
-
- >>> w = lentil.Wavefront(wavelength=650e-9)
- >>> w = w * pupil
-
-Now we can propagate the wavefront from the pupil plane to an image plane with 5 micron
-pixels. We'll perform this propagation 10x oversampled and look at the resulting intensity
-pattern:
-
-.. code-block:: pycon
-
- >>> w = lentil.propagate_image(w, pixelscale=5e-6, npix=32, oversample=10)
- >>> plt.imshow(w.intensity)
-
-.. image:: /_static/img/getting_started_psf_oversample.png
- :width: 350px
-
-Finally, we will rescale the oversampled image to native sampling and include the
-blurring effects of the pixel MTF:
-
-.. code-block:: pycon
-
- >>> img = lentil.detector.pixellate(w.intensity, oversample=10)
- >>> plt.imshow(img)
-
-.. image:: /_static/img/getting_started_psf_detector.png
- :width: 350px
diff --git a/docs/ref/detector.rst b/docs/ref/detector.rst
new file mode 100644
index 0000000..c890d37
--- /dev/null
+++ b/docs/ref/detector.rst
@@ -0,0 +1,47 @@
+.. _api.detector:
+
+******************************
+Detector (``lentil.detector``)
+******************************
+
+Charge collection
+-----------------
+.. autosummary::
+ :toctree: generated/
+
+ lentil.detector.collect_charge
+ lentil.detector.collect_charge
+
+Pixel effects
+-------------
+.. autosummary::
+ :toctree: generated/
+
+ lentil.detector.pixel
+ lentil.detector.pixelate
+
+Noise
+-----
+.. autosummary::
+ :toctree: generated/
+
+ lentil.detector.shot_noise
+ lentil.detector.read_noise
+ lentil.detector.charge_diffusion
+ lentil.detector.dark_current
+ lentil.detector.rule07_dark_current
+
+Readout
+-------
+.. autosummary::
+ :toctree: generated/
+
+ lentil.detector.adc
+
+Cosmic rays
+-----------
+.. autosummary::
+ :toctree: generated/
+
+ lentil.detector.cosmic_rays
+
\ No newline at end of file
diff --git a/docs/ref/imaging_artifacts.rst b/docs/ref/imaging_artifacts.rst
new file mode 100644
index 0000000..174569b
--- /dev/null
+++ b/docs/ref/imaging_artifacts.rst
@@ -0,0 +1,13 @@
+.. _api.imaging_artifacts:
+
+*****************
+Imaging artifacts
+*****************
+
+.. autosummary::
+ :toctree: generated/
+
+ lentil.jitter
+ lentil.smear
+
+
diff --git a/docs/ref/index.rst b/docs/ref/index.rst
new file mode 100644
index 0000000..921ca70
--- /dev/null
+++ b/docs/ref/index.rst
@@ -0,0 +1,28 @@
+.. _api:
+
+.. module:: lentil
+
+****************
+Lentil reference
+****************
+
+:Release: |version|
+:Date: |today|
+
+This reference manual gives an overview of all public functions, modules, and
+objects included in Lentil. For learning how to use Lentil, see the
+:ref:`user guide `.
+
+.. toctree::
+ :maxdepth: 1
+
+ planes
+ wavefront
+ propagate
+ zernike
+ wavefront_errors
+ imaging_artifacts
+ radiometry
+ detector
+ util
+ internals
diff --git a/docs/ref/internals.rst b/docs/ref/internals.rst
new file mode 100644
index 0000000..cdf0414
--- /dev/null
+++ b/docs/ref/internals.rst
@@ -0,0 +1,42 @@
+.. _api.internals:
+
+*********
+Internals
+*********
+
+Field
+-----
+.. autosummary::
+ :toctree: generated/
+
+ lentil.field.Field
+ lentil.field.boundary
+ lentil.field.extent
+ lentil.field.insert
+ lentil.field.merge
+ lentil.field.overlap
+ lentil.field.reduce
+
+Tilt interface
+--------------
+.. autosummary::
+ :toctree: generated/
+
+ lentil.plane.TiltInterface
+
+Fourier transforms
+------------------
+.. autosummary::
+ :toctree: generated/
+
+ lentil.fourier.dft2
+
+Helper functions
+----------------
+.. autosummary::
+ :toctree: generated/
+
+ lentil.helper.mesh
+ lentil.helper.gaussian2d
+ lentil.helper.boundary_slice
+ lentil.helper.slice_offset
diff --git a/docs/ref/planes.rst b/docs/ref/planes.rst
new file mode 100644
index 0000000..3ac62cb
--- /dev/null
+++ b/docs/ref/planes.rst
@@ -0,0 +1,37 @@
+.. _api.planes:
+
+*************
+Plane objects
+*************
+
+.. autosummary::
+ :toctree: generated/
+
+ lentil.Plane
+ lentil.Pupil
+ lentil.Image
+ lentil.Detector
+
+Plane transformations
+---------------------
+.. autosummary::
+ :toctree: generated/
+
+ lentil.Tilt
+ lentil.Rotate
+ lentil.Flip
+
+Dispersive Planes
+-----------------
+.. autosummary::
+ :toctree: generated/
+
+ lentil.DispersiveTilt
+
+
+Deprecated
+----------
+.. autosummary::
+ :toctree: generated/
+
+ lentil.Grism
diff --git a/docs/ref/propagate.rst b/docs/ref/propagate.rst
new file mode 100644
index 0000000..66e05cd
--- /dev/null
+++ b/docs/ref/propagate.rst
@@ -0,0 +1,10 @@
+.. _api.propagate:
+
+***********
+Propagation
+***********
+
+.. autosummary::
+ :toctree: generated/
+
+ lentil.propagate_dft
\ No newline at end of file
diff --git a/docs/ref/radiometry.rst b/docs/ref/radiometry.rst
new file mode 100644
index 0000000..5ff20ce
--- /dev/null
+++ b/docs/ref/radiometry.rst
@@ -0,0 +1,23 @@
+.. _api.radiometry:
+
+**********************************
+Radiometry (``lentil.radiometry``)
+**********************************
+
+.. autosummary::
+ :toctree: generated/
+
+ lentil.radiometry.Spectrum
+ lentil.radiometry.Blackbody
+ lentil.radiometry.Material
+
+Utilities
+---------
+.. autosummary::
+ :toctree: generated/
+
+ lentil.radiometry.path_transmission
+ lentil.radiometry.path_emission
+ lentil.radiometry.planck_radiance
+ lentil.radiometry.planck_exitance
+ lentil.radiometry.vegaflux
\ No newline at end of file
diff --git a/docs/ref/util.rst b/docs/ref/util.rst
new file mode 100644
index 0000000..0f25c08
--- /dev/null
+++ b/docs/ref/util.rst
@@ -0,0 +1,36 @@
+.. _api.util:
+
+*********
+Utilities
+*********
+
+Shapes
+------
+.. autosummary::
+ :toctree: generated/
+
+ lentil.circle
+ lentil.circlemask
+ lentil.hexagon
+ lentil.slit
+
+Array manipulation
+------------------
+.. autosummary::
+ :toctree: generated/
+
+ lentil.boundary
+ lentil.centroid
+ lentil.pad
+ lentil.window
+ lentil.rebin
+ lentil.rescale
+
+Miscellaneous
+-------------
+.. autosummary::
+ :toctree: generated/
+
+ lentil.min_sampling
+ lentil.pixelscale_nyquist
+ lentil.normalize_power
diff --git a/docs/ref/wavefront.rst b/docs/ref/wavefront.rst
new file mode 100644
index 0000000..f455efa
--- /dev/null
+++ b/docs/ref/wavefront.rst
@@ -0,0 +1,10 @@
+.. _api.wavefront:
+
+*********
+Wavefront
+*********
+
+.. autosummary::
+ :toctree: generated/
+
+ lentil.Wavefront
\ No newline at end of file
diff --git a/docs/ref/wavefront_errors.rst b/docs/ref/wavefront_errors.rst
new file mode 100644
index 0000000..becf1ba
--- /dev/null
+++ b/docs/ref/wavefront_errors.rst
@@ -0,0 +1,13 @@
+.. _api.wfe:
+
+****************
+Wavefront errors
+****************
+
+.. autosummary::
+ :toctree: generated/
+
+ lentil.power_spectrum
+ lentil.translation_defocus
+
+
diff --git a/docs/ref/zernike.rst b/docs/ref/zernike.rst
new file mode 100644
index 0000000..860f909
--- /dev/null
+++ b/docs/ref/zernike.rst
@@ -0,0 +1,15 @@
+.. _api.zernike:
+
+*******************
+Zernike polynomials
+*******************
+
+.. autosummary::
+ :toctree: generated/
+
+ lentil.zernike
+ lentil.zernike_compose
+ lentil.zernike_fit
+ lentil.zernike_remove
+ lentil.zernike_basis
+ lentil.zernike_coordinates
\ No newline at end of file
diff --git a/docs/reference.rst b/docs/reference.rst
deleted file mode 100644
index 52725ac..0000000
--- a/docs/reference.rst
+++ /dev/null
@@ -1,36 +0,0 @@
-.. _api:
-
-.. currentmodule:: lentil
-
-*************
-API reference
-*************
-
-This page gives an overview of all public functions, modules, and objects included
-in Lentil. All classes and functions exposed in the ``lentil`` namespace are public.
-
-Some subpackages are public including ``lentil.radiometry`` and
-``lentil.detector``.
-
-.. _api.planes:
-
-Planes
-======
-
-.. autosummary::
- :caption: Planes
- :toctree: generated/
-
- lentil.Plane
- lentil.Pupil
- lentil.Image
-
-
-Wavefront
-=========
-
-.. autosummary::
- :caption: Wavefront
- :toctree: generated/
-
- lentil.Wavefront
\ No newline at end of file
diff --git a/docs/user/basics.coordinates.rst b/docs/user/basics.coordinates.rst
index 9b010a0..a4df4a3 100644
--- a/docs/user/basics.coordinates.rst
+++ b/docs/user/basics.coordinates.rst
@@ -28,16 +28,18 @@ of the complex exponential in the Fourier kernel are provided below:
* :ref:`user.wavefront_error.sign`
* :ref:`user.diffraction.sign`
+.. _user.coordinate_system.origin:
+
.. note::
Matplotlib's ``imshow()`` method (and MATLAB's ``imagesc()`` method) place
the origin in the upper left corner of the plotted image by default. This presents
arrays in the standard (row, column) ordering. The result is that the direction of
y-axis is flipped relative to Lentil's coordinate system. This doesn't necessarily
- present a problem as long as results are consistently plotted "incorrectly", but
- to be completely correct (particularly when comparing model-generated images against
- intuition or measured data) the origin should be located in the lower left corner
- of the displayed image.
+ present a problem as long as results are consistently plotted, but to be completely
+ correct (particularly when comparing model-generated images against intuition or
+ measured data) the origin should be located in the lower left corner of the
+ displayed image.
.. image:: /_static/img/coordinate_system_plot.png
:width: 700px
diff --git a/docs/user/basics.diffraction.rst b/docs/user/basics.diffraction.rst
index c03a9aa..6ea92ec 100644
--- a/docs/user/basics.diffraction.rst
+++ b/docs/user/basics.diffraction.rst
@@ -60,20 +60,17 @@ follows the same basic flow:
.. code-block:: pycon
- >>> plt.imshow(np.abs(w2.field), origin='lower')
+ >>> plt.imshow(np.abs(w2.field))
.. plot::
:context: reset
:scale: 50
- import matplotlib.pyplot as plt
- import lentil
-
pupil = lentil.Pupil(amplitude=lentil.circle((256, 256), 120),
pixelscale=1/240, focal_length=10)
w1 = lentil.Wavefront(650e-9)
w2 = w1 * pupil
- plt.imshow(np.abs(w2.field), origin='lower')
+ plt.imshow(np.abs(w2.field))
Additionally, because ``w2`` was propagated through a |Pupil| plane, it has inherited the
pupil's focal length:
@@ -120,7 +117,7 @@ follows the same basic flow:
:scale: 50
>>> w2 = lentil.propagate_dft(w2, pixelscale=5e-6, shape=(64,64), oversample=5)
- >>> plt.imshow(w2.intensity**0.1, origin='lower')
+ >>> plt.imshow(w2.intensity, norm='log')
.. note::
@@ -142,10 +139,6 @@ different wavelengths and accumulates the resulting image plane intensity:
:scale: 50
:include-source:
- import matplotlib.pyplot as plt
- import numpy as np
- import lentil
-
pupil = lentil.Pupil(amplitude=lentil.circle((256, 256), 120),
pixelscale=1/240, focal_length=10)
@@ -158,7 +151,7 @@ different wavelengths and accumulates the resulting image plane intensity:
w = lentil.propagate_dft(w, pixelscale=5e-6, shape=(64,64), oversample=5)
img += w.intensity
- plt.imshow(img**0.1, origin='lower')
+ plt.imshow(img, norm='log')
Keep in mind the output ``img`` array must be sized to accommodate the oversampled
wavefront intensity given by ``npix`` * ``oversample``.
@@ -195,7 +188,7 @@ appropriate location in the (potentially larger) output plane when a |Wavefront|
:attr:`~lentil.Wavefront.field` or :attr:`~lentil.Wavefront.intensity`
attribute is accessed.
-.. image:: /_static/img/propagate_npix_prop.png
+.. image:: images/propagate_npix_prop.png
:width: 450px
:align: center
@@ -203,7 +196,7 @@ It can be advantageous to specify ``npix_prop`` < ``npix`` for performance
reasons, although care must be taken to ensure needed data is not accidentally
left out:
-.. plot:: _img/python/npix_prop.py
+.. plot:: user/plots/npix_prop.py
:scale: 50
For most pupil to image plane propagations, setting ``npix_prop`` to 128 or 256
@@ -253,10 +246,10 @@ optics texts. The implications of this choice are as follows:
:math:`\exp\left[i\frac{k}{2z} (x^2 + y^2)\right]`
-.. .. _user_guide.diffraction.sampling:
+.. _user.diffraction.sampling:
-.. Sampling considerations
-.. =======================
+Sampling considerations
+=======================
.. .. plot:: _img/python/dft_discrete_Q_sweep.py
.. :scale: 50
@@ -269,10 +262,10 @@ optics texts. The implications of this choice are as follows:
.. :width: 550px
.. :align: center
-.. .. _user_guide.diffraction.tilt:
+.. _user.diffraction.tilt:
-.. Working with large tilts
-.. ========================
+Working with large tilts
+========================
.. .. image:: /_static/img/propagate_tilt_phase.png
.. :width: 450px
.. :align: center
@@ -289,10 +282,10 @@ optics texts. The implications of this choice are as follows:
.. :width: 600px
.. :align: center
-.. .. _user_guide.diffraction.segmented:
+.. _user.diffraction.segmented:
-.. Differences for segmented apertures
-.. ===================================
+Differences for segmented apertures
+===================================
diff --git a/docs/user/basics.optical_systems.rst b/docs/user/basics.optical_systems.rst
index b45aca1..aa2cd7f 100644
--- a/docs/user/basics.optical_systems.rst
+++ b/docs/user/basics.optical_systems.rst
@@ -74,8 +74,6 @@ represented by a single |Pupil| plane:
:scale: 50
:include-source:
- >>> import matplotlib.pyplot as plt
- >>> import lentil
>>> amplitude = lentil.circle(shape=(256, 256), radius=120)
>>> opd = lentil.zernike_compose(mask=amplitude,
... coeffs=[0, 0, 0, 100e-9, 300e-9, 0, -100e-9])
diff --git a/docs/user/basics.planes.rst b/docs/user/basics.planes.rst
index 718c469..8251202 100644
--- a/docs/user/basics.planes.rst
+++ b/docs/user/basics.planes.rst
@@ -15,8 +15,8 @@ Lentil plane types
All Lentil planes are derived from the |Plane| class. This base class defines the
interface to represent any discretely sampled plane in an optical model. It can also
be used directly in a model. Planes typically have some influence on the propagation
-of a wavefront though this is not strictly required and some models may use *dummy*
-or *reference* planes as needed.
+of a wavefront though this is not strictly required and models may use *dummy* or
+*reference* planes as needed.
Lentil provides several general planes that are the building blocks for most optical
models:
@@ -74,7 +74,7 @@ plane. A plane is defined by the following parameters:
.. note::
All Plane attributes have sensible default values that have no effect on
- propagations when not defined.
+ propagations when not specified.
Create a new Plane with
@@ -83,8 +83,6 @@ Create a new Plane with
:include-source:
:scale: 50
- >>> import matplotlib.pyplot as plt
- >>> import lentil
>>> p = lentil.Plane(amplitude=lentil.util.circle((256,256), 120))
>>> plt.imshow(p.amplitude, origin='lower')
@@ -94,8 +92,6 @@ Once a Plane is defined, its attributes can be modified at any time:
:include-source:
:scale: 50
- >>> import matplotlib.pyplot as plt
- >>> import lentil
>>> p = lentil.Plane(amplitude=lentil.util.circle((256,256), 120))
>>> p.phase = 2e-6 * lentil.zernike(p.mask, index=4)
>>> plt.imshow(p.phase, origin='lower')
@@ -232,14 +228,12 @@ Given the following |Pupil| and |Detector| planes:
:include-source:
:scale: 50
- >>> import matplotlib.pyplot as plt
- >>> import lentil
>>> pupil = lentil.Pupil(amplitude=lentil.util.circle((256, 256), 120),
... focal_length=10, pixelscale=1/250)
>>> w = lentil.Wavefront(650e-9)
>>> w *= pupil
>>> w = lentil.propagate_dft(w, pixelscale=5e-6, shape=(64,64), oversample=2)
- >>> plt.imshow(w.intensity, origin='lower')
+ >>> plt.imshow(w.intensity)
It is simple to see the effect of introducing a tilted wavefront into the system:
@@ -247,8 +241,6 @@ It is simple to see the effect of introducing a tilted wavefront into the system
:include-source:
:scale: 50
- >>> import matplotlib.pyplot as plt
- >>> import lentil
>>> pupil = lentil.Pupil(amplitude=lentil.util.circle((256, 256), 120),
... focal_length=10, pixelscale=1/250)
>>> tilt = lentil.Tilt(x=10e-6, y=-5e-6)
@@ -258,6 +250,11 @@ It is simple to see the effect of introducing a tilted wavefront into the system
>>> w = lentil.propagate_dft(w, pixelscale=5e-6, shape=(64,64), oversample=2)
>>> plt.imshow(w.intensity, origin='lower')
+.. note::
+
+ Notice the use of ``origin='lower'`` in the plot above. For an explanation, see
+ the note :ref:`here `.
+
.. .. _user_guide.planes.transformations:
.. Plane transformations
diff --git a/docs/user/basics.wavefront_error.rst b/docs/user/basics.wavefront_error.rst
index 00f6f59..6d85c37 100644
--- a/docs/user/basics.wavefront_error.rst
+++ b/docs/user/basics.wavefront_error.rst
@@ -32,7 +32,7 @@ in a shift in the image plane in the positive y direction. A positive y-tilt
rotates the xz plane clockwise about the y-axis resulting in a shift in the
image plane in the negative x direction.
-.. plot:: _img/python/tilt_images.py
+.. plot:: user/plots/tilt_images.py
:scale: 50
Focus
@@ -53,7 +53,7 @@ image to be flipped about both axes relative to the aperture (consistent with
observing the image after passing through focus). The results of this exercise are
presented below:
-.. plot:: _img/python/focus_images.py
+.. plot:: user/plots/focus_images.py
:scale: 50
Static Errors
@@ -88,36 +88,32 @@ polynomials `_.
Lentil uses the Noll indexing scheme for defining Zernike polynomials [1]_.
Wavefront error maps are easily computed using either the :func:`~lentil.zernike` or
-:func:`~lentil.zernike_compose` functions. For example, we can represent 100 nm of focus over a
-circular aperture with :func:`~lentil.zernike`:
+:func:`~lentil.zernike_compose` functions. For example, we can represent 100 nm of
+astigmatism over a circular aperture with :func:`~lentil.zernike`:
.. plot::
:include-source:
:scale: 50
- >>> import matplotlib.pyplot as plt
- >>> import lentil
>>> mask = lentil.circlemask((256,256), 120)
- >>> z4 = 100e-9 * lentil.zernike(mask, index=4)
- >>> plt.imshow(z4, origin='lower')
+ >>> astig = 100e-9 * lentil.zernike(mask, index=6)
+ >>> plt.imshow(astig, origin='lower')
-Any combination of Zernike polynomials can be combined by providing a list of coefficients
-to the :func:`~lentil.zernike_compose` function. For example, we can represent 200 nm of
-focus and -100 nm of astigmatism as:
+Any arbitrary combination of Zernike polynomials can be represented by providing a
+list of coefficients to the :func:`~lentil.zernike_compose` function:
.. plot::
:include-source:
:scale: 50
- >>> import matplotlib.pyplot as plt
- >>> import lentil
>>> mask = lentil.circlemask((256,256), 120)
- >>> coefficients = [0, 0, 0, 200e-9, 0, -100e-9]
- >>> z = lentil.zernike_compose(mask, coefficients)
+ >>> coeff = np.random.uniform(low=-200e-9, high=200e-9, size=10)
+ >>> z = lentil.zernike_compose(mask, coeff)
>>> plt.imshow(z, origin='lower')
-Note that the coefficients list is ordered according to the Noll indexing scheme so the
+Note that the coefficients list is ordered according to the `Noll indexing scheme
+`_ so the
first entry in the list represents piston, the second represents, tilt, and so on.
For models requiring many random trials, it may make more sense to pre-compute the
@@ -127,36 +123,30 @@ each independent term using Numpy's `einsum
`_ function.
Note that in this case we are only computing the Zernike modes we intend to use (Noll
-indices 4 and 6) so now the first entry in ``coefficients`` corresponds to focus and the
+indices 4 and 6) so now the first entry in ``coeff`` corresponds to focus and the
second corresponds to astigmatism.
.. plot::
:include-source:
:scale: 50
- >>> import matplotlib.pyplot as plt
- >>> import numpy as np
- >>> import lentil
>>> mask = lentil.circlemask((256,256), 120)
- >>> coefficients = [200e-9, -100e-9]
+ >>> coeff = [200e-9, -100e-9]
>>> basis = lentil.zernike_basis(mask, modes=(4,6))
- >>> z = np.einsum('ijk,i->jk', basis, coefficients)
+ >>> z = np.einsum('ijk,i->jk', basis, coeff)
>>> plt.imshow(z, origin='lower')
-If you don't love ``einsum``, it's possible to achieve the same result with Numpy's
+It's also possible to achieve the same result using Numpy's
`tensordot `_:
.. plot::
:include-source:
:scale: 50
- >>> import matplotlib.pyplot as plt
- >>> import numpy as np
- >>> import lentil
>>> mask = lentil.circlemask((256,256), 120)
- >>> coefficients = [200e-9, -100e-9]
+ >>> coeff = [200e-9, -100e-9]
>>> basis = lentil.zernike_basis(mask, modes=(4,6))
- >>> z = np.tensordot(basis, coefficients, axes=(0,0))
+ >>> z = np.tensordot(basis, coeff, axes=(0,0))
>>> plt.imshow(z, origin='lower')
Normalization
@@ -174,8 +164,6 @@ the error magnitude:
.. code-block:: pycon
- >>> import numpy as np
- >>> import lentil
>>> mask = lentil.circlemask((256,256), 128)
>>> z4 = 100e-9 * lentil.zernike(mask, mode=4, normalize=True)
>>> np.std(z4[np.nonzero(z4)])
@@ -188,8 +176,6 @@ the discretely sampled mode spans [-0.5 0.5] before multiplying by the error mag
.. code-block:: pycon
- >>> import numpy as np
- >>> import lentil
>>> mask = lentil.circlemask((256,256), 128)
>>> z4 = lentil.zernike(mask, mode=4)
>>> z4 /= np.max(z4) - np.min(z4)
@@ -213,8 +199,6 @@ the center of the defined array:
:include-source:
:scale: 50
- >>> import matplotlib.pyplot as plt
- >>> import lentil
>>> mask = lentil.circlemask((256,256), radius=50, shift=(0,60))
>>> rho, theta = lentil.zernike_coordinates(mask, shift=(0,60))
>>> z4 = lentil.zernike(mask, 4, rho=rho, theta=theta)
@@ -226,8 +210,6 @@ If we wish to align a tilt mode with one side of a hexagon:
:include-source:
:scale: 50
- >>> import matplotlib.pyplot as plt
- >>> import lentil
>>> mask = lentil.hexagon((256,256), radius=120)
>>> rho, theta = lentil.zernike_coordinates(mask, shift=(0,0), rotate=30)
>>> z2 = lentil.zernike(mask, 2, rho=rho, theta=theta)
@@ -281,11 +263,9 @@ wavefront error map given a PSD:
:include-source:
:scale: 50
- >>> import matplotlib.pyplot as plt
- >>> import lentil
>>> mask = lentil.circle((256, 256), 120)
- >>> w = lentil.power_spectrum(mask, pixelscale=1/120, rms=25e-9, half_power_freq=8,
- ... exp=3)
+ >>> w = lentil.power_spectrum(mask, pixelscale=1/120, rms=25e-9,
+ ... half_power_freq=8, exp=3)
>>> plt.imshow(w, origin='lower')
diff --git a/docs/_static/img/propagate_npix_prop.png b/docs/user/images/propagate_npix_prop.png
similarity index 100%
rename from docs/_static/img/propagate_npix_prop.png
rename to docs/user/images/propagate_npix_prop.png
diff --git a/docs/user/plots/focus_images.py b/docs/user/plots/focus_images.py
new file mode 100644
index 0000000..e8492d7
--- /dev/null
+++ b/docs/user/plots/focus_images.py
@@ -0,0 +1,47 @@
+import matplotlib.pyplot as plt
+import numpy as np
+import lentil
+
+amp = np.zeros((256, 256))
+amp += lentil.circlemask((256, 256), 64, shift=(32, 8))
+amp -= lentil.circlemask((256, 256), 40, shift=(32, 8))
+amp[:, 0:128+8] = 0
+amp[32:225, 104-16:128-16] = 1
+amp[96:120, 128-16:144-8] = 1
+amp[201:225, 128-16:144-8] = 1
+
+focus = lentil.zernike(mask=np.ones((256, 256)), index=4)
+
+pupil_neg = lentil.Pupil(amplitude=amp, pixelscale=1/240, focal_length=10)
+pupil_neg.phase = -6e-6 * focus
+w_neg = lentil.Wavefront(650e-9)
+w_neg *= pupil_neg
+w_neg = lentil.propagate_dft(w_neg, pixelscale=5e-6, shape=200, oversample=2)
+
+pupil_pos = lentil.Pupil(amplitude=amp, pixelscale=1/240, focal_length=10)
+pupil_pos.phase = 6e-6 * focus
+w_pos = lentil.Wavefront(650e-9)
+w_pos *= pupil_pos
+w_pos = lentil.propagate_dft(w_pos, pixelscale=5e-6, shape=200, oversample=2)
+
+fig, (ax1,ax2,ax3) = plt.subplots(nrows=1, ncols=3, figsize=(5, 3))
+
+ax1.imshow(amp, origin='lower')
+ax1.set_title('Aperture')
+ax1.set_xticks(np.linspace(0, 256, 5), labels=np.linspace(-1, 1, 5))
+ax1.set_yticks(np.linspace(0, 256, 5), labels=np.linspace(-1, 1, 5))
+ax1.set_xlabel('[m]')
+
+ax2.imshow(w_pos.intensity, origin='lower')
+ax2.set_title('Image plane (+focus)')
+ax2.set_xticks(np.linspace(0, 400, 5), labels=np.linspace(-1, 1, 5))
+ax2.set_yticks(np.linspace(0, 400, 5), labels=np.linspace(-1, 1, 5))
+ax2.set_xlabel('[mm]')
+
+ax3.imshow(w_neg.intensity, origin='lower')
+ax3.set_title('Image plane (-focus)')
+ax3.set_xticks(np.linspace(0, 400, 5), labels=np.linspace(-1, 1, 5))
+ax3.set_yticks(np.linspace(0, 400, 5), labels=np.linspace(-1, 1, 5))
+ax3.set_xlabel('[mm]')
+
+plt.tight_layout()
diff --git a/docs/_img/python/npix_prop.py b/docs/user/plots/npix_prop.py
similarity index 69%
rename from docs/_img/python/npix_prop.py
rename to docs/user/plots/npix_prop.py
index 843ea9c..69a062a 100644
--- a/docs/_img/python/npix_prop.py
+++ b/docs/user/plots/npix_prop.py
@@ -16,12 +16,13 @@
w2 *= pupil
w2 = lentil.propagate_dft(w2, pixelscale=5e-6, shape=128, prop_shape=40, oversample=5)
-plt.subplot(1, 2, 1)
-plt.imshow(w1.intensity, origin='lower')
-plt.title('npix_prop ok')
-plt.axis('off')
-
-plt.subplot(1, 2, 2)
-plt.imshow(w2.intensity, origin='lower')
-plt.title('npix_prop too small')
-plt.axis('off')
+
+fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(4, 4))
+
+ax1.imshow(w1.intensity, origin='lower')
+ax1.set_title('npix_prop ok')
+ax1.axis('off')
+
+ax2.imshow(w2.intensity, origin='lower')
+ax2.set_title('npix_prop too small')
+ax2.axis('off')
diff --git a/docs/user/plots/tilt_images.py b/docs/user/plots/tilt_images.py
new file mode 100644
index 0000000..a116a31
--- /dev/null
+++ b/docs/user/plots/tilt_images.py
@@ -0,0 +1,45 @@
+import matplotlib.pyplot as plt
+import numpy as np
+import lentil
+
+amp = lentil.circle((256, 256), 120)
+x_tilt = 8e-6 * lentil.zernike(amp, 3) # +x tilt
+y_tilt = 8e-6 * lentil.zernike(amp, 2) # +y tilt
+
+py = lentil.Pupil(focal_length=10, pixelscale=1 / 240, amplitude=amp, phase=y_tilt)
+wy = lentil.Wavefront(650e-9)
+wy *= py
+wy = lentil.propagate_dft(wy, pixelscale=5e-6, shape=200, oversample=5)
+
+px = lentil.Pupil(focal_length=10, pixelscale=1 /240, amplitude=amp, phase=x_tilt)
+wx = lentil.Wavefront(650e-9)
+wx *= px
+wx = lentil.propagate_dft(wx, pixelscale=5e-6, shape=200, oversample=5)
+
+fig, ((ax1,ax2),(ax3,ax4)) = plt.subplots(nrows=2, ncols=2, figsize=(3.5, 3.5))
+
+ax1.imshow(x_tilt, origin='lower')
+ax1.set_title('Pupil plane ($+R_x$)')
+ax1.set_xticks(np.linspace(0, 256, 5), labels=np.linspace(-0.5, 0.5, 5))
+ax1.set_yticks(np.linspace(0, 256, 5), labels=np.linspace(-0.5, 0.5, 5))
+ax1.set_xlabel('[m]')
+
+ax2.imshow(y_tilt, origin='lower')
+ax2.set_title('Pupil plane ($+R_y$)')
+ax2.set_xticks(np.linspace(0, 256, 5), labels=np.linspace(-0.5, 0.5, 5))
+ax2.set_yticks(np.linspace(0, 256, 5), labels=np.linspace(-0.5, 0.5, 5))
+ax2.set_xlabel('[m]')
+
+ax3.imshow(wx.intensity ** 0.2, origin='lower')
+ax3.set_title('Image plane ($+R_x$)')
+ax3.set_xticks(np.linspace(0, 200 * 5, 5), labels=np.linspace(-1, 1, 5))
+ax3.set_yticks(np.linspace(0, 200 * 5, 5), labels=np.linspace(-1, 1, 5))
+ax3.set_xlabel('[mm]')
+
+ax4.imshow(wy.intensity ** 0.2, origin='lower')
+ax4.set_title('Image plane ($+R_y$)')
+ax4.set_xticks(np.linspace(0, 200 * 5, 5), labels=np.linspace(-1, 1, 5))
+ax4.set_yticks(np.linspace(0, 200 * 5, 5), labels=np.linspace(-1, 1, 5))
+ax4.set_xlabel('[mm]')
+
+fig.tight_layout()
diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst
index 9934ca7..32a477d 100644
--- a/docs/user/quickstart.rst
+++ b/docs/user/quickstart.rst
@@ -9,7 +9,7 @@
Quickstart
**********
This is a short introduction to Lentil, mainly written for new users. More
-complex recipes are available in the Cookbook.
+detailed examples are available :ref:`here`.
First, we import Lentil:
@@ -23,55 +23,54 @@ We'll also import `Matplotlib `_ to visualize results:
>>> import matplotlib.pyplot as plt
-.. note::
-
- Lentil is "unitless" in the sense that it doesn't enforce a specific base
- unit. All calculations are well behaved for both metric and imperial units.
- It is important that units are consistent however, and this task is left to
- the user.
-
- That being said, it is recommended that all calculations be performed in
- terms of either meters, millimeters, or microns.
-
Creating planes
===============
-Most Lentil models can be constructed using |Pupil| and |Image| planes. We'll
-create a circular |Pupil| with a focal length of 10 meters and a diameter of
-1 meter:
+Most simple diffraction simulations can be represented by a single far-field
+propagation from a pupil plane to an image plane. First, we'll create a
+pupil amplitude map and corresponding optical path difference (OPD) map which
+represents the wavefront error of the system. We'll construct the OPD map from
+a combination of Zernike modes:
-.. code-block:: pycon
+.. plot::
+ :context: reset
+ :include-source:
+ :scale: 50
>>> amp = lentil.circle(shape=(256,256), radius=120)
- >>> pupil = lentil.Pupil(amplitude=amp, pixelscale=1/240, focal_length=10)
- >>> plt.imshow(pupil.amplitude, origin='lower')
+ >>> coef = [0, 0, 0, 300e-9, 50e-9, -100e-9, 50e-9]
+ >>> opd = lentil.zernike_compose(mask=amp, coeffs=coef)
+ >>> fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(5, 4))
+ >>> ax1.imshow(amp)
+ >>> ax1.set_title('Amplitude')
+ >>> ax2.imshow(opd)
+ >>> ax2.set_title('OPD')
+
+Now we can use the amplitude and OPD maps to construct a |Pupil| plane with
+a focal length of 20 meters and a diameter of 1 meter:
.. plot::
- :scale: 50
- :context: reset
+ :context: close-figs
+ :include-source:
- import matplotlib.pyplot as plt
- import lentil
- amp = lentil.circle(shape=(256,256), radius=120)
- pupil = lentil.Pupil(amplitude=amp, pixelscale=1/240, focal_length=10)
- plt.imshow(pupil.amplitude, origin='lower')
+ >>> pupil = lentil.Pupil(amplitude=amp, phase=opd, pixelscale=1/240,
+ ... focal_length=20)
-Note the diameter is defined via the :attr:`~lentil.Pupil.pixelscale`
-attribute:
+Note the diameter is implicitly defined via the
+:attr:`~lentil.Pupil.pixelscale` attribute:
.. image:: /_static/img/pixelscale.png
:width: 500px
:align: center
-Here, we'll create an |Image| plane with spatial sampling of 5 microns,
-represented here in trems of meters:
-
-.. code-block:: pycon
-
- >>> image = lentil.Image(pixelscale=5e-6)
+.. note::
+ Lentil is "unitless" in the sense that it doesn't enforce a specific base
+ unit. All calculations are well behaved for both metric and imperial units.
+ It is important that units are consistent however, and this task is left to
+ the user.
-.. Wavefront error
-.. ===============
+ That being said, it is recommended that all calculations be performed in
+ terms of either meters, millimeters, or microns.
Diffraction
===========
@@ -79,56 +78,61 @@ Diffraction
Pupil to image plane propagation
--------------------------------
The simplest diffraction propagation is from a pupil to image plane. Here, we
-construct a |Wavefront| with wavelength of 650 nanometers, again represented
+construct a |Wavefront| with wavelength of 500 nm, again represented
in meters:
-.. code-block:: pycon
+.. plot::
+ :context:
+ :include-source:
- >>> w = lentil.Wavefront(wavelength=650e-9)
+ >>> w0 = lentil.Wavefront(wavelength=500e-9)
Next, we'll propagate the wavefront through the pupil plane we defined above.
Lentil uses multiplication represent the interaction between a |Plane| and
|Wavefront|:
-.. code-block:: pycon
+.. plot::
+ :context:
+ :include-source:
- >>> w = w * pupil
+ >>> w1 = w0 * pupil
Finally, we'll propagate the wavefront to a discreetely sampled image plane
using :func:`~lentil.propagate_dft`. In this case, we'll sample
-the result every 5e-6 meters and perform the propagation 3 times oversampled:
+the result on a grid with spacing of 5e-6 meters and perform the propagation
+2 times oversampled:
-.. code-block:: pycon
+.. plot::
+ :context:
+ :include-source:
- >>> w = lentil.propagate_dft(w, shape=(64,64), pixelscale=5e-6, oversample=5)
+ >>> w2 = lentil.propagate_dft(w1, shape=(64,64), pixelscale=5e-6, oversample=2)
The resulting intensity (point spread function) can now be observed:
-.. code-block:: pycon
-
- >>> plt.imshow(w.intensity, origin='lower')
-
.. plot::
- :context: close-figs
+ :context:
+ :include-source:
:scale: 50
- w = lentil.Wavefront(wavelength=650e-9)
- w *= pupil
- w = lentil.propagate_dft(w, shape=(64,64), pixelscale=5e-6, oversample=5)
- plt.imshow(w.intensity, origin='lower')
+ >>> plt.imshow(w2.intensity)
-.. Multi-plane propagation
-.. -----------------------
+Finally, we will rescale the oversampled image to native sampling and include the
+blurring effects due to the pixel MTF:
-.. Free-space propagation
-.. ----------------------
+.. plot::
+ :context: close-figs
+ :include-source:
+ :scale: 50
+ >>> img = lentil.detector.pixelate(w2.intensity, oversample=2)
+ >>> plt.imshow(img)
-Focal planes
-============
+.. Focal planes
+.. ============
-Radiometry
-==========
+.. Radiometry
+.. ==========
diff --git a/lentil/field.py b/lentil/field.py
index e60ecc6..980423c 100644
--- a/lentil/field.py
+++ b/lentil/field.py
@@ -25,18 +25,32 @@ class Field:
and return an updated x and y shift. If None (default), tilt = [].
- Attributes
- ----------
- extent : tuple
- Array indices defining the extent of the offset Field.
"""
__slots__ = ('data', 'offset', 'tilt', 'pixelscale', 'extent')
def __init__(self, data, pixelscale=None, offset=None, tilt=None):
- self.data = np.asarray(data, dtype=complex)
+ #: ndarray : Complex field data
+ self.data = np.asarray(data, dtype=complex)
+
self.pixelscale = pixelscale
+ """Spatial sampling of data
+
+ If None (default), the Field is assumed to be broadcastable to any
+ legal shape without interpolation.
+
+ Returns
+ -------
+ tuple of ints or None
+ """
+
+ #: tuple of ints : Field offset from (0, 0).
self.offset = offset if offset is not None else [0, 0]
+
+ #: list : List of objects that implement the tilt interface defined
+ # in :class:`~lentil.plane.TiltInterface`
self.tilt = tilt if tilt else []
+
+ #: tuple of ints : Extent of ``data``
self.extent = extent(self.shape, self.offset)
@property
@@ -46,7 +60,7 @@ def shape(self):
Returns
-------
- shape : tuple
+ tuple of ints
"""
return self.data.shape
@@ -57,7 +71,7 @@ def size(self):
Returns
-------
- size : int
+ int
"""
return self.data.size
@@ -232,7 +246,8 @@ def extent(shape, offset):
def insert(field, out, intensity=False, weight=1):
- """
+ """Insert a field into an array.
+
Parameters
----------
field : :class:`~lentil.field.Field`
@@ -247,7 +262,7 @@ def insert(field, out, intensity=False, weight=1):
Returns
-------
- out : ndarray
+ ndarray
"""
#if indexing not in ('xy', 'ij'):
diff --git a/lentil/plane.py b/lentil/plane.py
index fc9fddb..094fe98 100644
--- a/lentil/plane.py
+++ b/lentil/plane.py
@@ -38,24 +38,21 @@ class Plane:
Physical sampling of each pixel in the plane. If ``pixelscale`` is a
scalar, uniform sampling in x and y is assumed. If None (default),
``pixelscale`` is left undefined.
-
- Attributes
- ----------
- tilt : list
- List of :class:`~lentil.Tilt` terms associated wirth this Plane
-
+ diameter : float, optional
+ Outscribing diameter around mask. If not provided (default), it is computed
+ from the boundary of :attr:`mask`.
+ ptype : ptype object
+ Plane type
+
"""
def __init__(self, amplitude=1, phase=0, mask=None, pixelscale=None, diameter=None,
ptype=None):
self.amplitude = np.asarray(amplitude)
self.phase = np.asarray(phase)
self.mask = mask
- self.pixelscale = None if pixelscale is None else np.broadcast_to(pixelscale, (2,))
+ self.pixelscale = pixelscale
self.diameter = diameter
self.ptype = lentil.ptype(ptype)
-
- self._mask = np.asarray(mask) if mask is not None else None
-
self._slice = _plane_slice(self._mask)
self.tilt = []
@@ -63,8 +60,44 @@ def __init__(self, amplitude=1, phase=0, mask=None, pixelscale=None, diameter=No
def __repr__(self):
return f'{self.__class__.__name__}()'
+ @property
+ def amplitude(self):
+ """Electric field amplitude transmission
+
+ Returns
+ -------
+ ndarray
+
+ """
+ return self._amplitude
+
+ @amplitude.setter
+ def amplitude(self, value):
+ self._amplitude = np.asarray(value)
+
+ @property
+ def phase(self):
+ """Electric field phase
+
+ Returns
+ -------
+ ndarray
+
+ """
+ return self._phase
+
+ @phase.setter
+ def phase(self, value):
+ self._phase = np.asarray(value)
+
@property
def mask(self):
+ """Binary transmission mask
+
+ Returns
+ -------
+ ndarray
+ """
if self._mask is not None:
return self._mask
else:
@@ -84,19 +117,47 @@ def mask(self, value):
@property
def global_mask(self):
"""
- Flattened view of :attr:`mask`
+ Flattened view of :attr:`~mask`
Returns
-------
- mask : ndarray
+ ndarray
+
"""
- if self.depth < 2:
+ if self.size < 2:
return self.mask
else:
return np.sum(self.mask, axis=0)
+ @property
+ def pixelscale(self):
+ """Physical sampling of each pixel in the plane
+
+ Returns
+ -------
+ tuple of ints or None
+
+ """
+ return self._pixelscale
+
+ @pixelscale.setter
+ def pixelscale(self, value):
+ self._pixelscale = None if value is None else np.broadcast_to(value, (2,))
+
@property
def diameter(self):
+ """Plane diameter
+
+ Notes
+ -----
+ If :attr:`diameter` was no provided during Plane creation, it is
+ autocomputed if possible. If it is not possible, None is returned.
+
+ Returns
+ -------
+ float or None
+
+ """
if self._diameter is None:
[rmin, rmax, cmin, cmax] = lentil.boundary(self.global_mask)
# since pixelscale has shape=(2,), we need to return the overall
@@ -114,22 +175,27 @@ def shape(self):
"""
Plane dimensions computed from :attr:`mask`.
- Returns (mask.shape[1], mask.shape[2]) if mask has ``ndim == 3``. Returns
+ Returns (mask.shape[1], mask.shape[2]) if :attr:`size: > 1. Returns
None if :attr:`mask` is None.
+
+ Returns
+ -------
+ tuple of ints
"""
- if self.depth == 1:
+ if self.size == 1:
return self.mask.shape
else:
return self.mask.shape[1], self.mask.shape[2]
@property
- def depth(self):
+ def size(self):
"""
Number of independent masks (segments) in :attr:`mask`
Returns
-------
- depth : int
+ int
+
"""
if self.mask.ndim in (0, 1, 2):
return 1
@@ -141,11 +207,11 @@ def ptt_vector(self):
"""
2D vector representing piston and tilt in x and y.
- Planes with no mask have ``ptt_vector = None``.
+ Planes with no mask have :attr:`ptt_vector` = None.
Returns
-------
- ptt_vector : ndarray or None
+ ndarray or None
"""
# if there's no mask, we just set ptt_vector to None and move on
@@ -164,14 +230,14 @@ def ptt_vector(self):
unmasked_ptt_vector = np.einsum('ij,i->ij', [np.ones(r.size), r.ravel(), -c.ravel()],
[1, self.pixelscale[0], self.pixelscale[1]])
- if self.depth == 1:
+ if self.size == 1:
ptt_vector = np.einsum('ij,j->ij', unmasked_ptt_vector, self.mask.ravel())
else:
# prepare empty ptt_vector
- ptt_vector = np.empty((self.depth * 3, np.prod(self.shape)))
+ ptt_vector = np.empty((self.size * 3, np.prod(self.shape)))
# loop over the masks and fill in the masked ptt_vectors
- for mask in np.arange(self.depth):
+ for mask in np.arange(self.size):
ptt_vector[3*mask:3*mask+3] = unmasked_ptt_vector * self.mask[mask].ravel()
return ptt_vector
@@ -182,7 +248,7 @@ def copy(self):
Returns
-------
- copy : :class:`~lentil.Plane`
+ :class:`~lentil.Plane`
"""
return copy.deepcopy(self)
@@ -199,7 +265,7 @@ def fit_tilt(self, inplace=False):
Returns
-------
- plane : :class:`~lentil.Plane`
+ :class:`~lentil.Plane`
"""
if inplace:
plane = self
@@ -213,31 +279,30 @@ def fit_tilt(self, inplace=False):
if ptt_vector is None or plane.phase.size == 1:
return plane
- if self.depth == 1:
+ if self.size == 1:
t = np.linalg.lstsq(ptt_vector.T, plane.phase.ravel(), rcond=None)[0]
phase_tilt = np.einsum('ij,i->j', ptt_vector[1:3], t[1:3])
plane.phase -= phase_tilt.reshape(plane.phase.shape)
plane.tilt.append(Tilt(x=t[1], y=t[2]))
else:
- t = np.empty((self.depth, 3))
- phase_no_tilt = np.empty((self.depth, plane.phase.shape[0], plane.phase.shape[1]))
+ t = np.empty((self.size, 3))
+ phase_no_tilt = np.empty((self.size, plane.phase.shape[0], plane.phase.shape[1]))
# iterate over the segments and compute the tilt term
- for seg in np.arange(self.depth):
+ for seg in np.arange(self.size):
t[seg] = np.linalg.lstsq(ptt_vector[3 * seg:3 * seg + 3].T, plane.phase.ravel(),
rcond=None)[0]
seg_tilt = np.einsum('ij,i->j', ptt_vector[3 * seg + 1:3 * seg + 3], t[seg, 1:3])
phase_no_tilt[seg] = (plane.phase - seg_tilt.reshape(plane.phase.shape)) * self.mask[seg]
plane.phase = np.sum(phase_no_tilt, axis=0)
- plane.tilt.extend([Tilt(x=t[seg, 1], y=t[seg, 2]) for seg in range(self.depth)])
+ plane.tilt.extend([Tilt(x=t[seg, 1], y=t[seg, 2]) for seg in range(self.size)])
return plane
def rescale(self, scale):
- """
- Rescale a plane via interpolation.
+ """Rescale a plane via interpolation.
The following Plane attributes are resampled:
@@ -257,8 +322,8 @@ def rescale(self, scale):
-------
plane : :class:`Plane`
- Note
- ----
+ Notes
+ -----
All interpolation is performed via `scipy.ndimage.map_coordinates`
See Also
@@ -318,13 +383,13 @@ def resample(self, pixelscale):
plane : :class:`Plane`
Resampled Plane.
- Note
- ----
+ Notes
+ -----
All interpolation is performed via `scipy.ndimage.map_coordinates`
See Also
--------
- * :func:`Plane.rescale`
+ Plane.rescale
"""
if not self.pixelscale:
@@ -342,8 +407,8 @@ def multiply(self, wavefront):
wavefront : :class:`~lentil.wavefront.Wavefront` object
Wavefront to be multiplied
- Note
- ----
+ Notes
+ -----
It is possible to customize the way multiplication is performed by
creating a subclass and overloading its ``multiply`` method.
@@ -374,7 +439,7 @@ def multiply(self, wavefront):
for n, s in enumerate(self._slice):
# We have to multiply amplitude[s] by mask[n][s] because the requested
# slice of the amplitude array may contain parts of adjacent segments
- mask = self.mask if self.depth == 1 else self.mask[n]
+ mask = self.mask if self.size == 1 else self.mask[n]
amp = self.amplitude if self.amplitude.size == 1 else self.amplitude[s] * mask[s]
phase = self.phase if self.phase.size == 1 else self.phase[s]
@@ -459,11 +524,12 @@ def _plane_slice(mask):
slices : list
List of slices corresponding to the data extent defined by ``mask``.
- See also
+ See Also
--------
- * :func:`~lentil.helper.boundary_slice`
- * :func:`~lentil.Plane.slice_offset`
- """
+ helper.boundary_slice
+ Plane.slice_offset
+
+ """
# self.mask may still return None so we catch that here
if mask is None:
@@ -507,8 +573,8 @@ class Pupil(Plane):
.. plot:: _img/python/segmask.py
:scale: 50
- Note
- ----
+ Notes
+ -----
By definition, a pupil is represented by a spherical wavefront. Any
aberrations in the optical system appear as deviations from this perfect
sphere. The primary use of :class:`Pupil` is to represent these aberrations
@@ -548,8 +614,8 @@ class Image(Plane):
:class:`Image` is assumed to be square with nrows = ncols = shape.
Default is None.
- Note
- ----
+ Notes
+ -----
If image plane intensity is desired, significant performance improvements
can be realized by using a :class:`Detector` plane instead.
@@ -600,7 +666,7 @@ class Detector(Image):
----------
pixelscale : float, optional
Pixel size in meters. Pixels are assumed to be square. Default is None.
- shape : {int, (2,) array_like}, optional
+ shape : tuple of ints, optional
Number of pixels as (rows, cols). If a single value is provided,
:class:`Image` is assumed to be square with nrows = ncols = shape.
Default is None.
@@ -613,10 +679,25 @@ class Detector(Image):
pass
-class TiltInterfcace(Plane):
- # Utility class for holding some common logic shared by
- # classes that implement the Tilt interface
+class TiltInterface(Plane):
+ """Utility class for holding common lofic shared by classes that need to
+ implement the tilt interface.
+
+ Other Parameters
+ ----------------
+ **kwargs : :class:`~lentil.Plane` parameters
+ Keyword arguments passed to :class:`~lentil.Plane` constructor
+ Notes
+ -----
+ If :attr:`ptype` is not provided, it defaults to `lentil.tilt`.
+
+ See Also
+ --------
+ Tilt
+ DispersiveTilt
+
+ """
def __init__(self, **kwargs):
# if ptype is provided as a kwarg use that, otherwise default
# to lentil.tilt
@@ -626,16 +707,37 @@ def __init__(self, **kwargs):
super().__init__(ptype=ptype, **kwargs)
def multiply(self, wavefront):
+ """Multiply with a wavefront. This is a custom implementation
+ supporting the tilt interface.
+
+ Notes
+ -----
+ This method performs the following actions:
+
+ .. code:: python
+
+ wavefront = super().multiply(wavefront)
+ for field in wavefront.data:
+ field.tilt.append(self)
+ return wavefront
+
+ Returns
+ -------
+ :class:`~lentil.Wavefront`
+
+ """
wavefront = super().multiply(wavefront)
for field in wavefront.data:
field.tilt.append(self)
return wavefront
def shift(self, wavelength, x0, y0, **kwargs):
+ """TODO
+ """
raise NotImplementedError
-class Tilt(TiltInterfcace):
+class Tilt(TiltInterface):
"""Object for representing tilt in terms of angle
Parameters
@@ -674,7 +776,7 @@ def shift(self, xs=0, ys=0, z=0, **kwargs):
return x, y
-class DispersiveTilt(TiltInterfcace):
+class DispersiveTilt(TiltInterface):
r"""Class for representing spectral dispersion that appears as a tilt.
Light is dispersed along a line called the the spectral trace. The position
@@ -883,8 +985,8 @@ class Grism(DispersiveTilt):
and should return units of meters of wavelength provided an input distance
along the spectral trace.
- Note
- ----
+ Notes
+ -----
Lentil supports trace and dispersion functions with any arbitrary polynomial
order. While a simple analytic solution exists for modeling first-order trace
and/or dispersion, there is no general solution for higher order functions.
@@ -936,8 +1038,8 @@ class Rotate(Plane):
The order of the spline interpolation (if needed), default is 3. The
order has to be in the range 0-5.
- Note
- ----
+ Notes
+ -----
If the angle is an even multiple of 90 degrees, ``numpy.rot90`` is used to
perform the rotation rather than ``scipy.ndimage.rotate``. In this case,
the order parameter is irrelevant because no interpolation occurs.
diff --git a/lentil/wavefront.py b/lentil/wavefront.py
index 772f597..3c6aade 100644
--- a/lentil/wavefront.py
+++ b/lentil/wavefront.py
@@ -23,33 +23,32 @@ class Wavefront:
focal_length : float or np.inf, optional
Wavefront focal length. A plane wave (default) has an infinite focal
length (``np.inf``).
- tilt: list_like, optional
-
- shape : (2,) array_like
+ tilt: (2,) array_like, optional
+ Radians of wavefront tilt about the x and y axes provided as
+ ``[rx, ry]``. Default is ``[0, 0]`` (no tilt).
+ shape : (2,) array_like, optional
Wavefront shape. If ``shape`` is None (default), the wavefront is
assumed to be infinite (broadcastable to any shape).
- ptype : lentil.ptype
- Plane type.
-
- Attributes
- ----------
- data : list_like
- Wavefront data. Default is [1+0j] (a plane wave).
+ ptype : lentil.ptype, optional
+ Plane type. Default is ``lentil.none``.
"""
- __slots__ = ('wavelength', 'pixelscale', 'focal_length', 'diameter',
- 'focal_length', '_ptype', 'shape', 'data')
-
def __init__(self, wavelength, pixelscale=None, diameter=None, focal_length=None,
tilt=None, ptype=None):
-
- self.wavelength = wavelength
- self.pixelscale = None if pixelscale is None else np.broadcast_to(pixelscale, (2,))
+
+ #: float: Wavefront focal length
self.focal_length = focal_length if focal_length else np.inf
+
+ #: float: Wavefront diameter
self.diameter = diameter
- self.ptype = lentil.ptype(ptype)
+
+ #: tuple of ints: Wavefront shape
self.shape = ()
+ self._wavelength = wavelength
+ self._pixelscale = None if pixelscale is None else np.broadcast_to(pixelscale, (2,))
+ self.ptype = lentil.ptype(ptype)
+
if tilt is not None:
if len(tilt) != 2:
raise ValueError('tilt must be specified as [rx, ry]')
@@ -65,8 +64,34 @@ def __mul__(self, plane):
def __rmul__(self, other):
return self.__mul__(other)
+ @property
+ def wavelength(self):
+ """Wavefront wavelength
+
+ Returns
+ -------
+ float
+ """
+ return self._wavelength
+
+ @property
+ def pixelscale(self):
+ """Physical sampling of wavefront
+
+ Returns
+ -------
+ tuple of floats
+ """
+ return self._pixelscale
+
@property
def ptype(self):
+ """Wavefront plane type
+
+ Returns
+ -------
+ ptype object
+ """
return self._ptype
@ptype.setter
@@ -78,7 +103,12 @@ def ptype(self, value):
@property
def field(self):
- """Wavefront complex field"""
+ """Wavefront complex field
+
+ Returns
+ -------
+ ndarray
+ """
out = np.zeros(self.shape, dtype=complex)
for field in self.data:
out = lentil.field.insert(field, out)
@@ -86,7 +116,12 @@ def field(self):
@property
def intensity(self):
- """Wavefront intensity"""
+ """Wavefront intensity
+
+ Returns
+ -------
+ ndarray
+ """
out = np.zeros(self.shape, dtype=float)
for field in lentil.field.reduce(self.data):
out = lentil.field.insert(field, out, intensity=True)
@@ -95,14 +130,20 @@ def intensity(self):
@classmethod
def empty(cls, wavelength, pixelscale=None, diameter=None, focal_length=None,
tilt=None, shape=None, ptype=None):
+ """Create an empty Wavefront
+
+ The resulting wavefront will have an empty :attr:`data` attribute.
+
+ Parameters
+ ----------
+
+ """
w = cls(wavelength=wavelength, pixelscale=pixelscale, diameter=diameter,
focal_length=focal_length, tilt=tilt, ptype=ptype)
w.data = []
w.shape = () if shape is None else shape
return w
-
- def copy(self):
- return copy.deepcopy(self)
+
def insert(self, out, weight=1):
"""Directly insert wavefront intensity data into an output array.
]