> ## Documentation Index
> Fetch the complete documentation index at: https://docs.atomscale.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# RHEED Features and Masks

> Query low-level RHEED features and per-frame segmentation masks

`client.get()` returns the standard RHEED feature set. When you need the full per-region feature space, a filtered subset of properties, a time window, or the segmentation masks behind the features, use `get_rheed_timeseries()` and `get_frame_masks()`.

| Method                   | Use case                                                         |
| ------------------------ | ---------------------------------------------------------------- |
| `get_rheed_timeseries()` | Feature timeseries with low-level features, masks, and windowing |
| `get_frame_masks()`      | Per-frame segmentation masks on their own, optionally decoded    |
| `get_frame()`            | A single extracted frame as a `RHEEDImageResult`                 |
| `decode_mask_rle()`      | Turn an RLE string into an `(H, W)` binary array                 |

## Fetch the feature timeseries

`get_rheed_timeseries()` returns a DataFrame indexed by `["Angle", "Frame Number"]` when both axes are available. For stationary videos there is a single angle; rotating videos carry one block per sampled azimuth.

```python theme={null}
from atomscale import Client

client = Client()
data_id = "44fa63b0-74da-4d25-a362-2276c80a670a"

df = client.get_rheed_timeseries(data_id)
print(df.index.names)   # ['Angle', 'Frame Number']
print(df.columns)
```

Standard columns use display names such as `Specular Intensity`, `Strain`, `Cumulative Strain`, `Oscillation Period`, `Diffraction Spot Count`, and `Lattice Spacing`.

## Include low-level features

Set `include_low_level_features=True` to add the full set of per-point, per-region features (spot areas, eccentricities, FWHM values, and similar) as extra columns.

```python theme={null}
df = client.get_rheed_timeseries(data_id, include_low_level_features=True)
print(df.filter(like="area").columns)
```

Low-level columns keep their raw backend names, so they are not renamed to display labels the way standard metrics are. Nested values are flattened into dotted column paths. Where a low-level feature shares a name with an existing standard column, the standard column wins and the low-level value is dropped, so known metrics are never overwritten.

<Note>
  Low-level features are per-point: a frame that was not featurized contributes NA to those columns.
</Note>

## Restrict to specific properties

Pass `property_names` to fetch only the features you need. These are the underlying property names, which differ from the display column names in the returned DataFrame.

```python theme={null}
df = client.get_rheed_timeseries(
    data_id,
    property_names=["specular_intensity", "referenced_strain"],
)
```

| Property name             | Display column         |
| ------------------------- | ---------------------- |
| `specular_intensity`      | Specular Intensity     |
| `referenced_strain`       | Strain                 |
| `nearest_neighbor_strain` | Cumulative Strain      |
| `spot_count`              | Diffraction Spot Count |
| `oscillation_period`      | Oscillation Period     |
| `lattice_spacing`         | Lattice Spacing        |
| `cluster_id`              | Cluster ID             |
| `first_order_intensity`   | First Order Intensity  |
| `half_order_intensity`    | Half Order Intensity   |
| `specular_fwhm_1`         | Specular FWHM          |

## Limit the time window

Two parameters narrow which points come back, which matters for long growths and for live streams where you only want the tail:

```python theme={null}
# Last 500 points
recent = client.get_rheed_timeseries(data_id, last_n=500)

# Last 60 seconds of the recording
tail = client.get_rheed_timeseries(data_id, elapsed_seconds=60.0)
```

## Attach segmentation masks

Every featurized frame of a processed RHEED video carries a binary segmentation mask of the diffraction pattern. Pass `include_masks=True` to join those masks onto the timeseries on the `Frame Number` axis:

```python theme={null}
from atomscale.results import decode_mask_rle

df = client.get_rheed_timeseries(
    data_id,
    include_low_level_features=True,
    include_masks=True,
)

row = df.dropna(subset=["mask_rle"]).iloc[0]
mask = decode_mask_rle(row["mask_rle"], row["mask_height"], row["mask_width"])
print(mask.shape)   # (H, W), uint8 with values 0 or 1
```

Three columns are added:

| Column        | Description                         |
| ------------- | ----------------------------------- |
| `mask_rle`    | COCO run-length-encoded mask string |
| `mask_height` | Mask height in pixels               |
| `mask_width`  | Mask width in pixels                |

Mask coverage is sparse. Masks exist only for featurized frames, so rows whose frame has no mask contain NA in these columns, as do all rows when the video has no mask artifact at all. Drop the NA rows before decoding.

The mask fetch is scoped to the frame range the returned series actually spans, so combining `include_masks=True` with `last_n` or `elapsed_seconds` pulls only the masks for that window rather than the whole video.

## Fetch masks on their own

When you want masks without the feature columns, call `get_frame_masks()` directly. Frame numbers are absolute and keyed identically to the processed video frames and to the `Frame Number` axis of the timeseries, so a decoded mask overlays the matching frame of the video you get from `download()`.

```python theme={null}
# Raw rows with the RLE string intact
rows = client.get_frame_masks(data_id)
print(rows[0].keys())
# dict_keys(['data_id', 'processed_data_id', 'frame_number',
#            'mask_rle', 'mask_height', 'mask_width'])

# Decoded arrays keyed by absolute frame number
masks = client.get_frame_masks(data_id, decode=True)
print(masks[0].shape)   # (H, W) uint8
```

Bound the request with `from_frame` and `to_frame`, both inclusive:

```python theme={null}
masks = client.get_frame_masks(
    data_id,
    from_frame=1000,
    to_frame=1200,
    decode=True,
)
```

<Note>
  For stationary videos every frame is featurized, so frame numbers are contiguous. For rotating and per-azimuth videos only a sampled subset is featurized, and frames without a mask are simply absent from the result.
</Note>

An empty list (or empty dict with `decode=True`) means the video has no per-frame mask artifact, either because the item is not RHEED or because it was processed before per-frame masks were persisted.

## Overlay a mask on a frame

Combine `get_frame()` with a decoded mask to inspect a specific frame:

```python theme={null}
import numpy as np

frame = client.get_frame(data_id, frame_index=0)
masks = client.get_frame_masks(data_id, from_frame=0, to_frame=0, decode=True)

image = np.array(frame.processed_image)
mask = masks[0]
highlighted = np.where(mask[..., None] == 1, image, image // 3)
```

`get_frame()` accepts negative indices, so `frame_index=-1` returns the last extracted frame. It returns `None` when the video has no extracted frames, the index is out of range, or the selected frame has no image.

For a single RHEED image item (rather than a video), the mask is already on the result object as `result.mask`, and `get_plot(show_mask=True)` renders it as an overlay. See [Inspect Results](/sdk/inspect-results).

## Decode masks yourself

`decode_mask_rle()` handles the COCO RLE format used by every Atomscale mask endpoint. Use it when working with raw rows or with API responses fetched outside the SDK:

```python theme={null}
from atomscale.results import decode_mask_rle

rows = client.get_frame_masks(data_id, from_frame=0, to_frame=99)
decoded = {
    row["frame_number"]: decode_mask_rle(
        row["mask_rle"], row["mask_height"], row["mask_width"]
    )
    for row in rows
}
```

The counts string is column-major (Fortran order), matching `pycocotools`.

## Next steps

<CardGroup cols={2}>
  <Card title="Similarity and Embeddings" icon="vector-square" href="/sdk/similarity">
    Compare growths with embedding vectors.
  </Card>

  <Card title="Client Reference" icon="code" href="/sdk/reference/client">
    Full parameter reference for every method.
  </Card>
</CardGroup>
