Source code for visualization.plot_camera

"""Plot camera pixel layout."""

import logging

import matplotlib.colors as mcolors
import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.collections import PatchCollection

import simtools.visualization.legend_handlers as leg_h
from simtools.model.model_utils import is_two_mirror_telescope
from simtools.utils import names

logger = logging.getLogger(__name__)


[docs] def plot_pixel_layout(camera, camera_in_sky_coor=False, pixels_id_to_print=50): """ Plot the pixel layout for an observer facing the camera. Including in the plot edge pixels, off pixels, pixel ID for the first 50 pixels, coordinate systems, FOV, focal length and the average edge radius. Parameters ---------- camera : Camera Camera object to plot. camera_in_sky_coor : bool, optional Flag to plot the camera in the sky coordinate system. pixels_id_to_print : int, optional Number of pixel IDs to print in the plot. Returns ------- fig : plt.Figure Figure with the pixel layout. """ logger.info(f"Plotting the {camera.telescope_model_name} camera") fig, ax = plt.subplots(figsize=(8, 8)) if not is_two_mirror_telescope(camera.telescope_model_name) and not camera_in_sky_coor: camera.pixels["y"] = [(-1) * y_val for y_val in camera.pixels["y"]] on_pixels, edge_pixels, off_pixels = _pixel_type_lists(camera) for i_pix, (x, y) in enumerate(zip(camera.pixels["x"], camera.pixels["y"])): if camera.pixels["pix_id"][i_pix] < pixels_id_to_print + 1: font_size = ( 4 if "SCT" in names.get_array_element_type_from_name(camera.telescope_model_name) else 2 ) plt.text( x, y, camera.pixels["pix_id"][i_pix], ha="center", va="center", fontsize=font_size ) ax.add_collection( PatchCollection(on_pixels, facecolor="none", edgecolor="black", linewidth=0.2) ) ax.add_collection( PatchCollection( edge_pixels, facecolor=(*mcolors.to_rgb("brown"), 0.5), edgecolor=(*mcolors.to_rgb("black"), 1), linewidth=0.2, ) ) ax.add_collection( PatchCollection(off_pixels, facecolor="black", edgecolor="black", linewidth=0.2) ) legend_objects = [leg_h.PixelObject(), leg_h.EdgePixelObject()] legend_labels = ["Pixel", "Edge pixel"] legend_handler_map = { leg_h.PixelObject: ( leg_h.HexPixelHandler() if isinstance(on_pixels[0], mpatches.RegularPolygon) else leg_h.SquarePixelHandler() ), leg_h.EdgePixelObject: ( leg_h.HexEdgePixelHandler() if isinstance(on_pixels[0], mpatches.RegularPolygon) else leg_h.SquareEdgePixelHandler() ), leg_h.OffPixelObject: ( leg_h.HexOffPixelHandler() if isinstance(on_pixels[0], mpatches.RegularPolygon) else leg_h.SquareOffPixelHandler() ), } if len(off_pixels) > 0: legend_objects.append(leg_h.OffPixelObject()) legend_labels.append("Disabled pixel") plt.axis("equal") plt.grid(True) ax.set_axisbelow(True) plt.axis( [ min(camera.pixels["x"]), max(camera.pixels["x"]), min(camera.pixels["y"]) * 1.42, max(camera.pixels["y"]) * 1.42, ] ) plt.xlabel("Horizontal scale [cm]", fontsize=18, labelpad=0) plt.ylabel("Vertical scale [cm]", fontsize=18, labelpad=0) ax.set_title( f"Pixels layout in {camera.telescope_model_name:s} camera", fontsize=15, y=1.02, ) plt.tick_params(axis="both", which="major", labelsize=15) _plot_axes_def(camera, plt, camera.pixels["rotate_angle"]) description = { False: "For an observer facing the camera", True: "For an observer behind the camera looking through", None: "For an observer looking from secondary to camera", }[camera_in_sky_coor and not is_two_mirror_telescope(camera.telescope_model_name)] ax.text(0.02, 0.02, description, transform=ax.transAxes, color="black", fontsize=12) fov, r_edge_avg = camera.calc_fov() ax.text( 0.02, 0.96, rf"$f_{{\mathrm{{eff}}}} = {camera.focal_length:.3f}\,\mathrm{{cm}}$", transform=ax.transAxes, color="black", fontsize=12, ) ax.text( 0.02, 0.92, rf"Avg. edge radius = {r_edge_avg:.3f}$\,\mathrm{{cm}}$", transform=ax.transAxes, color="black", fontsize=12, ) ax.text( 0.02, 0.88, rf"FoV = {fov:.3f}$\,\mathrm{{deg}}$", transform=ax.transAxes, color="black", fontsize=12, ) plt.legend( legend_objects, legend_labels, handler_map=legend_handler_map, prop={"size": 11}, loc="upper right", ) ax.set_aspect("equal", "datalim") plt.tight_layout() return fig
def _pixel_type_lists(camera): """ Return on, off, and edge pixel lists. Parameters ---------- camera : Camera Camera object. Returns ------- on_pixels : list List of on pixels. edge_pixels : list List of edge pixels. off_pixels : list List of off pixels. """ on_pixels, edge_pixels, off_pixels = [], [], [] for i_pix, (x, y) in enumerate(zip(camera.pixels["x"], camera.pixels["y"])): shape = _pixel_shape(camera, x, y) if camera.pixels["pix_on"][i_pix]: neighbors = camera.get_neighbour_pixels()[i_pix] if len(neighbors) < 6 and camera.pixels["pixel_shape"] in (1, 3): edge_pixels.append(shape) elif len(neighbors) < 4 and camera.pixels["pixel_shape"] == 2: edge_pixels.append(shape) else: on_pixels.append(shape) else: off_pixels.append(shape) return on_pixels, edge_pixels, off_pixels def _pixel_shape(camera, x, y): """ Return the shape of the pixel. Parameters ---------- camera : Camera Camera object. x : float x-coordinate of the pixel. y : float y-coordinate of the pixel. Returns ------- shape : matplotlib.patches.Patch Shape of the pixel. """ if camera.pixels["pixel_shape"] in (1, 3): return mpatches.RegularPolygon( (x, y), numVertices=6, radius=camera.pixels["pixel_diameter"] / np.sqrt(3), orientation=np.deg2rad(camera.pixels["orientation"]), ) if camera.pixels["pixel_shape"] == 2: return mpatches.Rectangle( ( x - camera.pixels["pixel_diameter"] / 2.0, y - camera.pixels["pixel_diameter"] / 2.0, ), width=camera.pixels["pixel_diameter"], height=camera.pixels["pixel_diameter"], ) return None def _plot_axes_def(camera, plot, rotate_angle): """ Plot three axes definitions on the pyplot.plt instance provided. The three axes are Alt/Az, the camera coordinate system and the original coordinate system the pixel list was provided. Parameters ---------- camera : Camera Camera object. plot: pyplot.plt instance A pyplot.plt instance where to add the axes definitions. rotate_angle: float The rotation angle applied """ invert_yaxis = False x_left = 0.7 # Position of the left most axis if not is_two_mirror_telescope(camera.telescope_model_name): invert_yaxis = True x_left = 0.8 x_title = r"$x_{\!pix}$" y_title = r"$y_{\!pix}$" x_pos, y_pos = (x_left, 0.12) # The rotation of LST (above 100 degrees) raises the axes. # In this case, lower the starting point. if np.rad2deg(rotate_angle) > 100: y_pos -= 0.09 x_pos -= 0.05 kwargs = { "x_title": x_title, "y_title": y_title, "x_pos": x_pos, "y_pos": y_pos, "rotate_angle": rotate_angle - (1 / 2.0) * np.pi, "fc": "black", "ec": "black", "invert_yaxis": invert_yaxis, } _plot_one_axis_def(plot, **kwargs) x_title = r"$x_{\!cam}$" y_title = r"$y_{\!cam}$" x_pos, y_pos = (x_left + 0.15, 0.12) kwargs = { "x_title": x_title, "y_title": y_title, "x_pos": x_pos, "y_pos": y_pos, "rotate_angle": (3 / 2.0) * np.pi, "fc": "blue", "ec": "blue", "invert_yaxis": invert_yaxis, } _plot_one_axis_def(plot, **kwargs) x_title = "Alt" y_title = "Az" x_pos, y_pos = (x_left + 0.15, 0.25) kwargs = { "x_title": x_title, "y_title": y_title, "x_pos": x_pos, "y_pos": y_pos, "rotate_angle": (3 / 2.0) * np.pi, "fc": "red", "ec": "red", "invert_yaxis": invert_yaxis, } _plot_one_axis_def(plot, **kwargs) def _plot_one_axis_def(plot, **kwargs): """ Plot an axis on the pyplot.plt instance provided. Parameters ---------- plot: pyplot.plt instance A pyplot.plt instance where to add the axes definitions. **kwargs: dict x_title: str x-axis title y_title: str y-axis title, x_pos: float x position of the axis to draw y_pos: float y position of the axis to draw rotate_angle: float rotation angle of the axis in radians fc: str face colour of the axis ec: str edge colour of the axis invert_yaxis: bool Flag to invert the y-axis (for dual mirror telescopes). """ x_title = kwargs["x_title"] y_title = kwargs["y_title"] x_pos, y_pos = (kwargs["x_pos"], kwargs["y_pos"]) r = 0.1 # size of arrow sign = 1.0 if kwargs["invert_yaxis"]: sign *= -1.0 x_text1 = x_pos + sign * r * np.cos(kwargs["rotate_angle"]) y_text1 = y_pos + r * np.sin(0 + kwargs["rotate_angle"]) x_text2 = x_pos + sign * r * np.cos(np.pi / 2.0 + kwargs["rotate_angle"]) y_text2 = y_pos + r * np.sin(np.pi / 2.0 + kwargs["rotate_angle"]) plot.gca().annotate( x_title, xy=(x_pos, y_pos), xytext=(x_text1, y_text1), xycoords="axes fraction", ha="center", va="center", size="xx-large", arrowprops={ "arrowstyle": "<|-", "shrinkA": 0, "shrinkB": 0, "fc": kwargs["fc"], "ec": kwargs["ec"], }, ) plot.gca().annotate( y_title, xy=(x_pos, y_pos), xytext=(x_text2, y_text2), xycoords="axes fraction", ha="center", va="center", size="xx-large", arrowprops={ "arrowstyle": "<|-", "shrinkA": 0, "shrinkB": 0, "fc": kwargs["fc"], "ec": kwargs["ec"], }, )