Skip to content
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
fcf4bf7
Add script on metrics
lauraporta Feb 14, 2025
fd64605
Remove old slurm related script
lauraporta Feb 14, 2025
169f55e
Make fit ellipse more resiliant to nans, change loss
lauraporta Feb 14, 2025
bfd619f
Improve fitting boundaries
lauraporta Feb 14, 2025
2ad7137
Configs can now be passed as a dict
lauraporta Feb 14, 2025
5839ad5
General refactoring and add velocity method
lauraporta Feb 14, 2025
46bc182
Add script to analyse datasets in batches
lauraporta Feb 14, 2025
12108f7
Use biased center
lauraporta Feb 14, 2025
97cab25
Small bug fix
lauraporta Feb 14, 2025
9e2cb40
WIP find optimal params
lauraporta Feb 21, 2025
bb9ea41
WIP: all datasets fixed but derotation is broken
lauraporta Feb 27, 2025
7f6bc81
Fix derotation
lauraporta Feb 27, 2025
6316cb6
Apply linting and cleanup from some commented out code
lauraporta Mar 10, 2025
2b42ac5
Further linting.
lauraporta Mar 10, 2025
6784471
Imsave is deprecated in latest tifffile version
lauraporta Mar 10, 2025
31d6f33
Fix bug on defining starting and ending times of rotations appearing …
lauraporta Mar 13, 2025
c6c09b5
Fix bug related to csv creation
lauraporta Mar 14, 2025
62d19a6
Remove logging messages that are not necessary
lauraporta Mar 14, 2025
a34b2d7
Add function to plot all speed profiles
lauraporta Mar 14, 2025
b2777dd
Store center of rotation previously found in a file
lauraporta Mar 17, 2025
a6cee9a
Fix velocity calculation bug
lauraporta Mar 17, 2025
964c124
Log unlinking and only unlink pngs
lauraporta Mar 17, 2025
19ecda3
Update derotation/analysis/full_derotation_pipeline.py
lauraporta Mar 17, 2025
4a91c89
Add missing docstring in BO class
lauraporta Mar 17, 2025
8f35d68
Improve signature and add docstring to metrics
lauraporta Mar 17, 2025
250e5e1
Some I/O fixes
lauraporta Mar 17, 2025
d7cc917
Type bug fix
lauraporta Mar 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ repos:
- id: requirements-txt-fixer
- id: trailing-whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.6
rev: v0.9.10
hooks:
- id: ruff
args: [ --config=pyproject.toml ]
- id: ruff-format
args: [ --config=pyproject.toml ]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.13.0
rev: v1.15.0
hooks:
- id: mypy
additional_dependencies:
Expand All @@ -39,8 +39,9 @@ repos:
additional_dependencies: [setuptools-scm, wheel]
- repo: https://github.com/codespell-project/codespell
# Configuration for codespell is in pyproject.toml
rev: v2.3.0
rev: v2.4.1
hooks:
- id: codespell
additional_dependencies:
- tomli
args: ["--ignore-words-list=ptd"]
96 changes: 96 additions & 0 deletions derotation/analysis/bayesian_optimization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import logging
from pathlib import Path
from typing import Tuple

import matplotlib.pyplot as plt
import numpy as np
from bayes_opt import BayesianOptimization

from derotation.analysis.mean_images import calculate_mean_images
from derotation.analysis.metrics import ptd_of_most_detected_blob
from derotation.derotate_by_line import derotate_an_image_array_line_by_line


class BO_for_derotation:
def __init__(
self,
movie: np.ndarray,
rot_deg_line: np.ndarray,
rot_deg_frame: np.ndarray,
blank_pixels_value: float,
center: Tuple[int, int],
delta: int,
init_points: int = 2,
n_iter: int = 10,
debug_plots_folder: Path = Path("./debug_plots"),
):
self.movie = movie
self.rot_deg_line = rot_deg_line
self.rot_deg_frame = rot_deg_frame
self.blank_pixels_value = blank_pixels_value
x, y = center
self.pbounds = {

Check warning on line 32 in derotation/analysis/bayesian_optimization.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/bayesian_optimization.py#L27-L32

Added lines #L27 - L32 were not covered by tests
"x": (x - delta, x + delta),
"y": (y - delta, y + delta),
}
self.init_points = init_points
self.n_iter = n_iter
self.debug_plots_folder = debug_plots_folder

