Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Find centre of rotation (tentative 2) #30

Merged
merged 22 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 1 addition & 2 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ include README.md
include *.yaml
recursive-include * *.yaml
recursive-include * *.yml
recursive-include tests *.py
recursive-include examples *.py
recursive-include tests *.png
recursive-include images *.png

recursive-exclude * __pycache__
recursive-exclude * *.py[co]
recursive-exclude docs *
recursive-exclude tests *
89 changes: 66 additions & 23 deletions derotation/analysis/full_derotation_pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -168,14 +168,21 @@
self.debugging_plots = self.config["debugging_plots"]

if self.debugging_plots:
self.debug_plots_folder = self.config["paths_write"][
"debug_plots_folder"
]
Path(self.debug_plots_folder).mkdir(parents=True, exist_ok=True)
self.debug_plots_folder = Path(

Check warning on line 171 in derotation/analysis/full_derotation_pipeline.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/full_derotation_pipeline.py#L171

Added line #L171 was not covered by tests
self.config["paths_write"]["debug_plots_folder"]
)
lauraporta marked this conversation as resolved.
Show resolved Hide resolved
self.debug_plots_folder.mkdir(parents=True, exist_ok=True)

Check warning on line 174 in derotation/analysis/full_derotation_pipeline.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/full_derotation_pipeline.py#L174

Added line #L174 was not covered by tests

logging.info(f"Dataset {self.filename_raw} loaded")
logging.info(f"Filename: {self.filename}")

# by default the center of rotation is the center of the image
self.center_of_rotation = (

Check warning on line 180 in derotation/analysis/full_derotation_pipeline.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/full_derotation_pipeline.py#L180

Added line #L180 was not covered by tests
self.num_lines_per_frame // 2,
lauraporta marked this conversation as resolved.
Show resolved Hide resolved
self.num_lines_per_frame // 2,
)
self.hooks = {}

Check warning on line 184 in derotation/analysis/full_derotation_pipeline.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/full_derotation_pipeline.py#L184

Added line #L184 was not covered by tests

### ----------------- Analog signals processing pipeline ------------- ###
def process_analog_signals(self):
"""From the analog signals (frame clock, line clock, full rotation,
Expand Down Expand Up @@ -790,26 +797,39 @@
plt.savefig(self.debug_plots_folder / "rotation_angles.png")

### ----------------- Derotation ----------------- ###

def plot_max_projection_with_center(self):
lauraporta marked this conversation as resolved.
Show resolved Hide resolved
"""Plots the maximum projection of the image stack with the center
of rotation.
This plot will be saved in the debug_plots folder.
Please inspect it to check that the center of rotation is correctly
placed.
"""
logging.info("Plotting max projection with center...")

Check warning on line 808 in derotation/analysis/full_derotation_pipeline.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/full_derotation_pipeline.py#L808

Added line #L808 was not covered by tests

max_projection = np.max(self.image_stack, axis=0)

Check warning on line 810 in derotation/analysis/full_derotation_pipeline.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/full_derotation_pipeline.py#L810

Added line #L810 was not covered by tests

fig, ax = plt.subplots(1, 1, figsize=(5, 5))

Check warning on line 812 in derotation/analysis/full_derotation_pipeline.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/full_derotation_pipeline.py#L812

Added line #L812 was not covered by tests

ax.imshow(max_projection, cmap="gray")
ax.scatter(

Check warning on line 815 in derotation/analysis/full_derotation_pipeline.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/full_derotation_pipeline.py#L814-L815

Added lines #L814 - L815 were not covered by tests
self.center_of_rotation[0],
self.center_of_rotation[1],
color="red",
marker="x",
)

ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)

Check warning on line 823 in derotation/analysis/full_derotation_pipeline.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/full_derotation_pipeline.py#L822-L823

Added lines #L822 - L823 were not covered by tests

ax.axis("off")

Check warning on line 825 in derotation/analysis/full_derotation_pipeline.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/full_derotation_pipeline.py#L825

Added line #L825 was not covered by tests

plt.savefig(self.debug_plots_folder / "max_projection_with_center.png")

Check warning on line 827 in derotation/analysis/full_derotation_pipeline.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/full_derotation_pipeline.py#L827

Added line #L827 was not covered by tests

def derotate_frames_line_by_line(self) -> np.ndarray:
"""Rotates the image stack line by line, using the rotation angles
by line calculated from the analog signals.

Description of the algorithm:
- takes one line from the image stack
- creates a new image with only that line
- rotates the line by the given angle
- substitutes the line in the new image
- adds the new image to the rotated image stack

Edge cases and how they are handled:
- the rotation starts in the middle of the image -> the previous lines
are copied from the first frame
- the rotation ends in the middle of the image -> the remaining lines
are copied from the last frame

Before derotation, it finds the image offset, which is the peak of
the gaussian mixture model fitted to the image histogram. It is
useful to fill in the blank pixels that appear during the derotation.
"""Wrapper for the function `derotate_an_image_array_line_by_line`.
Before calling the function, it finds the F0 image offset with
`find_image_offset`.
lauraporta marked this conversation as resolved.
Show resolved Hide resolved

Returns
-------
Expand All @@ -818,12 +838,22 @@
"""
logging.info("Starting derotation by line...")

if self.debugging_plots:
self.plot_max_projection_with_center()

Check warning on line 842 in derotation/analysis/full_derotation_pipeline.py

View check run for this annotation

Codecov / codecov/patch

derotation/analysis/full_derotation_pipeline.py#L842

Added line #L842 was not covered by tests

offset = self.find_image_offset(self.image_stack[0])

rotated_image_stack = derotate_an_image_array_line_by_line(
self.image_stack,
self.rot_deg_line,
blank_pixels_value=offset,
center=self.center_of_rotation,
plotting_hook_line_addition=self.hooks.get(
"plotting_hook_line_addition"
),
plotting_hook_image_completed=self.hooks.get(
"plotting_hook_image_completed"
),
)

logging.info("✨ Image stack rotated ✨")
Expand All @@ -833,6 +863,19 @@
def find_image_offset(img):
"""Find the "F0", also called "image offset" for a given image.

Explanations
------------
What is the image offset?
The PMT (photo-multiplier tube) adds an arbitrary offset to the
image that corresponds to 0 photons received. We can use a Gaussian
Mixture Model to find this offset by assuming that it will be the
smallest mean of the Gaussian components.

Why do we need to find it?
When we rotate the image, the pixels of the image that are not
sampled will be filled with the offset is order to correctly express
"0 photons received".

Parameters
----------
img : np.ndarray
Expand Down
Loading