Skip to content

Commit

Permalink
Merge pull request #28 from IvoVellekoop/further-revisions
Browse files Browse the repository at this point in the history
Further revisions
  • Loading branch information
JeroenDoornbos authored Oct 3, 2024
2 parents 72ac933 + df6e858 commit c8169aa
Show file tree
Hide file tree
Showing 41 changed files with 446 additions and 892 deletions.
18 changes: 13 additions & 5 deletions .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,23 @@ jobs:
python-version: ["3.9", "3.10", "3.11"]

steps:
- uses: actions/checkout@v4
- name: Check out the code
uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies

- name: Install Poetry
run: |
python -m pip install --upgrade pip
pip install .[dev]
- name: Test with pytest
pip install poetry
- name: Install dependencies
run: |
poetry install --with dev
- name: Run tests
run: |
pytest
poetry run pytest
123 changes: 73 additions & 50 deletions README.md

Large diffs are not rendered by default.

20 changes: 5 additions & 15 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,9 @@
# basic project information
project = "OpenWFS"
copyright = "2023-, Ivo Vellekoop, Daniël W. S. Cox, and Jeroen H. Doornbos, University of Twente"
author = (
"Jeroen H. Doornbos, Daniël W. S. Cox, Tom Knop, Harish Sasikumar, Ivo M. Vellekoop"
)
author = "Jeroen H. Doornbos, Daniël W. S. Cox, Tom Knop, Harish Sasikumar, Ivo M. Vellekoop"
release = "0.1.0rc2"
html_title = (
"OpenWFS - a library for conducting and simulating wavefront shaping experiments"
)
html_title = "OpenWFS - a library for conducting and simulating wavefront shaping experiments"
# \renewenvironment{sphinxtheindex}{\setbox0\vbox\bgroup\begin{theindex}}{\end{theindex}}

# latex configuration
Expand Down Expand Up @@ -167,23 +163,17 @@ def setup(app):
def source_read(app, docname, source):
if docname == "readme" or docname == "conclusion":
if (app.builder.name == "latex") == (docname == "conclusion"):
source[0] = source[0].replace(
"%endmatter%", ".. include:: acknowledgements.rst"
)
source[0] = source[0].replace("%endmatter%", ".. include:: acknowledgements.rst")
else:
source[0] = source[0].replace("%endmatter%", "")


def builder_inited(app):
if app.builder.name == "html":
exclude_patterns.extend(
["conclusion.rst", "index_latex.rst", "index_markdown.rst"]
)
exclude_patterns.extend(["conclusion.rst", "index_latex.rst", "index_markdown.rst"])
app.config.master_doc = "index"
elif app.builder.name == "latex":
exclude_patterns.extend(
["auto_examples/*", "index_markdown.rst", "index.rst", "api*"]
)
exclude_patterns.extend(["auto_examples/*", "index_markdown.rst", "index.rst", "api*"])
app.config.master_doc = "index_latex"
elif app.builder.name == "markdown":
include_patterns.clear()
Expand Down
27 changes: 10 additions & 17 deletions docs/source/readme.rst
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
.. _root-label:

OpenWFS
=====================================

..
NOTE: README.MD IS AUTO-GENERATED FROM DOCS/SOURCE/README.RST. DO NOT EDIT README.MD DIRECTLY.
.. only:: html

.. image:: https://readthedocs.org/projects/openwfs/badge/?version=latest
:target: https://openwfs.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status

.. only:: markdown

.. raw:: html

