Note
Go to the end to download the full example code. or to run this example in your browser via Binder
Simulate out-of-plane rotation during line-scanning acquisition#
In this tutorial, we simulate how image stacks appear when the scanning happens across a plane that is not aligned with the imaging plane.
- We will:
Generate synthetic 2D frames with circular cell structures.
Simulate line-by-line rotations with varying angles.
Visualise how the appearance of the image is distorted when the rotation plane is tilted or oriented differently.
Display all frames and a max projection.
Imports#
from pathlib import Path
import matplotlib.pyplot as plt
from derotation.simulate.line_scanning_microscope import Rotator
from derotation.simulate.synthetic_data import SyntheticData
Define rotation + plotting functions#
def rotate_image_stack(
plane_angle: float = 0,
pad: int = 20,
orientation: float = 0,
):
"""
Create and rotate a synthetic image stack using the specified
rotation parameters.
"""
s_data = SyntheticData(
radius=1,
second_cell=False,
pad=pad,
background_value=80,
num_frames=50,
)
s_data.image = s_data.create_sample_image_with_cells()
image_stack = s_data.create_image_stack()
_, angles = s_data.create_rotation_angles(image_stack.shape)
rotator = Rotator(
angles,
image_stack,
rotation_plane_angle=plane_angle,
blank_pixel_val=0,
rotation_plane_orientation=orientation,
)
rotated_image_stack = rotator.rotate_by_line()
return image_stack, rotated_image_stack, rotator, image_stack.shape[0]
def make_plot(
rotated_image_stack,
title="",
):
"""
Plot all frames of the rotated stack and their associated angles.
"""
fig, ax = plt.subplots(figsize=(12, 6))
max_proj = rotated_image_stack.max(axis=0)
ax.imshow(max_proj, cmap="gray", vmin=0, vmax=255)
ax.plot(
max_proj.shape[1] / 2,
max_proj.shape[0] / 2,
"rx",
markersize=10,
)
ax.set_title("Max projection")
ax.axis("off")
plt.tight_layout()
plt.suptitle(title, fontsize=14)
plt.subplots_adjust(top=0.92)
plt.show()
Create output folder
Path("debug").mkdir(exist_ok=True)
Example 1 – rotation out of imaging plane#
Here we simulate a 25° tilt in the rotation plane, with no orientation shift. This simulates a case where the imaging scan plane is not aligned with the rotation axis.
image_stack, rotated_image_stack, rotator, num_frames = rotate_image_stack(
plane_angle=25, pad=20
)
print("Rotation plane angle: 25°")
print("Rotation orientation: 0°")
make_plot(
rotated_image_stack,
title="Out-of-plane rotation (25° tilt)",
)

