Skip to content

eyepy.core.annotations

EyeBscanLayerAnnotation(eyevolumelayerannotation, index)

Layer annotation for a single B-scan.

Parameters:

Name Type Description Default
eyevolumelayerannotation EyeVolumeLayerAnnotation

EyeVolumeLayerAnnotation object

required
index int

Index of the B-scan

required

Returns:

Type Description
None

None

Source code in src/eyepy/core/annotations.py
def __init__(self, eyevolumelayerannotation: EyeVolumeLayerAnnotation,
             index: int) -> None:
    """Layer annotation for a single B-scan.

    Args:
        eyevolumelayerannotation: EyeVolumeLayerAnnotation object
        index: Index of the B-scan

    Returns:
        None
    """
    self.eyevolumelayerannotation = eyevolumelayerannotation
    self.volume = eyevolumelayerannotation.volume
    self.index = index

data: npt.NDArray[np.float_] property writable

Layer heights.

knots: list property writable

Knots parameterizing the layer heights.

name: str property writable

Name of the layer annotation.

EyeEnfacePixelAnnotation(enface, data=None, meta=None, **kwargs)

Pixel annotation for an enface image.

Parameters:

Name Type Description Default
enface EyeEnface

EyeEnface object

required
data Optional[NDArray[bool_]]

Pixel annotation data

None
meta Optional[dict]

Metadata

None
**kwargs

Additional metadata specified as keyword arguments

{}

Returns:

Type Description
None

None

Source code in src/eyepy/core/annotations.py
def __init__(
    self,
    enface: EyeEnface,
    data: Optional[npt.NDArray[np.bool_]] = None,
    meta: Optional[dict] = None,
    **kwargs,
) -> None:
    """Pixel annotation for an enface image.

    Args:
        enface: EyeEnface object
        data: Pixel annotation data
        meta: Metadata
        **kwargs: Additional metadata specified as keyword arguments

    Returns:
        None
    """
    self.enface = enface

    if data is None:
        self.data = np.full(self.enface.shape,
                            fill_value=False,
                            dtype=bool)
    else:
        self.data = data

    if meta is None:
        self.meta = kwargs
    else:
        self.meta = meta
        self.meta.update(**kwargs)

    if 'name' not in self.meta:
        self.meta['name'] = 'Pixel Annotation'

name: str property writable

Name of the pixel annotation.

EyeVolumeLayerAnnotation(volume, data=None, meta=None, **kwargs)

Layer annotation for a single layer in an EyeVolume.

Parameters:

Name Type Description Default
volume EyeVolume

EyeVolume object

required
data Optional[NDArray[float_]]

2D array of shape (n_bscans, bscan_width) holding layer height values

None
meta Optional[dict]

dict with additional meta data

None
**kwargs

additional meta data specified as parameters

{}

Returns:

Type Description
None

None

Source code in src/eyepy/core/annotations.py
def __init__(
    self,
    volume: EyeVolume,
    data: Optional[npt.NDArray[np.float_]] = None,
    meta: Optional[dict] = None,
    **kwargs,
) -> None:
    """Layer annotation for a single layer in an EyeVolume.

    Args:
        volume: EyeVolume object
        data: 2D array of shape (n_bscans, bscan_width) holding layer height values
        meta: dict with additional meta data
        **kwargs: additional meta data specified as parameters

    Returns:
        None
    """
    self.volume = volume
    if data is None:
        self.data = np.full((volume.size_z, volume.size_x), np.nan)
    else:
        self.data = data

    if meta is None:
        self.meta = kwargs
    else:
        self.meta = meta
        self.meta.update(**kwargs)

    # knots is a dict layername: list of curves where every curve is a list of knots
    if 'knots' not in self.meta:
        self.meta['knots'] = defaultdict(lambda: [])
    elif type(self.meta['knots']) is dict:
        self.meta['knots'] = defaultdict(lambda: [], self.meta['knots'])

    if 'name' not in self.meta:
        self.meta['name'] = 'Layer Annotation'

    self.meta['current_color'] = config.layer_colors[self.name]

knots: dict property

Knots parameterizing the layer.

name: str property writable

Layer name.

layer_indices()

Returns pixel indices of the layer in the volume.

While the layer is stored as the offset from the bottom of the OCT volume, some applications require layer discretized to voxel positions. This method returns the layer as indices into the OCT volume.

The indices can be used for example to create layer maps for semantic segmentation.

import matplotlib.pyplot as plt
import numpy as np
import eyepy as ep

eye_volume = ep.data.load("drusen_patient")
rpe_annotation = eye_volume.layers["RPE"]
rpe_indices = rpe_annotation.layer_indices()
rpe_map = np.zeros(eye_volume.shape)
rpe_map[rpe_indices] = 1
plt.imshow(rpe_map[0]) # (1)
  1. Visualize layer map for the first B-scan

