Source code for atomscale.results.rheed_video

"""Result objects for RHEED video analysis."""

from __future__ import annotations

import warnings
from uuid import UUID

from monty.json import MSONable
from pandas import DataFrame

from atomscale.results import RHEEDImageResult
from atomscale.rheed_metadata import legacy_views_frame


[docs] class RHEEDVideoResult(MSONable):
[docs] def __init__( self, data_id: UUID | str, timeseries_data: DataFrame, snapshot_image_data: list[RHEEDImageResult] | None, views: DataFrame | bool | None = None, collected_datetime: str | None = None, rotating: bool | None = None, ): """RHEED video result Args: data_id (UUID | str): Data ID for the entry in the data catalogue. timeseries_data (DataFrame): Pandas DataFrame with per-frame RHEED features, indexed against a "Time" column. Columns are capitalized labels such as "Cluster ID", "Specular Intensity", "Strain", "Cumulative Strain", "Oscillation Period", "Diffraction Spot Count", and "Lattice Spacing". snapshot_image_data (list[atomscale.results.rheed_image.RHEEDImageResult] | None): One :class:`atomscale.results.rheed_image.RHEEDImageResult` per snapshot extracted from the video, or None if no snapshots were extracted. views (DataFrame | None): Motion intervals and effective azimuth annotations. Omitted or None means no views are known, which reads as not rotating. collected_datetime (str | None): Datetime when the data was collected. rotating (bool | None): Deprecated. This argument held a bare rotating flag before rotation became a stored rate; it is accepted so callers written against that signature — and ``MSONable`` payloads serialized under it, which carry no ``views`` — still construct. It is translated to the placeholder view :func:`atomscale.rheed_metadata.legacy_views_frame` builds for the matching legacy type, so :attr:`rotating` answers the same. Ignored when ``views`` is given. """ # The flag used to sit in the ``views`` position, so a caller passing it # positionally lands here. A DataFrame is never a bool, so this is # unambiguous. if isinstance(views, bool): views, rotating = None, views if views is None: if rotating is not None: warnings.warn( "RHEEDVideoResult(rotating=...) is deprecated; pass the " "`views` frame from Client.get_rheed_azimuths instead. " "Rotation is now read from the stored per-view rpm.", DeprecationWarning, stacklevel=2, ) # An unrecognized type yields the same empty frame, so a result # built with neither argument is simply one with no views. views = legacy_views_frame( "rheed_rotating" if rotating else "rheed_stationary" if rotating is not None else "" ) self.data_id = data_id self.timeseries_data = timeseries_data self.snapshot_image_data = snapshot_image_data self.views = views self.collected_datetime = collected_datetime
@property def rotating(self) -> bool: """Whether the stage turned during this recording. Read from the stored per-view ``rpm`` rather than from the catalogue type, so a recording answers the same before and after the RHEED types were unified — ``rheed_stationary`` is simply rpm 0. Kept because callers relied on this attribute before rotation became a stored rate. A NaN rpm comes only from the legacy fallback in :func:`atomscale.rheed_metadata.legacy_views_frame` — a backend that recorded rotation as a type name and never a rate. That still reads as rotating. """ if self.views.empty or "rpm" not in self.views: return False rpm = self.views["rpm"].astype("float64") return bool((rpm > 0).any() or rpm.isna().all())
# NOTE: This is temporarily deprecated # # def get_plot(self) -> Figure: # """Get plot of timeseries data associated with this RHEED video # # Returns: # (Figure): Matplotlib Figure object containing plot data # """ # fig, axes = plt.subplots(nrows=6, sharex=True, figsize=(10, 10)) # # time = self.timeseries_data["Time"] # # timeseries_data = self.timeseries_data.drop(columns=["Time"]) # timeseries_data = timeseries_data.rename( # columns={"Oscillation Period": "Oscillation Period [s]"} # ) # colors = { # "Cluster ID": "black", # "Specular Intensity": "#0D74CE", # "First Order Intensity": "#0588F0", # "Cumulative Strain": "#CA244D", # "Relative Strain": "#DC3B5D", # "Oscillation Period [s]": "#AB4ABA", # "Diffraction Spot Count": "#CC4E00", # "Lattice Spacing": "#CC4E00", # } # # linewidth = 3 # for col, axis in zip(timeseries_data.columns, axes): # (line,) = axis.plot( # time, # timeseries_data[col].values, # label=col, # color=colors[col], # linewidth=linewidth, # ) # axis.grid(color="#E0E0E0", linestyle="--", linewidth=0.5) # axis.legend([line], [col]) # # axes[-1].set_xlabel("Time [s]", fontsize=12) # plt.close() # return fig