File: laser_beam.py

package info (click to toggle)
python-sigima 1.0.3-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 24,956 kB
  • sloc: python: 33,326; makefile: 3
file content (297 lines) | stat: -rw-r--r-- 10,931 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
# Copyright (c) DataLab Platform Developers, BSD 3-Clause license, see LICENSE file.

"""
Laser Beam Size Measurement Example
===================================

This example demonstrates comprehensive laser beam analysis techniques following
the laser beam tutorial workflow. It shows how to load multiple laser beam images,
analyze background noise with histograms, apply proper clipping, detect beam
centroids, extract line and radial profiles, compute FWHM measurements, and
track beam size evolution along the propagation axis.

The script demonstrates advanced optical beam characterization workflows commonly
used in laser physics, beam quality assessment, and optical system design.
"""

# %%
# Importing necessary modules
# --------------------------------
# We start by importing all the required modules for image processing
# and visualization. To run this example, ensure you have all the required
# dependencies installed.

import numpy as np

import sigima.io
import sigima.objects
import sigima.params
import sigima.proc.image
import sigima.proc.signal
from sigima.tests import helpers, vistools

# %%
# Load all laser beam images
# ------------------------------------------------
# We load a series of laser beam images taken at different positions along
# the propagation axis (z-axis). The images are contained in the folder laser_beam and
# named following the pattern TEM00_z_*.jpg, where * is the z position in arbitrary
# units.


def load_laser_beam_images():
    """Load all laser beam test images from the test data directory.

    Returns:
        List of image objects loaded from TEM00_z_*.jpg files
    """
    # Get all TEM00 laser beam image files
    image_files = helpers.get_test_fnames("laser_beam/TEM00_z_*.jpg")

    # Sort files by z-position (extract number from filename)
    image_files.sort(key=lambda f: int(f.split("_z_")[1].split(".")[0]))

    # Load images
    images = []
    for filepath in image_files:
        img = sigima.io.read_image(filepath)
        # Extract z position from filename for proper naming
        z_pos = filepath.split("_z_")[1].split(".")[0]
        img.title = f"TEM00_z_{z_pos}"
        images.append(img)

    return images


images = load_laser_beam_images()

print(f"✓ Loaded {len(images)} laser beam images")
print("Image details:")
for i, img in enumerate(images):
    intensity_range = f"{img.data.min()}-{img.data.max()}"
    print(f"  {i + 1}. {img.title}: {img.data.shape}, range {intensity_range}")


# %%
# Visualize the first few images
print("\n✓ Visualizing sample images...")
vistools.view_images_side_by_side(images[:3], title="Sample Laser Beam Images")

# %%
# Background noise analysis with histogram
# ------------------------------------------------
# To analyze the background noise characteristics of the laser beam images,
# we create a histogram of pixel values from the first image. This helps us
# identify the noise floor and determine an appropriate clipping threshold.


print("\n--- Background Noise Analysis ---")

hist_param = sigima.params.HistogramParam()
hist_param.bins = 100
hist_param.range = (0, images[0].data.max())

hist = sigima.proc.image.histogram(images[0], hist_param)
hist.title = "Pixel value histogram of image 1"

print(f"✓ Generated histogram with {hist_param.bins} bins")
print(f"Histogram range: {hist_param.range[0]} - {hist_param.range[1]}")
print("The histogram shows background noise distribution")

# Visualize histogram
vistools.view_curves([hist], title="Pixel Value Histogram - Background Analysis")

# %%
# Based on the histogram analysis, we determine a clipping threshold around 30-35 LSB to
# effectively remove background noise from all images.

# %%
# Background noise removal via clipping
# ------------------------------------------------
# We set the threshold to 35 and we apply this clipping to each image in the dataset.

background_threshold = 35

print(f"Will use clipping threshold of {background_threshold} LSB")
# %%
# In order to perform the clipping, we create a ClipParam object
# and set the minimum value to the background threshold. We then apply
# the clipping to each image in the dataset.

print("\n--- Applying Background Clipping ---")
clip_param = sigima.params.ClipParam()
clip_param.lower = background_threshold  # Remove background noise below 35 LSB

clipped_images = []
for img in images:
    clipped_img = sigima.proc.image.clip(img, clip_param)
    clipped_img.title = f"{img.title}_clipped"
    clipped_images.append(clipped_img)

print(f"✓ Applied clipping of {clip_param.lower} LSB to all {len(images)} images")
print("Background noise below threshold has been removed")

# %%
# We can now visualize some clipped images:
vistools.view_images_side_by_side(
    images[:3] + clipped_images[:3],
    rows=2,
    title="Original and Clipped Images (First 3)",
)

# %%
# Compute centroids for beam center detection
# ------------------------------------------------
# Next, we compute the centroid of each clipped image to determine the beam center. This
# is important for accurate profile extraction and FWHM measurements.

print("\n--- Computing Beam Centroids ---")

centroids = []
for img in clipped_images:
    centroid_result = sigima.proc.image.centroid(img)
    centroids.append(centroid_result.value)  # (x, y)
    print(f"  ✓ {img.title}: centroid at {centroid_result.value}")

print(f"\n✓ Successfully detected {len(centroids)}/{len(images)} centroids")

# %%
# Extract line profiles through beam centers
# ------------------------------------------------
# We extract horizontal line profiles through the detected centroids of each clipped
# image. This provides insight into the beam intensity distribution along a horizontal
# cross-section.

