Source code for exr.align.align_eval

import os
import json
import h5py
import numpy as np
from exr.align.align_utils import alignment_NCC
from exr.utils import configure_logger
from exr.config import Config
from typing import List,Optional,Dict


logger = configure_logger('ExR-Tools')


[docs] def measure_round_alignment_NCC(config: Config, round: int, roi: int) -> List[float]: r""" Measures the alignment of a specific round and ROI (Region Of Interest) against a reference round using Normalized Cross-Correlation (NCC). The results are saved to a JSON file at a specified path. :param config: Configuration options. This should be an instance of the Config class. :type config: Config :param round: The round to measure alignment for. :type round: int :param roi: The ROI to measure alignment for. :type roi: int :return: List of distance errors after alignment. :rtype: List[float] :note: The results are saved to a JSON file located at `<processed_data_path>/alignment_evaluation/ROI<roi>/NCC_ROI<roi>.json`. """ distance_errors = [] logger.info( f"Alignment Evaluation: Analyzing alignment between ref round:{config.ref_round} and round:{round} - ROI:{roi}") try: with h5py.File(config.h5_path.format(config.ref_round, roi), "r") as f: ref_vol = f[config.ref_channel][()] with h5py.File(config.h5_path.format(round, roi), "r") as f: aligned_vol = f[config.ref_channel][()] if np.count_nonzero(aligned_vol) > config.nonzero_thresh: ref_vol = (ref_vol - np.min(ref_vol)) / \ (np.max(ref_vol) - np.min(ref_vol)) aligned_vol = (aligned_vol - np.min(aligned_vol)) / \ (np.max(aligned_vol) - np.min(aligned_vol)) keepers = [] for zz in range(aligned_vol.shape[0]): if np.count_nonzero(aligned_vol[zz, :, :]) > 0: keepers.append(zz) logger.info( f"Alignment Evaluation: Round:{round} - ROI:{roi}, {len(keepers)} slices of {aligned_vol.shape[0]} kept.") if len(keepers) < 10: logger.info( f"Alignment Evaluation: Round:{round} - ROI:{roi}, fewer than 10 slices. Skipping evaluation...") else: ref_vol = ref_vol[keepers, :, :] aligned_vol = aligned_vol[keepers, :, :] distance_errors = alignment_NCC(config, ref_vol, aligned_vol) eval_file = os.path.join( config.processed_data_path, "alignment_evaluation", f"ROI{roi}", f"NCC_ROI{roi}.json") eval_data = {} if os.path.exists(eval_file): with open(eval_file, 'r') as f: eval_data = json.load(f) eval_data[f"R{config.ref_round}-{round}"] = distance_errors.tolist() with open(eval_file, 'w') as f: json.dump(eval_data, f) return distance_errors except Exception as e: logger.error( f"Error during NCC alignment measurement for Round: {round}, ROI: {roi}, Error: {e}") raise
[docs] def plot_alignment_evaluation(config: Config, roi: int, percentile: int = 95, save_fig: Optional[bool] = False) -> None: """ Plots alignment evaluation data using a violin plot. :param config: Configuration options. This should be an instance of the Config class. :type config: Config :param roi: The ROI to plot. :type roi: int :param percentile: The percentile to filter the data. Default is 95. :type percentile: int :param save_fig: Flag to save the figure. If False, the figure is not saved. Default is False. :type save_fig: Optional[bool] :raises: Raises an exception if an error occurs during the plotting process. :note: The function shows the plot using plt.show() and optionally saves it to `<processed_data_path>/alignment_evaluation/ROI<roi>/Alignment_Evaluation_ROI<roi>.json` if save_dir is True. """ def load_eval_data(config: Config, roi: int) -> dict: eval_file = os.path.join(config.processed_data_path, "alignment_evaluation", f"ROI{roi}", f"NCC_ROI{roi}.json") with open(eval_file, 'r') as f: eval_data = json.load(f) return eval_data import seaborn as sns import matplotlib.pyplot as plt try: eval_data = load_eval_data(config, roi) sorted_keys = sorted(eval_data.keys(), key=lambda x: int(x.split('-')[1])) keys_list = [] arrays_list = [] for key in sorted_keys: value = eval_data[key] keys_list.append(key) eval_array = np.array(value) percentile_value = np.percentile(eval_array, percentile) eval_filtered = eval_array[eval_array <= percentile_value] arrays_list.append(np.array(eval_filtered)) plt.figure(figsize=(10, 6)) sns.violinplot(data=arrays_list, inner="quartile") plt.ylabel("Registration Error (\u00B5m)") plt.xticks(np.arange(len(keys_list)), keys_list, rotation=45) plt.title(f"Violin Plot of Alignment Evaluation Results ROI{roi}") if save_fig: save_path = os.path.join(config.processed_data_path,"alignment_evaluation", f"ROI{roi}",f"Alignment_Evaluation_ROI{roi}.png") plt.savefig(save_path) logger.info(f'Figure saved at {save_path}') plt.show() logger.info(f'Successfully plotted alignment evaluation for ROI: {roi}.') except Exception as e: logger.error(f'Error during alignment evaluation plotting for ROI: {roi}, Error: {e}') raise
[docs] def calculate_alignment_evaluation_ci(config: Config, roi: int, ci: float = 95, percentile_filter: float = 95) -> Dict[str, Dict[str, float]]: r""" Calculate the confidence interval (CI) for alignment evaluation and save it as a JSON file. :param config: Configuration options. This should be an instance of the Config class. :type config: Config :param roi: The ROI (Region Of Interest) for which alignment evaluation will be calculated. :type roi: int :param ci: Confidence level for CI calculation. Default is 95. :type ci: float :param percentile_filter: Percentile value for filtering the data before CI calculation. Default is 95. :type percentile_filter: float :return: A dictionary containing the lower and upper bounds of CI for each alignment key. :rtype: Dict[str, Dict[str, float]] """ def bootstrap_mean_ci(data: np.array, n_bootstrap_samples: int, ci: float) -> (float, float): bootstrap_means = np.zeros(n_bootstrap_samples) for i in range(n_bootstrap_samples): bootstrap_sample = np.random.choice(data, size=len(data), replace=True) bootstrap_means[i] = np.mean(bootstrap_sample) lower = (100 - ci) / 2 upper = 100 - lower lower_bound, upper_bound = np.percentile(bootstrap_means, [lower, upper]) return lower_bound, upper_bound ci_data = {} eval_file_path = os.path.join(config.processed_data_path, "alignment_evaluation", f"ROI{roi}", f"NCC_ROI{roi}.json") try: with open(eval_file_path, 'r') as f: eval_data = json.load(f) sorted_keys = sorted(eval_data.keys(), key=lambda x: int(x.split('-')[1])) for key in sorted_keys: value = eval_data[key] eval_array = np.array(value) percentile_value = np.percentile(eval_array, percentile_filter) eval_filtered = eval_array[eval_array <= percentile_value] lower_bound, upper_bound = bootstrap_mean_ci(eval_filtered, len(eval_data)//10, ci) ci_data[key] = { "lower_bound": lower_bound, "upper_bound": upper_bound } ci_file_path = os.path.join(config.processed_data_path, "alignment_evaluation", f"ROI{roi}", f"CI_ROI{roi}.json") with open(ci_file_path, 'w') as f: json.dump(ci_data, f) logger.info(f"CI calculation for ROI:{roi} completed successfully.") except Exception as e: logger.error(f"Error during CI calculation for ROI:{roi}. Error: {e}") raise return ci_data