From b01e5fd68851a6c5e1aa31311c51025a41a9125e Mon Sep 17 00:00:00 2001 From: Laura Porta Date: Fri, 26 Jul 2024 11:46:25 +0100 Subject: [PATCH] Refactoring of the main classes (#10) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Find optimal threshold for frames * Assign correct sign for rotation * Get the rotation angle per tick * Add draft logic to fit the rotation degree changes to curve [not working] * Linting * Add working logic to calculate the rotation degree for each frame * Visualize effects of derotation and correct some errors * Add draft pipeline to find centroid * Add libraries to pyproject.toml * Clean up * Improve plots * Optimization v1 * Add changes to gitignore * Add functioning algorithm to optimize rotation degrees * Add improvements in the optimization algorithms for centroid location and derotation angles * Add new plotting of centroids * Move part of the analysis code in methods in other files - analog preprocessing - extraction of rotation angles * Update pyproject.toml * Refactor plots and other methods * dummy napari plotting widget * make a napari-MPL widget * Add functioning napari plugin * Add dependencies * Use unique configs and make path relative * Big refactoring * Remove curve fitting * Clean up gitignore * Move code to find best centroid * Rename method * Use pipeline in old script * Add new strategy for derotation * Small edits * Visualize labels * Use pilot data * Fix imports and attributes * Add less permissive angle * Rename files * Load grid data * More workarounds to play with grid data 🥲 * Renaming functions to make them generic and add WORKING DEROTATION BY LINE 🥳 * Further refinement and changes in napari and debugging script * Improve the algorithm for end of rotation * Improve naming of conditions * Remove bugs for negative rotations * Handle rotation ticks missing better * Add script to visualize how different params of scipy.rotate affect the image * Handle better initialization of original image at the beginning and at the end of the rotation - no interpolation with scipy * Choose data * Improve plotting and image handling in script * Add two images regarding scipy rotate params exploration * Fix error in detection of false rotations * Workaround for suboptimal threshold identification * Clean up napari script * Temporary adjustament of rotation algorithm * Include other datasets * Complete pipeline including masking and saving of tif file * Renaming file * Refactoring, move modules in different folders * More refactoring and deletion of unused methods * More refactoring * More refactoring related to data in input, changes in the config files * Add readme * Renaming * Change dependencies * Add logging * Fix small import bug and change order of rotate method * Upadate readme * Fix wrong log name * Edits to the readme * Add extra method (draft) * Add script to saved tifs for unique rotations * Add script to correct an artefact * Add script to remove rotations for completed tif file * Move script to isolate rotations * Add other plotting scripts * Get data retreives file name * Plots are not saved here anymore * Improve rotation by line and add fileter for incremental rotation * Delete older plots * Load data refactoring * Load data refactoring * Remove hard coded values * Add improvements to analog signals processing and a test * Add debugging plots, more logging and reformatting of checks * Remove method with bisect * Change method signature * Rearrange checks logic * Add useful counts * Extend adjust rotation increment * Remove obsolete method * Add further method to correct ticks number * Add better angle interpolation method * Use new interpolation method * Change order of methods * Improve mask generation * Read speed info as well * Fix interpolation bugs * Add converters * Add more debugging plots * Reordering methods * Improve calculation of rotation increments * Add typing and docstrings * Add first working test * Add test on correction of start and end times * Add test for the creation of the signed rotation array * Make drop ticks method static and create related test * Update manifest file * Update docstring * Fix random seed bug * Make adjust rotation increment static and add test * Remove over-use of static methods, just one was useful * Fix tick drop bug and add more tests * Add test for angles * Add new visualization and clean up * Add more docs and edit config * Add more docs * Refactor and clean up * Add working pipeline for incremental rotations * Add saving of angle table, BUT there is a bug in the derotation, solution not found yet * Add working derotation of incremental images * Add script to identify the displacement of the frames during rotation * Finish up script to test incremental derotation registration * Add incremental derotation registration to the pipeline * Allow to change diameter of circular mask * Fix derotation by line bug * Delete all exploration plots * Delete napari related files * Add typing and docstrings * Move scripts in a dedicated folder * Remove leftover from napari plugin * Fix error in installation of dependencies * Simplify derotation by line * Add regression tests for image rotation * Update readme file * Expand readme * Add generic paths to config files * Remove not so useful comments * Update .gitignore and MANIFEST.in * Adapt sphinx docs and improve some docstrings * Renaming * Move the main method, derotate by line, out of the pipeline * Add more examples in the readme * Fix used dog image * Add script to correct csv * Move the logic to generate a better csv inside the class * Fixed manifest * Fix docs building bug (missing imports) * Add script to run derotation in the cluster * Add the dogs 🐕 * Fix image naming mistake * Fix bug in angle interpolation * Include contrast enhancment * Move threshold and contrast values to config file * Use the image offset to fill in the blank pixels * Fix typo * Use the same diameter for masking when doing derotations one after the other * Fix plotting and tick estimation bugs in incremental rotation pipeline * Add script to create plots from sample video * Add script to merge csv and tiff files * Add script to make mean image * Add missing dependency * Add script to import extracted df/f from suite2p * Maintained the masked image in the incremental rotation object * Add script to work on suite2p registered video * Fix bug in incremental rotation pipeline * wip * wip * Removed unused modules * Remove contrast enhancement * Remove contrast enhancement * Clean up methods * Correct error in pyproject.toml * Override a method in incremental rotation - rotation number should be treated differently * Linting and commenting out the method to find a circle as it is work in progress * Add wip notebook for dashboard made with fastplotlib * Correct method name in tests * Delete files related to the development of other features * Delete commented method * Add some comments to explain the handling of number of rotations * Delete unwanted method * Delete repated imports * Remove reference to "adjust increment" in the readme * Fix int overflow bug due to older numpy version * Remove reference to contrast enhacement from config file --- README.md | 1 - derotation/adjust_rotation_degrees.py | 262 ------------------ derotation/analog_preprocessing.py | 113 -------- derotation/analysis/full_rotation_pipeline.py | 153 +++++----- .../analysis/incremental_rotation_pipeline.py | 34 ++- derotation/config/full_rotation.yml | 4 +- derotation/config/incremental_rotation.yml | 2 - derotation/find_centroid.py | 91 ------ derotation/get_data.py | 43 --- derotation/load_data/custom_data_loaders.py | 3 +- derotation/napari.yaml | 10 - derotation/optimizers.py | 27 -- derotation/plots.py | 225 --------------- derotation/plotting.py | 40 --- derotation/rotate_images.py | 54 ---- docs/source/conf.py | 1 + ..._correct_start_end_times_with_threshold.py | 2 +- 17 files changed, 106 insertions(+), 959 deletions(-) delete mode 100644 derotation/adjust_rotation_degrees.py delete mode 100644 derotation/analog_preprocessing.py delete mode 100644 derotation/find_centroid.py delete mode 100644 derotation/get_data.py delete mode 100644 derotation/napari.yaml delete mode 100644 derotation/optimizers.py delete mode 100644 derotation/plots.py delete mode 100644 derotation/plotting.py delete mode 100644 derotation/rotate_images.py diff --git a/README.md b/README.md index 20f02e6..9ad4a95 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,6 @@ Here's a quick rundown of the configuration parameters you'll need to adjust: * `channel_names`: List the names of the signals in the aux_stim file. * `rotation_increment`: Set the motor's angle increment. -* `adjust_increment`: Enable this to adjust the rotation_increment if the motor ticks don't sum to exactly 360 degrees. * `rot_deg`: Define a full rotation degree count. * `debugging_plots`: Toggle this to save debugging plots. * `analog_signals_processing`: Configure parameters for analog signal processing, including peak finding and pulse processing. diff --git a/derotation/adjust_rotation_degrees.py b/derotation/adjust_rotation_degrees.py deleted file mode 100644 index 4fdb56b..0000000 --- a/derotation/adjust_rotation_degrees.py +++ /dev/null @@ -1,262 +0,0 @@ -import copy -import pickle -from pathlib import Path - -import numpy as np -import scipy.optimize as opt -from find_centroid import ( - detect_blobs, - extract_blob_centers, - get_optimized_centroid_location, - in_region, - not_center_of_image, - pipeline, - preprocess_image, -) -from matplotlib import pyplot as plt -from scipy.ndimage import rotate - - -def find_rotation_blocks(image_rotation_degree_per_frame): - blocks = [] - indexes = [] - - cumulative_index = 0 - # find idx of first non zero value - while len(image_rotation_degree_per_frame) > 100: - first_non_zero_idx = np.where(image_rotation_degree_per_frame != 0)[0][ - 0 - ] - len_first_group = np.where( - image_rotation_degree_per_frame[first_non_zero_idx:] == 0 - )[0][0] - blocks.append( - image_rotation_degree_per_frame[ - first_non_zero_idx : first_non_zero_idx + len_first_group - ] - ) - image_rotation_degree_per_frame = image_rotation_degree_per_frame[ - first_non_zero_idx + len_first_group : - ] - - indexes.append( - np.arange( - cumulative_index + first_non_zero_idx, - cumulative_index + first_non_zero_idx + len_first_group, - ) - ) - cumulative_index += first_non_zero_idx + len_first_group - - return blocks, indexes - - -def find_parametric_curves(blocks): - parametric_curves = [] - for block in blocks: - x = np.arange(len(block)) - y = block - # fit a 4th order polynomial - popt = np.polyfit(x, y, 4) - parametric_curves.append(popt) - - return parametric_curves - - -def make_parametric_curve(x, a, b, c, d, e): - return a * x**4 + b * x**3 + c * x**2 + d * x + e - - -def get_rotation_degrees_from_a_curve(parametric_curve, frame_number): - a, b, c, d, e = parametric_curve - return ( - a * frame_number**4 - + b * frame_number**3 - + c * frame_number**2 - + d * frame_number - + e - ) - - -def rotate_all_images( - image, - image_rotation_degree_per_frame, -): - rotated_images = np.empty_like(image) - for i in range(len(image)): - rotated_images[i] = rotate( - image[i], image_rotation_degree_per_frame[i], reshape=False - ) - - return rotated_images - - -def get_centers(image): - img = preprocess_image(image) - labels, properties = detect_blobs(img) - centroids = extract_blob_centers(properties) - - if (len(centroids) == 1) and ( - (not not_center_of_image(centroids[0])) - or (not in_region(centroids[0])) - ): - print("only one blob found") - - return centroids - - -def get_mean_centroid(centroids): - if len(centroids) > 0: - x = [] - y = [] - for img_centroids in centroids[:10]: - if len(img_centroids) >= 3: - pass - for c in img_centroids: - if not_center_of_image(c) and in_region(c): - x.append(c[1]) - y.append(c[0]) - - mean_x = np.mean(x) - mean_y = np.mean(y) - - return mean_x, mean_y - - -def optimize_image_rotation_degrees( - _image, _image_rotation_degree_per_frame, use_curve_fit=False -): - blocks, indexes = find_rotation_blocks(_image_rotation_degree_per_frame) - if use_curve_fit: - parametric_curves = find_parametric_curves(blocks) - - results = [] - for i in range(len(blocks)): - frames_to_consider = indexes[i] - images = _image[frames_to_consider] - - centers = [] - optimized_parameters = [] - for img in images: - c, x = get_optimized_centroid_location(img) - centers.append(c) - optimized_parameters.append(x) - - assert len(optimized_parameters) == len(images) - - mean_x, mean_y = 150, 160 # get_mean_centroid(centers) - - iteration = [0] # use mutable object to hold the iteration number - - def cb(xk): - iteration[0] += 1 - print( - "============================================" - + "\n" - + "Iteration: {0} - Function value: {1}".format( - iteration[0], f(xk) - ) - ) - - def f(parameters): - fig, ax = plt.subplots(2, 1) - ax[0].imshow(images[0], cmap="gist_ncar") - ax[1].plot(blocks[i], marker=".", color="red") - - if use_curve_fit: - rots = make_parametric_curve( - np.arange(len(images)), *parameters - ) - else: - rots = parameters - ax[1].plot(rots, marker=".", color="black") - - rotated_images = rotate_all_images(images, rots) - diff_x = [] - diff_y = [] - for k, img in enumerate(rotated_images): - try: - centers_rotated_image = pipeline( - img, optimized_parameters[k] - ) - center_dim_blob = mean_x, mean_y - for c in centers_rotated_image: - if not_center_of_image(c) and in_region(c): - center_dim_blob = c - # if center_dim_blob == (mean_x, mean_y): - # # in this rotation, the blob is not found very well - # print("no blob found") - ax[0].plot( - center_dim_blob[1], - center_dim_blob[0], - marker="*", - color="red", - ) - - diff_x.append(np.abs(center_dim_blob[1] - mean_x)) - diff_y.append(np.abs(center_dim_blob[0] - mean_y)) - except IndexError: - pass - - path = Path(f"derotation/figures/block_{i}/") - path.mkdir(parents=True, exist_ok=True) - - fig.savefig(f"{path}/iteration_{iteration[0]}.png") - plt.close() - - # almost like arc length for small angles - hypothenuse = [ - np.sqrt(x**2 + y**2) for x, y in zip(diff_x, diff_y) - ] - return np.sum(hypothenuse) - - result = opt.minimize( - f, - parametric_curves[i] if use_curve_fit else blocks[i], - method="Nelder-Mead", - options={ - "xatol": 1e-5, - "fatol": 1e-5, - "maxiter": 140, - "maxfev": 1000, - }, - # callback=cb, - ) - results.append(result.x) - - return results, indexes, optimized_parameters - - -def get_optimal_rotation_degs(image, image_rotation_degree_per_frame): - try: - with open("derotation/optimized_parameters.pkl", "rb") as f: - optimized_parameters = pickle.load(f) - with open("derotation/indexes.pkl", "rb") as f: - indexes = pickle.load(f) - with open("derotation/opt_result.pkl", "rb") as f: - opt_result = pickle.load(f) - except FileNotFoundError: - ( - opt_result, - indexes, - optimized_parameters, - ) = optimize_image_rotation_degrees( - image, image_rotation_degree_per_frame - ) - with open("derotation/optimized_parameters.pkl", "wb") as f: - pickle.dump(optimized_parameters, f) - with open("derotation/indexes.pkl", "wb") as f: - pickle.dump(indexes, f) - with open("derotation/opt_result.pkl", "wb") as f: - pickle.dump(opt_result, f) - - return opt_result, indexes, optimized_parameters - - -def apply_new_rotations(opt_result, image_rotation_degree_per_frame, indexes): - new_image_rotation_degree_per_frame = copy.deepcopy( - image_rotation_degree_per_frame - ) - for i, block in enumerate(opt_result): - new_image_rotation_degree_per_frame[indexes[i]] = block - - return new_image_rotation_degree_per_frame diff --git a/derotation/analog_preprocessing.py b/derotation/analog_preprocessing.py deleted file mode 100644 index 9b75ba3..0000000 --- a/derotation/analog_preprocessing.py +++ /dev/null @@ -1,113 +0,0 @@ -import copy - -import numpy as np -from optimizers import find_best_k - - -def get_missing_frames(frame_clock): - diffs = np.diff(frame_clock) - missing_frames = np.where(diffs > 0.1)[0] - - return missing_frames, diffs - - -def get_starting_and_ending_frames(frame_clock, image): - # Calculate the threshold using a percentile of the total signal - best_k = find_best_k(frame_clock, image) - threshold = np.mean(frame_clock) + best_k * np.std(frame_clock) - print(f"Best threshold: {threshold}") - frames_start = np.where(np.diff(frame_clock) > threshold)[0] - frames_end = np.where(np.diff(frame_clock) < -threshold)[0] - - return frames_start, frames_end, threshold - - -def check_number_of_rotations(rotation_ticks_peaks, direction, rot_deg, dt): - # sanity check for the number of rotation ticks - number_of_rotations = len(direction) - expected_tiks_per_rotation = rot_deg / dt - ratio = len(rotation_ticks_peaks) / expected_tiks_per_rotation - if ratio > number_of_rotations: - print( - f"There are more rotation ticks than expected, \ - {len(rotation_ticks_peaks)}" - ) - elif ratio < number_of_rotations: - print( - f"There are less rotation ticks than expected, \ - {len(rotation_ticks_peaks)}" - ) - - -def when_is_rotation_on(full_rotation): - # identify the rotation ticks that correspond to - # clockwise and counter clockwise rotations - threshold = 0.5 # Threshold to consider "on" or rotation occurring - rotation_on = np.zeros_like(full_rotation) - rotation_on[full_rotation > threshold] = 1 - - return rotation_on - - -def apply_rotation_direction(rotation_on, direction): - rotation_signal_copy = copy.deepcopy(rotation_on) - latest_rotation_on_end = 0 - - i = 0 - while i < len(direction): - # find the first rotation_on == 1 - first_rotation_on = np.where(rotation_signal_copy == 1)[0][0] - # now assign the value in dir to all the first set of ones - len_first_group = np.where( - rotation_signal_copy[first_rotation_on:] == 0 - )[0][0] - if len_first_group < 1000: - # skip this short rotation because it is a false one - # done one additional time to clean up the trace at the end - rotation_signal_copy = rotation_signal_copy[ - first_rotation_on + len_first_group : - ] - latest_rotation_on_end = ( - latest_rotation_on_end + first_rotation_on + len_first_group - ) - continue - - rotation_on[ - latest_rotation_on_end - + first_rotation_on : latest_rotation_on_end - + first_rotation_on - + len_first_group - ] = direction[i] - latest_rotation_on_end = ( - latest_rotation_on_end + first_rotation_on + len_first_group - ) - rotation_signal_copy = rotation_signal_copy[ - first_rotation_on + len_first_group : - ] - i += 1 # Increment the loop counter - - return rotation_on - - -def find_rotation_for_each_frame_from_motor( - frame_clock, rotation_ticks_peaks, rotation_on, frames_start -): - # calculate the rotation degrees for each frame - rotation_degrees = np.empty_like(frame_clock) - rotation_degrees[0] = 0 - current_rotation: float = 0 - tick_peaks_corrected = np.insert(rotation_ticks_peaks, 0, 0, axis=0) - for i in range(0, len(tick_peaks_corrected)): - time_interval = tick_peaks_corrected[i] - tick_peaks_corrected[i - 1] - if time_interval > 2000 and i != 0: - current_rotation = 0 - else: - current_rotation += 0.2 - rotation_degrees[ - tick_peaks_corrected[i - 1] : tick_peaks_corrected[i] - ] = current_rotation - signed_rotation_degrees = rotation_degrees * rotation_on - image_rotation_degree_per_frame = signed_rotation_degrees[frames_start] - image_rotation_degree_per_frame *= -1 - - return image_rotation_degree_per_frame, signed_rotation_degrees diff --git a/derotation/analysis/full_rotation_pipeline.py b/derotation/analysis/full_rotation_pipeline.py index 5a826a8..c917333 100644 --- a/derotation/analysis/full_rotation_pipeline.py +++ b/derotation/analysis/full_rotation_pipeline.py @@ -11,7 +11,6 @@ import yaml from fancylog import fancylog from scipy.signal import find_peaks -from skimage.exposure import rescale_intensity from sklearn.mixture import GaussianMixture from tifffile import imsave @@ -27,6 +26,7 @@ class FullPipeline: acquired with a rotating sample under a microscope. """ + ### ----------------- Main pipeline ----------------- ### def __init__(self, config_name): """DerotationPipeline is a class that derotates an image stack acquired with a rotating sample under a microscope. @@ -55,7 +55,6 @@ def __call__(self): - adding a circular mask to the rotated image stack - saving the masked image stack """ - self.contrast_enhancement() self.process_analog_signals() rotated_images = self.rotate_frames_line_by_line() masked = self.add_circle_mask(rotated_images, self.mask_diameter) @@ -153,7 +152,6 @@ def load_data(self): self.rotation_increment = self.config["rotation_increment"] self.rot_deg = self.config["rot_deg"] - self.adjust_increment = self.config["adjust_increment"] self.filename_raw = Path( self.config["paths_read"]["path_to_tif"] @@ -172,45 +170,7 @@ def load_data(self): logging.info(f"Dataset {self.filename_raw} loaded") logging.info(f"Filename: {self.filename}") - def contrast_enhancement(self): - """Applies contrast enhancement to the image stack. - It is useful to visualize the image stack before derotation. - """ - logging.info("Applying contrast enhancement...") - - self.image_stack = np.array( - [ - self.contrast_enhancement_single_image( - img, self.config["contrast_enhancement"] - ) - for img in self.image_stack - ] - ) - - @staticmethod - def contrast_enhancement_single_image( - img: np.ndarray, saturated_percentage=0.35 - ) -> np.ndarray: - """Applies contrast enhancement to a single image. - It is useful to visualize the image stack before derotation. - - Parameters - ---------- - img : np.ndarray - The image to enhance. - saturated_percentage : float, optional - The percentage of saturated pixels, by default 0.35 - - Returns - ------- - np.ndarray - The enhanced image. - """ - v_min, v_max = np.percentile( - img, (saturated_percentage, 100 - saturated_percentage) - ) - return rescale_intensity(img, in_range=(v_min, v_max)) - + ### ----------------- Analog signals processing pipeline ------------- ### def process_analog_signals(self): """From the analog signals (frame clock, line clock, full rotation, rotation ticks) calculates the rotation angles by line and frame. @@ -230,16 +190,18 @@ def process_analog_signals(self): self.rotation_ticks_peaks = self.find_rotation_peaks() - start, end = self.get_start_end_times(self.full_rotation, self.k) + start, end = self.get_start_end_times_with_threshold( + self.full_rotation, self.std_coef + ) self.rot_blocks_idx = self.correct_start_and_end_rotation_signal( start, end ) self.rotation_on = self.create_signed_rotation_array() self.drop_ticks_outside_of_rotation() - self.check_number_of_rotations() - if not self.is_number_of_ticks_correct() and self.adjust_increment: + + if not self.is_number_of_ticks_correct(): ( self.corrected_increments, self.ticks_per_rotation, @@ -252,11 +214,15 @@ def process_analog_signals(self): ( self.line_start, self.line_end, - ) = self.get_start_end_times(self.line_clock, self.k) + ) = self.get_start_end_times_with_threshold( + self.line_clock, self.std_coef + ) ( self.frame_start, self.frame_end, - ) = self.get_start_end_times(self.frame_clock, self.k) + ) = self.get_start_end_times_with_threshold( + self.frame_clock, self.std_coef + ) ( self.rot_deg_line, @@ -297,7 +263,7 @@ def find_rotation_peaks(self) -> np.ndarray: return peaks @staticmethod - def get_start_end_times( + def get_start_end_times_with_threshold( signal: np.ndarray, std_coef: float ) -> Tuple[np.ndarray, np.ndarray]: """Finds the start and end times of the on periods of the signal. @@ -307,7 +273,7 @@ def get_start_end_times( ---------- signal : np.ndarray An analog signal. - k : float + std_coef : float The factor used to quantify the threshold. Returns @@ -323,8 +289,8 @@ def get_start_end_times( thresholded_signal = np.zeros_like(signal) thresholded_signal[signal > threshold] = 1 - start = np.nonzero(np.diff(thresholded_signal) > 0)[0] - end = np.nonzero(np.diff(thresholded_signal) < 0)[0] + start = np.where(np.diff(thresholded_signal) > 0)[0] + end = np.where(np.diff(thresholded_signal) < 0)[0] return start, end @@ -419,7 +385,11 @@ def drop_ticks_outside_of_rotation(self) -> np.ndarray: inter_roatation_interval = [ idx - for i in range(self.number_of_rotations + 1) + # Effective number of rotations can be different than the one + # assumed in the config file. Therefore at this stage it is + # estimated by the number of start and end of rotations + # calculated from the rotation signal. + for i in range(len(edited_ends)) for idx in range( edited_ends[i], rolled_starts[i], @@ -428,7 +398,7 @@ def drop_ticks_outside_of_rotation(self) -> np.ndarray: self.rotation_ticks_peaks = np.delete( self.rotation_ticks_peaks, - np.nonzero( + np.where( np.isin(self.rotation_ticks_peaks, inter_roatation_interval) ), ) @@ -458,7 +428,10 @@ def check_number_of_rotations(self): "Start and end of rotations have different lengths" ) if self.rot_blocks_idx["start"].shape[0] != self.number_of_rotations: - raise ValueError("Number of rotations is not as expected") + logging.info( + "Number of rotations is not as expected. Adjusting..." + ) + self.number_of_rotations = self.rot_blocks_idx["start"].shape[0] logging.info("Number of rotations is as expected") @@ -504,7 +477,7 @@ def get_peaks_in_rotation(self, start: int, end: int) -> int: int The number of ticks in the rotation. """ - return np.nonzero( + return np.where( np.logical_and( self.rotation_ticks_peaks >= start, self.rotation_ticks_peaks <= end, @@ -550,7 +523,7 @@ def get_interpolated_angles(self) -> np.ndarray: ticks_with_increment = [ item - for i in range(self.number_of_rotations) + for i in range(len(self.corrected_increments)) for item in [self.corrected_increments[i]] * self.ticks_per_rotation[i] ] @@ -588,29 +561,28 @@ def remove_artifacts_from_interpolated_angles(self): """ logging.info("Cleaning interpolated angles...") - # find very short rotation periods in self.interpolated_angles - self.config["analog_signals_processing"][ "angle_interpolation_artifact_threshold" ] thresholded = np.zeros_like(self.interpolated_angles) thresholded[np.abs(self.interpolated_angles) > 0.15] = 1 - rotation_start = np.nonzero(np.diff(thresholded) > 0)[0] - rotation_end = np.nonzero(np.diff(thresholded) < 0)[0] + rotation_start = np.where(np.diff(thresholded) > 0)[0] + rotation_end = np.where(np.diff(thresholded) < 0)[0] - self.check_rotation_number(start=rotation_start, end=rotation_end) + self.check_rotation_number_after_interpolation( + rotation_start, rotation_end + ) - for i, (start, end) in enumerate( - zip(rotation_start[1:], rotation_end[:-1]) - ): + for start, end in zip(rotation_start[1:], rotation_end[:-1]): self.interpolated_angles[end:start] = 0 self.interpolated_angles[: rotation_start[0]] = 0 self.interpolated_angles[rotation_end[-1] :] = 0 - def check_rotation_number(self, start: np.ndarray, end: np.ndarray): + def check_rotation_number_after_interpolation( + self, start: np.ndarray, end: np.ndarray + ): """Checks that the number of rotations is as expected. - Raises ------ ValueError @@ -624,7 +596,10 @@ def check_rotation_number(self, start: np.ndarray, end: np.ndarray): "Start and end of rotations have different lengths" ) if start.shape[0] != self.number_of_rotations: - raise ValueError("Number of rotations is not as expected") + raise ValueError( + f"Number of rotations is not as expected, {start.shape[0]}" + + "instead of {self.number_of_rotations}" + ) def calculate_angles_by_line_and_frame( self, @@ -670,7 +645,7 @@ def clock_to_latest_line_start(self, clock_time: int) -> int: int The index of the line """ - return np.nonzero(self.line_start < clock_time)[0][-1] + return np.where(self.line_start < clock_time)[0][-1] def clock_to_latest_frame_start(self, clock_time: int) -> int: """Get the index of the frame that is being acquired at the given clock @@ -686,7 +661,7 @@ def clock_to_latest_frame_start(self, clock_time: int) -> int: int The index of the frame """ - return np.nonzero(self.frame_start < clock_time)[0][-1] + return np.where(self.frame_start < clock_time)[0][-1] def clock_to_latest_rotation_start(self, clock_time: int) -> int: """Get the index of the latest rotation that happened. @@ -701,7 +676,7 @@ def clock_to_latest_rotation_start(self, clock_time: int) -> int: int The index of the latest rotation """ - return np.nonzero(self.rot_blocks_idx["start"] < clock_time)[0][-1] + return np.where(self.rot_blocks_idx["start"] < clock_time)[0][-1] def plot_rotation_on_and_ticks(self): """Plots the rotation ticks and the rotation on signal. @@ -740,6 +715,9 @@ def plot_rotation_on_and_ticks(self): ax.spines["top"].set_visible(False) ax.spines["right"].set_visible(False) + Path(self.config["paths_write"]["debug_plots_folder"]).mkdir( + parents=True, exist_ok=True + ) plt.savefig( Path(self.config["paths_write"]["debug_plots_folder"]) / "rotation_ticks_and_rotation_on.png" @@ -758,7 +736,7 @@ def plot_rotation_angles(self): speeds = set(self.speed) last_idx_for_each_speed = [ - np.nonzero(self.speed == s)[0][-1] for s in speeds + np.where(self.speed == s)[0][-1] for s in speeds ] last_idx_for_each_speed = sorted(last_idx_for_each_speed) @@ -806,11 +784,15 @@ def plot_rotation_angles(self): handles, labels = ax.get_legend_handles_labels() fig.legend(handles, labels, loc="upper right") + Path(self.config["paths_write"]["debug_plots_folder"]).mkdir( + parents=True, exist_ok=True + ) plt.savefig( Path(self.config["paths_write"]["debug_plots_folder"]) / "rotation_angles.png" ) + ### ----------------- Derotation ----------------- ### def rotate_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. @@ -870,6 +852,7 @@ def find_image_offset(img): offset = np.min(gm.means_) return offset + ### ----------------- Saving ----------------- ### @staticmethod def add_circle_mask( image_stack: np.ndarray, @@ -949,6 +932,7 @@ def save(self, masked: np.ndarray): The masked derotated image stack. """ path = self.config["paths_write"]["derotated_tiff_folder"] + Path(path).mkdir(parents=True, exist_ok=True) imsave( path + self.config["paths_write"]["saving_name"] + ".tif", @@ -973,27 +957,30 @@ def save_csv_with_derotation_data(self): df["rotation_angle"] = self.rot_deg_frame[: self.num_frames] df["clock"] = self.frame_start[: self.num_frames] - df["direction"] = np.nan - df["speed"] = np.nan - df["rotation_count"] = np.nan + df["direction"] = np.nan * np.ones(len(df)) + df["speed"] = np.nan * np.ones(len(df)) + df["rotation_count"] = np.nan * np.ones(len(df)) + rotation_counter = 0 adding_roatation = False - for i, row in df.iterrows(): - if np.abs(row["rotation_angle"]) > 0.0: + for i in range(len(df)): + if np.abs(df.loc[i, "rotation_angle"]) > 0.0: adding_roatation = True - row["direction"] = self.direction[rotation_counter] - row["speed"] = self.speed[rotation_counter] - row["rotation_count"] = rotation_counter + df.loc[i, "direction"] = self.direction[rotation_counter] + df.loc[i, "speed"] = self.speed[rotation_counter] + df.loc[i, "rotation_count"] = rotation_counter if ( - rotation_counter < self.number_of_rotations - 1 + rotation_counter < 79 and adding_roatation - and np.isclose( - np.abs(df.loc[i + 1, "rotation_angle"]), 0.0, 1e-3 - ) + and np.abs(df.loc[i + 1, "rotation_angle"]) == 0.0 ): rotation_counter += 1 adding_roatation = False + Path(self.config["paths_write"]["derotated_tiff_folder"]).mkdir( + parents=True, exist_ok=True + ) + df.to_csv( self.config["paths_write"]["derotated_tiff_folder"] + self.config["paths_write"]["saving_name"] diff --git a/derotation/analysis/incremental_rotation_pipeline.py b/derotation/analysis/incremental_rotation_pipeline.py index c5b6699..d857630 100644 --- a/derotation/analysis/incremental_rotation_pipeline.py +++ b/derotation/analysis/incremental_rotation_pipeline.py @@ -6,7 +6,6 @@ import numpy as np import pandas as pd from matplotlib import pyplot as plt -from numpy import ndarray from scipy.ndimage import rotate from tqdm import tqdm @@ -37,7 +36,6 @@ def __call__(self): After processing the analog signals, the image stack is rotated by frame and then registered using phase cross correlation. """ - self.contrast_enhancement() super().process_analog_signals() rotated_images = self.roatate_by_frame() masked_unregistered = self.add_circle_mask(rotated_images) @@ -60,7 +58,7 @@ def __call__(self): self.save(masked) self.save_csv_with_derotation_data() - def create_signed_rotation_array(self) -> ndarray: + def create_signed_rotation_array(self) -> np.ndarray: logging.info("Creating signed rotation array...") rotation_on = np.zeros(self.total_clock_time) for i, (start, end) in enumerate( @@ -156,6 +154,29 @@ def get_interpolated_angles(self) -> np.ndarray: return interpolated_angles * -1 + def check_rotation_number_after_interpolation( + self, start: np.ndarray, end: np.ndarray + ): + """Checks that the number of rotations is as expected. + Raises + ------ + ValueError + if the number of start and end of rotations is different + ValueError + if the number of rotations is not as expected + """ + + if start.shape[0] != end.shape[0]: + raise ValueError( + "Start and end of rotations have different lengths" + ) + if ( + start.shape[0] != 1 + ): # The incremental rotation is a unique rotation + raise ValueError( + f"Number of rotations is not as expected: {start.shape[0]}" + ) + def roatate_by_frame(self) -> np.ndarray: """Rotate the image stack by frame. @@ -252,6 +273,9 @@ def plot_rotation_angles(self): handles, labels = ax.get_legend_handles_labels() fig.legend(handles, labels, loc="upper right") + Path(self.config["paths_write"]["debug_plots_folder"]).mkdir( + parents=True, exist_ok=True + ) plt.savefig( Path(self.config["paths_write"]["debug_plots_folder"]) / "rotation_angles.png" @@ -422,6 +446,10 @@ def save_csv_with_derotation_data(self): df["rotation_angle"] = self.rot_deg_frame[: self.num_frames] df["clock"] = self.frame_start[: self.num_frames] + Path(self.config["paths_write"]["derotated_tiff_folder"]).mkdir( + parents=True, exist_ok=True + ) + df.to_csv( self.config["paths_write"]["derotated_tiff_folder"] + self.config["paths_write"]["saving_name"] diff --git a/derotation/config/full_rotation.yml b/derotation/config/full_rotation.yml index 56f4bb5..bac1bbb 100644 --- a/derotation/config/full_rotation.yml +++ b/derotation/config/full_rotation.yml @@ -18,14 +18,12 @@ channel_names: [ "PI_rotticks", ] + rotation_increment: 0.2 -adjust_increment: True rot_deg: 360 debugging_plots: True -contrast_enhancement: 0.35 - analog_signals_processing: find_rotation_ticks_peaks: height: 4 diff --git a/derotation/config/incremental_rotation.yml b/derotation/config/incremental_rotation.yml index 56f4bb5..69e0c0d 100644 --- a/derotation/config/incremental_rotation.yml +++ b/derotation/config/incremental_rotation.yml @@ -24,8 +24,6 @@ rot_deg: 360 debugging_plots: True -contrast_enhancement: 0.35 - analog_signals_processing: find_rotation_ticks_peaks: height: 4 diff --git a/derotation/find_centroid.py b/derotation/find_centroid.py deleted file mode 100644 index e013c80..0000000 --- a/derotation/find_centroid.py +++ /dev/null @@ -1,91 +0,0 @@ -import numpy as np -import scipy.optimize as opt -from scipy.ndimage import gaussian_filter -from skimage import measure - - -def not_center_of_image(c): - return not (110 <= c[0] <= 145 and 110 <= c[1] <= 145) - - -def in_region(c): - return 50 <= c[0] <= 200 and 50 <= c[1] <= 200 - - -def preprocess_image(image, lower_threshold=-2700, higher_threshold=-2600): - image = np.where( - (image >= lower_threshold) & (image <= higher_threshold), 255, 0 - ).astype(np.uint8) - - return image - - -def detect_blobs(image, sigma=2.5, threshold_value=32): - filtered_image = gaussian_filter(image, sigma=sigma) - - # Apply thresholding to obtain a binary image - binary_image = filtered_image > threshold_value - - # Perform blob detection using connected component labeling - labels = measure.label(binary_image) - properties = measure.regionprops(labels) - - # Return the labels and regions of the detected blobs - return labels, properties - - -def extract_blob_centers(properties): - centroids = [p.centroid for p in properties] - - return centroids - - -def pipeline(image, x): - lower_threshold, higher_threshold, binary_threshold, sigma = x - img = preprocess_image(image, lower_threshold, higher_threshold) - labels, properties = detect_blobs(img, sigma, binary_threshold) - centroids = extract_blob_centers(properties) - return centroids - - -def get_optimized_centroid_location(image): - # initial parameters - lower_threshold = -2700 - higher_threshold = -2600 - binary_threshold = 32 - sigma = 2.5 - - x = [lower_threshold, higher_threshold, binary_threshold, sigma] - - iteration = [0] # use mutable object to hold the iteration number - - def cb(xk): - iteration[0] += 1 - print( - "Iteration: {0} - Function value: {1}".format(iteration[0], f(xk)) - ) - - def f(x): - centroids = pipeline(image, x) - count_valid_centroids = 0 - for c in centroids: - if not_center_of_image(c) and in_region(c): - count_valid_centroids += 1 - - if count_valid_centroids == 1: - return 0 - else: - return 1 - - res = opt.minimize( - f, - x, - method="nelder-mead", - options={"xtol": 1e-8, "disp": True}, - # callback=cb - ) - - # now find the centroid - centroids = pipeline(image, res.x) - - return centroids, res.x diff --git a/derotation/get_data.py b/derotation/get_data.py deleted file mode 100644 index 0981a9f..0000000 --- a/derotation/get_data.py +++ /dev/null @@ -1,43 +0,0 @@ -import numpy as np -import tifffile as tiff -from read_binary import read_rc2_bin -from scipy.io import loadmat - - -def get_paths(path): - path_tif = path / "imaging/runtest_00001.tif" - path_aux = path / "aux_stim/202303271657_21_005.bin" - path_config = "derotation/config.yml" - path_randperm = path / "stimlus_randperm.mat" - - return path_tif, path_aux, path_config, path_randperm - - -def get_data(path): - path_tif, path_aux, path_config, path_randperm = get_paths(path) - - image = tiff.imread(path_tif) - - pseudo_random = loadmat(path_randperm) - full_rotation_blocks_direction = pseudo_random["stimulus_random"][:, 2] > 0 - direction = np.ones(5) - direction[full_rotation_blocks_direction[0:5]] = -1 - - data, dt, chan_names, config = read_rc2_bin(path_aux, path_config) - data_dict = {chan: data[:, i] for i, chan in enumerate(chan_names)} - - frame_clock = data_dict["scanimage_frameclock"] - line_clock = data_dict["camera"] - full_rotation = data_dict["PI_rotCW"] - rotation_ticks = data_dict["Vistim_ttl"] - - return ( - image, - frame_clock, - line_clock, - full_rotation, - rotation_ticks, - dt, - config, - direction, - ) diff --git a/derotation/load_data/custom_data_loaders.py b/derotation/load_data/custom_data_loaders.py index 66ff6e9..e9e81b3 100644 --- a/derotation/load_data/custom_data_loaders.py +++ b/derotation/load_data/custom_data_loaders.py @@ -67,7 +67,8 @@ def get_analog_signals(path_to_aux: str, channel_names: list) -> tuple: _description_ """ - data = np.fromfile(path_to_aux, dtype=np.int16) + data = np.fromfile(path_to_aux, dtype=np.int16) # Has to be read as int16 + data = data.astype(np.int32) # cast data to int32 to avoid overflow data = data.reshape((-1, len(channel_names))) data = convert_to_volts(data) diff --git a/derotation/napari.yaml b/derotation/napari.yaml deleted file mode 100644 index 68110ba..0000000 --- a/derotation/napari.yaml +++ /dev/null @@ -1,10 +0,0 @@ -name: derotation -display_name: Neuroinformatics derotation tools -contributions: - commands: - - id: derotation.make_plotting_widget - python_name: derotation.plotting:Plotting - title: Make Plotting Widget - widgets: - - command: derotation.make_plotting_widget - display_name: NIU derotation plotting diff --git a/derotation/optimizers.py b/derotation/optimizers.py deleted file mode 100644 index 2e0a69e..0000000 --- a/derotation/optimizers.py +++ /dev/null @@ -1,27 +0,0 @@ -import numpy as np -from scipy.optimize import bisect - - -def count_frames(k, frame_clock, image): - # Calculate the threshold using a percentile of the total signal - mean = np.mean(frame_clock) - std = np.std(frame_clock) - threshold = mean + k * std - - frames_start = np.where(np.diff(frame_clock) > threshold)[0] - return len(frames_start) - len(image) - - -def find_best_k(frame_clock, image): - result = bisect(count_frames, -1, 1, args=(frame_clock, image)) - best_k = result - - # Check if the best value of k satisfies the assertions - threshold = np.mean(frame_clock) + best_k * np.std(frame_clock) - frames_start = np.where(np.diff(frame_clock) > threshold)[0] - - assert len(frames_start) == len( - image - ), f"{len(frames_start)} != {len(image)}" - - return best_k diff --git a/derotation/plots.py b/derotation/plots.py deleted file mode 100644 index 8c21587..0000000 --- a/derotation/plots.py +++ /dev/null @@ -1,225 +0,0 @@ -from find_centroid import ( - in_region, - not_center_of_image, -) -from matplotlib import pyplot as plt - - -def plot_drift_of_centroids( - centers, centers_rotated, centers_rotated_corrected -): - # plot drift of centers - fig, ax = plt.subplots(3, 1) - for k, centroid in enumerate(centers): - for c in centroid: - if not_center_of_image(c) and in_region(c): - ax[0].plot(k, c[1], marker="o", color="red") - ax[0].plot(k, c[0], marker="o", color="blue") - # ax[0].set_ylim(80, 180) - for k, centroid in enumerate(centers_rotated): - for c in centroid: - if not_center_of_image(c) and in_region(c): - ax[1].plot(k, c[1], marker="o", color="red") - ax[1].plot(k, c[0], marker="o", color="blue") - # ax[0].set_ylim(80, 180) - for k, centroid in enumerate(centers_rotated_corrected): - for c in centroid: - if not_center_of_image(c) and in_region(c): - ax[2].plot(k, c[1], marker="o", color="red") - ax[2].plot(k, c[0], marker="o", color="blue") - # ax[0].set_ylim(80, 180) - - return fig - - -def derotation_video_with_rotation_plot( - rotated_image, - image, - rotated_image_corrected, - centers, - centers_rotated, - centers_rotated_corrected, - frames_start, - signed_rotation_degrees, - image_rotation_degree_per_frame, -): - # Create a figure and axis for displaying the images - fig, ax = plt.subplots(1, 4) - - ax[2].set_title("Rotation degrees per frame") - - # Iterate through each image - for i, (image_rotated, image_original, image_corrected) in enumerate( - zip(rotated_image, image, rotated_image_corrected) - ): - ax[0].imshow(image_original, cmap="gist_ncar") - ax[1].imshow(image_rotated, cmap="gist_ncar") - ax[2].imshow(image_corrected, cmap="gist_ncar") - - for c in centers[i]: - if not_center_of_image(c) and in_region(c): - # dim blob - ax[0].plot(c[1], c[0], marker="*", color="red") - if not not_center_of_image(c): - # bright blob - ax[0].plot(c[1], c[0], marker="*", color="white") - for c in centers_rotated[i]: - if not_center_of_image(c) and in_region(c): - # dim blob - ax[1].plot(c[1], c[0], marker="*", color="red") - if not not_center_of_image(c): - # bright blob - ax[1].plot(c[1], c[0], marker="*", color="white") - for c in centers_rotated_corrected[i]: - if not_center_of_image(c) and in_region(c): - # dim blob - ax[2].plot(c[1], c[0], marker="*", color="red") - if not not_center_of_image(c): - # bright blob - ax[2].plot(c[1], c[0], marker="*", color="white") - - ax[0].axis("off") - ax[1].axis("off") - ax[2].axis("off") - - # add a vertical line on the plot on ax 2 - ax[3].axvline(frames_start[i], color="black", linestyle="--") - ax[3].plot(signed_rotation_degrees, label="rotation degrees") - ax[3].plot( - frames_start, - image_rotation_degree_per_frame, - linestyle="none", - marker="o", - color="red", - ) - - plt.pause(0.001) - ax[0].clear() - ax[1].clear() - ax[2].clear() - ax[3].clear() - - ax[0].set_title("Original image") - ax[1].set_title("Rotated image") - ax[2].set_title("Corrected image") - ax[3].set_title("Rotation degrees per frame") - - # axis off for the first two plots - ax[0].axis("off") - ax[1].axis("off") - - return fig - - -def threshold_boxplot(diffs, threshold): - fig, ax = plt.subplots(1, 1, sharex=True) - ax.boxplot(diffs) - ax.set_title("Threshold to identify frames start and end") - ax.set_ylabel("Difference between frames") - - ax.axhline(threshold, 0, len(diffs), color="red", label="threshold") - ax.axhline(-threshold, 0, len(diffs), color="red", label="threshold") - - return fig - - -def analog_signals_overview_plots( - diffs, - frame_clock, - frames_start, - frames_end, - line_clock, - full_rotation, - rotation_on, - rotation_ticks, - rotation_ticks_peaks, -): - fig, ax = plt.subplots(1, 1, sharex=True) - ax.plot(diffs, label="frame clock", color="black", alpha=0.5) - - fig, ax = plt.subplots(4, 1, sharex=True) - ax[0].plot( - frame_clock, - label="frame clock", - color="black", - alpha=0.5, - rasterized=True, - ) - # plot dots for starting and ending points of the frame_clock signal - ax[0].plot( - frames_start, - frame_clock[frames_start], - linestyle="none", - marker="o", - color="red", - alpha=0.5, - rasterized=True, - ) - ax[0].plot( - frames_end, - frame_clock[frames_end], - linestyle="none", - marker="o", - color="green", - alpha=0.5, - rasterized=True, - ) - ax[1].plot( - line_clock, - label="line clock", - color="red", - alpha=0.5, - rasterized=True, - ) - ax[2].plot( - full_rotation, - label="rot tick", - color="blue", - alpha=0.5, - rasterized=True, - ) - ax[2].plot( - rotation_on, - label="rotation with direction, 1 = CW, -1 = CCW", - color="green", - alpha=0.5, - rasterized=True, - ) - ax[3].plot( - rotation_ticks, - label="rot tick 2", - color="green", - alpha=0.5, - marker="o", - rasterized=True, - ) - ax[3].plot( - rotation_ticks_peaks, - # np.ones(len(rot_tick2_peaks)) * 5.2, - rotation_ticks[rotation_ticks_peaks], - linestyle="none", - marker="*", - color="red", - alpha=0.5, - rasterized=True, - ) - - # set the initial x axis limits - for axis in ax: - # axis.set_xlim(1610000, 1800000) - # axis.set_xlim(1680000, 1710000) - axis.spines["top"].set_visible(False) - axis.spines["right"].set_visible(False) - - # set subplots titles - ax[0].set_title( - "Frame clock (black) and starting/ending points (red/green)" - ) - ax[1].set_title("Line clock") - ax[2].set_title("Full rotation info") - ax[3].set_title("Rotation ticks, 0.2 deg for 1 tick (green), peaks (red)") - - # plot title - fig.suptitle("Frame clock and rotation ticks") - - return fig diff --git a/derotation/plotting.py b/derotation/plotting.py deleted file mode 100644 index f26cc6d..0000000 --- a/derotation/plotting.py +++ /dev/null @@ -1,40 +0,0 @@ -from typing import Optional - -import numpy as np -from napari.viewer import Viewer -from napari_matplotlib.base import SingleAxesWidget -from qtpy.QtWidgets import ( - QPushButton, - QVBoxLayout, - QWidget, -) - - -class DerotationCanvas(SingleAxesWidget): - def __init__( - self, - napari_viewer: Viewer, - parent: Optional[QWidget] = None, - ): - super().__init__(napari_viewer, parent=parent) - self.angles_over_time = np.sin(np.linspace(0, 10, 100)) - self._update_layers(None) - - def draw(self): - self.axes.plot(self.angles_over_time, color="red") - self.axes.set_title(f"z={self.current_z}") - self.axes.axvline(self.current_z) - - -class Plotting(QWidget): - def __init__(self, napari_viewer: Viewer): - super().__init__() - - self._viewer = napari_viewer - self.setLayout(QVBoxLayout()) - self.button = QPushButton() - self.button.setText("Make plot") - self.layout().addWidget(self.button) - - self.mpl_widget = DerotationCanvas(self._viewer) - self.layout().addWidget(self.mpl_widget) diff --git a/derotation/rotate_images.py b/derotation/rotate_images.py deleted file mode 100644 index cdc57a0..0000000 --- a/derotation/rotate_images.py +++ /dev/null @@ -1,54 +0,0 @@ -import numpy as np -from find_centroid import pipeline -from scipy.ndimage import rotate - - -def rotate_images( - image, - image_rotation_degree_per_frame, - new_image_rotation_degree_per_frame, -): - # rotate the image to the correct position according to the frame_degrees - rotated_image = np.empty_like(image) - rotated_image_corrected = np.empty_like(image) - centers = [] - centers_rotated = [] - centers_rotated_corrected = [] - for i in range(len(image)): - lower_threshold = -2700 - higher_threshold = -2600 - binary_threshold = 32 - sigma = 2.5 - - defoulting_parameters = [ - lower_threshold, - higher_threshold, - binary_threshold, - sigma, - ] - - rotated_image[i] = rotate( - image[i], image_rotation_degree_per_frame[i], reshape=False - ) - rotated_image_corrected[i] = rotate( - image[i], new_image_rotation_degree_per_frame[i], reshape=False - ) - - # params = optimized_parameters[i] - # if i in indexes else defoulting_parameters - - centers.append(pipeline(image[i], defoulting_parameters)) - centers_rotated.append( - pipeline(rotated_image[i], defoulting_parameters) - ) - centers_rotated_corrected.append( - pipeline(rotated_image_corrected[i], defoulting_parameters) - ) - - return ( - rotated_image, - rotated_image_corrected, - centers, - centers_rotated, - centers_rotated_corrected, - ) diff --git a/docs/source/conf.py b/docs/source/conf.py index ef73a02..e598e9c 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -43,6 +43,7 @@ "sphinx.ext.napoleon", "myst_parser", "numpydoc", + "numpydoc", "nbsphinx", "sphinx_autodoc_typehints", "sphinx_design", diff --git a/tests/test_unit/test_finding_correct_start_end_times_with_threshold.py b/tests/test_unit/test_finding_correct_start_end_times_with_threshold.py index 292b5d7..edd134b 100644 --- a/tests/test_unit/test_finding_correct_start_end_times_with_threshold.py +++ b/tests/test_unit/test_finding_correct_start_end_times_with_threshold.py @@ -10,7 +10,7 @@ def test_finding_correct_start_end_times_with_threshold( number_of_rotations: int, rotation_len: int, ): - start, end = derotation_pipeline.get_start_end_times( + start, end = derotation_pipeline.get_start_end_times_with_threshold( full_rotation, std_coef )