gammasimtools 0.24.0__py3-none-any.whl → 0.26.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.
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/METADATA +2 -1
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/RECORD +134 -130
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/entry_points.txt +3 -1
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/licenses/LICENSE +1 -1
- simtools/_version.py +2 -2
- simtools/application_control.py +78 -0
- simtools/applications/calculate_incident_angles.py +0 -2
- simtools/applications/convert_geo_coordinates_of_array_elements.py +1 -2
- simtools/applications/db_add_file_to_db.py +1 -1
- simtools/applications/db_add_simulation_model_from_repository_to_db.py +1 -1
- simtools/applications/db_add_value_from_json_to_db.py +1 -1
- simtools/applications/db_generate_compound_indexes.py +1 -1
- simtools/applications/db_get_array_layouts_from_db.py +2 -6
- simtools/applications/db_get_file_from_db.py +1 -1
- simtools/applications/db_get_parameter_from_db.py +1 -1
- simtools/applications/db_inspect_databases.py +1 -1
- simtools/applications/db_upload_model_repository.py +1 -1
- simtools/applications/derive_ctao_array_layouts.py +1 -2
- simtools/applications/derive_mirror_rnda.py +1 -3
- simtools/applications/derive_psf_parameters.py +5 -1
- simtools/applications/derive_pulse_shape_parameters.py +194 -0
- simtools/applications/derive_trigger_rates.py +1 -1
- simtools/applications/docs_produce_array_element_report.py +2 -8
- simtools/applications/docs_produce_calibration_reports.py +1 -3
- simtools/applications/docs_produce_model_parameter_reports.py +0 -2
- simtools/applications/docs_produce_simulation_configuration_report.py +1 -3
- simtools/applications/generate_array_config.py +0 -1
- simtools/applications/generate_corsika_histograms.py +48 -235
- simtools/applications/generate_regular_arrays.py +5 -35
- simtools/applications/generate_simtel_event_data.py +2 -2
- simtools/applications/maintain_simulation_model_add_production.py +2 -2
- simtools/applications/maintain_simulation_model_write_array_element_positions.py +87 -0
- simtools/applications/plot_array_layout.py +64 -108
- simtools/applications/plot_simulated_event_distributions.py +57 -0
- simtools/applications/plot_tabular_data.py +0 -1
- simtools/applications/plot_tabular_data_for_model_parameter.py +1 -6
- simtools/applications/production_derive_corsika_limits.py +1 -1
- simtools/applications/production_generate_grid.py +0 -1
- simtools/applications/run_application.py +1 -1
- simtools/applications/simulate_flasher.py +3 -4
- simtools/applications/simulate_illuminator.py +0 -1
- simtools/applications/simulate_pedestals.py +2 -6
- simtools/applications/simulate_prod.py +9 -28
- simtools/applications/simulate_prod_htcondor_generator.py +8 -1
- simtools/applications/submit_array_layouts.py +7 -7
- simtools/applications/submit_model_parameter_from_external.py +1 -3
- simtools/applications/validate_camera_efficiency.py +0 -1
- simtools/applications/validate_camera_fov.py +0 -1
- simtools/applications/validate_cumulative_psf.py +0 -2
- simtools/applications/validate_file_using_schema.py +49 -123
- simtools/applications/validate_optics.py +0 -13
- simtools/camera/camera_efficiency.py +1 -6
- simtools/camera/single_photon_electron_spectrum.py +2 -1
- simtools/configuration/commandline_parser.py +43 -8
- simtools/configuration/configurator.py +6 -11
- simtools/corsika/corsika_config.py +204 -99
- simtools/corsika/corsika_histograms.py +411 -1735
- simtools/corsika/primary_particle.py +1 -1
- simtools/data_model/metadata_collector.py +5 -2
- simtools/data_model/metadata_model.py +0 -4
- simtools/data_model/model_data_writer.py +27 -17
- simtools/data_model/schema.py +112 -5
- simtools/data_model/validate_data.py +80 -48
- simtools/db/db_handler.py +19 -8
- simtools/db/db_model_upload.py +2 -1
- simtools/db/mongo_db.py +133 -42
- simtools/dependencies.py +83 -44
- simtools/io/ascii_handler.py +4 -2
- simtools/io/table_handler.py +1 -1
- simtools/job_execution/htcondor_script_generator.py +0 -2
- simtools/layout/array_layout.py +4 -12
- simtools/layout/array_layout_utils.py +227 -58
- simtools/model/array_model.py +37 -18
- simtools/model/calibration_model.py +0 -4
- simtools/model/legacy_model_parameter.py +134 -0
- simtools/model/model_parameter.py +24 -14
- simtools/model/model_repository.py +18 -5
- simtools/model/model_utils.py +1 -6
- simtools/model/site_model.py +0 -4
- simtools/model/telescope_model.py +6 -11
- simtools/production_configuration/derive_corsika_limits.py +6 -11
- simtools/production_configuration/interpolation_handler.py +16 -16
- simtools/ray_tracing/incident_angles.py +5 -11
- simtools/ray_tracing/mirror_panel_psf.py +3 -7
- simtools/ray_tracing/psf_analysis.py +29 -27
- simtools/ray_tracing/psf_parameter_optimisation.py +822 -680
- simtools/ray_tracing/ray_tracing.py +6 -15
- simtools/reporting/docs_auto_report_generator.py +8 -13
- simtools/reporting/docs_read_parameters.py +70 -16
- simtools/runners/corsika_runner.py +15 -10
- simtools/runners/corsika_simtel_runner.py +9 -8
- simtools/runners/runner_services.py +17 -7
- simtools/runners/simtel_runner.py +11 -58
- simtools/runners/simtools_runner.py +2 -4
- simtools/schemas/model_parameters/flasher_pulse_exp_decay.schema.yml +2 -0
- simtools/schemas/model_parameters/flasher_pulse_shape.schema.yml +50 -0
- simtools/schemas/model_parameters/flasher_pulse_width.schema.yml +2 -0
- simtools/schemas/simulation_models_info.schema.yml +2 -0
- simtools/settings.py +154 -0
- simtools/sim_events/file_info.py +128 -0
- simtools/{simtel/simtel_io_event_histograms.py → sim_events/histograms.py} +25 -15
- simtools/{simtel/simtel_io_event_reader.py → sim_events/reader.py} +20 -17
- simtools/{simtel/simtel_io_event_writer.py → sim_events/writer.py} +84 -25
- simtools/simtel/pulse_shapes.py +273 -0
- simtools/simtel/simtel_config_writer.py +146 -22
- simtools/simtel/simtel_table_reader.py +6 -4
- simtools/simtel/simulator_array.py +62 -23
- simtools/simtel/simulator_camera_efficiency.py +4 -6
- simtools/simtel/simulator_light_emission.py +101 -19
- simtools/simtel/simulator_ray_tracing.py +4 -10
- simtools/simulator.py +360 -353
- simtools/telescope_trigger_rates.py +3 -4
- simtools/testing/assertions.py +115 -8
- simtools/testing/configuration.py +2 -3
- simtools/testing/helpers.py +2 -3
- simtools/testing/log_inspector.py +5 -1
- simtools/testing/sim_telarray_metadata.py +1 -1
- simtools/testing/validate_output.py +69 -23
- simtools/utils/general.py +37 -0
- simtools/utils/geometry.py +0 -77
- simtools/utils/names.py +7 -9
- simtools/version.py +37 -0
- simtools/visualization/legend_handlers.py +21 -10
- simtools/visualization/plot_array_layout.py +312 -41
- simtools/visualization/plot_corsika_histograms.py +143 -605
- simtools/visualization/plot_mirrors.py +834 -0
- simtools/visualization/plot_pixels.py +2 -4
- simtools/visualization/plot_psf.py +0 -1
- simtools/visualization/plot_simtel_event_histograms.py +4 -4
- simtools/visualization/plot_simtel_events.py +6 -11
- simtools/visualization/plot_tables.py +8 -19
- simtools/visualization/visualize.py +22 -2
- simtools/applications/db_development_tools/write_array_elements_positions_to_repository.py +0 -160
- simtools/applications/print_version.py +0 -53
- simtools/io/hdf5_handler.py +0 -139
- simtools/simtel/simtel_io_file_info.py +0 -62
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/WHEEL +0 -0
- {gammasimtools-0.24.0.dist-info → gammasimtools-0.26.0.dist-info}/top_level.txt +0 -0
|
@@ -1,22 +1,29 @@
|
|
|
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
|
-
|
|
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
|
-
"MST": {"color": "dodgerblue", "radius": 9.15, "shape": "circle", "filled":
|
|
15
|
-
"SCT": {"color": "black", "radius": 7.15, "shape": "square", "filled":
|
|
16
|
-
"SST": {"color": "darkgreen", "radius": 3.0, "shape": "circle", "filled":
|
|
13
|
+
"MST": {"color": "dodgerblue", "radius": 9.15, "shape": "circle", "filled": False},
|
|
14
|
+
"SCT": {"color": "black", "radius": 7.15, "shape": "square", "filled": False},
|
|
15
|
+
"SST": {"color": "darkgreen", "radius": 3.0, "shape": "circle", "filled": False},
|
|
17
16
|
"HESS": {"color": "grey", "radius": 6.0, "shape": "hexagon", "filled": True},
|
|
18
17
|
"MAGIC": {"color": "grey", "radius": 8.5, "shape": "hexagon", "filled": True},
|
|
19
18
|
"VERITAS": {"color": "grey", "radius": 6.0, "shape": "hexagon", "filled": True},
|
|
19
|
+
"CEI": {"color": "purple", "radius": 2.0, "shape": "hexagon", "filled": True},
|
|
20
|
+
"RLD": {"color": "brown", "radius": 2.0, "shape": "hexagon", "filled": True},
|
|
21
|
+
"STP": {"color": "olive", "radius": 2.0, "shape": "hexagon", "filled": True},
|
|
22
|
+
"MSP": {"color": "teal", "radius": 2.0, "shape": "hexagon", "filled": True},
|
|
23
|
+
"ILL": {"color": "red", "radius": 2.0, "shape": "hexagon", "filled": False},
|
|
24
|
+
"WST": {"color": "maroon", "radius": 2.0, "shape": "hexagon", "filled": True},
|
|
25
|
+
"ASC": {"color": "cyan", "radius": 2.0, "shape": "hexagon", "filled": True},
|
|
26
|
+
"DUS": {"color": "magenta", "radius": 2.0, "shape": "hexagon", "filled": True},
|
|
20
27
|
}
|
|
21
28
|
|
|
22
29
|
REFERENCE_RADIUS = 12.5
|
|
@@ -30,7 +37,7 @@ def get_telescope_config(telescope_type):
|
|
|
30
37
|
|
|
31
38
|
Parameters
|
|
32
39
|
----------
|
|
33
|
-
telescope_type : str
|
|
40
|
+
telescope_type : str, None
|
|
34
41
|
The type of the telescope (e.g., "LSTN", "MSTS").
|
|
35
42
|
|
|
36
43
|
Returns
|
|
@@ -38,10 +45,12 @@ def get_telescope_config(telescope_type):
|
|
|
38
45
|
dict
|
|
39
46
|
The configuration dictionary for the telescope type.
|
|
40
47
|
"""
|
|
48
|
+
if telescope_type is None:
|
|
49
|
+
return {"color": "blue", "radius": 2.0, "shape": "hexagon", "filled": True}
|
|
41
50
|
config = TELESCOPE_CONFIG.get(telescope_type)
|
|
42
51
|
if not config and len(telescope_type) >= 3:
|
|
43
52
|
config = TELESCOPE_CONFIG.get(telescope_type[:3])
|
|
44
|
-
return config
|
|
53
|
+
return config.copy() if config else None
|
|
45
54
|
|
|
46
55
|
|
|
47
56
|
def calculate_center(handlebox, width_factor=3, height_factor=3):
|
|
@@ -262,6 +271,8 @@ class BaseLegendHandler:
|
|
|
262
271
|
x0, y0 = calculate_center(handlebox)
|
|
263
272
|
radius = handlebox.height
|
|
264
273
|
patch = self._create_hexagon(handlebox, x0, y0, radius)
|
|
274
|
+
else:
|
|
275
|
+
raise ValueError(f"Unknown shape: {shape}")
|
|
265
276
|
|
|
266
277
|
handlebox.add_artist(patch)
|
|
267
278
|
return patch
|
|
@@ -2,16 +2,91 @@
|
|
|
2
2
|
"""Plot array elements for a layout."""
|
|
3
3
|
|
|
4
4
|
from collections import Counter
|
|
5
|
+
from typing import NamedTuple
|
|
5
6
|
|
|
6
7
|
import astropy.units as u
|
|
8
|
+
import matplotlib as mpl
|
|
7
9
|
import matplotlib.patches as mpatches
|
|
8
10
|
import matplotlib.pyplot as plt
|
|
11
|
+
import numpy as np
|
|
12
|
+
from adjustText import adjust_text
|
|
9
13
|
from astropy.table import Column
|
|
10
14
|
from matplotlib.collections import PatchCollection
|
|
11
15
|
|
|
12
16
|
from simtools.utils import geometry as transf
|
|
13
17
|
from simtools.utils import names
|
|
14
18
|
from simtools.visualization import legend_handlers as leg_h
|
|
19
|
+
from simtools.visualization import visualize
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PlotBounds(NamedTuple):
|
|
23
|
+
"""Axis-aligned bounds for the layout in meters.
|
|
24
|
+
|
|
25
|
+
Attributes
|
|
26
|
+
----------
|
|
27
|
+
x_lim : tuple[float, float]
|
|
28
|
+
Min/max for x (meters).
|
|
29
|
+
y_lim : tuple[float, float]
|
|
30
|
+
Min/max for y (meters).
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
x_lim: tuple[float, float]
|
|
34
|
+
y_lim: tuple[float, float]
|
|
35
|
+
|
|
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()
|
|
15
90
|
|
|
16
91
|
|
|
17
92
|
def plot_array_layout(
|
|
@@ -23,6 +98,10 @@ def plot_array_layout(
|
|
|
23
98
|
grayed_out_elements=None,
|
|
24
99
|
highlighted_elements=None,
|
|
25
100
|
legend_location="best",
|
|
101
|
+
bounds_mode="exact",
|
|
102
|
+
padding=0.1,
|
|
103
|
+
x_lim=None,
|
|
104
|
+
y_lim=None,
|
|
26
105
|
):
|
|
27
106
|
"""
|
|
28
107
|
Plot telescope array layout.
|
|
@@ -50,10 +129,25 @@ def plot_array_layout(
|
|
|
50
129
|
-------
|
|
51
130
|
fig : Figure
|
|
52
131
|
Matplotlib figure object.
|
|
132
|
+
|
|
133
|
+
Other Parameters
|
|
134
|
+
----------------
|
|
135
|
+
bounds_mode : {"symmetric", "exact"}
|
|
136
|
+
Controls axis limits calculation. "symmetric" uses +-R where R is the padded
|
|
137
|
+
maximum extent (default), while "exact" uses individual x/y min/max bounds.
|
|
138
|
+
padding : float
|
|
139
|
+
Fractional padding applied around computed extents (used for both modes).
|
|
140
|
+
x_lim, y_lim : tuple(float, float), optional
|
|
141
|
+
Explicit axis limits in meters. If provided, these override axes_range and bounds_mode
|
|
142
|
+
for the respective axis. If only one is provided, the other axis is derived per mode.
|
|
53
143
|
"""
|
|
54
144
|
fig, ax = plt.subplots(1)
|
|
55
145
|
|
|
56
|
-
|
|
146
|
+
# If explicit limits are provided (one or both), filter patches accordingly
|
|
147
|
+
filter_x = x_lim
|
|
148
|
+
filter_y = y_lim
|
|
149
|
+
|
|
150
|
+
patches, plot_range, highlighted_patches, bounds, text_objects = get_patches(
|
|
57
151
|
ax,
|
|
58
152
|
telescopes,
|
|
59
153
|
show_tel_label,
|
|
@@ -61,20 +155,145 @@ def plot_array_layout(
|
|
|
61
155
|
marker_scaling,
|
|
62
156
|
grayed_out_elements,
|
|
63
157
|
highlighted_elements,
|
|
158
|
+
filter_x_lim=filter_x,
|
|
159
|
+
filter_y_lim=filter_y,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
plot_range, bounds = _get_patches_for_background_telescopes(
|
|
163
|
+
ax,
|
|
164
|
+
background_telescopes,
|
|
165
|
+
axes_range,
|
|
166
|
+
marker_scaling,
|
|
167
|
+
bounds_mode,
|
|
168
|
+
plot_range,
|
|
169
|
+
bounds,
|
|
170
|
+
filter_x_lim=filter_x,
|
|
171
|
+
filter_y_lim=filter_y,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
if legend_location != "no_legend":
|
|
175
|
+
update_legend(ax, telescopes, grayed_out_elements, legend_location)
|
|
176
|
+
|
|
177
|
+
x_lim, y_lim = _get_axis_limits(
|
|
178
|
+
axes_range, bounds_mode, padding, plot_range, bounds, x_lim, y_lim
|
|
64
179
|
)
|
|
65
180
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
181
|
+
finalize_plot(ax, patches, "Easting [m]", "Northing [m]", x_lim, y_lim, highlighted_patches)
|
|
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,
|
|
69
192
|
)
|
|
70
|
-
|
|
71
|
-
|
|
193
|
+
|
|
194
|
+
return fig
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _get_axis_limits(
|
|
198
|
+
axes_range,
|
|
199
|
+
bounds_mode,
|
|
200
|
+
padding,
|
|
201
|
+
plot_range,
|
|
202
|
+
bounds,
|
|
203
|
+
x_lim_override=None,
|
|
204
|
+
y_lim_override=None,
|
|
205
|
+
):
|
|
206
|
+
"""Get axis limits based on mode and padding."""
|
|
207
|
+
|
|
208
|
+
def _derive_axis(axis: str) -> tuple[float, float]:
|
|
209
|
+
if bounds_mode == "exact":
|
|
210
|
+
if axis == "x":
|
|
211
|
+
span = bounds.x_lim[1] - bounds.x_lim[0]
|
|
212
|
+
pad = padding * span
|
|
213
|
+
return (bounds.x_lim[0] - pad, bounds.x_lim[1] + pad)
|
|
214
|
+
span = bounds.y_lim[1] - bounds.y_lim[0]
|
|
215
|
+
pad = padding * span
|
|
216
|
+
return (bounds.y_lim[0] - pad, bounds.y_lim[1] + pad)
|
|
217
|
+
# symmetric
|
|
218
|
+
sym = plot_range
|
|
219
|
+
padf = max(0.0, min(1.0, float(padding))) if padding is not None else 0.0
|
|
220
|
+
sym *= 1.0 + padf
|
|
221
|
+
return (-sym, sym)
|
|
222
|
+
|
|
223
|
+
# Highest priority: explicit overrides (per axis)
|
|
224
|
+
if x_lim_override is not None or y_lim_override is not None:
|
|
225
|
+
x_lim = x_lim_override if x_lim_override is not None else _derive_axis("x")
|
|
226
|
+
y_lim = y_lim_override if y_lim_override is not None else _derive_axis("y")
|
|
227
|
+
return x_lim, y_lim
|
|
228
|
+
|
|
229
|
+
if axes_range is not None:
|
|
230
|
+
return (-axes_range, axes_range), (-axes_range, axes_range)
|
|
231
|
+
# Derive both axes using selected mode
|
|
232
|
+
return _derive_axis("x"), _derive_axis("y")
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def _get_patches_for_background_telescopes(
|
|
236
|
+
ax,
|
|
237
|
+
background_telescopes,
|
|
238
|
+
axes_range,
|
|
239
|
+
marker_scaling,
|
|
240
|
+
bounds_mode,
|
|
241
|
+
plot_range,
|
|
242
|
+
bounds,
|
|
243
|
+
filter_x_lim=None,
|
|
244
|
+
filter_y_lim=None,
|
|
245
|
+
):
|
|
246
|
+
"""Get background telescope patches and update plot range/bounds."""
|
|
247
|
+
if background_telescopes is None:
|
|
248
|
+
return plot_range, bounds
|
|
249
|
+
|
|
250
|
+
bg_patches, bg_range, _, bg_bounds, _ = get_patches(
|
|
251
|
+
ax,
|
|
252
|
+
background_telescopes,
|
|
253
|
+
False,
|
|
254
|
+
axes_range,
|
|
255
|
+
marker_scaling,
|
|
256
|
+
None,
|
|
257
|
+
None,
|
|
258
|
+
filter_x_lim=filter_x_lim,
|
|
259
|
+
filter_y_lim=filter_y_lim,
|
|
260
|
+
)
|
|
261
|
+
ax.add_collection(PatchCollection(bg_patches, match_original=True, alpha=0.1))
|
|
262
|
+
if axes_range is None:
|
|
263
|
+
if bounds_mode == "symmetric":
|
|
72
264
|
plot_range = max(plot_range, bg_range)
|
|
265
|
+
else:
|
|
266
|
+
bounds = PlotBounds(
|
|
267
|
+
x_lim=(
|
|
268
|
+
min(bounds.x_lim[0], bg_bounds.x_lim[0]),
|
|
269
|
+
max(bounds.x_lim[1], bg_bounds.x_lim[1]),
|
|
270
|
+
),
|
|
271
|
+
y_lim=(
|
|
272
|
+
min(bounds.y_lim[0], bg_bounds.y_lim[0]),
|
|
273
|
+
max(bounds.y_lim[1], bg_bounds.y_lim[1]),
|
|
274
|
+
),
|
|
275
|
+
)
|
|
276
|
+
return plot_range, bounds
|
|
73
277
|
|
|
74
|
-
update_legend(ax, telescopes, grayed_out_elements, legend_location)
|
|
75
|
-
finalize_plot(ax, patches, "Easting [m]", "Northing [m]", plot_range, highlighted_patches)
|
|
76
278
|
|
|
77
|
-
|
|
279
|
+
def _apply_limits_filter(telescopes, pos_x, pos_y, filter_x_lim, filter_y_lim):
|
|
280
|
+
"""Filter telescope table and positions by optional axis limits."""
|
|
281
|
+
if filter_x_lim is None and filter_y_lim is None:
|
|
282
|
+
return telescopes, pos_x, pos_y
|
|
283
|
+
|
|
284
|
+
px = np.asarray(pos_x.to_value(u.m))
|
|
285
|
+
py = np.asarray(pos_y.to_value(u.m))
|
|
286
|
+
mask = np.ones(px.shape, dtype=bool)
|
|
287
|
+
if filter_x_lim is not None:
|
|
288
|
+
mask &= (px >= float(filter_x_lim[0])) & (px <= float(filter_x_lim[1]))
|
|
289
|
+
if filter_y_lim is not None:
|
|
290
|
+
mask &= (py >= float(filter_y_lim[0])) & (py <= float(filter_y_lim[1]))
|
|
291
|
+
|
|
292
|
+
if mask.size and mask.any():
|
|
293
|
+
return telescopes[mask], pos_x[mask], pos_y[mask]
|
|
294
|
+
|
|
295
|
+
# No telescopes within limits
|
|
296
|
+
return telescopes[:0], pos_x[:0], pos_y[:0]
|
|
78
297
|
|
|
79
298
|
|
|
80
299
|
def get_patches(
|
|
@@ -85,6 +304,8 @@ def get_patches(
|
|
|
85
304
|
marker_scaling,
|
|
86
305
|
grayed_out_elements=None,
|
|
87
306
|
highlighted_elements=None,
|
|
307
|
+
filter_x_lim=None,
|
|
308
|
+
filter_y_lim=None,
|
|
88
309
|
):
|
|
89
310
|
"""
|
|
90
311
|
Get plot patches and axis range.
|
|
@@ -111,38 +332,63 @@ def get_patches(
|
|
|
111
332
|
patches : list
|
|
112
333
|
List of telescope patches.
|
|
113
334
|
axes_range : float
|
|
114
|
-
Calculated or input axis range.
|
|
335
|
+
Calculated or input symmetric axis range (meters).
|
|
115
336
|
highlighted_patches : list
|
|
116
337
|
List of highlighted telescope patches.
|
|
338
|
+
bounds : PlotBounds
|
|
339
|
+
Min/max for x and y in meters.
|
|
340
|
+
text_objects : list
|
|
341
|
+
List of text objects for labels.
|
|
117
342
|
"""
|
|
118
343
|
pos_x, pos_y = get_positions(telescopes)
|
|
119
|
-
|
|
120
|
-
|
|
344
|
+
tel_table, pos_x, pos_y = _apply_limits_filter(
|
|
345
|
+
telescopes, pos_x, pos_y, filter_x_lim, filter_y_lim
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
tel_table["pos_x_rotated"] = Column(pos_x)
|
|
349
|
+
tel_table["pos_y_rotated"] = Column(pos_y)
|
|
121
350
|
|
|
122
|
-
patches, radii, highlighted_patches = create_patches(
|
|
123
|
-
|
|
351
|
+
patches, radii, highlighted_patches, text_objects = create_patches(
|
|
352
|
+
tel_table, marker_scaling, show_tel_label, ax, grayed_out_elements, highlighted_elements
|
|
124
353
|
)
|
|
125
354
|
|
|
355
|
+
if len(radii) == 0:
|
|
356
|
+
r = 0.0
|
|
357
|
+
else:
|
|
358
|
+
radii_q = u.Quantity(radii)
|
|
359
|
+
r = float(np.nanmax(radii_q).to_value(u.m))
|
|
360
|
+
|
|
361
|
+
if len(pos_x) == 0:
|
|
362
|
+
bounds = PlotBounds(x_lim=(0.0, 0.0), y_lim=(0.0, 0.0))
|
|
363
|
+
if axes_range:
|
|
364
|
+
return patches, axes_range, highlighted_patches, bounds, text_objects
|
|
365
|
+
return patches, 0.0, highlighted_patches, bounds, text_objects
|
|
366
|
+
|
|
367
|
+
x_min = float(np.nanmin(pos_x).to_value(u.m)) - r
|
|
368
|
+
x_max = float(np.nanmax(pos_x).to_value(u.m)) + r
|
|
369
|
+
y_min = float(np.nanmin(pos_y).to_value(u.m)) - r
|
|
370
|
+
y_max = float(np.nanmax(pos_y).to_value(u.m)) + r
|
|
371
|
+
bounds = PlotBounds(x_lim=(x_min, x_max), y_lim=(y_min, y_max))
|
|
372
|
+
|
|
126
373
|
if axes_range:
|
|
127
|
-
return patches, axes_range, highlighted_patches
|
|
374
|
+
return patches, axes_range, highlighted_patches, bounds, text_objects
|
|
128
375
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
max_y = max(abs(pos_y.min().value), abs(pos_y.max().value)) + r
|
|
376
|
+
max_x = max(abs(x_min), abs(x_max))
|
|
377
|
+
max_y = max(abs(y_min), abs(y_max))
|
|
132
378
|
updated_axes_range = max(max_x, max_y) * 1.1
|
|
133
379
|
|
|
134
|
-
return patches, updated_axes_range, highlighted_patches
|
|
380
|
+
return patches, updated_axes_range, highlighted_patches, bounds, text_objects
|
|
135
381
|
|
|
136
382
|
|
|
137
383
|
@u.quantity_input(x=u.m, y=u.m, radius=u.m)
|
|
138
|
-
def get_telescope_patch(
|
|
384
|
+
def get_telescope_patch(tel_type, x, y, radius, is_grayed_out=False):
|
|
139
385
|
"""
|
|
140
386
|
Create patch for a telescope.
|
|
141
387
|
|
|
142
388
|
Parameters
|
|
143
389
|
----------
|
|
144
|
-
|
|
145
|
-
Telescope
|
|
390
|
+
tel_type: str
|
|
391
|
+
Telescope type.
|
|
146
392
|
x : Quantity
|
|
147
393
|
X position.
|
|
148
394
|
y : Quantity
|
|
@@ -157,24 +403,34 @@ def get_telescope_patch(name, x, y, radius, is_grayed_out=False):
|
|
|
157
403
|
patch : Patch
|
|
158
404
|
Circle or rectangle patch.
|
|
159
405
|
"""
|
|
160
|
-
|
|
406
|
+
config = leg_h.get_telescope_config(tel_type)
|
|
161
407
|
x, y, r = x.to(u.m), y.to(u.m), radius.to(u.m)
|
|
162
408
|
|
|
163
|
-
color = "gray" if is_grayed_out else
|
|
409
|
+
color = "gray" if is_grayed_out else config["color"]
|
|
410
|
+
fill_flag = True if is_grayed_out else bool(config.get("filled", True))
|
|
164
411
|
|
|
165
|
-
if
|
|
412
|
+
if config.get("shape", "circle") == "square":
|
|
166
413
|
return mpatches.Rectangle(
|
|
167
414
|
((x - r / 2).value, (y - r / 2).value),
|
|
168
415
|
width=r.value,
|
|
169
416
|
height=r.value,
|
|
170
|
-
fill=
|
|
417
|
+
fill=fill_flag,
|
|
418
|
+
color=color,
|
|
419
|
+
)
|
|
420
|
+
if config.get("shape") == "hexagon":
|
|
421
|
+
return mpatches.RegularPolygon(
|
|
422
|
+
(x.value, y.value),
|
|
423
|
+
numVertices=6,
|
|
424
|
+
radius=r.value * np.sqrt(3) / 2,
|
|
425
|
+
orientation=np.pi / 6,
|
|
426
|
+
fill=fill_flag,
|
|
171
427
|
color=color,
|
|
172
428
|
)
|
|
173
429
|
|
|
174
430
|
return mpatches.Circle(
|
|
175
431
|
(x.value, y.value),
|
|
176
432
|
radius=r.value,
|
|
177
|
-
fill=
|
|
433
|
+
fill=fill_flag,
|
|
178
434
|
alpha=0.5 if is_grayed_out else 1.0,
|
|
179
435
|
color=color,
|
|
180
436
|
)
|
|
@@ -232,8 +488,10 @@ def create_patches(
|
|
|
232
488
|
Telescope radii.
|
|
233
489
|
highlighted_patches : list
|
|
234
490
|
List of highlighted telescope patches.
|
|
491
|
+
text_objects : list
|
|
492
|
+
List of text objects for labels.
|
|
235
493
|
"""
|
|
236
|
-
patches, radii, highlighted_patches = [], [], []
|
|
494
|
+
patches, radii, highlighted_patches, text_objects = [], [], [], []
|
|
237
495
|
fontsize, scale_factor = (4, 2) if len(telescopes) > 30 else (8, 1)
|
|
238
496
|
|
|
239
497
|
grayed_out_set = set(grayed_out_elements) if grayed_out_elements else set()
|
|
@@ -243,7 +501,10 @@ def create_patches(
|
|
|
243
501
|
name = get_telescope_name(tel)
|
|
244
502
|
radius = get_sphere_radius(tel)
|
|
245
503
|
radii.append(radius)
|
|
246
|
-
|
|
504
|
+
try:
|
|
505
|
+
tel_type = names.get_array_element_type_from_name(name)
|
|
506
|
+
except ValueError:
|
|
507
|
+
tel_type = None
|
|
247
508
|
|
|
248
509
|
is_grayed_out = name in grayed_out_set
|
|
249
510
|
is_highlighted = name in highlighted_set
|
|
@@ -269,16 +530,18 @@ def create_patches(
|
|
|
269
530
|
highlighted_patches.append(highlight_patch)
|
|
270
531
|
|
|
271
532
|
if show_label:
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
+
)
|
|
279
542
|
)
|
|
280
543
|
|
|
281
|
-
return patches, radii, highlighted_patches
|
|
544
|
+
return patches, radii, highlighted_patches, text_objects
|
|
282
545
|
|
|
283
546
|
|
|
284
547
|
def get_telescope_name(tel):
|
|
@@ -344,7 +607,15 @@ def update_legend(ax, telescopes, grayed_out_elements=None, legend_location="bes
|
|
|
344
607
|
ax.legend(objs, labels, handler_map=handler_map, prop={"size": 11}, loc=legend_location)
|
|
345
608
|
|
|
346
609
|
|
|
347
|
-
def finalize_plot(
|
|
610
|
+
def finalize_plot(
|
|
611
|
+
ax,
|
|
612
|
+
patches,
|
|
613
|
+
x_title,
|
|
614
|
+
y_title,
|
|
615
|
+
x_lim=None,
|
|
616
|
+
y_lim=None,
|
|
617
|
+
highlighted_patches=None,
|
|
618
|
+
):
|
|
348
619
|
"""Finalize plot appearance and limits."""
|
|
349
620
|
ax.add_collection(PatchCollection(patches, match_original=True))
|
|
350
621
|
|
|
@@ -354,7 +625,7 @@ def finalize_plot(ax, patches, x_title, y_title, axes_range, highlighted_patches
|
|
|
354
625
|
ax.set(xlabel=x_title, ylabel=y_title)
|
|
355
626
|
ax.tick_params(labelsize=8)
|
|
356
627
|
ax.axis("square")
|
|
357
|
-
if
|
|
358
|
-
ax.set_xlim(
|
|
359
|
-
ax.set_ylim(
|
|
628
|
+
if x_lim is not None and y_lim is not None:
|
|
629
|
+
ax.set_xlim(*x_lim)
|
|
630
|
+
ax.set_ylim(*y_lim)
|
|
360
631
|
plt.tight_layout()
|