Source code for wetting_angle_kit.contact_angle_method.contact_angle_analyzer
from abc import ABC, abstractmethod
from typing import Any, Dict, List, Optional
import numpy as np
from .binning_method.angle_fitting_binning import ContactAngleBinning
from .sliced_method.multi_processing import ContactAngleSlicedParallel
[docs]
class BaseContactAngleAnalyzer(ABC):
"""Abstract base for contact angle analysis across trajectories."""
[docs]
@abstractmethod
def analyze(
self, frame_range: Optional[List[int]] = None, **kwargs
) -> Dict[str, Any]:
"""Run the analysis and return statistics."""
pass
@abstractmethod
def get_method_name(self) -> str:
pass
[docs]
def summary(self) -> Dict[str, float]:
"""Return quick summary statistics."""
results = self.analyze()
return {
"mean": results["mean_angle"],
"std": results["std_angle"],
"n_samples": len(results["angles"]),
}
[docs]
class SlicedContactAngleAnalyzer(BaseContactAngleAnalyzer):
"""
This class is a wrapper around the ContactAngleSlicedParallel class.
It is used to analyze the contact angle of a liquid on a solid surface.
"""
def __init__(self, parser, output_repo: str, **kwargs):
self.parser = parser
self.output_repo = output_repo
self._processor = ContactAngleSlicedParallel(
filename=parser.filepath, output_repo=output_repo, **kwargs
)
[docs]
def analyze(
self, frame_range: Optional[List[int]] = None, **kwargs
) -> Dict[str, Any]:
if frame_range is None:
frame_range = list(range(self.parser.frame_count()))
frame_to_angle = self._processor.process_frames_parallel(
frames_to_process=frame_range, **kwargs
)
angles = np.array(list(frame_to_angle.values()))
return {
"mean_angle": np.mean(angles),
"std_angle": np.std(angles),
"angles": frame_to_angle,
"frames_analyzed": list(frame_to_angle.keys()),
"method_metadata": {"frames_per_angle": 1},
}
def get_method_name(self) -> str:
return "sliced_parallel"
[docs]
class BinningContactAngleAnalyzer(BaseContactAngleAnalyzer):
"""
This class is a wrapper around the ContactAngleBinning class.
It is used to analyze the contact angle of a liquid on a solid surface.
"""
def __init__(self, parser, output_dir: str, **kwargs):
self.parser = parser
self.output_dir = output_dir
self._analyzer = ContactAngleBinning(
parser=parser, output_dir=output_dir, **kwargs
)
[docs]
def analyze(
self,
frame_range: Optional[List[int]] = None,
split_factor: Optional[int] = None,
**kwargs,
) -> Dict[str, Any]:
if frame_range is None:
frame_range = list(range(self.parser.frame_count()))
if split_factor is None:
angle, _ = self._analyzer.process_batch(frame_range)
angles = np.array([angle])
method_metadata = {"frames_per_angle": len(frame_range)}
else:
angles = []
for batch_idx, start in enumerate(range(0, len(frame_range), split_factor)):
end = min(start + split_factor, len(frame_range))
angle, _ = self._analyzer.process_batch(
frame_range[start:end],
batch_index=batch_idx + 1, # Pass batch index
)
angles.append(angle)
angles = np.array(angles)
method_metadata = {"frames_per_trajectory": split_factor}
return {
"mean_angle": np.mean(angles),
"std_angle": np.std(angles),
"angles": angles,
"frames_analyzed": frame_range,
"method_metadata": method_metadata,
}
def get_method_name(self) -> str:
return "binning_density"