Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into further-revisions
Browse files Browse the repository at this point in the history
# Conflicts:
#	examples/sample_microscope.py
#	openwfs/algorithms/custom_iter_dual_reference.py
  • Loading branch information
JeroenDoornbos committed Oct 3, 2024
2 parents 0eb7617 + 72ac933 commit 5921f8d
Show file tree
Hide file tree
Showing 8 changed files with 59 additions and 98 deletions.
60 changes: 16 additions & 44 deletions examples/micro_manager_microscope.py
Original file line number Diff line number Diff line change
@@ -1,41 +1,30 @@
""" Sample microscope
""" Micro-Manager simulated microscope
=======================
This script simulates a microscopic imaging system, generating a random noise image as a mock source and capturing it
through a microscope with adjustable magnification, numerical aperture, and wavelength. It visualizes the original and
processed images dynamically, demonstrating how changes in optical parameters affect image quality and resolution.
This script simulates a microscope with a random noise image as a mock specimen.
The numerical aperture, stage position, and other parameters can be modified through the Micro-Manager GUI.
To use this script as a device in Micro-Manager, make sure you have the PyDevice adapter installed and
select this script in the hardware configuration wizard for the PyDevice component.
This script should be opened from the μManager microscope GUI software using the PyDevice plugin.
To do so, add a PyDevice adapter to the μManager hardware configuration, and select this script as the device script.
See the 'Sample Microscope' example for a microscope simulation that runs from Python directly.
"""

import astropy.units as u
import numpy as np

from openwfs.simulation import Microscope, StaticSource

# height × width, and resolution, of the specimen image
specimen_size = (1024, 1024)
specimen_resolution = 60 * u.nm

# magnification from object plane to camera.
magnification = 40

# numerical aperture of the microscope objective
numerical_aperture = 0.85

# wavelength of the light, for computing diffraction.
wavelength = 532.8 * u.nm

# Size of the pixels on the camera
pixel_size = 6.45 * u.um

# number of pixels on the camera
camera_resolution = (256, 256)
specimen_resolution = (1024, 1024) # height × width in pixels of the specimen image
specimen_pixel_size = 60 * u.nm # resolution (pixel size) of the specimen image
magnification = 40 # magnification from object plane to camera.
numerical_aperture = 0.85 # numerical aperture of the microscope objective
wavelength = 532.8 * u.nm # wavelength of the light, for computing diffraction.
camera_resolution = (256, 256) # number of pixels on the camera
camera_pixel_size = 6.45 * u.um # Size of the pixels on the camera

# Create a random noise image with a few bright spots
src = StaticSource(
data=np.maximum(np.random.randint(-10000, 100, specimen_size, dtype=np.int16), 0),
pixel_size=specimen_resolution,
data=np.maximum(np.random.randint(-10000, 100, specimen_resolution, dtype=np.int16), 0),
pixel_size=specimen_pixel_size,
)

# Create a microscope with the given parameters
Expand All @@ -51,25 +40,8 @@
shot_noise=True,
digital_max=255,
data_shape=camera_resolution,
pixel_size=pixel_size,
pixel_size=camera_pixel_size,
)

# expose the xy-stage of the microscope
stage = mic.xy_stage

# construct dictionary of objects to expose to Micro-Manager
devices = {"camera": cam, "stage": stage}

if __name__ == "__main__":
# When running this script directly (not from μManager)
# the code below shows how to operate the stage and change the numerical aperture of the microscope
import matplotlib.pyplot as plt

for i in range(20):
stage.x = stage.x + 2 * u.um
mic.numerical_aperture -= 0.025
plt.imshow(mic.read(), cmap="gray")
if i == 0:
plt.colorbar()
plt.show(block=False)
plt.pause(0.5)
13 changes: 7 additions & 6 deletions examples/micro_manager_scanning_microscope.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
"""
Constructs a scanning microscope controller for use with Micro-Manager
The microscope object can be loaded into Micro-Manager through the PyDevice
device adapter.
""" Micro-Manager simulated scanning microscope
=======================
This script simulates a scanning microscope with a pre-set image as a mock specimen.
The scan parameters can be modified through the Micro-Manager GUI.
To use this script as a device in Micro-Manager, make sure you have the PyDevice adapter installed and
select this script in the hardware configuration wizard for the PyDevice component.
"""

