Skip to content

Commit

Permalink
Merge pull request #4 from MREYE-LUMC/examples
Browse files Browse the repository at this point in the history
Examples
  • Loading branch information
LucVV authored Oct 15, 2022
2 parents d590038 + d9d765e commit 964ea92
Show file tree
Hide file tree
Showing 6 changed files with 195 additions and 0 deletions.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
"""
Supplementary materials to the scientific publication:
van Vught L, Que I, Luyten GPM, Beenakker JWM. Effect of anatomical differences and intraocular lens design on Negative
Dysphotopsia. Journal of Cataract & Refractive Surgery: September 06, 2022. doi: 10.1097/j.jcrs.0000000000001054
These supplementary materials consist of:
- Eye models with specific anatomical characteristics for patients with Negative Dysphotopsia and for pseudophakic
controls
- A python script to automatically determine the retinal illumination in Zemax Optic Studio through the ZOSPy API
When using these data/scripts, please cite the above mentioned paper.
The presented code and data are made available for research purposes only, no rights can be derived from them.
These methods has been tested using Zemax Optic Studio version 20.3.2, python version 3.7.5 and ZOSPy version 0.6.0.
Prior to running the script, make sure that the STL files supplied with this script are copied to the OpticStudio
r'Objects/CAD Files' folder. The exact location (if unknown) can be obtained by running:
```
import zospy as zp
zos = zp.ZOS()
zos.wakeup()
zos.create_new_application()
objdir = zos.Application.ObjectsDir
zos.Application.CloseApplication()
```
"""

import os

import zospy as zp

import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np


def set_axes_equal_3d(ax):
"""Make axes of 3D plot have equal scale so that spheres appear as spheres,
cubes as cubes, etc.. This is one possible solution to Matplotlib's
ax.set_aspect('equal') and ax.axis('equal') not working for 3D.
Parameters
----------
ax: a matplotlib axis, e.g., as output from plt.gca().
"""

x_limits = ax.get_xlim3d()
y_limits = ax.get_ylim3d()
z_limits = ax.get_zlim3d()

x_range = abs(x_limits[1] - x_limits[0])
x_middle = np.mean(x_limits)
y_range = abs(y_limits[1] - y_limits[0])
y_middle = np.mean(y_limits)
z_range = abs(z_limits[1] - z_limits[0])
z_middle = np.mean(z_limits)

# The plot bounding box is a sphere in the sense of the infinity
# norm, hence I call half the max range the plot radius.
plot_radius = 0.5*max([x_range, y_range, z_range])

ax.set_xlim3d([x_middle - plot_radius, x_middle + plot_radius])
ax.set_ylim3d([y_middle - plot_radius, y_middle + plot_radius])
ax.set_zlim3d([z_middle - plot_radius, z_middle + plot_radius])


zos = zp.ZOS()
zos.wakeup()

# Make sure that Zemax OpticStudio in in `Interactive Extension` mode (Programming > Interactive Extension)
zos.connect_as_extension()

oss = zos.get_primary_system()

pachy = 0.55
cornea_irisdist = 3.12
object_distance = 30 # distance object -> pupil center

results = {}
for model in ['NegativeDysphotopsia',
'PseudophakicControl'
]:
fp = os.path.join(os.getcwd(), rf'{model}Model.zmx')
oss.load(fp)

# Get pointers to source and retina objects
obj_source = oss.NCE.GetObjectAt(2)
obj_retina = oss.NCE.GetObjectAt(14)

# Set number of rays
obj_source.GetCellAt(12).Value = str(1e5)

first = True # use this to get position of detectors on first analysis only
results[model] = {'Irradiance': {}, 'AbsorbedIrradiance': {}, 'Flux': {}, 'AbsorbedFlux': {}}
for angle in range(0, 165, 5):
xnew = np.sin(np.deg2rad(angle)) * object_distance
znew = np.cos(np.deg2rad(angle)) * object_distance
obj_source.XPosition = xnew
obj_source.ZPosition = -(znew - pachy - cornea_irisdist)
obj_source.TiltAboutY = -angle

