| from dataclasses import dataclass, field | |
| from pathlib import Path | |
| from typing import Optional | |
| import numpy as np | |
| from .params import AnalysisParameters | |
| class BubbleData: | |
| frame_name: str | |
| bubble_id: int | |
| diameter_um: float | |
| volume_um3: float | |
| area_px: float | |
| centroid_x: float # column (image x) | |
| centroid_y: float # row (image y) | |
| circularity: float | |
| aspect_ratio: float | |
| is_valid: bool = True | |
| class FrameResult: | |
| frame_name: str | |
| num_bubbles: int | |
| num_rejected: int | |
| bubbles: list = field(default_factory=list) | |
| mask: Optional[np.ndarray] = None | |
| image_path: Optional[Path] = None | |
| class AnalysisResults: | |
| sample_name: str | |
| frames: list = field(default_factory=list) | |
| all_bubbles: list = field(default_factory=list) | |
| parameters: AnalysisParameters = field(default_factory=AnalysisParameters) | |
| def diameters(self) -> np.ndarray: | |
| return np.array([b.diameter_um for b in self.all_bubbles if b.is_valid]) | |
| def volumes(self) -> np.ndarray: | |
| return np.array([b.volume_um3 for b in self.all_bubbles if b.is_valid]) | |
| def num_frames(self) -> int: | |
| return len(self.frames) | |
| def total_bubbles(self) -> int: | |
| return len([b for b in self.all_bubbles if b.is_valid]) | |
| def total_rejected(self) -> int: | |
| return sum(f.num_rejected for f in self.frames) | |