Derotate a TIFF movie line-by-line using precomputed rotation angles#

This script demonstrates how to load a 3D TIFF image stack (frames × height × width) and apply line-by-line derotation using a set of precomputed rotation angles.

Each horizontal line in the image is rotated by a specific angle (in degrees), allowing correction for sample motion or intentional rotation during acquisition.

Inputs: - angles_per_line.npy: A 1D NumPy array of angles in degrees, with one value

per image line

  • rotation_sample.tif: A TIFF stack to be derotated.

The script computes the derotated movie and displays the mean image of the derotated stack.

import matplotlib.pyplot as plt
import numpy as np
import tifffile

from derotation.derotate_by_line import 
from derotation.sample_data import fetch_data

Load per-line rotation angles

angles_path = fetch_data("angles_per_line.npy")
print(f"Loading per-line rotation angles from {angles_path}")
angles_per_line = np.load(angles_path)  # Shape: (height,)
Loading per-line rotation angles from /home/runner/.derotation/data/angles_per_line.npy

Load the TIFF stack (shape: num_frames × height × width)

tif_path = fetch_data("rotation_sample.tif")
print(f"Loading image stack from {tif_path}")
image_stack = tifffile.imread(tif_path)

print(f"Image stack shape: {image_stack.shape}")
print(f"Angles per line shape: {angles_per_line.shape}")
Loading image stack from /home/runner/.derotation/data/rotation_sample.tif
Image stack shape: (270, 256, 256)
Angles per line shape: (75471,)

See the angles

fig, ax = plt.subplots(figsize=(10, 5))
ax.scatter(np.arange(angles_per_line.shape[0]), angles_per_line, s=1)
ax.set_title("Rotation angles per line")
ax.set_xlabel("Line number")
ax.set_ylabel("Angle (degrees)")
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
plt.tight_layout()
plt.show()
Rotation angles per line

Set derotation parameters see derotation.analysis.full_derotation_pipeline.FullPipeline .find_image_offset() for more details on finding the offset value

rotation_center = (129, 128)  # (y, x) format
blank_pixels_value = -3623  # Value for pixels outside the rotated area

print(f"Applying derotation with center: {rotation_center}")
Applying derotation with center: (129, 128)

plot first frame with rotation center

fig, ax = plt.subplots(figsize=(10, 5))
ax.imshow(image_stack[0], cmap="gray")
ax.scatter(rotation_center[1], rotation_center[0], color="red", s=20)
ax.set_title("Rotation center")
ax.axis("off")
plt.tight_layout()
plt.show()
Rotation center

Perform line-by-line derotation

derotated_stack = (
    image_stack=image_stack,
    rot_deg_line=angles_per_line,
    center=rotation_center,
    blank_pixels_value=blank_pixels_value,
)
  0%|          | 0/69120 [00:00<?, ?it/s]
 48%|████▊     | 33016/69120 [00:00<00:00, 330141.42it/s]
 96%|█████████▌| 66031/69120 [00:00<00:00, 109203.37it/s]
100%|██████████| 69120/69120 [00:00<00:00, 124882.26it/s]

Visualize the result by taking the mean

mean_image = np.mean(derotated_stack, axis=0)

fig, ax = plt.subplots(figsize=(10, 5))
ax.imshow(mean_image, cmap="viridis")
ax.set_title("Mean image of the derotated stack")
ax.axis("off")
plt.tight_layout()
plt.show()
Mean image of the derotated stack

Total running time of the script: (0 minutes 0.958 seconds)

Gallery generated by Sphinx-Gallery