0%| | 0/50 [00:00<?, ?it/s]
2%|▏ | 1/50 [00:00<00:05, 8.29it/s]
4%|▍ | 2/50 [00:00<00:05, 8.22it/s]
6%|▌ | 3/50 [00:00<00:05, 8.22it/s]
8%|▊ | 4/50 [00:00<00:05, 8.24it/s]
10%|█ | 5/50 [00:00<00:05, 8.23it/s]
12%|█▏ | 6/50 [00:00<00:05, 8.26it/s]
14%|█▍ | 7/50 [00:00<00:05, 8.22it/s]
16%|█▌ | 8/50 [00:00<00:05, 8.22it/s]
18%|█▊ | 9/50 [00:01<00:04, 8.25it/s]
20%|██ | 10/50 [00:01<00:04, 8.25it/s]
22%|██▏ | 11/50 [00:01<00:04, 8.23it/s]
24%|██▍ | 12/50 [00:01<00:04, 8.20it/s]
26%|██▌ | 13/50 [00:01<00:04, 8.17it/s]
28%|██▊ | 14/50 [00:01<00:04, 8.16it/s]
30%|███ | 15/50 [00:01<00:04, 8.17it/s]
32%|███▏ | 16/50 [00:01<00:04, 8.20it/s]
34%|███▍ | 17/50 [00:02<00:04, 8.24it/s]
36%|███▌ | 18/50 [00:02<00:03, 8.24it/s]
38%|███▊ | 19/50 [00:02<00:03, 8.22it/s]
40%|████ | 20/50 [00:02<00:03, 8.24it/s]
42%|████▏ | 21/50 [00:02<00:03, 8.22it/s]
44%|████▍ | 22/50 [00:02<00:03, 8.23it/s]
46%|████▌ | 23/50 [00:02<00:03, 8.22it/s]
48%|████▊ | 24/50 [00:02<00:03, 8.23it/s]
50%|█████ | 25/50 [00:03<00:03, 8.23it/s]
52%|█████▏ | 26/50 [00:03<00:02, 8.23it/s]
54%|█████▍ | 27/50 [00:03<00:02, 8.19it/s]
56%|█████▌ | 28/50 [00:03<00:02, 8.20it/s]
58%|█████▊ | 29/50 [00:03<00:02, 8.22it/s]
60%|██████ | 30/50 [00:03<00:02, 8.21it/s]
62%|██████▏ | 31/50 [00:03<00:02, 8.24it/s]
64%|██████▍ | 32/50 [00:03<00:02, 8.21it/s]
66%|██████▌ | 33/50 [00:04<00:02, 8.21it/s]
68%|██████▊ | 34/50 [00:04<00:01, 8.24it/s]
70%|███████ | 35/50 [00:04<00:01, 8.24it/s]
72%|███████▏ | 36/50 [00:04<00:01, 8.22it/s]
74%|███████▍ | 37/50 [00:04<00:01, 8.18it/s]
76%|███████▌ | 38/50 [00:04<00:01, 8.16it/s]
78%|███████▊ | 39/50 [00:04<00:01, 8.15it/s]
80%|████████ | 40/50 [00:04<00:01, 8.15it/s]
82%|████████▏ | 41/50 [00:04<00:01, 8.20it/s]
84%|████████▍ | 42/50 [00:05<00:00, 8.23it/s]
86%|████████▌ | 43/50 [00:05<00:00, 8.22it/s]
88%|████████▊ | 44/50 [00:05<00:00, 8.20it/s]
90%|█████████ | 45/50 [00:05<00:00, 8.23it/s]
92%|█████████▏| 46/50 [00:05<00:00, 8.22it/s]
94%|█████████▍| 47/50 [00:05<00:00, 8.22it/s]
96%|█████████▌| 48/50 [00:05<00:00, 8.22it/s]
98%|█████████▊| 49/50 [00:05<00:00, 8.22it/s]
100%|██████████| 50/50 [00:06<00:00, 8.23it/s]
100%|██████████| 50/50 [00:06<00:00, 8.22it/s]
Rotation plane angle: 25°
Rotation orientation: 0°
Example 2 – rotation + in-plane orientation#
Now we also add a 45° orientation to the rotation plane, so it’s both tilted and diagonally oriented relative to the image.
image_stack, rotated_image_stack, rotator, num_frames = rotate_image_stack(
plane_angle=25, pad=20, orientation=45
)
print("Rotation plane angle: 25°")
print("Rotation orientation: 45°")
make_plot(
rotated_image_stack,
title="Tilted + Oriented Rotation Plane (25°, 45°)",
)

