Skip to content

Commit

Permalink
refactor(eyepy/core/base.py): rename enface to localizer in Oct objec…
Browse files Browse the repository at this point in the history
…t for more consistency

BREAKING CHANGE:
  • Loading branch information
Oli4 committed Feb 10, 2021
1 parent 397deca commit 09f8746
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 31 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ python:
- 3.8
- 3.7
- 3.6
- 3.5

# Command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors
install: pip install -U tox-travis
Expand Down
53 changes: 53 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@
eyepy
=====

.. image:: https://img.shields.io/pypi/v/eyepie.svg
:target: https://pypi.python.org/pypi/eyepie

.. image:: https://api.travis-ci.com/MedVisBonn/eyepy.svg?branch=master&status=failed
:target: https://travis-ci.com/MedVisBonn/eyepy

.. image:: https://readthedocs.org/projects/eyepy/badge/?version=latest
:target: https://eyepy.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status


This software is under active development and can not be considered stable
Expand All @@ -12,7 +21,51 @@ Features

* Read the HEYEX XML export
* Read the HEYEX VOL export
* Read B-Scans from a folder
* Read the public OCT Dataset from Duke University
* Plot OCT Scans
* Compute Drusen from BM and RPE segmentations


Getting started
---------------

Installation
^^^^^^^^^^^^
Install eyepy with :code:`pip install -U eyepie`. Yes it is :code:`eyepie` and not :code:`eyepy` for
installation with pip.

Loading Data
^^^^^^^^^^^^

.. code-block:: python
import eyepy as ep
# Load B-Scans from folder
data = ep.Oct.from_folder("path/to/folder")
# Load an OCT volume from the DUKE dataset
data = ep.Oct.from_duke_mat("path/to/file.mat")
# Load an HEYEX XML export
data = ep.Oct.from_heyex_xml("path/to/folder")
# Load an HEYEX VOL export
data = ep.Oct.from_heyex_vol("path/to/file.vol")
The Oct object
^^^^^^^^^^^^^^

When loading data as described above an Oct object is returned. You can use
this object to perform common actions on the OCT volume such as:

+ Iterating over the volume to retrieve Bscan objects :code:`for bscan in data`
+ Plotting a localizer (NIR) image associated to the OCT :code:`data.plot(localizer=True)`
+ Accessing an associated localizer image :code:`data.localizer`
+ Reading Meta information from the loaded data if available :code:`data.ScaleXSlo`