# Trace
RayTrace = oss.Tools.OpenNSCRayTrace()
RayTrace.NumberOfCores = 8
RayTrace.ClearDetectors(0) # clear the old detector data!
RayTrace.ScatterNSCRays = True
RayTrace.UsePolarization = False
RayTrace.SplitNSCRays = False
RayTrace.IgnoreErrors = True
RayTrace.RunAndWaitForCompletion()
RayTrace.Close()

# Get data
fd = obj_retina.GetFacetedObjectData()

centroids = []
irradiance = []
absorbed_irradiance = []
flux = []
absorbed_flux = []

for facenum in range(fd.NumberOfFaces):
fd.CurrentFace = facenum
if first:
verts = np.array([list(fd.GetVertex(vertnum, 0, 0, 0)[1:]) for vertnum in range(fd.NumberOfVertices)])
centroids.append(verts.mean(axis=0))
irradiance.append(fd.Irradiance)
absorbed_irradiance.append(fd.AbsorbedIrradiance)
flux.append(fd.Flux)
absorbed_flux.append(fd.AbsorbedFlux)

if first:
results[model]['Centroids'] = np.array(centroids)
first = False # Make sure centroids are only read in once
results[model]['Irradiance'][angle] = np.array(irradiance)
results[model]['AbsorbedIrradiance'][angle] = np.array(absorbed_irradiance)
results[model]['Flux'][angle] = np.array(flux)
results[model]['AbsorbedFlux'][angle] = np.array(absorbed_flux)

# Show results
cumulative_irr_nd = np.array([results['NegativeDysphotopsia']['Irradiance'][key]
for key in results['NegativeDysphotopsia']['Irradiance'].keys()]).sum(axis=0)

cumulative_irr_co = np.array([results['PseudophakicControl']['Irradiance'][key]
for key in results['PseudophakicControl']['Irradiance'].keys()]).sum(axis=0)

fig = plt.figure()
ax1 = fig.add_subplot(121, projection=Axes3D.name)
ax2 = fig.add_subplot(122, projection=Axes3D.name)

vmax = np.max([cumulative_irr_nd.max(), cumulative_irr_co.max()])
filter_nd = cumulative_irr_nd != 0
ax1.scatter(*results['NegativeDysphotopsia']['Centroids'][filter_nd].T[np.array([0, 2, 1])],
c=cumulative_irr_nd[filter_nd], cmap='Greys_r', vmin=0, vmax=vmax, s=1)

filter_co = cumulative_irr_co != 0
ax2.scatter(*results['PseudophakicControl']['Centroids'][filter_co].T[np.array([0, 2, 1])],
c=cumulative_irr_co[filter_co], cmap='Greys_r', vmin=0, vmax=vmax, s=1)

ax1.set_title('ND')
ax2.set_title('Control')

set_axes_equal_3d(ax1)
set_axes_equal_3d(ax2)

ax1.set_xlabel('x')
ax1.set_ylabel('z')
ax1.set_zlabel('y')

ax2.set_xlabel('x')
ax2.set_ylabel('z')
ax2.set_zlabel('y')

plt.show()
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Example: Retinal illumination in pseudophakic eyes with and without Negative Dysphotopsia

This example shows how to determine peripheral retinal illumination in models of the (pseudophakic) human eye using ZOSPy. The analysis presented in the *Analyze.py* script is a basic example of the analyses perfomed for the scientific paper [Effect of anatomical differences and intraocular lens design on Negative Dysphotopsia](https://doi.org/10.1097/j.jcrs.0000000000001054). The required eye models and STL files are kindly provided by the authors.

## Warranty and liability
The presented code and data are made available for research purposes only. There is no warranty and rights can not be
derived from them, as is also stated in the general license of this repository.

## Citation
Please cite the following research when using this example or the data provided with this example:

van Vught L, Que I, Luyten GPM and Beenakker JWM.
_Effect of anatomical differences and intraocular lens design on Negative Dysphotopsia._
JCRS: Sep 06, 2022.<br>
[doi: [10.1097/j.jcrs.0000000000001054](https://doi.org/10.1097/j.jcrs.0000000000001054) ] [[JCRS](https://journals.lww.com/jcrs/Abstract/9900/Effect_of_anatomical_differences_and_intraocular.107.aspx)]
Binary file not shown.

0 comments on commit 964ea92

Please sign in to comment.