print("\n--- Extracting Line Profiles ---")

line_profiles = []
for i, (img, centroid_coords) in enumerate(zip(clipped_images, centroids)):
    # Create line profile parameters for horizontal line through centroid
    line_param = sigima.proc.image.LineProfileParam()
    line_param.direction = "horizontal"
    line_param.row = int(centroid_coords[1])  # Use centroid y-coordinate as row

    # Extract line profile
    profile = sigima.proc.image.line_profile(img, line_param)
    profile.title = f"Line_profile_{img.title}"
    line_profiles.append(profile)
    print(f"  ✓ Extracted line profile for {img.title} at row {line_param.row}")

print(f"\n✓ Generated {len(line_profiles)} line profiles")

# Visualize some line profiles
vistools.view_curves(
    line_profiles[:3], title="Horizontal Line Profiles (First 3 Images)"
)

# %%
# Extract radial profiles around beam centers
# ------------------------------------------------
# We extract radial profiles centered on the detected centroids of each clipped image.
# This provides a circular intensity distribution useful for FWHM measurements.

print("\n--- Extracting Radial Profiles ---")

radial_profiles = []
for img in clipped_images:
    # Create radial profile parameters using automatic centroid detection
    radial_param = sigima.proc.image.RadialProfileParam()
    radial_param.center = "centroid"  # Use automatic centroid detection

    # Extract radial profile
    profile = sigima.proc.image.radial_profile(img, radial_param)
    profile.title = f"Radial_profile_{img.title}"
    radial_profiles.append(profile)
    print(f"  ✓ Extracted radial profile for {img.title}")

print(f"\n✓ Generated {len(radial_profiles)} radial profiles")

# %%
# We can now visualize some radial profiles
vistools.view_curves(radial_profiles[:3], title="Radial Profiles (First 3 Images)")

# %%
# Compute FWHM for radial profiles
# ------------------------------------------------
# We compute the Full Width at Half Maximum (FWHM) for each radial profile to
# quantify the beam size. The FWHM is a standard metric for beam width.

print("\n--- Computing FWHM Measurements ---")

fwhm_vals = []
fwhm_param = sigima.params.FWHMParam()
fwhm_param.method = "zero-crossing"  # Standard FWHM method

for profile in radial_profiles:
    fwhm_result = sigima.proc.signal.fwhm(profile, fwhm_param)
    fwhm_vals.append(fwhm_result.value)
    print(f"  ✓ {profile.title}: FWHM = {fwhm_result.value:.2f} pixels")
# %%
# That's done, we can now print some FWHM statistics to check our results:
print("\n✓ FWHM Statistics:")
print(f"  Valid measurements: {len(fwhm_vals)}/{len(fwhm_vals)}")
print(f"  Beam size range: {min(fwhm_vals):.2f} - {max(fwhm_vals):.2f} pixels")
print(f"  Average beam size: {np.mean(fwhm_vals):.2f} ± {np.std(fwhm_vals):.2f} pixels")
# %%
# Everything seems fine, we can now analyze the beam size evolution along the z-axis.

# %%
# Analyze beam size evolution
# ------------------------------------------------
# Having computed the FWHM for each radial profile, we can now analyze how the
# beam size evolves along the propagation axis (z-axis). This is useful to extract some
# meaningful information from all the numbers we have obtained. We create a signal
# representing the beam size evolution and visualize it.

print("\n--- Beam Size Evolution Analysis ---")

# Create a signal showing beam size evolution along z-axis
z_positions = list(range(len(fwhm_vals)))
beam_evolution = sigima.objects.create_signal(
    "Beam size evolution",
    np.array(z_positions),
    np.array(fwhm_vals),
    units=("image_index", "pixels"),
)

print(f"✓ Created beam evolution signal with {len(z_positions)} data points")

# Visualize beam size evolution
vistools.view_curves(
    [beam_evolution], title="Beam Size Evolution vs Z-Position (uncalibrated)"
)

# %%
# The beam evolution signal currently uses image index as the x-axis, and even if we can
# see the trend, it is not very informative. It would be way better to have the x-axis
# in mm.

# %%
# Apply z-axis calibration
# ------------------------------------------------
# We apply a linear calibration to the x-axis of the beam evolution signal to convert
# image index to physical distance in mm. In Sigima there is the possibility to perform
# a linear axis calibration. We use the formula x' = 5*x + 15, where x is
# the image index and x' is the calibrated distance in mm.

print("\n--- Applying Z-Axis Calibration ---")

# Calibrate x-axis using the formula: x' = 5*x + 15 (convert to mm)
calib_param = sigima.proc.signal.XYCalibrateParam()
calib_param.axis = "x"
calib_param.a = 5.0  # Scale factor
calib_param.b = 15.0  # Offset

beam_evolution_calibrated = sigima.proc.signal.calibration(beam_evolution, calib_param)
beam_evolution_calibrated.title = f"{beam_evolution.title} (z calibrated)"

print(f"✓ Applied calibration: z' = {calib_param.a}*z + {calib_param.b}")
print("Z-axis now represents physical distance in mm")

# Visualize calibrated beam evolution
vistools.view_curves(
    [beam_evolution_calibrated],
    title="Beam Size Evolution vs Z-Position (calibrated)",
    xlabel="Z Position (mm)",
    ylabel="Beam Size (pixels)",
)