Visisipy: vision simulations in Python

Visisipy (pronounced /ˌvɪsəˈsɪpi/, like Mississippi but with a V) is a Python library for optical simulations of the eye. It provides an easy-to-use interface to define and build eye models, and to perform common ophthalmic analyses on these models.

Project goals

  • Uniform interface to define, build and analyze various types of eye models, using abstractions that make sense in a clinical context
  • Collection of ready-to-use eye models, such as the Navarro model, that can be customized at need
  • Accessible interface to clinically relevant analyses on these models

Open source

The source code is available on GitHub: github.com/MREYE-LUMC/visisipy.

GitHub repository

Contributions are welcomed! If you want to contribute, please talk to Corné Haasjes or Jan-Willem Beenakker, or email us ([email protected]).

Example: Patient-specific mapping of fundus photographs to three-dimensional ocular imaging

This notebook illustrates the ray tracing simulations used for a patient-specific mapping method of fundus photographs to three-dimensional ocular imaging on an eye model based on clinical data. Similar code written without visisipy can be found here. This project will be presented Friday at 9:45 by Jan-Willem Beenakker.

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
from helpers import InputOutputAngles

import visisipy

sns.set_style(
    "ticks",
    {
        "axes.grid": True,
        "grid.linestyle": "--",
        "patch.edgecolor": "black",
    },
)
geometry_parameters = {
    "axial_length": 24.305,  # mm
    "cornea_thickness": 0.5615,  # mm
    "anterior_chamber_depth": 3.345,  # mm
    "lens_thickness": 3.17,  # mm
    "cornea_front_radius": 7.6967,  # mm
    "cornea_front_asphericity": -0.2304,
    "cornea_back_radius": 6.2343,  # mm
    "cornea_back_asphericity": -0.1444,
    "pupil_radius": 0.5,  # mm
    "lens_front_radius": 10.2,  # mm
    "lens_front_asphericity": -3.1316,
    "lens_back_radius": -5.4537,  # mm
    "lens_back_asphericity": -4.1655,
    "retina_radius": -11.3357,  # mm
    "retina_asphericity": -0.0631,
}

geometry = visisipy.models.create_geometry(**geometry_parameters)
model = visisipy.models.EyeModel(geometry=geometry)
field_angles = np.arange(0, 90, 5).astype(float)

raytrace_results = visisipy.analysis.raytrace(model, coordinates=zip(len(field_angles) * [0], field_angles))
fig, ax = plt.subplots()

visisipy.plots.plot_eye(ax, model, lens_edge_thickness=0.5)
sns.lineplot(data=raytrace_results, x="z", y="y", hue=[f[1] for f in raytrace_results.field], palette="plasma", ax=ax)

ax.set_aspect("equal")
ax.set_xlim(-5, 25)
ax.set_ylim(-15, 15)
ax.set_xlabel("z (mm)")
ax.set_ylabel("y (mm)")

sns.move_legend(ax, "lower right")

Ray trace result

# Calculate cardinal point locations
cardinal_points = visisipy.analysis.cardinal_points(model)

# Get the location of the second nodal point with respect to the pupil location, which is the origin in OpticStudio
second_nodal_point = cardinal_points.nodal_points.image + (geometry.lens_thickness + geometry.vitreous_thickness)

# In the Navarro model, the second nodal point is located 7.45 mm behind the cornea apex
second_nodal_point_navarro = 7.45 - (geometry.cornea_thickness + geometry.anterior_chamber_depth)

# Calculate the location of the retina center
retina_center = geometry.lens_thickness + geometry.vitreous_thickness + geometry.retina.half_axes.axial
input_output_angles = pd.DataFrame(
    [
        InputOutputAngles.from_ray_trace_result(
            g.set_index("index"),
            np2=second_nodal_point,
            np2_navarro=second_nodal_point_navarro,
            retina_center=retina_center,
        )
        for _, g in raytrace_results.groupby("field")
    ]
)
fig, ax = plt.subplots()

sns.lineplot(
    data=input_output_angles,
    x="input_angle_field",
    y="output_angle_np2",
    label="$2^{\\mathrm{nd}}$ nodal point",
)
sns.lineplot(
    data=input_output_angles,
    x="input_angle_field",
    y="output_angle_retina_center",
    label="Retina center",
)
sns.lineplot(
    data=input_output_angles,
    x="input_angle_field",
    y="output_angle_pupil",
    label="Pupil",
)

ax.set_xlabel("Camera angle [°]")
ax.set_ylabel("Retina angle [°]")
ax.set_aspect("equal")

Relation between camera angles and retina angles