Returns:

Type Description
tuple[ndarray, ndarray, ndarray]

A tuple with indices for the layers position in the volume - Tuple[bscan_indices, row_indices, column_indices]

Source code in src/eyepy/core/annotations.py
def layer_indices(self) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
    """Returns pixel indices of the layer in the volume.

    While the layer is stored as the offset from the bottom of the OCT volume, some applications require
    layer discretized to voxel positions. This method returns the layer as indices into the OCT volume.

    The indices can be used for example to create layer maps for semantic segmentation.

    ```python
    import matplotlib.pyplot as plt
    import numpy as np
    import eyepy as ep

    eye_volume = ep.data.load("drusen_patient")
    rpe_annotation = eye_volume.layers["RPE"]
    rpe_indices = rpe_annotation.layer_indices()
    rpe_map = np.zeros(eye_volume.shape)
    rpe_map[rpe_indices] = 1
    plt.imshow(rpe_map[0]) # (1)
    ```

    1.  Visualize layer map for the first B-scan

    Returns:
        A tuple with indices for the layers position in the volume - Tuple[bscan_indices, row_indices, column_indices]
    """
    layer = self.data[:, np.newaxis, :]
    nan_indices = np.isnan(layer)
    row_indices = np.rint(layer).astype(int)[~nan_indices]
    x = np.ones(layer.shape)
    x[nan_indices] = 0
    bscan_indices, _, col_indices = np.nonzero(x)
    return (bscan_indices, row_indices, col_indices)

EyeVolumePixelAnnotation(volume, data=None, meta=None, radii=(1.5, 2.5), n_sectors=(1, 4), offsets=(0, 45), center=None, **kwargs)

Pixel annotation for an EyeVolume.

Parameters:

Name Type Description Default
volume EyeVolume

EyeVolume object

required
data Optional[NDArray[bool_]]

3D array of shape (n_bscans, bscan_height, bscan_width) holding boolean pixel annotations

None
meta Optional[dict]

dict with additional meta data

None
radii Iterable[float]

radii for quantification on circular grid

(1.5, 2.5)
n_sectors Iterable[int]

number of sectors for quantification on circular grid

(1, 4)
offsets Iterable[int]

offsets from x axis for first sector, for quantification on circular grid

(0, 45)
center Optional[tuple[float, float]]

center of circular grid for quantification

None
**kwargs

additional meta data specified as parameters

{}

Returns:

Type Description
None

None

Source code in src/eyepy/core/annotations.py
def __init__(
    self,
    volume: EyeVolume,
    # Type hint for an optional boolean numpy array
    data: Optional[npt.NDArray[np.bool_]] = None,
    meta: Optional[dict] = None,
    radii: Iterable[float] = (1.5, 2.5),
    n_sectors: Iterable[int] = (1, 4),
    offsets: Iterable[int] = (0, 45),
    center: Optional[tuple[float, float]] = None,
    **kwargs,
) -> None:
    """Pixel annotation for an EyeVolume.

    Args:
        volume: EyeVolume object
        data: 3D array of shape (n_bscans, bscan_height, bscan_width) holding boolean pixel annotations
        meta: dict with additional meta data
        radii: radii for quantification on circular grid
        n_sectors: number of sectors for quantification on circular grid
        offsets: offsets from x axis for first sector, for quantification on circular grid
        center: center of circular grid for quantification
        **kwargs: additional meta data specified as parameters

    Returns:
        None
    """
    self.volume = volume

    if data is None:
        self.data = np.full(self.volume.shape,
                            fill_value=False,
                            dtype=bool)
    else:
        self.data = data

    self._masks = None
    self._quantification = None

    if meta is None:
        self.meta = kwargs
    else:
        self.meta = meta
        self.meta.update(**kwargs)

    self.meta.update(
        **{
            'radii': radii,
            'n_sectors': n_sectors,
            'offsets': offsets,
            'center': center,
        })

    if 'name' not in self.meta:
        self.meta['name'] = 'Voxel Annotation'

center: tuple[float, float] property writable

Center of circular grid for quantification.

enface: np.ndarray property

Transformed projection of the annotation to the enface plane.

masks: dict[str, np.ndarray] property

Masks for quantification on circular grid.

Returns:

Type Description
dict[str, ndarray]

A dictionary of masks with the keys being the names of the masks.

n_sectors: Iterable[int] property writable

Number of sectors for quantification on circular grid.

name: str property writable

Annotation name.

offsets: Iterable[int] property writable

Offsets from x axis for first sector, for quantification on circular grid.

projection: np.ndarray property

Projection of the annotation to the enface plane.

quantification: dict[str, Union[float, str]] property

Quantification of the annotation on the specified circular grid.

Returns:

Type Description
dict[str, Union[float, str]]

A dictionary of quantifications with the keys being the names of the regions.

