# SPDX-FileCopyrightText: 2021 International Photoacoustics Standardisation Consortium (IPASC)
# SPDX-FileCopyrightText: 2021 Janek Gröhl
# SPDX-License-Identifier: BSD 3-Clause License
import matplotlib.pylab as plt
from matplotlib.patches import Rectangle, Circle, Polygon
import numpy as np
from pacfish import MetadataDeviceTags
[docs]def visualize_device(device_dictionary: dict, save_path: str = None, title: str = None,
only_show_xz: bool = False, show_legend: bool = True):
"""
Visualises a given device from the device_dictionary.
Parameters
----------
device_dictionary: dict
The dictionary containing the device description.
save_path: str
Optional save_path with the path and file name to save the visualisation to.
title: str
Optional custom title for the plot.
only_show_xz: bool
Optional bool parameter specifying if only the first window should be shown instead of all
show_legend: bool
Optional parameter whether the figure legend should be shown (default: True)
"""
MARGIN = 0.001
def define_boundary_values(_device_dictionary: dict):
mins = np.ones(3) * 100000
maxs = np.ones(3) * -100000
if "illuminators" in _device_dictionary:
for illuminator in _device_dictionary["illuminators"]:
position = _device_dictionary["illuminators"][illuminator][MetadataDeviceTags.ILLUMINATOR_POSITION.tag]
for i in range(3):
if position[i] < mins[i]:
mins[i] = position[i]
if position[i] > maxs[i]:
maxs[i] = position[i]
for detector in _device_dictionary["detectors"]:
position = _device_dictionary["detectors"][detector][MetadataDeviceTags.DETECTOR_POSITION.tag]
for i in range(3):
if position[i] < mins[i]:
mins[i] = position[i]
if position[i] > maxs[i]:
maxs[i] = position[i]
fov = _device_dictionary["general"][MetadataDeviceTags.FIELD_OF_VIEW.tag]
for i in range(3):
if fov[2 * i] < mins[i]:
mins[i] = fov[2 * i]
if fov[2 * i + 1] < mins[i]:
mins[i] = fov[2 * i + 1]
if fov[2 * i] > maxs[i]:
maxs[i] = fov[2 * i]
if fov[2 * i + 1] > maxs[i]:
maxs[i] = fov[2 * i + 1]
maxs += MARGIN
mins -= MARGIN
return mins, maxs
def add_arbitrary_plane(_device_dictionary: dict, _mins, _maxs, _axes, _draw_axis):
_draw_axis.set_xlim(_mins[_axes[0]], _maxs[_axes[0]])
_draw_axis.set_ylim(_maxs[_axes[1]], _mins[_axes[1]])
x_ticks = np.asarray(np.round(np.linspace(_mins[_axes[0]] + MARGIN, _maxs[_axes[0]] - MARGIN, 5), decimals=4))
y_ticks = np.asarray(np.round(np.linspace(_mins[_axes[1]] + MARGIN, _maxs[_axes[1]] - MARGIN, 5), decimals=4))
_draw_axis.set_xticks(x_ticks, x_ticks*100)
_draw_axis.set_yticks(y_ticks, y_ticks * 100)
_draw_axis.set_title(f"axes {_axes[0]}/{_axes[1]} projection view")
_draw_axis.set_xlabel(f"{_axes[0]}-axis [cm]")
_draw_axis.set_ylabel(f"{_axes[1]}-axis [cm]")
fov = _device_dictionary["general"][MetadataDeviceTags.FIELD_OF_VIEW.tag]
for detector in _device_dictionary["detectors"]:
if not (MetadataDeviceTags.DETECTOR_POSITION.tag in _device_dictionary["detectors"][detector] and
MetadataDeviceTags.DETECTOR_GEOMETRY.tag in _device_dictionary["detectors"][detector]):
return
detector_geometry_type = _device_dictionary["detectors"][detector][
MetadataDeviceTags.DETECTOR_GEOMETRY_TYPE.tag]
detector_position = _device_dictionary["detectors"][detector][MetadataDeviceTags.DETECTOR_POSITION.tag]
detector_geometry = np.asarray(
_device_dictionary["detectors"][detector][MetadataDeviceTags.DETECTOR_GEOMETRY.tag])
if detector_geometry_type == "CUBOID":
if detector_geometry[_axes[0]] < 0.001:
detector_geometry[_axes[0]] = 0.001
if detector_geometry[_axes[1]] < 0.001:
detector_geometry[_axes[1]] = 0.001
_draw_axis.add_patch(Rectangle((detector_position[_axes[0]] - detector_geometry[_axes[0]] / 2,
detector_position[_axes[1]] - detector_geometry[_axes[1]] / 2),
detector_geometry[_axes[0]], detector_geometry[_axes[1]], color="blue"))
elif detector_geometry_type == "SPHERE" or detector_geometry_type == "CIRCLE":
_draw_axis.add_patch(Circle((detector_position[_axes[0]], detector_position[_axes[1]]), detector_geometry,
color="blue"))
else:
print("UNSUPPORTED GEOMETRY TYPE FOR VISUALISATION. WILL DEFAULT TO 'x' visualisation.")
_draw_axis.plot(detector_position[_axes[0]], detector_position[_axes[1]], "x", color="blue")
if "illuminators" in _device_dictionary:
for illuminator in _device_dictionary["illuminators"]:
if not (MetadataDeviceTags.ILLUMINATOR_POSITION.tag in _device_dictionary["illuminators"][illuminator] and
MetadataDeviceTags.ILLUMINATOR_GEOMETRY.tag in _device_dictionary["illuminators"][illuminator]):
return
illuminator_position = _device_dictionary["illuminators"][illuminator][
MetadataDeviceTags.ILLUMINATOR_POSITION.tag]
illuminator_orientation = np.asarray(
_device_dictionary["illuminators"][illuminator][MetadataDeviceTags.ILLUMINATOR_ORIENTATION.tag])
illuminator_divergence = _device_dictionary["illuminators"][illuminator][
MetadataDeviceTags.BEAM_DIVERGENCE_ANGLES.tag]
illuminator_geometry = np.asarray(
_device_dictionary["illuminators"][illuminator][MetadataDeviceTags.ILLUMINATOR_GEOMETRY.tag])
diameter = np.sqrt(np.sum(np.asarray([a ** 2 for a in illuminator_geometry]))) / 2
illuminator_geometry_type = _device_dictionary["illuminators"][illuminator][
MetadataDeviceTags.ILLUMINATOR_GEOMETRY_TYPE.tag]
_draw_axis.scatter(illuminator_position[_axes[0]], illuminator_position[_axes[1]],
marker="+", color="red")
x = [illuminator_position[_axes[0]],
illuminator_position[_axes[0]] +
illuminator_orientation[_axes[0]] / 25]
y = [illuminator_position[_axes[1]],
illuminator_position[_axes[1]] +
illuminator_orientation[_axes[1]] / 25]
plt.plot(x, y, color="yellow", alpha=1, linewidth=25, zorder=-10)
start_indexes = np.asarray(_axes) * 2
end_indexes = start_indexes + 1
_draw_axis.add_patch(
Rectangle((fov[start_indexes[0]], fov[start_indexes[1]]),
-fov[start_indexes[0]] + fov[end_indexes[0]],
-fov[start_indexes[1]] + fov[end_indexes[1]],
color="green", fill=False, label="Field of View"))
if title is None:
title = "Device Visualisation based on IPASC data format specifications"
mins, maxs = define_boundary_values(device_dictionary)
num_subplots = 3
if only_show_xz:
num_subplots = 1
if only_show_xz:
plt.figure(figsize=(3.33, 4))
else:
plt.figure(figsize=(10, 4))
plt.suptitle(title)
ax = plt.subplot(1, num_subplots, 1)
# ax.axes.xaxis.set_visible(False)
# ax.axes.yaxis.set_visible(False)
add_arbitrary_plane(device_dictionary, mins, maxs, _axes=(0, 2), _draw_axis=ax)
if not only_show_xz:
ax = plt.subplot(1, num_subplots, 2)
ax.axes.xaxis.set_visible(False)
ax.axes.yaxis.set_visible(False)
add_arbitrary_plane(device_dictionary, mins, maxs, _axes=(0, 1), _draw_axis=ax)
ax = plt.subplot(1, num_subplots, 3)
ax.axes.xaxis.set_visible(False)
ax.axes.yaxis.set_visible(False)
add_arbitrary_plane(device_dictionary, mins, maxs, _axes=(1, 2), _draw_axis=ax)
plt.scatter(None, None, color="blue", marker="o", label="Detector Element")
plt.scatter(None, None, color="red", marker="+", label="Illumination Element")
plt.scatter(None, None, color="green", marker="s", label="Field of View")
plt.scatter(None, None, color="Yellow", marker="s", label="Illumination Profile")
if show_legend:
plt.legend(loc="lower left")
plt.tight_layout()
if save_path is None:
plt.show()
else:
plt.savefig(save_path, dpi=300)