[![PyTest Status](https://github.com/IvoVellekoop/openwfs/actions/workflows/pytest.yml/badge.svg)](https://github.com/IvoVellekoop/openwfs/actions/workflows/pytest.yml)
[![Black Code Style Status](https://github.com/IvoVellekoop/openwfs/actions/workflows/black.yml/badge.svg)](https://github.com/IvoVellekoop/openwfs/actions/workflows/black.yml)</p>

[![PyTest](https://github.com/IvoVellekoop/openwfs/actions/workflows/pytest.yml/badge.svg)](https://github.com/IvoVellekoop/openwfs/actions/workflows/pytest.yml)
[![Black](https://github.com/IvoVellekoop/openwfs/actions/workflows/black.yml/badge.svg)](https://github.com/IvoVellekoop/openwfs/actions/workflows/black.yml)

..
NOTE: README.MD IS AUTO-GENERATED FROM DOCS/SOURCE/README.RST. DO NOT EDIT README.MD DIRECTLY.
..
What is wavefront shaping?
--------------------------------

Expand Down
4 changes: 1 addition & 3 deletions examples/slm_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,7 @@
p4.phases = 1
p4.additive_blend = False

pf.phases = patterns.lens(
100, f=1 * u.m, wavelength=0.8 * u.um, extent=(10 * u.mm, 10 * u.mm)
)
pf.phases = patterns.lens(100, f=1 * u.m, wavelength=0.8 * u.um, extent=(10 * u.mm, 10 * u.mm))
rng = np.random.default_rng()
for n in range(200):
random_data = rng.random([10, 10], np.float32) * 2.0 * np.pi
Expand Down
4 changes: 1 addition & 3 deletions examples/troubleshooter_demo.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,5 @@
roi_background = SingleRoi(cam, radius=10)

# Run WFS troubleshooter and output a report to the console
trouble = troubleshoot(
algorithm=alg, background_feedback=roi_background, frame_source=cam, shutter=shutter
)
trouble = troubleshoot(algorithm=alg, background_feedback=roi_background, frame_source=cam, shutter=shutter)
trouble.report()
4 changes: 2 additions & 2 deletions openwfs/algorithms/basic_fourier.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,14 @@ def __init__(
self.k_step = k_step
self._slm_shape = slm_shape
group_mask = np.zeros(slm_shape, dtype=bool)
group_mask[:, slm_shape[1] // 2:] = True
group_mask[:, slm_shape[1] // 2 :] = True
super().__init__(
feedback=feedback,
slm=slm,
phase_patterns=None,
group_mask=group_mask,
phase_steps=phase_steps,
amplitude='uniform',
amplitude="uniform",
iterations=iterations,
optimized_reference=optimized_reference,
analyzer=analyzer,
Expand Down
19 changes: 5 additions & 14 deletions openwfs/algorithms/custom_iter_dual_reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,7 @@ def __init__(
A, B, A, B, A. Should be at least 2
analyzer: The function used to analyze the phase stepping data. Must return a WFSResult object. Defaults to `analyze_phase_stepping`
"""
if (phase_patterns[0].shape[0:2] != group_mask.shape) or (
phase_patterns[1].shape[0:2] != group_mask.shape
):
if (phase_patterns[0].shape[0:2] != group_mask.shape) or (phase_patterns[1].shape[0:2] != group_mask.shape):
raise ValueError("The phase patterns and group mask must all have the same shape.")
if iterations < 2:
raise ValueError("The number of iterations must be at least 2.")
Expand All @@ -93,8 +91,7 @@ def __init__(

# Pre-compute the conjugate modes for reconstruction
self.modes = [
np.exp(-1j * self.phase_patterns[side]) * np.expand_dims(self.masks[side], axis=2)
for side in range(2)
np.exp(-1j * self.phase_patterns[side]) * np.expand_dims(self.masks[side], axis=2) for side in range(2)
]

def execute(self, capture_intermediate_results: bool = False, progress_bar=None) -> WFSResult:
Expand Down Expand Up @@ -123,9 +120,7 @@ def execute(self, capture_intermediate_results: bool = False, progress_bar=None)
None,
None,
] # The two latest results. Used for computing fidelity factors.
intermediate_results = np.zeros(
self.iterations
) # List to store feedback from full patterns
intermediate_results = np.zeros(self.iterations) # List to store feedback from full patterns

# Prepare progress bar
if progress_bar:
Expand All @@ -138,9 +133,7 @@ def execute(self, capture_intermediate_results: bool = False, progress_bar=None)
# Switch the phase sets back and forth multiple times
for it in range(self.iterations):
side = it % 2 # pick set A or B for phase stepping
ref_phases = -np.angle(
t_full
) # use the best estimate so far to construct an optimized reference
ref_phases = -np.angle(t_full) # use the best estimate so far to construct an optimized reference
side_mask = self.masks[side]
# Perform WFS experiment on one side, keeping the other side sized at the ref_phases
result = self._single_side_experiment(
Expand Down Expand Up @@ -201,9 +194,7 @@ def execute(self, capture_intermediate_results: bool = False, progress_bar=None)
result.intermediate_results = intermediate_results
return result

def _single_side_experiment(
self, mod_phases: nd, ref_phases: nd, mod_mask: nd, progress_bar=None
) -> WFSResult:
def _single_side_experiment(self, mod_phases: nd, ref_phases: nd, mod_mask: nd, progress_bar=None) -> WFSResult:
"""
Conducts experiments on one part of the SLM.
Expand Down
62 changes: 18 additions & 44 deletions openwfs/algorithms/dual_reference.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import Optional
from typing import Optional, Union

import numpy as np
from numpy import ndarray as nd
Expand Down Expand Up @@ -38,7 +38,7 @@ def __init__(
feedback: Detector,
slm: PhaseSLM,
phase_patterns: Optional[tuple[nd, nd]],
amplitude: Optional[tuple[nd, nd] | str],
amplitude: Optional[Union[tuple[nd, nd], str]],
group_mask: nd,
phase_steps: int = 4,
iterations: int = 2,
Expand Down Expand Up @@ -89,9 +89,7 @@ def __init__(
if iterations < 2:
raise ValueError("The number of iterations must be at least 2.")
if not optimized_reference and iterations != 2:
raise ValueError(
"When not using an optimized reference, the number of iterations must be 2."
)
raise ValueError("When not using an optimized reference, the number of iterations must be 2.")

self.slm = slm
self.feedback = feedback
Expand All @@ -108,7 +106,7 @@ def __init__(
~mask,
mask,
) # self.masks[0] is True for group A, self.masks[1] is True for group B
self.amplitude = amplitude # Note: when 'uniform' is passed, the shape of self.masks[0] is used.
self.amplitude = amplitude # Note: when 'uniform' is passed, the shape of self.masks[0] is used.
self.phase_patterns = phase_patterns

@property
Expand All @@ -121,19 +119,17 @@ def amplitude(self, value):
self._amplitude = None
return

if value == 'uniform':
if value == "uniform":
self._amplitude = tuple(
(np.ones(shape=self._shape) / np.sqrt(self.masks[side].sum())).astype(np.float32) for side in range(2))
(np.ones(shape=self._shape) / np.sqrt(self.masks[side].sum())).astype(np.float32) for side in range(2)
)
return

if value[0].shape != self._shape or value[1].shape != self._shape:
raise ValueError(
"The amplitude and group mask must all have the same shape."
)
raise ValueError("The amplitude and group mask must all have the same shape.")

self._amplitude = value


@property
def phase_patterns(self) -> tuple[nd, nd]:
return self._phase_patterns
Expand All @@ -148,26 +144,14 @@ def phase_patterns(self, value):
if not self.optimized_reference:
# find the modes in A and B that correspond to flat wavefronts with phase 0
try:
a0_index = next(
i
for i in range(value[0].shape[2])
if np.allclose(value[0][:, :, i], 0)
)
b0_index = next(
i
for i in range(value[1].shape[2])
if np.allclose(value[1][:, :, i], 0)
)
a0_index = next(i for i in range(value[0].shape[2]) if np.allclose(value[0][:, :, i], 0))
b0_index = next(i for i in range(value[1].shape[2]) if np.allclose(value[1][:, :, i], 0))
self.zero_indices = (a0_index, b0_index)
except StopIteration:
raise (
"For multi-target optimization, the both sets must contain a flat wavefront with phase 0."
)
raise ("For multi-target optimization, the both sets must contain a flat wavefront with phase 0.")

if (value[0].shape[0:2] != self._shape) or (value[1].shape[0:2] != self._shape):
raise ValueError(
"The phase patterns and group mask must all have the same shape."
)
raise ValueError("The phase patterns and group mask must all have the same shape.")

self._phase_patterns = (
value[0].astype(np.float32),
Expand Down Expand Up @@ -200,7 +184,7 @@ def _compute_cobasis(self):
denotes the matrix inverse, and ⁺ denotes the Moore-Penrose pseudo-inverse.
"""
if self.phase_patterns is None:
raise('The phase_patterns must be set before computing the cobasis.')
raise ("The phase_patterns must be set before computing the cobasis.")

cobasis = [None, None]
for side in range(2):
Expand All @@ -215,9 +199,7 @@ def _compute_cobasis(self):

self._cobasis = cobasis

def execute(
self, *, capture_intermediate_results: bool = False, progress_bar=None
) -> WFSResult:
def execute(self, *, capture_intermediate_results: bool = False, progress_bar=None) -> WFSResult:
"""
Executes the blind focusing dual reference algorithm and compute the SLM transmission matrix.
capture_intermediate_results: When True, measures the feedback from the optimized wavefront after each iteration.
Expand All @@ -237,9 +219,7 @@ def execute(

# Initialize storage lists
results_all = [None] * self.iterations # List to store all results
intermediate_results = np.zeros(
self.iterations
) # List to store feedback from full patterns
intermediate_results = np.zeros(self.iterations) # List to store feedback from full patterns

# Prepare progress bar
if progress_bar:
Expand Down Expand Up @@ -284,9 +264,7 @@ def execute(
relative = results_all[0].t[self.zero_indices[0], ...] + np.conjugate(
results_all[1].t[self.zero_indices[1], ...]
)
factor = (relative / np.abs(relative)).reshape(
(1, *self.feedback.data_shape)
)
factor = (relative / np.abs(relative)).reshape((1, *self.feedback.data_shape))

t_full = self.compute_t_set(results_all[0].t, self.cobasis[0]) + self.compute_t_set(
factor * results_all[1].t, self.cobasis[1]
Expand All @@ -304,9 +282,7 @@ def execute(
result.intermediate_results = intermediate_results
return result

def _single_side_experiment(
self, mod_phases: nd, ref_phases: nd, mod_mask: nd, progress_bar=None
) -> WFSResult:
def _single_side_experiment(self, mod_phases: nd, ref_phases: nd, mod_mask: nd, progress_bar=None) -> WFSResult:
"""
Conducts experiments on one part of the SLM.
Expand All @@ -322,9 +298,7 @@ def _single_side_experiment(
WFSResult: An object containing the computed SLM transmission matrix and related data.
"""
num_modes = mod_phases.shape[2]
measurements = np.zeros(
(num_modes, self.phase_steps, *self.feedback.data_shape)
)
measurements = np.zeros((num_modes, self.phase_steps, *self.feedback.data_shape))

for m in range(num_modes):
phases = ref_phases.copy()
Expand Down
4 changes: 1 addition & 3 deletions openwfs/algorithms/genetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,7 @@ def __init__(
self.elite_size = elite_size
self.generations = generations
self.generator = generator or np.random.default_rng()
self.mutation_count = round(
(population_size - elite_size) * np.prod(shape) * mutation_probability
)
self.mutation_count = round((population_size - elite_size) * np.prod(shape) * mutation_probability)

def _generate_random_phases(self, shape):
return self.generator.random(size=shape, dtype=np.float32) * (2 * np.pi)
Expand Down
4 changes: 1 addition & 3 deletions openwfs/algorithms/ssa.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,7 @@ def execute(self) -> WFSResult:
WFSResult: An object containing the computed transmission matrix and statistics.
"""
phase_pattern = np.zeros((self.n_y, self.n_x), "float32")
measurements = np.zeros(
(self.n_y, self.n_x, self.phase_steps, *self.feedback.data_shape)
)
measurements = np.zeros((self.n_y, self.n_x, self.phase_steps, *self.feedback.data_shape))

for y in range(self.n_y):
for x in range(self.n_x):
Expand Down
Loading

0 comments on commit c8169aa

Please sign in to comment.