radii: Iterable[float] property writable

Radii for quantification on circular grid.

plot(ax=None, region=np.s_[:, :], cmap='Reds', vmin=None, vmax=None, cbar=True, alpha=1)

Plot the annotation on the enface plane.

Parameters:

Name Type Description Default
ax Optional[Axes]

matplotlib axes object

None
region Union[slice, tuple[slice, slice]]

region of the enface projection to plot

s_[:, :]
cmap Union[str, Colormap]

colormap

'Reds'
vmin Optional[float]

minimum value for colorbar

None
vmax Optional[float]

maximum value for colorbar

None
cbar bool

whether to plot a colorbar

True
alpha float

alpha value for the annotation

1

Returns:

Type Description
None

None

Source code in src/eyepy/core/annotations.py
def plot(
    self,
    ax: Optional[plt.Axes] = None,
    region: Union[slice, tuple[slice, slice]] = np.s_[:, :],
    cmap: Union[str, mpl.colors.Colormap] = 'Reds',
    vmin: Optional[float] = None,
    vmax: Optional[float] = None,
    cbar: bool = True,
    alpha: float = 1,
) -> None:
    """Plot the annotation on the enface plane.

    Args:
        ax: matplotlib axes object
        region: region of the enface projection to plot
        cmap: colormap
        vmin: minimum value for colorbar
        vmax: maximum value for colorbar
        cbar: whether to plot a colorbar
        alpha: alpha value for the annotation

    Returns:
        None
    """
    enface_projection = self.enface

    ax = plt.gca() if ax is None else ax

    if vmin is None:
        vmin = 1
    if vmax is None:
        vmax = max([enface_projection.max(), vmin])

    enface_crop = enface_projection[region]
    visible = np.zeros(enface_crop.shape)
    visible[np.logical_and(vmin <= enface_crop, enface_crop <= vmax)] = 1

    if cbar:
        divider = make_axes_locatable(ax)
        cax = divider.append_axes('right', size='5%', pad=0.05)
        plt.colorbar(
            cm.ScalarMappable(colors.Normalize(vmin=vmin, vmax=vmax),
                              cmap=cmap),
            cax=cax,
        )

    ax.imshow(
        enface_crop,
        alpha=visible * alpha,
        cmap=cmap,
        vmin=vmin,
        vmax=vmax,
    )

plot_quantification(ax=None, region=np.s_[:, :], alpha=0.5, vmin=None, vmax=None, cbar=True, cmap='YlOrRd')

Plot circular grid quantification of the annotation (like ETDRS)

Parameters:

Name Type Description Default
ax Optional[Axes]

Matplotlib axes to plot on

None
region Union[slice, tuple[slice, slice]]

Region to plot

s_[:, :]
alpha float

Alpha value of the mask

0.5
vmin Optional[float]

Minimum value for the colorbar

None
vmax Optional[float]

Maximum value for the colorbar

None
cbar bool

Whether to plot a colorbar

True
cmap Union[str, Colormap]

Colormap to use

'YlOrRd'

Returns:

Type Description
None

None

Source code in src/eyepy/core/annotations.py
def plot_quantification(
    self,
    ax: Optional[plt.Axes] = None,
    region: Union[slice, tuple[slice, slice]] = np.s_[:, :],
    alpha: float = 0.5,
    vmin: Optional[float] = None,
    vmax: Optional[float] = None,
    cbar: bool = True,
    cmap: Union[str, mpl.colors.Colormap] = 'YlOrRd',
) -> None:
    """Plot circular grid quantification of the annotation (like ETDRS)

    Args:
        ax: Matplotlib axes to plot on
        region: Region to plot
        alpha: Alpha value of the mask
        vmin: Minimum value for the colorbar
        vmax: Maximum value for the colorbar
        cbar: Whether to plot a colorbar
        cmap: Colormap to use

    Returns:
        None
    """

    ax = plt.gca() if ax is None else ax

    mask_img = np.zeros(self.volume.localizer.shape, dtype=float)[region]
    visible = np.zeros_like(mask_img)
    for mask_name in self.masks.keys():
        mask_img += (self.masks[mask_name][region] *
                     self.quantification[mask_name + ' [mm³]'])
        visible += self.masks[mask_name][region]

    vmin = mask_img[visible.astype(int)].min() if vmin is None else vmin
    vmax = max([mask_img.max(), vmin]) if vmax is None else vmax

    if cbar:
        divider = make_axes_locatable(ax)
        cax = divider.append_axes('right', size='5%', pad=0.05)
        plt.colorbar(
            cm.ScalarMappable(colors.Normalize(vmin=vmin, vmax=vmax),
                              cmap=cmap),
            cax=cax,
        )

    ax.imshow(
        mask_img,
        alpha=visible * alpha,
        cmap=cmap,
        vmin=vmin,
        vmax=vmax,
    )