Code source de palm_tracer.Processing.Parsing

""" Fichier contenant des fonctions pour parser les entrées et sorties des DLLs externes. """
from typing import Any

import numpy as np
import pandas as pd

# Segmentation (Localization)
PARSING_COLUMNS: dict[str, dict[str, Any]] = {
		"Localization": {
				"columns": ["Id", "Plane", "Index", "Channel", "X", "Y", "Z", "Integrated Intensity",
							"Sigma X", "Sigma Y", "Theta", "MSE XY", "MSE Z", "Pair Distance",
							"Intensity 0", "Intensity Offset", "Intensity", "Surface", "Circularity"],
				"types":   {"Id": "int32", "Plane": "int32", "Index": "int32", "Surface": "int32", "Channel": "int32"}
				},
		"Tracking":     {
				"columns": ["Track", "Plane", "Id", "X", "Y", "Z", "Integrated Intensity", "Color", "Surface"],
				"types":   {"Track": "int32", "Plane": "int32", "Id": "int32", "Color": "int32", "Surface": "int32"}
				},
		}

COLS_FOR_TRACKING = ["Id", "X", "Y", "Z", "Intensity", "Pair Distance", "Surface"]

# Tracking
N_TRACK = 9  # Nombre de paramètres pour le tracking.


##################################################
[docs] def get_max_points(height: int = 256, width: int = 256, n_planes: int = 1, density: float = 0.2) -> int: """ Calcule le nombre maximal théorique de points détectables basé sur les dimensions et la densité de l'image. :param height: Hauteur de l'image (nombre de lignes). Par défaut 256. :param width: Largeur de l'image (nombre de colonnes). Par défaut 256. :param n_planes: Nombre de plans de l'image. Par défaut 1. :param density: Densité de points par pixel. Par défaut 0.2. :return: Nombre maximal théorique de points détectables. """ return int(height * width * density * n_planes) * len(PARSING_COLUMNS["Localization"]["columns"])
##################################################
[docs] def rearrange_dataframe_columns(data: pd.DataFrame, columns: list[str], remaining: bool = True) -> pd.DataFrame: """ Réorganise les colonnes d'un DataFrame en mettant certaines en premier, avec l'option d'ajouter les colonnes restantes dans leur ordre d'origine. :param data: Le DataFrame à réorganiser. :param columns: Liste des noms de colonnes à placer en premier. :param remaining: Si `True`, ajoute les colonnes non spécifiées après celles définies dans `columns`. :return: Un nouveau DataFrame avec les colonnes réorganisées. :raises ValueError: Si une colonne spécifiée dans `columns` n'existe pas dans `data`. """ # Vérifier que toutes les colonnes spécifiées existent dans le DataFrame missing_columns = [col for col in columns if col not in data.columns] if missing_columns: raise ValueError(f"Les colonnes suivantes sont absentes du DataFrame : {missing_columns}") if remaining: remaining_columns = [col for col in data.columns if col not in columns] # Colonnes restantes (toutes sauf celles déjà définies) columns = columns + remaining_columns # Ajout des colonnes restantes aux colonnes de départ if list(data.columns[:len(columns)]) == columns: return data # Optimisation : évite la copie si déjà bon ordre return data.loc[:, columns] # Réorganisation du DataFrame
##################################################
[docs] def parse_result(data: np.ndarray, file_type: str = "Localization") -> pd.DataFrame: """ Parsing du résultat de la DLL PALM. On a un tableau 1D de grande taille en entrée : - On le découpe en tableau 2D à 13 colonnes (`N_SEGMENTS`). La taille du tableau est vérifié et tronqué si nécessaire. - On le transforme en dataframe avec les colonnes définies par `SEGMENTS`. - On supprime les lignes remplies de 0 et de -1. Un test sur les colonnes X ou Y strictement positif suffit (le SigmaX et SigmaY peuvent être à 0). :param data: Donnée en entrée récupérées depuis la DLL PALM. :param file_type: Type de fichier à parser ("Localization" ou "Tracking") :return: Dataframe filtré """ # Récupération des éléments columns = PARSING_COLUMNS[file_type]["columns"] n_columns = len(columns) types = PARSING_COLUMNS[file_type]["types"] # Manipulation du tableau 1D. size = (data.size // n_columns) * n_columns # Récupération de la taille correcte si non multiple de N_SEGMENT data = data[:size].reshape(-1, n_columns) # Passage en tableau 2D data = data[data[:, columns.index("X")] > 0] # Filtrage en amont # data = data.astype(np.float32) # Conversion en float pour alléger la mémoire (à ce stade la précision est suffisante) res = pd.DataFrame(data, columns=columns) # Transformation en Dataframe res = res.astype(types) return res
##################################################
[docs] def parse_localization_to_tracking(data: pd.DataFrame) -> np.ndarray: """ Parsing du résultat de la localisation pour la DLL de Tracking. :param data: Donnée en entrée récupérées depuis la localisation. :return: Dataframe """ # Ajoute une ligne de -1 à chaque changement de Plan dans la localisation res = [] previous_plan = None blank = [-1 for _ in COLS_FOR_TRACKING] for _, row in data.iterrows(): if previous_plan is not None and row["Plane"] != previous_plan: res += blank res += row[COLS_FOR_TRACKING].to_list() previous_plan = row["Plane"] res += blank # Ajout d'une dernière ligne -1 à la fin return np.asarray(res, dtype=np.float64)