Source code for pyleem.analyzer
import matplotlib.pyplot as plt
from pyleem.roi import NoROI
import numpy as np
[docs]
def find_onset(profiles):
"""Find the onset of a profile.
The profiles can be full images or line profiles.
Here we look at the "relative difference". The
np.gradient is not used because it tracks two steps at a time.
:param list profiles: List of profiles to find the onset of.
:return: Index of the profile with the steepest rise.
:rtype: int
"""
profile_sums = np.array([profile.sum() for profile in profiles], dtype=np.float64)
profile_diff = np.diff(profile_sums) / profile_sums[:-1]
return np.argmax(profile_diff)
[docs]
class Analyzer:
"""Base analyzer class for LEEM data analysis.
Provides core functionality for domain-specific LEEM analysis.
Raw images come from readers. Processed images default to raw images.
Annotated images default to processed images.
All variable access should be index aware.
:param iterable readers: Reader objects used by the analyzer.
:ivar list readers: List of readers after onset.
:ivar ROI roi: ROI for measuring the image.
:ivar int onset: Index of the onset of the image.
"""
REGISTRY = {}
def __init_subclass__(cls, **kwargs):
"""Register the analyzer class in the registry."""
super().__init_subclass__(**kwargs)
cls.REGISTRY[cls.__name__] = cls
def __init__(self, readers, roi=None, onset=0):
if not readers:
raise ValueError("readers cannot be empty")
if onset is None:
onset = find_onset([reader.image for reader in readers])
self.onset = onset
self.readers = readers[self.onset :]
if not self.readers:
raise ValueError("readers empty after onset")
self.roi = roi or NoROI()
self.indices = range(len(self.readers))
[docs]
def annotate_image(self, index, ax):
"""Annotate the image for matplotlib plotting.
Override to annotate the image. Currently, the annotate_image method
does not take additional arguments. The idea is that the whole image
stack should be annotated the same way.
"""
return ax
[docs]
def get_autolevel(self, image):
"""Return percentile display limits for image contrast."""
vmin, vmax = np.percentile(image, [1, 99])
if vmin == vmax:
# in case the image is uniform
return float(vmin) - 0.5, float(vmax) + 0.5
return float(vmin), float(vmax)
[docs]
def plot_image(self, index, ax=None, annotate=False, autolevel=False):
"""Plot image data.
:param matplotlib.axes.Axes ax: Matplotlib axes object.
:param bool autolevel: auto leveling the image. Act as auto-contrast.
"""
ax = ax or plt.gca()
image = self.get_processed_image(index)
if autolevel:
vmin, vmax = self.get_autolevel(image)
ax.imshow(image, vmin=vmin, vmax=vmax)
else:
ax.imshow(image)
if annotate:
self.annotate_image(index, ax)
ax.set_xlabel("X [pixels]")
ax.set_ylabel("Y [pixels]")
ax.set_title("Image Data")
return ax
[docs]
def get_measurement(self, index):
"""Measure the image data."""
image = self.get_processed_image(index)
return self.roi.measure(image)
[docs]
def get_profile(self, index):
"""Extract the line profile from the image data."""
return self.get_measurement(index).profile
[docs]
def get_pixel(self, index):
"""Return the pixel positions for a profile."""
profile = self.get_profile(index)
if profile is None:
raise ValueError("Profile is not available")
return np.arange(len(profile))
[docs]
def get_raw_image(self, index):
"""Return the raw image."""
return self.readers[index].image
[docs]
def get_processed_image(self, index):
"""Return the processed image.
The method should be overridden if
the processed image is not the same.
"""
return self.get_raw_image(index)
[docs]
def analyze(self, **kwargs):
"""Perform the analysis.
The method is not required if the analyzer does not need to
work with configuration and workflow logic.
The analyze method is recommended to be stack analysis
logic instead of acting on individual readers.
For large data output, it is recommended for analyze to save
the result to a file.
"""
raise NotImplementedError("'analyze' method is not implemented")
Analyzer.REGISTRY[Analyzer.__name__] = Analyzer