Check warning on line 38 in derotation/analysis/bayesian_optimization.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/bayesian_optimization.py#L36-L38

Added lines #L36 - L38 were not covered by tests

self.subfolder = self.debug_plots_folder / "bo"

Check warning on line 40 in derotation/analysis/bayesian_optimization.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/bayesian_optimization.py#L40

Added line #L40 was not covered by tests
# remove previous dir
if self.subfolder.exists():
for file in self.subfolder.iterdir():
file.unlink()

Check warning on line 44 in derotation/analysis/bayesian_optimization.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/bayesian_optimization.py#L42-L44

Added lines #L42 - L44 were not covered by tests
else:
self.subfolder.mkdir(parents=True, exist_ok=True)

Check warning on line 46 in derotation/analysis/bayesian_optimization.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/bayesian_optimization.py#L46

Added line #L46 was not covered by tests

def optimize(self):
def derotate_and_get_metric(

Check warning on line 49 in derotation/analysis/bayesian_optimization.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/bayesian_optimization.py#L49

Added line #L49 was not covered by tests
x: float,
y: float,
):
derotated_chunk = derotate_an_image_array_line_by_line(

Check warning on line 53 in derotation/analysis/bayesian_optimization.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/bayesian_optimization.py#L53

Added line #L53 was not covered by tests
image_stack=self.movie,
rot_deg_line=self.rot_deg_line,
blank_pixels_value=self.blank_pixels_value,
center=(int(x), int(y)),
)

mean_images = calculate_mean_images(

Check warning on line 60 in derotation/analysis/bayesian_optimization.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/bayesian_optimization.py#L60

Added line #L60 was not covered by tests
derotated_chunk, self.rot_deg_frame, round_decimals=0
)

plt.imshow(mean_images[0], cmap="gray")
plt.savefig(self.subfolder / f"mean_image_0_{x:.2f}_{y:.2f}.png")
plt.close()

Check warning on line 66 in derotation/analysis/bayesian_optimization.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/bayesian_optimization.py#L64-L66

Added lines #L64 - L66 were not covered by tests

ptd = ptd_of_most_detected_blob(

Check warning on line 68 in derotation/analysis/bayesian_optimization.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/bayesian_optimization.py#L68

Added line #L68 was not covered by tests
mean_images,
debug_plots_folder=self.subfolder,
image_names=[
f"blobs_{x:.2f}_{y:.2f}.png",
f"blob_centers_{x:.2f}_{y:.2f}.png",
],
)

# we are maximizing the metric, so
# we need to return the negative of the metric
return -ptd

Check warning on line 79 in derotation/analysis/bayesian_optimization.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/bayesian_optimization.py#L79

Added line #L79 was not covered by tests

optimizer = BayesianOptimization(

Check warning on line 81 in derotation/analysis/bayesian_optimization.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/bayesian_optimization.py#L81

Added line #L81 was not covered by tests
f=derotate_and_get_metric,
pbounds=self.pbounds,
verbose=2,
random_state=1,
)

optimizer.maximize(

Check warning on line 88 in derotation/analysis/bayesian_optimization.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/bayesian_optimization.py#L88

Added line #L88 was not covered by tests
init_points=self.init_points,
n_iter=self.n_iter,
)

for i, res in enumerate(optimizer.res):
logging.info(f"Iteration {i}: {res}")

Check warning on line 94 in derotation/analysis/bayesian_optimization.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/bayesian_optimization.py#L93-L94

Added lines #L93 - L94 were not covered by tests

return optimizer.max