Credits
-------
Expand Down
56 changes: 27 additions & 29 deletions eyepy/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,17 +397,15 @@ def __init__(self,
Parameters
----------
bscans :
enfacereader :
meta :
drusenfinder :
"""
self.bscans = bscans
self._localizer = localizer
self._enface = None
self._meta = meta
self._drusenfinder = drusenfinder
self._eyequantifier = eyequantifier
self._tform_enface_to_oct = None
self._tform_localizer_to_oct = None

self._drusen = None
self._drusen_raw = None
Expand Down Expand Up @@ -481,7 +479,7 @@ def estimate_bscan_distance(self):
# Try to estimate B-Scan distances. Can be used if Bscan Positions
# but not their distance is in the meta information

# Pythagoras in case B-Scans are rotated with respect to the enface
# Pythagoras in case B-Scans are rotated with respect to the localizer
a = self[-1].StartY - self[0].StartY
b = self[-1].StartX - self[0].StartX
self.meta["Distance"] = np.sqrt(a ** 2 + b ** 2) / (
Expand Down Expand Up @@ -524,7 +522,7 @@ def NumBScans(self):
return len(self)

@property
def enface(self):
def localizer(self):
""" A numpy array holding the OCTs localizer enface if available """
try:
return self._localizer.data
Expand Down Expand Up @@ -632,23 +630,23 @@ def quantification(self):
return self._eyequantifier.quantify(self)

@property
def tform_enface_to_oct(self):
if self._tform_enface_to_oct is None:
self._tform_enface_to_oct = self._estimate_enface_to_oct_tform()
return self._tform_enface_to_oct
def tform_localizer_to_oct(self):
if self._tform_localizer_to_oct is None:
self._tform_localizer_to_oct = self._estimate_localizer_to_oct_tform()
return self._tform_localizer_to_oct

@property
def tform_oct_to_enface(self):
return self.tform_enface_to_oct.inverse
def tform_oct_to_localizer(self):
return self.tform_localizer_to_oct.inverse

@property
def enface_shape(self):
def localizer_shape(self):
try:
return self.enface.shape
return self.localizer.shape
except:
return (self.SizeX, self.SizeX)

def _estimate_enface_to_oct_tform(self):
def _estimate_localizer_to_oct_tform(self):
oct_projection_shape = (self.NumBScans, self.SizeX)
src = np.array(
[oct_projection_shape[0] - 1, 0, # Top left
Expand All @@ -664,7 +662,7 @@ def _estimate_enface_to_oct_tform(self):
]).reshape((-1, 2))

try:
# Try to map the oct projection to the enface image
# Try to map the oct projection to the localizer image
dst = np.array(
[self[-1].StartY / self.ScaleXSlo, self[-1].StartX / self.ScaleYSlo,
self[-1].EndY / self.ScaleXSlo, self[-1].EndX / self.ScaleYSlo,
Expand All @@ -674,8 +672,8 @@ def _estimate_enface_to_oct_tform(self):
except AttributeError:
# Map the oct projection to a square area of shape (bscan_width, bscan_width)
warnings.warn(
f"Bscan positions on enface image or the scale of the "
f"enface image is missing. We assume that the B-Scans cover "
f"Bscan positions on localizer image or the scale of the "
f"localizer image is missing. We assume that the B-Scans cover "
f"a square area and are equally spaced.",
UserWarning)
b_width = self[0].shape[1]
Expand All @@ -691,7 +689,7 @@ def _estimate_enface_to_oct_tform(self):
tform = transform.estimate_transform("affine", src, dst)

if not np.allclose(tform.inverse(tform(src)), src):
msg = f"Problem with transformation of OCT Projection to the enface image space."
msg = f"Problem with transformation of OCT Projection to the localizer image space."
raise ValueError(msg)

return tform
Expand All @@ -705,10 +703,10 @@ def drusen_projection(self):

@property
def drusen_enface(self):
""" Drusen projection warped into the enface space """
""" Drusen projection warped into the localizer space """
return transform.warp(self.drusen_projection.astype(float),
self.tform_oct_to_enface,
output_shape=self.enface_shape,
self.tform_oct_to_localizer,
output_shape=self.localizer_shape,
order=0)

@property
Expand All @@ -725,7 +723,7 @@ def drusenfinder(self, drusenfinder):
self._drusen_raw = None
self._drusenfinder = drusenfinder

def plot(self, ax=None, enface=True, drusen=False, bscan_region=False,
def plot(self, ax=None, localizer=True, drusen=False, bscan_region=False,
bscan_positions=None, masks=False, region=np.s_[...],
drusen_kwargs=None):
"""
Expand All @@ -749,8 +747,8 @@ def plot(self, ax=None, enface=True, drusen=False, bscan_region=False,
if ax is None:
ax = plt.gca()

if enface:
self.plot_enface(ax=ax, region=region)
if localizer:
self.plot_localizer(ax=ax, region=region)
if drusen:
if drusen_kwargs is None:
drusen_kwargs = {}
Expand Down Expand Up @@ -780,8 +778,8 @@ def plot_layer_distance(self, region=np.s_[...], ax=None, bot_layer="BM", top_la

dist = self.layers["BM"] - self.layers["RPE"]
img = transform.warp(dist.astype(float),
self.tform_oct_to_enface,
output_shape=self.enface_shape, order=0)
self.tform_oct_to_localizer,
output_shape=self.localizer_shape, order=0)
ax.imshow(img[region], cmap="gray", vmin=vmin, vmax=vmax)

def plot_masks(self, region=np.s_[...], ax=None, color="r", linewidth=0.5):
Expand Down Expand Up @@ -813,10 +811,10 @@ def plot_masks(self, region=np.s_[...], ax=None, color="r", linewidth=0.5):
y = [line["start"][1], line["end"][1]]
ax.plot(x, y, color=color, linewidth=linewidth)

def plot_enface(self, ax=None, region=np.s_[...]):
def plot_localizer(self, ax=None, region=np.s_[...]):
if ax is None:
ax = plt.gca()
ax.imshow(self.enface[region], cmap="gray")
ax.imshow(self.localizer[region], cmap="gray")

def plot_bscan_positions(self, bscan_positions="all", ax=None,
region=np.s_[...], line_kwargs=None):
Expand Down Expand Up @@ -879,7 +877,7 @@ def plot_drusen(self, ax=None, region=np.s_[...], cmap="Reds",
ax.imshow(drusen[region], alpha=visible[region] * alpha, cmap=cmap, vmin=vmin,
vmax=vmax)

def plot_enface_bscan(self, ax=None, n_bscan=0):
def plot_localizer_bscan(self, ax=None, n_bscan=0):
""" Plot Slo with one selected B-Scan """
raise NotImplementedError()

Expand Down
2 changes: 1 addition & 1 deletion eyepy/core/quantifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def quantify(self, oct_obj, space="enface"):
for name, mask in masks.items():
# The voxel size in enface space not in the measurement (oct) space
if space == "oct":
mask = transform.warp(mask, oct_obj.tform_enface_to_oct)[
mask = transform.warp(mask, oct_obj.tform_localizer_to_oct)[
:oct_obj.NumBScans, :oct_obj.SizeX]
results[f"{name} [mm³]"] = ((drusen_enface * mask).sum()
* voxel_size_µm3 / 1e9)
Expand Down

0 comments on commit 09f8746

Please sign in to comment.