将眼底照片特定于患者的映射到三维眼成像 - 第2部分:分析#
该示例包含对论文的光线追迹结果的分析Patient-specific mapping of fundus photographs to three-dimensional ocular imaging的分析,并将所提出的方法与其他的眼底映射方法进行比较。
引用#
除引用ZOSPy外,还请在使用此示例或此示例中提供的数据时引用以下论文:
Haasjes, C., Vu, T. H. K., & Beenakker, J.-W. M. (2024). Patient-specific mapping of fundus photographs to three-dimensional ocular imaging. Medical Physics. https://doi.org/10.1002/mp.17576
保修和责任#
提供的代码和数据仅用于研究目的。没有保证,也不能从中获得权利,正如该存储库的一般许可中所述。
导入依赖项#
[1]:
from __future__ import annotations
import json
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
from helpers import (
_upper_ellipse,
ellipse_arc_length,
euclidean_distance,
find_ellipse_intersection,
)
from scipy.optimize import curve_fit
[2]:
import warnings
warnings.filterwarnings("ignore", message="invalid value encountered in sqrt")
由rayTracing.ipynb生成的受试者的加载数据。此外,加载了Navarro眼模型的参考数据。
[3]:
with open("data/navarro_geometry.json") as f:
navarro_geometry = json.load(f)
with open("data/geometry.json") as f:
patient_geometry = json.load(f)
with open("data/geometry_lamberth.json") as f:
patient_geometry_lamberth = json.load(f)
navarro_ray_trace_data = pd.read_csv("data/navarro_ray_trace_results.csv")
navarro_input_output_angles = pd.read_csv("data/navarro_input_output_angles.csv")
ray_trace_data = pd.read_csv("data/ray_trace_results.csv")
ray_trace_data_lamberth = pd.read_csv("data/ray_trace_results_lamberth.csv")
input_output_angles = pd.read_csv("data/input_output_angles.csv")
input_output_angles_lamberth = pd.read_csv("data/input_output_angles_lamberth.csv")
# Parse tuples in the retina_location column
for df in [
navarro_input_output_angles,
input_output_angles,
input_output_angles_lamberth,
]:
df.retina_location = df.retina_location.apply(eval)
绘制Navarro眼模型(实线)和患者特异性眼模(虚线)的相机角度和视网膜角度之间的关系。
[4]:
from matplotlib.lines import Line2D
fig, ax = plt.subplots()
for df, ls in zip([navarro_input_output_angles, input_output_angles], ["-", "--"]):
sns.lineplot(
data=df,
x="input_angle_field",
y="output_angle_np2",
ls=ls,
color="tab:blue",
label="$2^{\\mathrm{nd}}$ nodal point",
)
sns.lineplot(
data=df,
x="input_angle_field",
y="output_angle_retina_center",
ls=ls,
color="tab:orange",
label="Retina center",
)
sns.lineplot(
data=df,
x="input_angle_field",
y="output_angle_pupil",
ls=ls,
color="tab:green",
label="Pupil",
)
# Edit legend
handles = ax.get_legend_handles_labels()[0][:3]
handles += [
Line2D([], [], linestyle="-", color="black", label="Navarro"),
Line2D([], [], linestyle="--", color="black", label="Patient"),
]
ax.legend(handles=handles, loc="upper left")
ax.set_xlabel("Camera angle [°]")
ax.set_ylabel("Retina angle [°]")
ax.set_aspect("equal")
ax.grid()
摄像机角度和视网膜角度之间的关系#
在Navarro Eye的光线追迹数据上,拟合不同参考点之间的摄像机角度和视网膜角度之间的线性关系。 然后,这些拟合被用作非光线追迹方法的参考,以从摄像头角度预测视网膜角度。 由于视网膜中心和通孔观察到的非线性高于40°,因此在高达40°的摄像头角度进行拟合。
[5]:
# Fit nodal point method on Navarro data
fit_input_output_angles = navarro_input_output_angles.query("input_angle_field <= 40")
(c1_np2,), _ = curve_fit(
lambda theta, c1: c1 * theta,
xdata=fit_input_output_angles.input_angle_field,
ydata=fit_input_output_angles.output_angle_np2,
p0=1,
)
(c1_retina_center,), _ = curve_fit(
lambda theta, c1: c1 * theta,
xdata=fit_input_output_angles.input_angle_field,
ydata=fit_input_output_angles.output_angle_retina_center,
p0=1,
)
(c1_pupil,), _ = curve_fit(
lambda theta, c1: c1 * theta,
xdata=fit_input_output_angles.input_angle_field,
ydata=fit_input_output_angles.output_angle_pupil,
p0=1,
)
print(f"NP2 fit : {c1_np2:.3f}")
print(f"Retinal center fit : {c1_retina_center:.3f}")
print(f"Pupil fit : {c1_pupil:.3f}")
NP2 fit : 0.999
Retinal center fit : 1.354
Pupil fit : 0.804
将拟合关系与其他方法进行比较#
使用使用Navarro Eye模型获得的拟合关系,以确定模拟受试者的相应视网膜位置而无需光线追迹。本课题前期获得的光线追踪数据作为基础真值。
参考点方法#
使用三个参考点之一(第二节点点,视网膜中心和学生)确定视网膜位置
[6]:
input_output_angles["output_angle_np2_fit"] = c1_np2 * input_output_angles.input_angle_field
input_output_angles["output_angle_retina_center_fit"] = c1_retina_center * input_output_angles.input_angle_field
input_output_angles["output_angle_pupil_fit"] = c1_pupil * input_output_angles.input_angle_field
# Calculate retinal locations
input_output_angles["retina_location_np2"] = [
find_ellipse_intersection(
r.location_np2,
np.deg2rad(r.output_angle_np2_fit),
patient_geometry["retina_radius_z"],
patient_geometry["retina_radius_y"],
r.location_retina_center,
)
for r in input_output_angles.itertuples()
]
input_output_angles["retina_location_retina_center"] = [
find_ellipse_intersection(
r.location_retina_center,
np.deg2rad(r.output_angle_retina_center_fit),
patient_geometry["retina_radius_z"],
patient_geometry["retina_radius_y"],
r.location_retina_center,
)
for r in input_output_angles.itertuples()
]
input_output_angles["retina_location_pupil"] = [
find_ellipse_intersection(
0,
np.deg2rad(r.output_angle_pupil_fit),
patient_geometry["retina_radius_z"],
patient_geometry["retina_radius_y"],
r.location_retina_center,
)
for r in input_output_angles.itertuples()
]
input_output_angles["distance_np2"] = euclidean_distance(
input_output_angles.retina_location, input_output_angles.retina_location_np2
)
input_output_angles["distance_retina_center"] = euclidean_distance(
input_output_angles.retina_location,
input_output_angles.retina_location_retina_center,
)
input_output_angles["distance_pupil"] = euclidean_distance(
input_output_angles.retina_location, input_output_angles.retina_location_pupil
)
Eyeplan#
通过Eyeplan中使用的方法确定摄像头角度和视网膜角度之间的关系。
[7]:
OPTIC_FIT_FACTOR = 0.126
FIELD_OF_VIEW = 53.4 # degrees
FILM_SIZE = 1 # cm
def inverse_eyeplan_formula(camera_angle: float, fov: float = FIELD_OF_VIEW, off: float = OPTIC_FIT_FACTOR) -> float:
"""Map `camera_angle` to a retinal angle according to EYEPLAN."""
return camera_angle * fov / (fov - camera_angle * off)
# EYEPLAN defines a "nodal point" at 3.5 mm behind the cornea
input_output_angles["location_np_eyeplan"] = 3.5 - (
patient_geometry["cornea_thickness"] + patient_geometry["anterior_chamber_depth"]
)
input_output_angles["output_angle_eyeplan_formula"] = inverse_eyeplan_formula(input_output_angles.input_angle_field)
# Calculate retinal locations according to EYEPLAN
input_output_angles["retina_location_eyeplan"] = [
find_ellipse_intersection(
r.location_np_eyeplan,
np.deg2rad(r.output_angle_eyeplan_formula),
patient_geometry["retina_radius_z"],
patient_geometry["retina_radius_y"],
r.location_retina_center,
)
for r in input_output_angles.itertuples()
]
input_output_angles["distance_eyeplan"] = euclidean_distance(
input_output_angles.retina_location, input_output_angles.retina_location_eyeplan
)
Corcoran#
Corcoran等人提出的公式,用于(早期版本的)Optos眼底镜。
[8]:
def corcoran_formula(
external_angle: float,
m: float = 0.819,
R: float = 12, # noqa: N803
x: float = 3.68,
) -> float:
"""Corcoran (Optos) mapping formula.
Converts an external angle (camera angle) to an internal angle (retinal angle) w.r.t. retina center using the
Corcoran formula.
"""
external_angle_rad = np.deg2rad(external_angle)
internal_angle = np.rad2deg(
m * external_angle_rad + 2 * np.arcsin((R - x) / R * np.sin(m * external_angle_rad / 2))
)
return internal_angle
input_output_angles["output_angle_corcoran"] = corcoran_formula(input_output_angles.input_angle_field)
# Calculate retinal locations according to Corcoran formula
input_output_angles["retina_location_corcoran"] = [
find_ellipse_intersection(
r.location_retina_center,
np.deg2rad(r.output_angle_corcoran),
patient_geometry["retina_radius_z"],
patient_geometry["retina_radius_y"],
r.location_retina_center,
)
for r in input_output_angles.itertuples()
]
input_output_angles["distance_corcoran"] = euclidean_distance(
input_output_angles.retina_location, input_output_angles.retina_location_corcoran
)
以下投影方法的工作与上述方法略有不同,因为它们采用笛卡尔坐标而不是角度作为输入。 这需要在输入角度和图像坐标之间进行附加的转换步骤。这种转换的常数是通过拟合Navarro Eye模型的光线追迹数据获得的。
Lamberth方位角相等的投影#
[9]:
def lamberth_image_to_retina_coordinate(
y_image: float, r: float = 1, z_retina_center: float = 0
) -> tuple[float, float]:
"""
Convert an image coordinate to a retinal location using the Lamberth Azimuthal Equal-Area projection.
Parameters
----------
y_image : float
Image coordinate.
r : float
Radius of the retina. Only spheres are supported.
Returns
-------
tuple[float, float]
Axial and radial retinal coordinates.
"""
# Lamberth projection uses coordinates on the unit sphere
y_retina_norm = np.sqrt(1 - y_image**2 / 4) * y_image
z_retina_norm = -1 + y_image**2 / 2
y_retina = y_retina_norm * r
z_retina = z_retina_norm * r
# Flip the z-axis: otherwise the back of the retina will get a negative z-coordinate
return -1 * z_retina + z_retina_center, y_retina
def lamberth_retina_to_image_coordinate(
z_retina: float, y_retina: float, r: float = 1, z_retina_center: float = 0
) -> float:
"""
Convert a retinal location to an image coordinate using the Lamberth Azimuthal Equal-Area projection.
Parameters
----------
z_retina : float
Axial retinal coordinate.
y_retina : float
Radial retinal coordinate.
r : float
Radius of the retina. Only spheres are supported.
Returns
-------
float
Image coordinate.
"""
y_retina_norm = y_retina / r
z_retina_norm = (z_retina - z_retina_center) / r
y_image = np.sqrt(2 / (1 + z_retina_norm)) * y_retina_norm
return y_image
assert np.isclose(
0.5,
lamberth_retina_to_image_coordinate(*lamberth_image_to_retina_coordinate(0.5)),
), "Projection roundtrip fails."
[10]:
def lamberth_angle_conversion_factor(angle: float = 5) -> float:
"""Calculate a scale factor to convert from a camera angle to a Lamberth projection image coordinate.
Image coordinates are in 'Lamberth projection space'. The Lamberth projection is defined on the unit
sphere, so all projected images have the same size.
Parameters
----------
angle : float
Angle for which the ray trace result is used to calculate the conversion factor.
Returns
-------
float
Conversion factor in millimeters / degree.
"""
geometry = navarro_geometry
ray_trace_data = navarro_ray_trace_data
mean_retinal_radius = (geometry["retina_radius_y"] + geometry["retina_radius_z"]) / 2
retina_center = geometry["axial_length"] - (
geometry["cornea_thickness"] + geometry["anterior_chamber_depth"] + mean_retinal_radius
)
retina_coordinate = ray_trace_data.query("Surf == '7' and InputAngle == @angle").iloc[0][
["Z-coordinate", "Y-coordinate"]
]
image_coordinate = lamberth_retina_to_image_coordinate(*retina_coordinate, mean_retinal_radius, retina_center)
return image_coordinate / angle
[11]:
input_output_angles_lamberth["lamberth_angle_conversion_factor"] = lamberth_angle_conversion_factor()
input_output_angles_lamberth["lamberth_projected_image_size"] = (
input_output_angles_lamberth.lamberth_angle_conversion_factor * input_output_angles_lamberth.input_angle_field
)
input_output_angles_lamberth["retina_location_lamberth"] = input_output_angles_lamberth.apply(
lambda r: lamberth_image_to_retina_coordinate(
r.lamberth_projected_image_size,
r=abs(patient_geometry_lamberth["retina_curvature"]),
z_retina_center=r.location_retina_center,
),
axis=1,
)
input_output_angles_lamberth["distance_lamberth"] = euclidean_distance(
input_output_angles_lamberth.retina_location,
input_output_angles_lamberth.retina_location_lamberth,
)
input_output_angles[["distance_lamberth", "retina_location_lamberth"]] = input_output_angles_lamberth[
["distance_lamberth", "retina_location_lamberth"]
]
等距投影#
[12]:
from scipy.optimize import minimize_scalar
def octopus_image_to_retina_coordinate(y_image: float, geometry: dict[str, float | int]) -> tuple[float, float]:
solve_z = minimize_scalar(
lambda z: abs(
ellipse_arc_length(
x1=z,
x2=geometry["retina_radius_z"],
r_x=geometry["retina_radius_z"],
r_y=geometry["retina_radius_y"],
)
- y_image
),
bounds=(-geometry["retina_radius_z"], geometry["retina_radius_z"]),
)
if not solve_z.success:
raise RuntimeError(f"Could not solve coordinate for arc length {y_image=}.")
z_retina = solve_z.x
y_retina = _upper_ellipse(
z_retina,
r_x=geometry["retina_radius_z"],
r_y=geometry["retina_radius_y"],
)
z_retina_center = geometry["lens_thickness"] + geometry["vitreous_thickness"] - geometry["retina_radius_z"]
z_retina += z_retina_center
return z_retina, y_retina
def octopus_retina_to_image_coordinate(z_retina: float, y_retina: float, geometry: dict[str, float | int]) -> float:
z_retina_center = geometry["lens_thickness"] + geometry["vitreous_thickness"] - geometry["retina_radius_z"]
z_retina -= z_retina_center
assert np.isclose(
_upper_ellipse(
z_retina,
r_x=geometry["retina_radius_z"],
r_y=geometry["retina_radius_y"],
),
y_retina,
)
arc_length = ellipse_arc_length(
z_retina,
geometry["retina_radius_z"],
r_x=geometry["retina_radius_z"],
r_y=geometry["retina_radius_y"],
)
return arc_length
assert np.isclose(
octopus_retina_to_image_coordinate(
*octopus_image_to_retina_coordinate(2 * np.pi * 12 / 8, navarro_geometry),
navarro_geometry,
),
2 * np.pi * 12 / 8,
), "Projection roundtrip fails."
[13]:
def octopus_angle_conversion_factor(angle: float = 5) -> float:
"""Calculate a scale factor to convert from a camera angle to a Lamberth projection image coordinate."""
retina_coordinate = navarro_ray_trace_data.query("Surf == '7' and InputAngle == @angle").iloc[0][
["Z-coordinate", "Y-coordinate"]
]
image_coordinate = octopus_retina_to_image_coordinate(*retina_coordinate, navarro_geometry)
return image_coordinate / angle
[14]:
input_output_angles["polar_angle_conversion_factor"] = octopus_angle_conversion_factor()
input_output_angles["polar_projected_image_size"] = (
input_output_angles.polar_angle_conversion_factor * input_output_angles.input_angle_field
)
input_output_angles["retina_location_polar"] = input_output_angles.apply(
lambda r: octopus_image_to_retina_coordinate(
r.polar_projected_image_size,
geometry=patient_geometry,
),
axis=1,
)
input_output_angles["distance_polar"] = euclidean_distance(
input_output_angles.retina_location,
input_output_angles.retina_location_polar,
)
## for debugging: plot the complete list of all angles for all of the methods
# input_output_angles
绘制结果#
绘制所有方法的真实(光线追迹)和预测的视网膜位置之间的差异
[15]:
plt.figure()
sns.lineplot(
input_output_angles,
x="input_angle_field",
y="distance_np2",
label="$2^{\\mathrm{nd}}$ nodal point",
)
sns.lineplot(
input_output_angles,
x="input_angle_field",
y="distance_retina_center",
label="Retina center",
)
sns.lineplot(input_output_angles, x="input_angle_field", y="distance_pupil", label="Pupil")
sns.lineplot(input_output_angles, x="input_angle_field", y="distance_eyeplan", label="EYEPLAN")
sns.lineplot(input_output_angles, x="input_angle_field", y="distance_corcoran", label="Corcoran")
sns.lineplot(
input_output_angles,
x="input_angle_field",
y="distance_lamberth",
label="Lamberth projection",
)
sns.lineplot(
input_output_angles,
x="input_angle_field",
y="distance_polar",
label="Polar projection",
)
plt.grid()
plt.xlabel("Camera angle [°]")
plt.ylabel("Euclidean distance [mm]")
[15]:
Text(0, 0.5, 'Euclidean distance [mm]')
[16]:
column_names = {
"input_angle_field": ("", "Camera angle [°]"),
"retina_location": ("", "Retina location"),
"retina_location_np2": ("2nd nodal point", "Retina location"),
"distance_np2": ("2nd nodal point", "Difference [mm]"),
"retina_location_retina_center": ("Retina center", "Difference [mm]"),
"distance_retina_center": ("Retina center", "Difference [mm]"),
"retina_location_pupil": ("Pupil", "Retina location"),
"distance_pupil": ("Pupil", "Difference [mm]"),
"retina_location_eyeplan": ("EYEPLAN", "Retina location"),
"distance_eyeplan": ("EYEPLAN", "Difference [mm]"),
"retina_location_corcoran": ("Corcoran", "Retina location"),
"distance_corcoran": ("Corcoran", "Difference [mm]"),
"retina_location_lamberth": ("Lamberth", "Retina location"),
"distance_lamberth": ("Lamberth", "Difference [mm]"),
"retina_location_polar": ("Polar", "Retina location"),
"distance_polar": ("Polar", "Difference [mm]"),
}
table = input_output_angles[column_names.keys()]
table.columns = pd.MultiIndex.from_tuples(column_names.values())
table.map(lambda x: tuple(round(y, 2) for y in x) if isinstance(x, tuple) else x).round(decimals=2)
[16]:
| 2nd nodal point | Retina center | Pupil | EYEPLAN | Corcoran | Lamberth | Polar | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Camera angle [°] | Retina location | Retina location | Difference [mm] | Difference [mm] | Difference [mm] | Retina location | Difference [mm] | Retina location | Difference [mm] | Retina location | Difference [mm] | Retina location | Difference [mm] | Retina location | Difference [mm] | |
| 0 | 0.0 | (20.4, 0.0) | (20.4, 0) | 0.00 | (20.4, 0) | 0.00 | (20.4, 0) | 0.00 | (20.4, 0) | 0.00 | (20.4, 0) | 0.00 | (20.4, 0.0) | 0.00 | (20.4, 0.01) | 0.01 |
| 1 | 10.0 | (20.02, 2.9) | (20.02, 2.92) | 0.02 | (20.04, 2.83) | -0.07 | (20.04, 2.83) | -0.07 | (19.8, 3.65) | 0.79 | (20.02, 2.89) | -0.00 | (20.06, 2.83) | -0.07 | (20.04, 2.84) | -0.06 |
| 2 | 20.0 | (18.93, 5.59) | (18.92, 5.62) | 0.03 | (19.0, 5.47) | -0.14 | (19.0, 5.47) | -0.14 | (17.96, 7.05) | 1.75 | (18.93, 5.59) | -0.01 | (19.03, 5.53) | -0.09 | (18.98, 5.51) | -0.10 |
| 3 | 30.0 | (17.23, 7.91) | (17.21, 7.92) | 0.03 | (17.36, 7.77) | -0.19 | (17.36, 7.77) | -0.19 | (15.01, 9.74) | 2.88 | (17.22, 7.91) | 0.00 | (17.33, 7.98) | 0.03 | (17.3, 7.83) | -0.10 |
| 4 | 40.0 | (15.07, 9.7) | (15.06, 9.71) | 0.02 | (15.23, 9.6) | -0.20 | (15.25, 9.58) | -0.22 | (11.28, 11.35) | 4.13 | (15.03, 9.73) | 0.05 | (14.94, 10.01) | 0.35 | (15.11, 9.68) | -0.04 |
| 5 | 50.0 | (12.66, 10.92) | (12.65, 10.93) | 0.01 | (12.77, 10.88) | -0.11 | (12.85, 10.85) | -0.20 | (7.26, 11.67) | 5.45 | (12.51, 10.98) | 0.16 | (11.87, 11.42) | 1.03 | (12.54, 10.97) | 0.13 |
| 6 | 60.0 | (10.2, 11.57) | (10.16, 11.57) | 0.03 | (10.09, 11.58) | 0.11 | (10.32, 11.55) | -0.12 | (3.53, 10.76) | 6.71 | (9.8, 11.62) | 0.40 | (8.12, 11.9) | 2.25 | (9.75, 11.63) | 0.45 |
| 7 | 70.0 | (7.85, 11.7) | (7.74, 11.7) | 0.11 | (7.33, 11.67) | 0.52 | (7.82, 11.7) | 0.03 | (0.56, 9.01) | 7.77 | (7.04, 11.65) | 0.81 | (3.68, 10.89) | 4.40 | (6.88, 11.63) | 0.97 |
| 8 | 80.0 | (5.74, 11.45) | (5.49, 11.39) | 0.25 | (4.61, 11.15) | 1.16 | (5.48, 11.39) | 0.26 | (-1.46, 6.93) | 8.50 | (4.36, 11.07) | 1.43 | (-1.44, 6.57) | 8.81 | (4.09, 10.98) | 1.71 |
| 9 | 5.0 | (20.3, 1.46) | (20.3, 1.47) | 0.01 | (20.31, 1.43) | -0.04 | (20.31, 1.43) | -0.04 | (20.25, 1.83) | 0.37 | (20.3, 1.46) | -0.00 | (20.31, 1.42) | -0.04 | (20.31, 1.43) | -0.03 |
| 10 | 15.0 | (19.56, 4.28) | (19.55, 4.31) | 0.02 | (19.6, 4.18) | -0.11 | (19.6, 4.19) | -0.11 | (19.03, 5.41) | 1.24 | (19.56, 4.28) | -0.01 | (19.63, 4.21) | -0.09 | (19.59, 4.21) | -0.08 |
| 11 | 25.0 | (18.14, 6.81) | (18.13, 6.83) | 0.03 | (18.25, 6.67) | -0.17 | (18.24, 6.67) | -0.17 | (16.61, 8.51) | 2.29 | (18.15, 6.8) | -0.01 | (18.27, 6.8) | -0.05 | (18.21, 6.72) | -0.11 |
| 12 | 35.0 | (16.19, 8.88) | (16.18, 8.89) | 0.02 | (16.35, 8.74) | -0.20 | (16.35, 8.74) | -0.21 | (13.22, 10.7) | 3.49 | (16.18, 8.89) | 0.02 | (16.22, 9.06) | 0.16 | (16.26, 8.82) | -0.08 |
| 13 | 45.0 | (13.89, 10.39) | (13.88, 10.39) | 0.01 | (14.04, 10.31) | -0.17 | (14.08, 10.29) | -0.22 | (9.27, 11.67) | 4.79 | (13.8, 10.43) | 0.09 | (13.49, 10.81) | 0.64 | (13.86, 10.4) | 0.03 |
| 14 | 55.0 | (11.42, 11.31) | (11.41, 11.32) | 0.02 | (11.45, 11.31) | -0.02 | (11.59, 11.27) | -0.17 | (5.33, 11.35) | 6.10 | (11.17, 11.38) | 0.26 | (10.08, 11.8) | 1.55 | (11.17, 11.38) | 0.27 |
| 15 | 65.0 | (9.0, 11.69) | (8.94, 11.7) | 0.06 | (8.71, 11.7) | 0.29 | (9.05, 11.69) | -0.06 | (1.93, 9.96) | 7.27 | (8.42, 11.71) | 0.58 | (5.98, 11.64) | 3.17 | (8.32, 11.71) | 0.68 |
| 16 | 75.0 | (6.76, 11.62) | (6.59, 11.59) | 0.17 | (5.96, 11.49) | 0.81 | (6.62, 11.6) | 0.14 | (-0.56, 7.97) | 8.18 | (5.68, 11.43) | 1.10 | (1.21, 9.42) | 6.12 | (5.47, 11.39) | 1.31 |
| 17 | 85.0 | (4.78, 11.21) | (4.45, 11.1) | 0.35 | (3.31, 10.67) | 1.57 | (4.41, 11.09) | 0.39 | (-2.14, 5.92) | 8.71 | (3.09, 10.57) | 1.81 | (-4.25, nan) | NaN | (2.77, 10.42) | 2.17 |