Source code for pylorenzmie.analysis.Localizer

from pylorenzmie.lib import LMObject, Azimuthal, CircleTransform
from pylorenzmie.lib.lmtypes import Image, Properties
import numpy as np
import trackpy as tp
import pandas as pd


[docs] class Localizer(LMObject): '''Detect and localize holographic features in a normalized image. Uses :class:`~pylorenzmie.lib.CircleTransform` to enhance ring-like patterns, then calls :func:`trackpy.locate` to find feature centers. Bounding boxes are sized to enclose a specified number of fringes. Inherits from :class:`pylorenzmie.lib.LMObject`. Parameters ---------- diameter : int, optional Nominal feature diameter passed to ``trackpy.locate`` [pixels]. Default: 31. nfringes : int, optional Number of interference fringes to enclose in each bounding box. Default: 20. ''' def __init__(self, diameter: int = 31, nfringes: int = 20) -> None: super().__init__() self.diameter = diameter self.nfringes = nfringes self._circletransform = CircleTransform() @LMObject.properties.getter def properties(self) -> Properties: '''Localization parameters: nfringes and diameter.''' return dict(nfringes=self.nfringes, diameter=self.diameter)
[docs] def localize(self, image: Image | list[Image], diameter: int | None = None, nfringes: int | None = None, **kwargs) -> pd.DataFrame: '''Localize features in a normalized holographic microscopy image. Parameters ---------- image : numpy.ndarray or list of numpy.ndarray Normalized hologram intensity. diameter : int, optional Override :attr:`diameter` for this call. nfringes : int, optional Override :attr:`nfringes` for this call. **kwargs Additional keyword arguments passed to ``trackpy.locate``. Returns ------- predictions : pandas.DataFrame Columns: ``x_p``, ``y_p``, ``bbox``. ``bbox`` is ``((x0, y0), width, height)``. ''' if isinstance(image, list): return [self.localize(b, diameter, nfringes, **kwargs) for b in image] diameter = self.diameter if diameter is None else diameter nfringes = self.nfringes if nfringes is None else nfringes a = self._circletransform.transform(image) features = tp.locate(a, diameter, characterize=False, **kwargs) predictions = [] for _, feature in features.iterrows(): r_p = feature[['x', 'y']].to_numpy() b = Azimuthal.avg(image, r_p) ndx = np.where(np.diff(np.sign(b - 1.)) != 0)[0] + 1 try: extent = ndx[nfringes] except IndexError: extent = ndx[-1] r0 = tuple((r_p - extent / 2).astype(int)) bbox = (r0, extent, extent) predictions.append(dict(x_p=r_p[0], y_p=r_p[1], bbox=bbox)) return pd.DataFrame(predictions)
detect = localize
[docs] def crop(self, image: Image, bbox: tuple[tuple[int, int], int, int]) -> Image: '''Crop an image to a bounding box. Parameters ---------- image : numpy.ndarray Full-frame image. bbox : tuple ``((x0, y0), width, height)``. Returns ------- cropped : numpy.ndarray Sub-image of shape ``(height, width)``. ''' (x0, y0), w, h = bbox return image[y0:y0 + h, x0:x0 + w]
[docs] @classmethod def example(cls) -> None: # pragma: no cover from time import perf_counter import matplotlib.pyplot as plt from matplotlib.patches import Rectangle from pylorenzmie.utilities import example_hologram localizer = cls() image = example_hologram('image0010.png').data start = perf_counter() features = localizer.localize(image) print(f'Elapsed time: {perf_counter() - start:.3f} s') print(features) style = dict(fill=False, linewidth=3, edgecolor='r') fig, ax = plt.subplots() ax.imshow(image, cmap='gray') for bbox in features.bbox: ax.add_patch(Rectangle(*bbox, **style)) plt.show()
if __name__ == '__main__': # pragma: no cover Localizer.example()