Check warning on line 96 in derotation/analysis/bayesian_optimization.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/bayesian_optimization.py#L96

Added line #L96 was not covered by tests
2 changes: 1 addition & 1 deletion derotation/analysis/blob_detection.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@
fig, ax = plt.subplots(4, 3, figsize=(10, 10))
for i, a in tqdm(enumerate(ax.flatten())):
a.imshow(image_stack[i])
a.set_title(f"{i*5} degrees")
a.set_title(f"{i * 5} degrees")

Check warning on line 99 in derotation/analysis/blob_detection.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/blob_detection.py#L99

Added line #L99 was not covered by tests
a.axis("off")

for j, blob in enumerate(blobs[i][:4]):
Expand Down
46 changes: 34 additions & 12 deletions derotation/analysis/fit_ellipse.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

def fit_ellipse_to_points(
centers: np.ndarray,
pixels_in_row: int = 256,
) -> Tuple[int, int, int, int, int]:
"""Fit an ellipse to the points using least squares optimization.

Expand All @@ -26,14 +27,19 @@
"""
# Convert centers to numpy array
centers = np.array(centers)
x = centers[:, 0]
y = centers[:, 1]
valid_points = centers[
~np.isnan(centers).any(axis=1)
] # Remove rows with NaN
if len(valid_points) < 5:
raise ValueError("Not enough valid points to fit an ellipse.")

Check warning on line 34 in derotation/analysis/fit_ellipse.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/fit_ellipse.py#L34

Added line #L34 was not covered by tests

# Find the extreme points for the initial ellipse estimate
topmost = centers[np.argmin(y)]
rightmost = centers[np.argmax(x)]
bottommost = centers[np.argmax(y)]
leftmost = centers[np.argmin(x)]
x, y = valid_points[:, 0], valid_points[:, 1]

# Find extreme points for the initial ellipse estimate
topmost = valid_points[np.argmin(y)]
rightmost = valid_points[np.argmax(x)]
bottommost = valid_points[np.argmax(y)]
leftmost = valid_points[np.argmin(x)]

# Initial parameters: (center_x, center_y, semi_major_axis,
# semi_minor_axis, rotation_angle)
Expand All @@ -42,8 +48,12 @@
)
semi_major_axis = np.linalg.norm(rightmost - leftmost) / 2
semi_minor_axis = np.linalg.norm(topmost - bottommost) / 2
rotation_angle = 0 # Start with no rotation

# Ensure axes are not zero
if semi_major_axis < 1e-3 or semi_minor_axis < 1e-3:
raise ValueError("Points are degenerate; cannot fit an ellipse.")

Check warning on line 54 in derotation/analysis/fit_ellipse.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/fit_ellipse.py#L54

Added line #L54 was not covered by tests

rotation_angle = 0 # Start with no rotation
initial_params = [
initial_center[0],
initial_center[1],
Expand All @@ -53,6 +63,7 @@
]

logging.info("Fitting ellipse to points...")
logging.info(f"Initial parameters: {initial_params}")

# Objective function to minimize: sum of squared distances to ellipse
def ellipse_residuals(params, x, y):
Expand All @@ -72,15 +83,26 @@
ellipse_residuals,
initial_params,
args=(x, y),
loss="huber", # minimize the influence of outliers
loss="huber", # Minimize the influence of outliers
bounds=(
# center_x, center_y, a, b, theta
[0, 0, 1e-3, 1e-3, -np.pi],
[
pixels_in_row,
pixels_in_row,
pixels_in_row,
pixels_in_row,
np.pi,
],
),
)

if not result.success:
raise RuntimeError("Ellipse fitting did not converge.")

Check warning on line 101 in derotation/analysis/fit_ellipse.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/fit_ellipse.py#L101

Added line #L101 was not covered by tests

# Extract optimized parameters
center_x, center_y, a, b, theta = result.x

# sometimes the fitted theta is a multiple of of 2pi
theta = theta % (2 * np.pi)

return center_x, center_y, a, b, theta


Expand Down
Loading