gammasimtools 0.25.0__py3-none-any.whl → 0.27.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/METADATA +6 -1
  2. {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/RECORD +135 -130
  3. {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/WHEEL +1 -1
  4. {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/entry_points.txt +3 -2
  5. {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/licenses/LICENSE +1 -1
  6. simtools/_version.py +2 -2
  7. simtools/application_control.py +35 -7
  8. simtools/applications/convert_geo_coordinates_of_array_elements.py +3 -3
  9. simtools/applications/db_add_file_to_db.py +1 -1
  10. simtools/applications/db_add_simulation_model_from_repository_to_db.py +1 -1
  11. simtools/applications/db_add_value_from_json_to_db.py +1 -1
  12. simtools/applications/db_generate_compound_indexes.py +1 -1
  13. simtools/applications/db_get_array_layouts_from_db.py +3 -7
  14. simtools/applications/db_get_file_from_db.py +1 -1
  15. simtools/applications/db_get_parameter_from_db.py +1 -1
  16. simtools/applications/db_inspect_databases.py +1 -1
  17. simtools/applications/db_upload_model_repository.py +1 -1
  18. simtools/applications/derive_ctao_array_layouts.py +1 -2
  19. simtools/applications/{calculate_incident_angles.py → derive_incident_angle.py} +16 -18
  20. simtools/applications/derive_mirror_rnda.py +112 -180
  21. simtools/applications/derive_psf_parameters.py +0 -1
  22. simtools/applications/derive_pulse_shape_parameters.py +0 -1
  23. simtools/applications/derive_trigger_rates.py +1 -1
  24. simtools/applications/docs_produce_array_element_report.py +2 -8
  25. simtools/applications/docs_produce_calibration_reports.py +1 -3
  26. simtools/applications/docs_produce_model_parameter_reports.py +0 -2
  27. simtools/applications/docs_produce_simulation_configuration_report.py +1 -3
  28. simtools/applications/generate_array_config.py +0 -1
  29. simtools/applications/generate_corsika_histograms.py +79 -229
  30. simtools/applications/generate_regular_arrays.py +76 -69
  31. simtools/applications/generate_simtel_event_data.py +2 -2
  32. simtools/applications/maintain_simulation_model_add_production.py +2 -2
  33. simtools/applications/maintain_simulation_model_write_array_element_positions.py +87 -0
  34. simtools/applications/plot_array_layout.py +5 -111
  35. simtools/applications/plot_simulated_event_distributions.py +57 -0
  36. simtools/applications/plot_tabular_data.py +0 -1
  37. simtools/applications/plot_tabular_data_for_model_parameter.py +1 -6
  38. simtools/applications/production_derive_corsika_limits.py +1 -1
  39. simtools/applications/production_generate_grid.py +0 -1
  40. simtools/applications/run_application.py +1 -1
  41. simtools/applications/simulate_flasher.py +3 -15
  42. simtools/applications/simulate_illuminator.py +2 -11
  43. simtools/applications/simulate_pedestals.py +1 -5
  44. simtools/applications/simulate_prod.py +8 -11
  45. simtools/applications/simulate_prod_htcondor_generator.py +1 -1
  46. simtools/applications/submit_array_layouts.py +2 -4
  47. simtools/applications/submit_data_from_external.py +2 -1
  48. simtools/applications/submit_model_parameter_from_external.py +1 -3
  49. simtools/applications/validate_camera_efficiency.py +28 -28
  50. simtools/applications/validate_camera_fov.py +0 -1
  51. simtools/applications/validate_cumulative_psf.py +1 -5
  52. simtools/applications/validate_optics.py +2 -14
  53. simtools/atmosphere.py +83 -0
  54. simtools/camera/camera_efficiency.py +171 -53
  55. simtools/camera/single_photon_electron_spectrum.py +8 -7
  56. simtools/configuration/commandline_parser.py +82 -11
  57. simtools/configuration/configurator.py +6 -11
  58. simtools/constants.py +5 -0
  59. simtools/corsika/corsika_config.py +100 -202
  60. simtools/corsika/corsika_histograms.py +561 -1708
  61. simtools/corsika/primary_particle.py +1 -1
  62. simtools/data_model/metadata_collector.py +5 -2
  63. simtools/data_model/metadata_model.py +0 -4
  64. simtools/data_model/model_data_writer.py +59 -64
  65. simtools/data_model/schema.py +2 -0
  66. simtools/data_model/validate_data.py +1 -3
  67. simtools/db/db_handler.py +23 -10
  68. simtools/db/mongo_db.py +2 -2
  69. simtools/dependencies.py +81 -38
  70. simtools/io/ascii_handler.py +55 -5
  71. simtools/io/io_handler.py +23 -12
  72. simtools/io/table_handler.py +1 -1
  73. simtools/job_execution/job_manager.py +154 -79
  74. simtools/job_execution/process_pool.py +137 -0
  75. simtools/layout/array_layout.py +4 -13
  76. simtools/layout/array_layout_utils.py +348 -57
  77. simtools/model/array_model.py +23 -63
  78. simtools/model/calibration_model.py +4 -8
  79. simtools/model/legacy_model_parameter.py +134 -0
  80. simtools/model/model_parameter.py +147 -86
  81. simtools/model/model_utils.py +40 -6
  82. simtools/model/site_model.py +4 -8
  83. simtools/model/telescope_model.py +10 -16
  84. simtools/production_configuration/derive_corsika_limits.py +6 -11
  85. simtools/production_configuration/interpolation_handler.py +16 -16
  86. simtools/ray_tracing/incident_angles.py +92 -17
  87. simtools/ray_tracing/mirror_panel_psf.py +338 -222
  88. simtools/ray_tracing/psf_analysis.py +62 -48
  89. simtools/ray_tracing/psf_parameter_optimisation.py +3 -3
  90. simtools/ray_tracing/ray_tracing.py +43 -25
  91. simtools/reporting/docs_auto_report_generator.py +8 -13
  92. simtools/reporting/docs_read_parameters.py +2 -8
  93. simtools/runners/corsika_runner.py +52 -195
  94. simtools/runners/corsika_simtel_runner.py +77 -108
  95. simtools/runners/runner_services.py +214 -213
  96. simtools/runners/simtel_runner.py +27 -160
  97. simtools/runners/simtools_runner.py +11 -73
  98. simtools/schemas/application_workflow.metaschema.yml +8 -0
  99. simtools/settings.py +173 -0
  100. simtools/{io/eventio_handler.py → sim_events/file_info.py} +3 -3
  101. simtools/{simtel/simtel_io_event_histograms.py → sim_events/histograms.py} +25 -15
  102. simtools/{simtel/simtel_io_event_reader.py → sim_events/reader.py} +20 -17
  103. simtools/{simtel/simtel_io_event_writer.py → sim_events/writer.py} +84 -25
  104. simtools/simtel/pulse_shapes.py +7 -2
  105. simtools/simtel/simtel_config_writer.py +79 -91
  106. simtools/simtel/simtel_seeds.py +184 -0
  107. simtools/simtel/simtel_table_reader.py +6 -4
  108. simtools/simtel/simulator_array.py +114 -109
  109. simtools/simtel/simulator_camera_efficiency.py +68 -46
  110. simtools/simtel/simulator_light_emission.py +164 -132
  111. simtools/simtel/simulator_ray_tracing.py +80 -71
  112. simtools/simulator.py +137 -355
  113. simtools/telescope_trigger_rates.py +3 -4
  114. simtools/testing/assertions.py +84 -33
  115. simtools/testing/configuration.py +1 -2
  116. simtools/testing/helpers.py +2 -3
  117. simtools/testing/log_inspector.py +1 -0
  118. simtools/testing/sim_telarray_metadata.py +14 -12
  119. simtools/testing/validate_output.py +121 -42
  120. simtools/utils/general.py +43 -17
  121. simtools/utils/geometry.py +0 -77
  122. simtools/utils/names.py +5 -5
  123. simtools/utils/random.py +36 -0
  124. simtools/visualization/legend_handlers.py +7 -6
  125. simtools/visualization/plot_array_layout.py +91 -16
  126. simtools/visualization/plot_corsika_histograms.py +145 -605
  127. simtools/visualization/plot_incident_angles.py +48 -1
  128. simtools/visualization/plot_mirrors.py +1 -4
  129. simtools/visualization/plot_pixels.py +2 -4
  130. simtools/visualization/plot_psf.py +160 -19
  131. simtools/visualization/plot_simtel_event_histograms.py +4 -4
  132. simtools/visualization/plot_simtel_events.py +6 -11
  133. simtools/visualization/plot_tables.py +8 -19
  134. simtools/visualization/visualize.py +22 -2
  135. simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +0 -160
  136. simtools/applications/print_version.py +0 -53
  137. simtools/io/hdf5_handler.py +0 -139
  138. {gammasimtools-0.25.0.dist-info → gammasimtools-0.27.0.dist-info}/top_level.txt +0 -0
@@ -1,88 +1,11 @@
1
1
  """A collection of functions related to geometrical transformations."""
2
2
 
3
- import logging
4
3
  import math
5
4
 
6
5
  import astropy.units as u
7
6
  import numpy as np
8
7
  from astropy.units import UnitsError
9
8
 
10
- _logger = logging.getLogger(__name__)
11
-
12
-
13
- def convert_2d_to_radial_distr(hist_2d, xaxis, yaxis, bins=50, max_dist=1000):
14
- """
15
- Convert a 2d histogram of positions, e.g. photon positions on the ground, to a 1D distribution.
16
-
17
- Parameters
18
- ----------
19
- hist_2d: numpy.ndarray
20
- The histogram counts.
21
- xaxis: numpy.array
22
- The values of the x axis (histogram bin edges) on the ground.
23
- yaxis: numpy.array
24
- The values of the y axis (histogram bin edges) on the ground.
25
- bins: float
26
- Number of bins in distance.
27
- max_dist: float
28
- Maximum distance to consider in the 1D histogram, usually in meters.
29
-
30
- Returns
31
- -------
32
- np.array
33
- The values of the 1D histogram with size = int(max_dist/bin_size).
34
- np.array
35
- The bin edges of the 1D histogram with size = int(max_dist/bin_size) + 1.
36
-
37
- """
38
- # Check if the histogram will make sense
39
- bins_step = 2 * max_dist / bins # in the 2d array, the positive and negative direction count.
40
- for axis in [xaxis, yaxis]:
41
- if (bins_step < np.diff(axis)).any():
42
- msg = (
43
- f"The histogram with number of bins {bins} and maximum distance of {max_dist} "
44
- f"resulted in a bin size smaller than the original array. Please adjust those "
45
- f"parameters to increase the bin size and avoid nan in the histogram values."
46
- )
47
- _logger.warning(msg)
48
- break
49
-
50
- grid_2d_x, grid_2d_y = np.meshgrid(xaxis[:-1], yaxis[:-1]) # [:-1], since xaxis and yaxis are
51
- # the hist bin_edges (n + 1).
52
- # radial_distance_map maps the distance to the center from each element in a square matrix.
53
- radial_distance_map = np.sqrt(grid_2d_x**2 + grid_2d_y**2)
54
- # The sorting and unravel_index give us the two indices for the position of the sorted element
55
- # in the original 2d matrix
56
- sorted_indices = np.unravel_index(
57
- np.argsort(radial_distance_map, axis=None), np.shape(radial_distance_map)
58
- )
59
- x_indices_sorted, y_indices_sorted = sorted_indices[0], sorted_indices[1]
60
-
61
- # We construct a 1D array with the histogram counts sorted according to the distance to the
62
- # center.
63
- hist_sorted = np.array(
64
- [hist_2d[i_x, i_y] for i_x, i_y in zip(x_indices_sorted, y_indices_sorted)]
65
- )
66
- distance_sorted = np.sort(radial_distance_map, axis=None)
67
-
68
- # For larger distances, we have more elements in a slice 'dr' in radius, hence, we need to
69
- # account for it using weights below.
70
-
71
- weights, radial_bin_edges = np.histogram(distance_sorted, bins=bins, range=(0, max_dist))
72
- histogram_1d = np.empty_like(weights, dtype=float)
73
-
74
- for i_radial, _ in enumerate(radial_bin_edges[:-1]):
75
- # Here we sum all the events within a radial interval 'dr' and then divide by the number of
76
- # bins that fit this interval.
77
- indices_to_sum = (distance_sorted >= radial_bin_edges[i_radial]) * (
78
- distance_sorted < radial_bin_edges[i_radial + 1]
79
- )
80
- if weights[i_radial] != 0:
81
- histogram_1d[i_radial] = np.sum(hist_sorted[indices_to_sum]) / weights[i_radial]
82
- else:
83
- histogram_1d[i_radial] = 0
84
- return histogram_1d, radial_bin_edges
85
-
86
9
 
87
10
  @u.quantity_input(rotation_angle_phi=u.rad, rotation_angle_theta=u.rad)
88
11
  def rotate(x, y, rotation_around_z_axis, rotation_around_y_axis=0):
simtools/utils/names.py CHANGED
@@ -189,13 +189,13 @@ def model_parameters(class_key_list=None):
189
189
  dict
190
190
  Model parameters definitions.
191
191
  """
192
- _parameters = {}
193
192
  if class_key_list is None:
194
193
  return _load_model_parameters()
195
- for key, value in _load_model_parameters().items():
196
- if value.get("instrument", {}).get("class", "") in class_key_list:
197
- _parameters[key] = value
198
- return _parameters
194
+ return {
195
+ key: value
196
+ for key, value in _load_model_parameters().items()
197
+ if value.get("instrument", {}).get("class", "") in class_key_list
198
+ }
199
199
 
200
200
 
201
201
  def site_parameters():
@@ -0,0 +1,36 @@
1
+ """Random numbers utilities."""
2
+
3
+ import secrets
4
+
5
+ import numpy as np
6
+
7
+
8
+ def seeds(n_seeds=1, min_seed=1, max_seed=2_000_000_000, fixed_seed=None):
9
+ """
10
+ Generate independent random seeds.
11
+
12
+ Parameters
13
+ ----------
14
+ n_seeds : int
15
+ Number of seeds to generate.
16
+ min_seed : int
17
+ Lower limit for the seed (inclusive).
18
+ max_seed : int
19
+ Upper limit for the seed (exclusive).
20
+ fixed_seed : int or None
21
+ If provided, use this fixed seed.
22
+
23
+ Returns
24
+ -------
25
+ int or list of int:
26
+ A single seed if n_seeds is 1, otherwise a list of seeds.
27
+ """
28
+ entropy = fixed_seed if fixed_seed is not None else secrets.randbits(128)
29
+ ss = np.random.SeedSequence(entropy)
30
+ rng = np.random.default_rng(ss)
31
+
32
+ seed_list = rng.integers(low=min_seed, high=max_seed, size=n_seeds)
33
+
34
+ if n_seeds == 1:
35
+ return int(seed_list[0])
36
+ return [int(x) for x in seed_list]
@@ -1,14 +1,13 @@
1
1
  """Helper functions for legend handlers used for plotting."""
2
2
 
3
+ # pylint: disable=too-few-public-methods
4
+
3
5
  import matplotlib.colors as mcolors
4
6
  import matplotlib.patches as mpatches
5
7
  import numpy as np
6
8
 
7
- """
8
- Define properties of different telescope types for visualization purposes.
9
-
10
- Radii are relative to a reference radius (REFERENCE_RADIUS).
11
- """
9
+ # Define properties of different telescope types for visualization purposes.
10
+ # Radii are relative to a reference radius (REFERENCE_RADIUS).
12
11
  TELESCOPE_CONFIG = {
13
12
  "LST": {"color": "darkorange", "radius": 12.5, "shape": "circle", "filled": False},
14
13
  "MST": {"color": "dodgerblue", "radius": 9.15, "shape": "circle", "filled": False},
@@ -51,7 +50,7 @@ def get_telescope_config(telescope_type):
51
50
  config = TELESCOPE_CONFIG.get(telescope_type)
52
51
  if not config and len(telescope_type) >= 3:
53
52
  config = TELESCOPE_CONFIG.get(telescope_type[:3])
54
- return config
53
+ return config.copy() if config else None
55
54
 
56
55
 
57
56
  def calculate_center(handlebox, width_factor=3, height_factor=3):
@@ -272,6 +271,8 @@ class BaseLegendHandler:
272
271
  x0, y0 = calculate_center(handlebox)
273
272
  radius = handlebox.height
274
273
  patch = self._create_hexagon(handlebox, x0, y0, radius)
274
+ else:
275
+ raise ValueError(f"Unknown shape: {shape}")
275
276
 
276
277
  handlebox.add_artist(patch)
277
278
  return patch
@@ -5,15 +5,18 @@ from collections import Counter
5
5
  from typing import NamedTuple
6
6
 
7
7
  import astropy.units as u
8
+ import matplotlib as mpl
8
9
  import matplotlib.patches as mpatches
9
10
  import matplotlib.pyplot as plt
10
11
  import numpy as np
12
+ from adjustText import adjust_text
11
13
  from astropy.table import Column
12
14
  from matplotlib.collections import PatchCollection
13
15
 
14
16
  from simtools.utils import geometry as transf
15
17
  from simtools.utils import names
16
18
  from simtools.visualization import legend_handlers as leg_h
19
+ from simtools.visualization import visualize
17
20
 
18
21
 
19
22
  class PlotBounds(NamedTuple):
@@ -31,6 +34,61 @@ class PlotBounds(NamedTuple):
31
34
  y_lim: tuple[float, float]
32
35
 
33
36
 
37
+ def plot_array_layouts(args_dict, output_path, layouts, background_layout=None):
38
+ """
39
+ Plot multiple array layouts.
40
+
41
+ Parameters
42
+ ----------
43
+ args_dict : dict
44
+ Application arguments.
45
+ output_path : Path
46
+ Output path for figures.
47
+ layouts : dict
48
+ Dictionary of layout name to telescope table.
49
+ background_layout : Table or None
50
+ Optional background telescope table.
51
+
52
+ Returns
53
+ -------
54
+ figs : dict
55
+ Dictionary of layout name to matplotlib figure object.
56
+
57
+ """
58
+ mpl.use("Agg")
59
+ for layout in layouts:
60
+ fig_out = plot_array_layout(
61
+ telescopes=layout["array_elements"],
62
+ show_tel_label=args_dict["show_labels"],
63
+ axes_range=args_dict["axes_range"],
64
+ marker_scaling=args_dict["marker_scaling"],
65
+ background_telescopes=background_layout,
66
+ grayed_out_elements=args_dict["grayed_out_array_elements"],
67
+ highlighted_elements=args_dict["highlighted_array_elements"],
68
+ legend_location=args_dict["legend_location"],
69
+ bounds_mode=args_dict["bounds"],
70
+ padding=args_dict["padding"],
71
+ x_lim=tuple(args_dict["x_lim"]) if args_dict["x_lim"] else None,
72
+ y_lim=tuple(args_dict["y_lim"]) if args_dict["y_lim"] else None,
73
+ )
74
+ site_string = ""
75
+ if layout.get("site") is not None:
76
+ site_string = f"_{layout['site']}"
77
+ elif args_dict["site"] is not None:
78
+ site_string = f"_{args_dict['site']}"
79
+ coordinate_system_string = (
80
+ f"_{args_dict['coordinate_system']}"
81
+ if args_dict["coordinate_system"] not in layout["name"]
82
+ else ""
83
+ )
84
+ plot_file_name = args_dict["figure_name"] or (
85
+ f"array_layout_{layout['name']}{site_string}{coordinate_system_string}"
86
+ )
87
+
88
+ visualize.save_figure(fig_out, output_path / plot_file_name, dpi=400)
89
+ plt.close()
90
+
91
+
34
92
  def plot_array_layout(
35
93
  telescopes,
36
94
  show_tel_label=False,
@@ -89,7 +147,7 @@ def plot_array_layout(
89
147
  filter_x = x_lim
90
148
  filter_y = y_lim
91
149
 
92
- patches, plot_range, highlighted_patches, bounds = get_patches(
150
+ patches, plot_range, highlighted_patches, bounds, text_objects = get_patches(
93
151
  ax,
94
152
  telescopes,
95
153
  show_tel_label,
@@ -122,6 +180,17 @@ def plot_array_layout(
122
180
 
123
181
  finalize_plot(ax, patches, "Easting [m]", "Northing [m]", x_lim, y_lim, highlighted_patches)
124
182
 
183
+ if text_objects:
184
+ adjust_text(
185
+ text_objects,
186
+ ax=ax,
187
+ arrowprops={"arrowstyle": "->", "color": "grey", "alpha": 0.8, "lw": 0.8, "ls": "--"},
188
+ expand=(2.0, 2.0),
189
+ prevent_crossings=True,
190
+ min_arrow_len=8,
191
+ ensure_inside_axes=True,
192
+ )
193
+
125
194
  return fig
126
195
 
127
196
 
@@ -178,7 +247,7 @@ def _get_patches_for_background_telescopes(
178
247
  if background_telescopes is None:
179
248
  return plot_range, bounds
180
249
 
181
- bg_patches, bg_range, _, bg_bounds = get_patches(
250
+ bg_patches, bg_range, _, bg_bounds, _ = get_patches(
182
251
  ax,
183
252
  background_telescopes,
184
253
  False,
@@ -268,6 +337,8 @@ def get_patches(
268
337
  List of highlighted telescope patches.
269
338
  bounds : PlotBounds
270
339
  Min/max for x and y in meters.
340
+ text_objects : list
341
+ List of text objects for labels.
271
342
  """
272
343
  pos_x, pos_y = get_positions(telescopes)
273
344
  tel_table, pos_x, pos_y = _apply_limits_filter(
@@ -277,7 +348,7 @@ def get_patches(
277
348
  tel_table["pos_x_rotated"] = Column(pos_x)
278
349
  tel_table["pos_y_rotated"] = Column(pos_y)
279
350
 
280
- patches, radii, highlighted_patches = create_patches(
351
+ patches, radii, highlighted_patches, text_objects = create_patches(
281
352
  tel_table, marker_scaling, show_tel_label, ax, grayed_out_elements, highlighted_elements
282
353
  )
283
354
 
@@ -290,8 +361,8 @@ def get_patches(
290
361
  if len(pos_x) == 0:
291
362
  bounds = PlotBounds(x_lim=(0.0, 0.0), y_lim=(0.0, 0.0))
292
363
  if axes_range:
293
- return patches, axes_range, highlighted_patches, bounds
294
- return patches, 0.0, highlighted_patches, bounds
364
+ return patches, axes_range, highlighted_patches, bounds, text_objects
365
+ return patches, 0.0, highlighted_patches, bounds, text_objects
295
366
 
296
367
  x_min = float(np.nanmin(pos_x).to_value(u.m)) - r
297
368
  x_max = float(np.nanmax(pos_x).to_value(u.m)) + r
@@ -300,13 +371,13 @@ def get_patches(
300
371
  bounds = PlotBounds(x_lim=(x_min, x_max), y_lim=(y_min, y_max))
301
372
 
302
373
  if axes_range:
303
- return patches, axes_range, highlighted_patches, bounds
374
+ return patches, axes_range, highlighted_patches, bounds, text_objects
304
375
 
305
376
  max_x = max(abs(x_min), abs(x_max))
306
377
  max_y = max(abs(y_min), abs(y_max))
307
378
  updated_axes_range = max(max_x, max_y) * 1.1
308
379
 
309
- return patches, updated_axes_range, highlighted_patches, bounds
380
+ return patches, updated_axes_range, highlighted_patches, bounds, text_objects
310
381
 
311
382
 
312
383
  @u.quantity_input(x=u.m, y=u.m, radius=u.m)
@@ -417,8 +488,10 @@ def create_patches(
417
488
  Telescope radii.
418
489
  highlighted_patches : list
419
490
  List of highlighted telescope patches.
491
+ text_objects : list
492
+ List of text objects for labels.
420
493
  """
421
- patches, radii, highlighted_patches = [], [], []
494
+ patches, radii, highlighted_patches, text_objects = [], [], [], []
422
495
  fontsize, scale_factor = (4, 2) if len(telescopes) > 30 else (8, 1)
423
496
 
424
497
  grayed_out_set = set(grayed_out_elements) if grayed_out_elements else set()
@@ -457,16 +530,18 @@ def create_patches(
457
530
  highlighted_patches.append(highlight_patch)
458
531
 
459
532
  if show_label:
460
- ax.text(
461
- tel["pos_x_rotated"].value,
462
- tel["pos_y_rotated"].value + scale_factor * radius.value,
463
- name,
464
- ha="center",
465
- va="bottom",
466
- fontsize=fontsize * 0.8,
533
+ text_objects.append(
534
+ ax.text(
535
+ tel["pos_x_rotated"].value,
536
+ tel["pos_y_rotated"].value + scale_factor * radius.value,
537
+ name,
538
+ ha="center",
539
+ va="center",
540
+ fontsize=fontsize * 0.8,
541
+ )
467
542
  )
468
543
 
469
- return patches, radii, highlighted_patches
544
+ return patches, radii, highlighted_patches, text_objects
470
545
 
471
546
 
472
547
  def get_telescope_name(tel):