import astropy.units as u
import skimage

# add 'openwfs' to the search path. This is only needed when developing openwfs
# otherwise it is just installed as a package
import set_path # noqa
import skimage
from openwfs.devices import ScanningMicroscope, Axis
from openwfs.devices.galvo_scanner import InputChannel

Expand Down
51 changes: 17 additions & 34 deletions examples/sample_microscope.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,40 +11,23 @@
import set_path # noqa - needed for setting the module search path to find openwfs
from openwfs.plot_utilities import grab_and_show, imshow
from openwfs.simulation import Microscope, StaticSource
from openwfs.utilities import set_pixel_size

# Parameters that can be altered

img_size_x = 1024
# Determines how wide the image is.

img_size_y = 1024
# Determines how high the image is.

magnification = 40
# magnification from object plane to camera.

numerical_aperture = 0.85
# numerical aperture of the microscope objective

wavelength = 532.8 * u.nm
# wavelength of the light, different wavelengths are possible, units can be adjusted accordingly.

pixel_size = 6.45 * u.um
# Size of the pixels on the camera

camera_resolution = (256, 256)
# number of pixels on the camera

p_limit = 100
# Number of iterations. Influences how quick the 'animation' is complete.

# Code
img = set_pixel_size(
np.maximum(np.random.randint(-10000, 100, (img_size_y, img_size_x), dtype=np.int16), 0),
60 * u.nm,
specimen_resolution = (1024, 1024) # height × width in pixels of the specimen image
specimen_pixel_size = 60 * u.nm # resolution (pixel size) of the specimen image
magnification = 40 # magnification from object plane to camera.
numerical_aperture = 0.85 # numerical aperture of the microscope objective
wavelength = 532.8 * u.nm # wavelength of the light, for computing diffraction.
camera_resolution = (256, 256) # number of pixels on the camera
camera_pixel_size = 6.45 * u.um # Size of the pixels on the camera
p_limit = 100 # Number steps in the animation

# Create a random noise image with a few bright spots
src = StaticSource(
data=np.maximum(np.random.randint(-10000, 100, specimen_resolution, dtype=np.int16), 0),
pixel_size=specimen_pixel_size,
)
src = StaticSource(img)

# Create a microscope with the given parameters
mic = Microscope(
src,
magnification=magnification,
Expand All @@ -57,15 +40,15 @@
shot_noise=True,
digital_max=255,
data_shape=camera_resolution,
pixel_size=pixel_size,
pixel_size=camera_pixel_size,
)
devices = {"camera": cam, "stage": mic.xy_stage}

if __name__ == "__main__":
import matplotlib.pyplot as plt

plt.subplot(1, 2, 1)
imshow(img)
imshow(src.data)
plt.title("Original image")
plt.subplot(1, 2, 2)
plt.title("Scanned image")
Expand Down
6 changes: 3 additions & 3 deletions examples/wfs_demonstration_experimental.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
WFS demo experiment
=====================
This script demonstrates how to perform a wavefront shaping experiment using the openwfs library.
It assumes that you have a genicam camera and an SLM connected to your computer.
Please adjust the path to the camera driver and (when needed) the monitor id in the Camera and SLM objects.
"""

import astropy.units as u
Expand Down Expand Up @@ -42,6 +45,3 @@
plt.pause(1.0)
slm.set_phases(0.0)
plt.pause(1.0)

# plt.show()
# input("press any key")
16 changes: 12 additions & 4 deletions openwfs/algorithms/custom_iter_dual_reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,10 @@ def __init__(
self.phase_steps = phase_steps
self.iterations = iterations
self.analyzer = analyzer
self.phase_patterns = (phase_patterns[0].astype(np.float32), phase_patterns[1].astype(np.float32))
self.phase_patterns = (
phase_patterns[0].astype(np.float32),
phase_patterns[1].astype(np.float32),
)
mask = group_mask.astype(bool)
self.masks = (~mask, mask) # masks[0] is True for group A, mask[1] is True for group B

Expand Down Expand Up @@ -113,7 +116,10 @@ def execute(self, capture_intermediate_results: bool = False, progress_bar=None)
# Initialize storage lists
t_set_all = [None] * self.iterations
results_all = [None] * self.iterations # List to store all results
results_latest = [None, None] # The two latest results. Used for computing fidelity factors.
results_latest = [
None,
None,
] # The two latest results. Used for computing fidelity factors.
intermediate_results = np.zeros(self.iterations) # List to store feedback from full patterns

# Prepare progress bar
Expand Down Expand Up @@ -155,7 +161,10 @@ def execute(self, capture_intermediate_results: bool = False, progress_bar=None)

# Compute average fidelity factors
fidelity_noise = weighted_average(
results_latest[0].fidelity_noise, results_latest[1].fidelity_noise, results_latest[0].n, results_latest[1].n
results_latest[0].fidelity_noise,
results_latest[1].fidelity_noise,
results_latest[0].n,
results_latest[1].n,
)
fidelity_amplitude = weighted_average(
results_latest[0].fidelity_amplitude,
Expand All @@ -172,7 +181,6 @@ def execute(self, capture_intermediate_results: bool = False, progress_bar=None)

result = WFSResult(
t=t_full,
t_f=None,
n=self.modes[0].shape[2] + self.modes[1].shape[2],
axis=2,
fidelity_noise=fidelity_noise,
Expand Down
2 changes: 1 addition & 1 deletion openwfs/algorithms/genetic.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def execute(self, *, progress_bar=None) -> WFSResult:

# Terminate after the specified number of generations, return the best wavefront
if i >= self.generations:
return WFSResult(t=np.exp(-1.0j * population[sorted_indices[-1]]), t_f=None, axis=2)
return WFSResult(t=np.exp(-1.0j * population[sorted_indices[-1]]), axis=2)

# We keep the elite individuals, and regenerate the rest by mixing the elite
# For this mixing, the probability of selecting an individual is proportional to its measured intensity.
Expand Down
5 changes: 0 additions & 5 deletions openwfs/algorithms/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ class WFSResult:
def __init__(
self,
t: np.ndarray,
t_f: np.ndarray,
axis: int,
fidelity_noise: ArrayLike = 1.0,
fidelity_amplitude: ArrayLike = 1.0,
Expand Down Expand Up @@ -60,7 +59,6 @@ def __init__(
"""
self.t = t
self.t_f = t_f
self.axis = axis
self.fidelity_noise = np.atleast_1d(fidelity_noise)
self.n = np.prod(t.shape[0:axis]) if n is None else n
Expand Down Expand Up @@ -107,7 +105,6 @@ def select_target(self, b) -> "WFSResult":
"""
return WFSResult(
t=self.t.reshape((*self.t.shape[0:2], -1))[:, :, b],
t_f=self.t_f.reshape((*self.t_f.shape[0:2], -1))[:, :, b],
axis=self.axis,
intensity_offset=self.intensity_offset[:][b],
fidelity_noise=self.fidelity_noise[:][b],
Expand Down Expand Up @@ -139,7 +136,6 @@ def weighted_average(attribute):

return WFSResult(
t=weighted_average("t"),
t_f=weighted_average("t_f"),
n=n,
axis=axis,
fidelity_noise=weighted_average("fidelity_noise"),
Expand Down Expand Up @@ -243,7 +239,6 @@ def analyze_phase_stepping(measurements: np.ndarray, axis: int, A: Optional[floa

return WFSResult(
t,
t_f=t_f,
axis=axis,
fidelity_amplitude=amplitude_factor,
fidelity_noise=noise_factor,
Expand Down
4 changes: 3 additions & 1 deletion openwfs/simulation/mockdevices.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from ..core import Detector, Processor, Actuator
from ..processors import CropProcessor
from ..utilities import ExtentType, get_pixel_size
from ..utilities import ExtentType, get_pixel_size, set_pixel_size


class StaticSource(Detector):
Expand Down Expand Up @@ -42,6 +42,8 @@ def __init__(
pixel_size = Quantity(extent) / data.shape
else:
pixel_size = get_pixel_size(data)
else:
data = set_pixel_size(data, pixel_size) # make sure the data array holds the pixel size

if pixel_size is not None and (np.isscalar(pixel_size) or pixel_size.size == 1) and data.ndim > 1:
pixel_size = pixel_size.repeat(data.ndim)
Expand Down

0 comments on commit 5921f8d

Please sign in to comment.