0%| | 0/50 [00:00<?, ?it/s]
2%|▏ | 1/50 [00:00<00:11, 4.17it/s]
4%|▍ | 2/50 [00:00<00:11, 4.17it/s]
6%|▌ | 3/50 [00:00<00:11, 4.17it/s]
8%|▊ | 4/50 [00:00<00:11, 4.17it/s]
10%|█ | 5/50 [00:01<00:10, 4.16it/s]
12%|█▏ | 6/50 [00:01<00:10, 4.18it/s]
14%|█▍ | 7/50 [00:01<00:10, 4.17it/s]
16%|█▌ | 8/50 [00:01<00:10, 4.16it/s]
18%|█▊ | 9/50 [00:02<00:09, 4.17it/s]
20%|██ | 10/50 [00:02<00:09, 4.18it/s]
22%|██▏ | 11/50 [00:02<00:09, 4.17it/s]
24%|██▍ | 12/50 [00:02<00:09, 4.16it/s]
26%|██▌ | 13/50 [00:03<00:08, 4.16it/s]
28%|██▊ | 14/50 [00:03<00:08, 4.15it/s]
30%|███ | 15/50 [00:03<00:08, 4.15it/s]
32%|███▏ | 16/50 [00:03<00:08, 4.16it/s]
34%|███▍ | 17/50 [00:04<00:07, 4.17it/s]
36%|███▌ | 18/50 [00:04<00:07, 4.17it/s]
38%|███▊ | 19/50 [00:04<00:07, 4.16it/s]
40%|████ | 20/50 [00:04<00:07, 4.17it/s]
42%|████▏ | 21/50 [00:05<00:06, 4.16it/s]
44%|████▍ | 22/50 [00:05<00:06, 4.17it/s]
46%|████▌ | 23/50 [00:05<00:06, 4.17it/s]
48%|████▊ | 24/50 [00:05<00:06, 4.17it/s]
50%|█████ | 25/50 [00:05<00:05, 4.17it/s]
52%|█████▏ | 26/50 [00:06<00:05, 4.17it/s]
54%|█████▍ | 27/50 [00:06<00:05, 4.17it/s]
56%|█████▌ | 28/50 [00:06<00:05, 4.17it/s]
58%|█████▊ | 29/50 [00:06<00:05, 4.18it/s]
60%|██████ | 30/50 [00:07<00:04, 4.17it/s]
62%|██████▏ | 31/50 [00:07<00:04, 4.18it/s]
64%|██████▍ | 32/50 [00:07<00:04, 4.17it/s]
66%|██████▌ | 33/50 [00:07<00:04, 4.17it/s]
68%|██████▊ | 34/50 [00:08<00:03, 4.17it/s]
70%|███████ | 35/50 [00:08<00:03, 4.18it/s]
72%|███████▏ | 36/50 [00:08<00:03, 4.17it/s]
74%|███████▍ | 37/50 [00:08<00:03, 4.16it/s]
76%|███████▌ | 38/50 [00:09<00:02, 4.15it/s]
78%|███████▊ | 39/50 [00:09<00:02, 4.15it/s]
80%|████████ | 40/50 [00:09<00:02, 4.15it/s]
82%|████████▏ | 41/50 [00:09<00:02, 4.16it/s]
84%|████████▍ | 42/50 [00:10<00:01, 4.17it/s]
86%|████████▌ | 43/50 [00:10<00:01, 4.17it/s]
88%|████████▊ | 44/50 [00:10<00:01, 4.17it/s]
90%|█████████ | 45/50 [00:10<00:01, 4.18it/s]
92%|█████████▏| 46/50 [00:11<00:00, 4.17it/s]
94%|█████████▍| 47/50 [00:11<00:00, 4.17it/s]
96%|█████████▌| 48/50 [00:11<00:00, 4.17it/s]
98%|█████████▊| 49/50 [00:11<00:00, 4.17it/s]
100%|██████████| 50/50 [00:12<00:00, 4.16it/s]
100%|██████████| 50/50 [00:12<00:00, 4.17it/s]
Rotation plane angle: 25°
Rotation orientation: 45°
Conclusion#
This simulation helps us visualise how image distortions appear during line-scanning acquisition when the imaging plane is misaligned with the physical rotation plane. The observed distortions depend on both the angle of the rotation plane and its orientation in space.
Total running time of the script: (0 minutes 18.232 seconds)