gammasimtools 0.15.0__py3-none-any.whl → 0.17.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.15.0.dist-info → gammasimtools-0.17.0.dist-info}/METADATA +5 -33
- {gammasimtools-0.15.0.dist-info → gammasimtools-0.17.0.dist-info}/RECORD +243 -229
- {gammasimtools-0.15.0.dist-info → gammasimtools-0.17.0.dist-info}/WHEEL +1 -1
- {gammasimtools-0.15.0.dist-info → gammasimtools-0.17.0.dist-info}/entry_points.txt +8 -3
- simtools/_version.py +2 -2
- simtools/applications/calculate_trigger_rate.py +10 -10
- simtools/applications/convert_all_model_parameters_from_simtel.py +16 -16
- simtools/applications/convert_model_parameter_from_simtel.py +1 -1
- simtools/applications/derive_ctao_array_layouts.py +5 -5
- simtools/applications/derive_psf_parameters.py +12 -9
- simtools/applications/docs_produce_array_element_report.py +3 -3
- simtools/applications/docs_produce_calibration_reports.py +49 -0
- simtools/applications/docs_produce_simulation_configuration_report.py +50 -0
- simtools/applications/{generate_simtel_array_histograms.py → generate_sim_telarray_histograms.py} +2 -2
- simtools/applications/generate_simtel_event_data.py +36 -46
- simtools/applications/merge_tables.py +104 -0
- simtools/applications/plot_array_layout.py +145 -258
- simtools/applications/production_derive_corsika_limits.py +35 -167
- simtools/applications/production_derive_statistics.py +159 -0
- simtools/applications/production_generate_grid.py +197 -0
- simtools/applications/simulate_light_emission.py +6 -13
- simtools/applications/simulate_prod.py +45 -21
- simtools/applications/simulate_prod_htcondor_generator.py +0 -1
- simtools/applications/submit_array_layouts.py +93 -0
- simtools/applications/validate_cumulative_psf.py +6 -4
- simtools/applications/validate_file_using_schema.py +7 -3
- simtools/applications/validate_optics.py +5 -4
- simtools/applications/verify_simulation_model_production_tables.py +52 -0
- simtools/camera/camera_efficiency.py +17 -42
- simtools/configuration/commandline_parser.py +32 -37
- simtools/configuration/configurator.py +10 -4
- simtools/corsika/corsika_config.py +120 -17
- simtools/corsika/primary_particle.py +46 -13
- simtools/data_model/format_checkers.py +9 -0
- simtools/data_model/metadata_collector.py +7 -3
- simtools/data_model/model_data_writer.py +3 -0
- simtools/data_model/schema.py +27 -16
- simtools/data_model/validate_data.py +27 -7
- simtools/db/db_handler.py +21 -15
- simtools/db/db_model_upload.py +2 -2
- simtools/io_operations/io_handler.py +2 -2
- simtools/io_operations/io_table_handler.py +345 -0
- simtools/job_execution/htcondor_script_generator.py +2 -2
- simtools/job_execution/job_manager.py +7 -121
- simtools/layout/array_layout.py +1 -0
- simtools/layout/array_layout_utils.py +385 -0
- simtools/model/array_model.py +68 -29
- simtools/model/model_parameter.py +76 -51
- simtools/model/model_repository.py +134 -0
- simtools/model/model_utils.py +43 -1
- simtools/model/site_model.py +3 -2
- simtools/model/telescope_model.py +4 -4
- simtools/production_configuration/{calculate_statistical_errors_grid_point.py → calculate_statistical_uncertainties_grid_point.py} +101 -116
- simtools/production_configuration/derive_corsika_limits.py +239 -111
- simtools/production_configuration/derive_corsika_limits_grid.py +189 -0
- simtools/production_configuration/derive_production_statistics.py +155 -0
- simtools/production_configuration/derive_production_statistics_handler.py +152 -0
- simtools/production_configuration/generate_production_grid.py +364 -0
- simtools/production_configuration/interpolation_handler.py +303 -96
- simtools/ray_tracing/mirror_panel_psf.py +16 -20
- simtools/ray_tracing/psf_analysis.py +2 -2
- simtools/ray_tracing/ray_tracing.py +12 -7
- simtools/reporting/docs_read_parameters.py +426 -81
- simtools/runners/corsika_runner.py +11 -1
- simtools/runners/corsika_simtel_runner.py +84 -90
- simtools/runners/runner_services.py +22 -8
- simtools/runners/simtel_runner.py +27 -10
- simtools/schemas/model_parameter.metaschema.yml +4 -0
- simtools/schemas/model_parameter_and_data_schema.metaschema.yml +1 -0
- simtools/schemas/model_parameters/adjust_gain.schema.yml +2 -2
- simtools/schemas/model_parameters/array_element_position_ground.schema.yml +2 -2
- simtools/schemas/model_parameters/array_element_position_utm.schema.yml +2 -2
- simtools/schemas/model_parameters/array_window.schema.yml +2 -2
- simtools/schemas/model_parameters/asum_offset.schema.yml +2 -2
- simtools/schemas/model_parameters/asum_shaping.schema.yml +2 -2
- simtools/schemas/model_parameters/asum_threshold.schema.yml +2 -2
- simtools/schemas/model_parameters/axes_offsets.schema.yml +2 -2
- simtools/schemas/model_parameters/camera_body_diameter.schema.yml +2 -2
- simtools/schemas/model_parameters/camera_body_shape.schema.yml +2 -2
- simtools/schemas/model_parameters/camera_config_file.schema.yml +2 -2
- simtools/schemas/model_parameters/camera_config_rotate.schema.yml +2 -2
- simtools/schemas/model_parameters/camera_degraded_efficiency.schema.yml +2 -2
- simtools/schemas/model_parameters/camera_degraded_map.schema.yml +2 -2
- simtools/schemas/model_parameters/camera_depth.schema.yml +2 -2
- simtools/schemas/model_parameters/camera_filter.schema.yml +2 -2
- simtools/schemas/model_parameters/camera_pixels.schema.yml +2 -2
- simtools/schemas/model_parameters/camera_transmission.schema.yml +2 -2
- simtools/schemas/model_parameters/channels_per_chip.schema.yml +2 -2
- simtools/schemas/model_parameters/correct_nsb_spectrum_to_telescope_altitude.schema.yml +2 -2
- simtools/schemas/model_parameters/corsika_starting_grammage.schema.yml +90 -1
- simtools/schemas/model_parameters/default_trigger.schema.yml +2 -2
- simtools/schemas/model_parameters/design_model.schema.yml +2 -2
- simtools/schemas/model_parameters/disc_ac_coupled.schema.yml +2 -2
- simtools/schemas/model_parameters/disc_bins.schema.yml +2 -2
- simtools/schemas/model_parameters/disc_start.schema.yml +2 -2
- simtools/schemas/model_parameters/discriminator_amplitude.schema.yml +2 -2
- simtools/schemas/model_parameters/discriminator_fall_time.schema.yml +2 -2
- simtools/schemas/model_parameters/discriminator_gate_length.schema.yml +2 -2
- simtools/schemas/model_parameters/discriminator_hysteresis.schema.yml +2 -2
- simtools/schemas/model_parameters/discriminator_output_amplitude.schema.yml +2 -2
- simtools/schemas/model_parameters/discriminator_output_var_percent.schema.yml +2 -2
- simtools/schemas/model_parameters/discriminator_pulse_shape.schema.yml +2 -2
- simtools/schemas/model_parameters/discriminator_rise_time.schema.yml +2 -2
- simtools/schemas/model_parameters/discriminator_scale_threshold.schema.yml +2 -2
- simtools/schemas/model_parameters/discriminator_sigsum_over_threshold.schema.yml +2 -2
- simtools/schemas/model_parameters/discriminator_threshold.schema.yml +2 -2
- simtools/schemas/model_parameters/discriminator_time_over_threshold.schema.yml +2 -2
- simtools/schemas/model_parameters/discriminator_var_gate_length.schema.yml +2 -2
- simtools/schemas/model_parameters/discriminator_var_sigsum_over_threshold.schema.yml +2 -2
- simtools/schemas/model_parameters/discriminator_var_threshold.schema.yml +2 -2
- simtools/schemas/model_parameters/discriminator_var_time_over_threshold.schema.yml +2 -2
- simtools/schemas/model_parameters/dish_shape_length.schema.yml +2 -2
- simtools/schemas/model_parameters/dsum_clipping.schema.yml +2 -2
- simtools/schemas/model_parameters/dsum_ignore_below.schema.yml +2 -2
- simtools/schemas/model_parameters/dsum_offset.schema.yml +2 -2
- simtools/schemas/model_parameters/dsum_pedsub.schema.yml +2 -2
- simtools/schemas/model_parameters/dsum_pre_clipping.schema.yml +2 -2
- simtools/schemas/model_parameters/dsum_prescale.schema.yml +2 -2
- simtools/schemas/model_parameters/dsum_presum_max.schema.yml +2 -2
- simtools/schemas/model_parameters/dsum_presum_shift.schema.yml +2 -2
- simtools/schemas/model_parameters/dsum_shaping.schema.yml +2 -2
- simtools/schemas/model_parameters/dsum_shaping_renormalize.schema.yml +2 -2
- simtools/schemas/model_parameters/dsum_threshold.schema.yml +44 -3
- simtools/schemas/model_parameters/dsum_zero_clip.schema.yml +2 -2
- simtools/schemas/model_parameters/effective_focal_length.schema.yml +2 -2
- simtools/schemas/model_parameters/fadc_ac_coupled.schema.yml +2 -2
- simtools/schemas/model_parameters/fadc_amplitude.schema.yml +2 -2
- simtools/schemas/model_parameters/fadc_bins.schema.yml +2 -2
- simtools/schemas/model_parameters/fadc_compensate_pedestal.schema.yml +2 -2
- simtools/schemas/model_parameters/fadc_err_compensate_pedestal.schema.yml +2 -2
- simtools/schemas/model_parameters/fadc_err_pedestal.schema.yml +2 -2
- simtools/schemas/model_parameters/fadc_lg_amplitude.schema.yml +2 -2
- simtools/schemas/model_parameters/fadc_lg_compensate_pedestal.schema.yml +2 -2
- simtools/schemas/model_parameters/fadc_lg_err_compensate_pedestal.schema.yml +2 -2
- simtools/schemas/model_parameters/fadc_lg_err_pedestal.schema.yml +2 -2
- simtools/schemas/model_parameters/fadc_lg_max_signal.schema.yml +2 -2
- simtools/schemas/model_parameters/fadc_lg_noise.schema.yml +2 -2
- simtools/schemas/model_parameters/fadc_lg_pedestal.schema.yml +2 -2
- simtools/schemas/model_parameters/fadc_lg_sensitivity.schema.yml +2 -2
- simtools/schemas/model_parameters/fadc_lg_sysvar_pedestal.schema.yml +2 -2
- simtools/schemas/model_parameters/fadc_lg_var_pedestal.schema.yml +2 -2
- simtools/schemas/model_parameters/fadc_lg_var_sensitivity.schema.yml +2 -2
- simtools/schemas/model_parameters/fadc_max_signal.schema.yml +2 -2
- simtools/schemas/model_parameters/fadc_mhz.schema.yml +2 -2
- simtools/schemas/model_parameters/fadc_noise.schema.yml +2 -2
- simtools/schemas/model_parameters/fadc_pedestal.schema.yml +2 -2
- simtools/schemas/model_parameters/fadc_pulse_shape.schema.yml +2 -2
- simtools/schemas/model_parameters/fadc_sensitivity.schema.yml +2 -2
- simtools/schemas/model_parameters/fadc_sum_bins.schema.yml +2 -2
- simtools/schemas/model_parameters/fadc_sum_offset.schema.yml +2 -2
- simtools/schemas/model_parameters/fadc_sysvar_pedestal.schema.yml +2 -2
- simtools/schemas/model_parameters/fadc_var_pedestal.schema.yml +2 -2
- simtools/schemas/model_parameters/fadc_var_sensitivity.schema.yml +2 -2
- simtools/schemas/model_parameters/fake_mirror_list.schema.yml +1 -1
- simtools/schemas/model_parameters/flatfielding.schema.yml +2 -2
- simtools/schemas/model_parameters/focal_length.schema.yml +2 -2
- simtools/schemas/model_parameters/focus_offset.schema.yml +2 -2
- simtools/schemas/model_parameters/gain_variation.schema.yml +2 -2
- simtools/schemas/model_parameters/hg_lg_variation.schema.yml +2 -2
- simtools/schemas/model_parameters/iobuf_maximum.schema.yml +2 -2
- simtools/schemas/model_parameters/iobuf_output_maximum.schema.yml +2 -2
- simtools/schemas/model_parameters/laser_events.schema.yml +1 -1
- simtools/schemas/model_parameters/lightguide_efficiency_vs_incidence_angle.schema.yml +2 -2
- simtools/schemas/model_parameters/lightguide_efficiency_vs_wavelength.schema.yml +2 -2
- simtools/schemas/model_parameters/min_photoelectrons.schema.yml +2 -2
- simtools/schemas/model_parameters/min_photons.schema.yml +2 -2
- simtools/schemas/model_parameters/mirror_align_random_distance.schema.yml +2 -2
- simtools/schemas/model_parameters/mirror_align_random_horizontal.schema.yml +2 -2
- simtools/schemas/model_parameters/mirror_align_random_vertical.schema.yml +2 -2
- simtools/schemas/model_parameters/mirror_class.schema.yml +2 -2
- simtools/schemas/model_parameters/mirror_degraded_reflection.schema.yml +2 -2
- simtools/schemas/model_parameters/mirror_focal_length.schema.yml +2 -2
- simtools/schemas/model_parameters/mirror_list.schema.yml +2 -2
- simtools/schemas/model_parameters/mirror_offset.schema.yml +2 -2
- simtools/schemas/model_parameters/mirror_reflection_random_angle.schema.yml +2 -2
- simtools/schemas/model_parameters/mirror_reflectivity.schema.yml +2 -2
- simtools/schemas/model_parameters/multiplicity_offset.schema.yml +2 -2
- simtools/schemas/model_parameters/muon_mono_threshold.schema.yml +2 -2
- simtools/schemas/model_parameters/nsb_autoscale_airmass.schema.yml +2 -2
- simtools/schemas/model_parameters/nsb_offaxis.schema.yml +2 -2
- simtools/schemas/model_parameters/nsb_pixel_rate.schema.yml +2 -2
- simtools/schemas/model_parameters/num_gains.schema.yml +2 -2
- simtools/schemas/model_parameters/only_triggered_telescopes.schema.yml +2 -2
- simtools/schemas/model_parameters/optics_properties.schema.yml +2 -2
- simtools/schemas/model_parameters/pedestal_events.schema.yml +7 -3
- simtools/schemas/model_parameters/photon_delay.schema.yml +2 -2
- simtools/schemas/model_parameters/pixeltrg_time_step.schema.yml +2 -2
- simtools/schemas/model_parameters/pm_average_gain.schema.yml +2 -2
- simtools/schemas/model_parameters/pm_collection_efficiency.schema.yml +2 -2
- simtools/schemas/model_parameters/pm_gain_index.schema.yml +2 -2
- simtools/schemas/model_parameters/pm_photoelectron_spectrum.schema.yml +2 -2
- simtools/schemas/model_parameters/pm_transit_time.schema.yml +2 -2
- simtools/schemas/model_parameters/pm_voltage_variation.schema.yml +2 -2
- simtools/schemas/model_parameters/primary_mirror_degraded_map.schema.yml +2 -2
- simtools/schemas/model_parameters/qe_variation.schema.yml +2 -2
- simtools/schemas/model_parameters/quantum_efficiency.schema.yml +2 -2
- simtools/schemas/model_parameters/random_focal_length.schema.yml +2 -2
- simtools/schemas/model_parameters/random_generator.schema.yml +2 -2
- simtools/schemas/model_parameters/random_mono_probability.schema.yml +2 -2
- simtools/schemas/model_parameters/sampled_output.schema.yml +2 -2
- simtools/schemas/model_parameters/save_pe_with_amplitude.schema.yml +2 -2
- simtools/schemas/model_parameters/store_photoelectrons.schema.yml +2 -2
- simtools/schemas/model_parameters/tailcut_scale.schema.yml +2 -2
- simtools/schemas/model_parameters/telescope_axis_height.schema.yml +2 -2
- simtools/schemas/model_parameters/telescope_random_angle.schema.yml +2 -2
- simtools/schemas/model_parameters/telescope_random_error.schema.yml +2 -2
- simtools/schemas/model_parameters/telescope_sphere_radius.schema.yml +2 -2
- simtools/schemas/model_parameters/telescope_transmission.schema.yml +2 -2
- simtools/schemas/model_parameters/teltrig_min_sigsum.schema.yml +2 -2
- simtools/schemas/model_parameters/teltrig_min_time.schema.yml +2 -2
- simtools/schemas/model_parameters/transit_time_calib_error.schema.yml +2 -2
- simtools/schemas/model_parameters/transit_time_compensate_error.schema.yml +2 -2
- simtools/schemas/model_parameters/transit_time_compensate_step.schema.yml +2 -2
- simtools/schemas/model_parameters/transit_time_error.schema.yml +2 -2
- simtools/schemas/model_parameters/transit_time_jitter.schema.yml +2 -2
- simtools/schemas/model_parameters/trigger_current_limit.schema.yml +2 -2
- simtools/schemas/model_parameters/trigger_delay_compensation.schema.yml +2 -2
- simtools/schemas/model_parameters/trigger_pixels.schema.yml +2 -2
- simtools/schemas/production_configuration_metrics.schema.yml +2 -2
- simtools/simtel/simtel_config_reader.py +21 -17
- simtools/simtel/simtel_config_writer.py +258 -66
- simtools/simtel/simtel_io_event_reader.py +301 -194
- simtools/simtel/simtel_io_event_writer.py +207 -227
- simtools/simtel/simtel_io_file_info.py +62 -0
- simtools/simtel/simtel_io_histogram.py +10 -14
- simtools/simtel/simtel_io_histograms.py +2 -2
- simtools/simtel/simtel_io_metadata.py +106 -0
- simtools/simtel/simulator_array.py +28 -14
- simtools/simtel/simulator_camera_efficiency.py +12 -6
- simtools/simtel/simulator_light_emission.py +85 -45
- simtools/simtel/simulator_ray_tracing.py +16 -6
- simtools/simulator.py +286 -89
- simtools/testing/configuration.py +5 -0
- simtools/testing/helpers.py +18 -0
- simtools/testing/sim_telarray_metadata.py +212 -0
- simtools/testing/validate_output.py +16 -6
- simtools/utils/general.py +18 -27
- simtools/utils/names.py +32 -10
- simtools/visualization/plot_array_layout.py +242 -0
- simtools/visualization/plot_pixels.py +681 -0
- simtools/visualization/visualize.py +5 -221
- simtools/applications/production_generate_simulation_config.py +0 -162
- simtools/applications/production_scale_events.py +0 -185
- simtools/layout/ctao_array_layouts.py +0 -172
- simtools/production_configuration/event_scaler.py +0 -120
- simtools/production_configuration/generate_simulation_config.py +0 -158
- {gammasimtools-0.15.0.dist-info → gammasimtools-0.17.0.dist-info}/licenses/LICENSE +0 -0
- {gammasimtools-0.15.0.dist-info → gammasimtools-0.17.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,681 @@
|
|
|
1
|
+
#!/usr/bin/python3
|
|
2
|
+
"""Functions for plotting pixel layout information."""
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import astropy.units as u
|
|
8
|
+
import matplotlib.colors as mcolors
|
|
9
|
+
import matplotlib.patches as mpatches
|
|
10
|
+
import matplotlib.pyplot as plt
|
|
11
|
+
import numpy as np
|
|
12
|
+
from matplotlib.collections import PatchCollection
|
|
13
|
+
|
|
14
|
+
from simtools.db import db_handler
|
|
15
|
+
from simtools.io_operations import io_handler
|
|
16
|
+
from simtools.model.model_utils import is_two_mirror_telescope
|
|
17
|
+
from simtools.utils import names
|
|
18
|
+
from simtools.visualization import legend_handlers as leg_h
|
|
19
|
+
from simtools.visualization import visualize
|
|
20
|
+
|
|
21
|
+
logger = logging.getLogger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def plot(config, output_file, db_config=None):
|
|
25
|
+
"""
|
|
26
|
+
Plot pixel layout based on configuration.
|
|
27
|
+
|
|
28
|
+
Parameters
|
|
29
|
+
----------
|
|
30
|
+
config : dict
|
|
31
|
+
Configuration dictionary containing:
|
|
32
|
+
- file_name : str, name of camera config file
|
|
33
|
+
- column_x : str, x-axis label
|
|
34
|
+
- column_y : str, y-axis label
|
|
35
|
+
- parameter_version: str, version of the parameter
|
|
36
|
+
- telescope : str, name of the telescope
|
|
37
|
+
output_file : str
|
|
38
|
+
Path where to save the plot
|
|
39
|
+
db_config : dict, optional
|
|
40
|
+
Database configuration.
|
|
41
|
+
|
|
42
|
+
Returns
|
|
43
|
+
-------
|
|
44
|
+
None
|
|
45
|
+
The function saves the plot to the specified output file.
|
|
46
|
+
"""
|
|
47
|
+
db = db_handler.DatabaseHandler(mongo_db_config=db_config)
|
|
48
|
+
db.export_model_file(
|
|
49
|
+
parameter=config["parameter"],
|
|
50
|
+
site=config["site"],
|
|
51
|
+
array_element_name=config.get("telescope"),
|
|
52
|
+
parameter_version=config.get("parameter_version"),
|
|
53
|
+
model_version=config.get("model_version"),
|
|
54
|
+
export_file_as_table=False,
|
|
55
|
+
)
|
|
56
|
+
data_file_path = Path(io_handler.IOHandler().get_output_directory() / f"{config['file_name']}")
|
|
57
|
+
fig = plot_pixel_layout_from_file(
|
|
58
|
+
data_file_path,
|
|
59
|
+
config["telescope"],
|
|
60
|
+
pixels_id_to_print=80,
|
|
61
|
+
)
|
|
62
|
+
visualize.save_figure(fig, output_file)
|
|
63
|
+
plt.close(fig)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def plot_pixel_layout_from_file(dat_file_path, telescope_model_name, **kwargs):
|
|
67
|
+
"""
|
|
68
|
+
Plot the pixel layout from a camera config file.
|
|
69
|
+
|
|
70
|
+
This function reads the pixel configuration from the specified camera config file and
|
|
71
|
+
generates a plot of the pixel layout for the given telescope model.
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
dat_file_path : str or Path
|
|
76
|
+
Path to the camera config file containing pixel configuration
|
|
77
|
+
telescope_model_name : str
|
|
78
|
+
Name/model of the telescope
|
|
79
|
+
**kwargs
|
|
80
|
+
pixels_id_to_print : int
|
|
81
|
+
Number of pixel IDs to print in the plot
|
|
82
|
+
title : str
|
|
83
|
+
Plot title
|
|
84
|
+
xtitle : str
|
|
85
|
+
X-axis label
|
|
86
|
+
ytitle : str
|
|
87
|
+
Y-axis label
|
|
88
|
+
|
|
89
|
+
Returns
|
|
90
|
+
-------
|
|
91
|
+
matplotlib.figure.Figure
|
|
92
|
+
The generated figure
|
|
93
|
+
"""
|
|
94
|
+
logger.info(f"Plotting pixel layout for {telescope_model_name} from {dat_file_path}")
|
|
95
|
+
|
|
96
|
+
pixel_data = _prepare_pixel_data(
|
|
97
|
+
dat_file_path,
|
|
98
|
+
telescope_model_name,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
return _create_pixel_plot(
|
|
102
|
+
pixel_data,
|
|
103
|
+
telescope_model_name,
|
|
104
|
+
pixels_id_to_print=kwargs.get("pixels_id_to_print", 50),
|
|
105
|
+
title=kwargs.get("title"),
|
|
106
|
+
xtitle=kwargs.get("xtitle"),
|
|
107
|
+
ytitle=kwargs.get("ytitle"),
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _prepare_pixel_data(dat_file_path, telescope_model_name):
|
|
112
|
+
"""Prepare pixel data from sim_telarray camera configuration file.
|
|
113
|
+
|
|
114
|
+
This function reads the pixel configuration from the specified camera config file and
|
|
115
|
+
prepares the data for plotting, including applying any necessary rotations.
|
|
116
|
+
|
|
117
|
+
Parameters
|
|
118
|
+
----------
|
|
119
|
+
dat_file_path : str or Path
|
|
120
|
+
Path to the camera config file containing pixel configuration
|
|
121
|
+
telescope_model_name : str
|
|
122
|
+
Name/model of the telescope
|
|
123
|
+
|
|
124
|
+
Returns
|
|
125
|
+
-------
|
|
126
|
+
dict
|
|
127
|
+
Dictionary containing pixel data
|
|
128
|
+
"""
|
|
129
|
+
config = _read_pixel_config(dat_file_path)
|
|
130
|
+
x_pos = np.array(config["x"])
|
|
131
|
+
y_pos = np.array(config["y"])
|
|
132
|
+
|
|
133
|
+
if not is_two_mirror_telescope(telescope_model_name):
|
|
134
|
+
y_pos = -y_pos
|
|
135
|
+
|
|
136
|
+
rotate_angle = (
|
|
137
|
+
config.get("rotate_angle") if config.get("rotate_angle") is not None else (0.0 * u.deg)
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Apply telescope-specific adjustments
|
|
141
|
+
if "SST" in telescope_model_name or "SCT" in telescope_model_name:
|
|
142
|
+
total_rotation = (90 * u.deg) - (rotate_angle)
|
|
143
|
+
else:
|
|
144
|
+
total_rotation = (-90 * u.deg) - (rotate_angle)
|
|
145
|
+
|
|
146
|
+
# Apply rotation
|
|
147
|
+
rot_angle = total_rotation.to(u.rad).value
|
|
148
|
+
x_rot = x_pos * np.cos(rot_angle) - y_pos * np.sin(rot_angle)
|
|
149
|
+
y_rot = y_pos * np.cos(rot_angle) + x_pos * np.sin(rot_angle)
|
|
150
|
+
x_pos, y_pos = x_rot, y_rot
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
"x": x_pos,
|
|
154
|
+
"y": y_pos,
|
|
155
|
+
"pixel_ids": config["pixel_ids"],
|
|
156
|
+
"pixels_on": config["pixels_on"],
|
|
157
|
+
"pixel_shape": config["pixel_shape"],
|
|
158
|
+
"pixel_diameter": config["pixel_diameter"],
|
|
159
|
+
"pixel_spacing": config["pixel_spacing"],
|
|
160
|
+
"module_number": config["module_number"],
|
|
161
|
+
"module_gap": config["module_gap"],
|
|
162
|
+
"rotation": total_rotation,
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _create_pixel_plot(
|
|
167
|
+
pixel_data, telescope_model_name, pixels_id_to_print=50, title=None, xtitle=None, ytitle=None
|
|
168
|
+
):
|
|
169
|
+
"""
|
|
170
|
+
Create and configure the pixel layout plot.
|
|
171
|
+
|
|
172
|
+
Parameters
|
|
173
|
+
----------
|
|
174
|
+
pixel_data : dict
|
|
175
|
+
Dictionary containing pixel configuration data
|
|
176
|
+
telescope_model_name : str
|
|
177
|
+
Name of telescope model
|
|
178
|
+
pixels_id_to_print : int, optional
|
|
179
|
+
Number of pixel IDs to print, default 50
|
|
180
|
+
title : str, optional
|
|
181
|
+
Plot title
|
|
182
|
+
xtitle : str, optional
|
|
183
|
+
X-axis label
|
|
184
|
+
ytitle : str, optional
|
|
185
|
+
Y-axis label
|
|
186
|
+
|
|
187
|
+
Returns
|
|
188
|
+
-------
|
|
189
|
+
matplotlib.figure.Figure
|
|
190
|
+
The generated figure
|
|
191
|
+
"""
|
|
192
|
+
fig, ax = plt.subplots(figsize=(8, 8))
|
|
193
|
+
|
|
194
|
+
# Create patches
|
|
195
|
+
on_pixels, edge_pixels, off_pixels = _create_pixel_patches(
|
|
196
|
+
pixel_data["x"],
|
|
197
|
+
pixel_data["y"],
|
|
198
|
+
pixel_data["pixel_diameter"],
|
|
199
|
+
pixel_data["module_number"],
|
|
200
|
+
pixel_data["module_gap"],
|
|
201
|
+
pixel_data["pixel_spacing"],
|
|
202
|
+
pixel_data["pixel_shape"],
|
|
203
|
+
pixel_data["pixels_on"],
|
|
204
|
+
pixel_data["pixel_ids"],
|
|
205
|
+
pixels_id_to_print,
|
|
206
|
+
telescope_model_name,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# Combine all patches into a single collection
|
|
210
|
+
all_patches = on_pixels + edge_pixels + off_pixels
|
|
211
|
+
facecolors = [
|
|
212
|
+
"none"
|
|
213
|
+
if i < len(on_pixels)
|
|
214
|
+
else (*mcolors.to_rgb("brown"), 0.5)
|
|
215
|
+
if i < len(on_pixels) + len(edge_pixels)
|
|
216
|
+
else "black"
|
|
217
|
+
for i in range(len(on_pixels) + len(edge_pixels) + len(off_pixels))
|
|
218
|
+
]
|
|
219
|
+
edgecolors = (
|
|
220
|
+
["black"] * len(on_pixels)
|
|
221
|
+
+ [(*mcolors.to_rgb("black"), 1)] * len(edge_pixels)
|
|
222
|
+
+ ["black"] * len(off_pixels)
|
|
223
|
+
)
|
|
224
|
+
linewidths = [0.2] * len(all_patches)
|
|
225
|
+
|
|
226
|
+
# Add the combined collection
|
|
227
|
+
ax.add_collection(
|
|
228
|
+
PatchCollection(
|
|
229
|
+
all_patches,
|
|
230
|
+
facecolor=facecolors,
|
|
231
|
+
edgecolor=edgecolors,
|
|
232
|
+
linewidth=linewidths,
|
|
233
|
+
match_original=True,
|
|
234
|
+
)
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# Configure plot with titles
|
|
238
|
+
_configure_plot(
|
|
239
|
+
ax,
|
|
240
|
+
pixel_data["x"],
|
|
241
|
+
pixel_data["y"],
|
|
242
|
+
rotation=pixel_data["rotation"],
|
|
243
|
+
title=title,
|
|
244
|
+
xtitle=xtitle,
|
|
245
|
+
ytitle=ytitle,
|
|
246
|
+
)
|
|
247
|
+
_add_legend(ax, on_pixels, off_pixels)
|
|
248
|
+
|
|
249
|
+
plt.tight_layout()
|
|
250
|
+
return fig
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _read_pixel_config(dat_file_path):
|
|
254
|
+
"""Read pixel configuration from a camera configuration file.
|
|
255
|
+
|
|
256
|
+
This function reads the pixel configuration from the specified camera config file and
|
|
257
|
+
returns it as a dictionary. It parses information such as pixel positions,
|
|
258
|
+
module numbers, and other relevant parameters.
|
|
259
|
+
|
|
260
|
+
Parameters
|
|
261
|
+
----------
|
|
262
|
+
dat_file_path : str or Path
|
|
263
|
+
Path to the camera config file containing pixel configuration
|
|
264
|
+
|
|
265
|
+
Returns
|
|
266
|
+
-------
|
|
267
|
+
dict
|
|
268
|
+
config containing pixel data
|
|
269
|
+
"""
|
|
270
|
+
config = {
|
|
271
|
+
"x": [],
|
|
272
|
+
"y": [],
|
|
273
|
+
"pixel_ids": [],
|
|
274
|
+
"pixels_on": [],
|
|
275
|
+
"pixel_shape": None,
|
|
276
|
+
"pixel_diameter": None,
|
|
277
|
+
"pixel_spacing": None,
|
|
278
|
+
"module_gap": None,
|
|
279
|
+
"trigger_groups": [],
|
|
280
|
+
"rotate_angle": None,
|
|
281
|
+
"module_number": [],
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
with open(dat_file_path, encoding="utf-8") as f:
|
|
285
|
+
for line in f:
|
|
286
|
+
line = line.strip()
|
|
287
|
+
|
|
288
|
+
if not line:
|
|
289
|
+
continue
|
|
290
|
+
|
|
291
|
+
# Parse specific information from the file
|
|
292
|
+
if line.startswith("Rotate"):
|
|
293
|
+
# Parse rotation angle from line like "Rotate 10.893" (u.deg)
|
|
294
|
+
config["rotate_angle"] = float(line.split()[1].strip()) * u.deg
|
|
295
|
+
|
|
296
|
+
elif line.startswith("PixType"):
|
|
297
|
+
parts = line.split()
|
|
298
|
+
config["pixel_shape"] = int(parts[5].strip())
|
|
299
|
+
config["pixel_diameter"] = float(parts[6].strip())
|
|
300
|
+
|
|
301
|
+
elif "Pixel spacing is" in line:
|
|
302
|
+
config["pixel_spacing"] = float(line.split("spacing is")[1].strip().split()[0])
|
|
303
|
+
|
|
304
|
+
elif "Between modules is an additional gap of" in line:
|
|
305
|
+
config["module_gap"] = float(line.split("gap of")[1].strip().split()[0])
|
|
306
|
+
|
|
307
|
+
elif line.startswith("Pixel"):
|
|
308
|
+
parts = line.split()
|
|
309
|
+
config["x"].append(float(parts[3].strip()))
|
|
310
|
+
config["y"].append(float(parts[4].strip()))
|
|
311
|
+
config["module_number"].append(float(parts[5].strip()))
|
|
312
|
+
config["pixel_ids"].append(int(parts[1].strip()))
|
|
313
|
+
config["pixels_on"].append(int(parts[9].strip()) != 0)
|
|
314
|
+
|
|
315
|
+
config["pixel_spacing"] = (
|
|
316
|
+
config["pixel_diameter"] if config["pixel_spacing"] is None else config["pixel_spacing"]
|
|
317
|
+
)
|
|
318
|
+
config["module_gap"] = 0.0 if config["module_gap"] is None else config["module_gap"]
|
|
319
|
+
|
|
320
|
+
return config
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def _create_patch(x, y, diameter, shape):
|
|
324
|
+
"""Create a single matplotlib patch for a pixel.
|
|
325
|
+
|
|
326
|
+
This function creates a matplotlib patch (shape) for a single pixel based on
|
|
327
|
+
its position, diameter, and shape type. Supported shapes are circles, squares,
|
|
328
|
+
and hexagons.
|
|
329
|
+
|
|
330
|
+
Parameters
|
|
331
|
+
----------
|
|
332
|
+
x, y : float
|
|
333
|
+
Center coordinates of the pixel
|
|
334
|
+
diameter : float
|
|
335
|
+
Diameter of the pixel
|
|
336
|
+
shape : int
|
|
337
|
+
Pixel shape type:
|
|
338
|
+
0: circular
|
|
339
|
+
1: hexagonal (flat x)
|
|
340
|
+
2: square
|
|
341
|
+
3: hexagonal (flat y)
|
|
342
|
+
|
|
343
|
+
Returns
|
|
344
|
+
-------
|
|
345
|
+
matplotlib.patches.Patch
|
|
346
|
+
The created patch object for the pixel
|
|
347
|
+
"""
|
|
348
|
+
if shape == 0: # Circular
|
|
349
|
+
return mpatches.Circle((x, y), radius=diameter / 2)
|
|
350
|
+
if shape in (1, 3): # Hexagonal
|
|
351
|
+
return mpatches.RegularPolygon(
|
|
352
|
+
(x, y),
|
|
353
|
+
numVertices=6,
|
|
354
|
+
radius=diameter / np.sqrt(3),
|
|
355
|
+
orientation=np.deg2rad(30 if shape == 3 else 0),
|
|
356
|
+
)
|
|
357
|
+
# Square
|
|
358
|
+
return mpatches.Rectangle((x - diameter / 2, y - diameter / 2), width=diameter, height=diameter)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _is_edge_pixel(
|
|
362
|
+
x, y, x_pos, y_pos, module_ids, pixel_spacing, module_gap, shape, current_module_id
|
|
363
|
+
):
|
|
364
|
+
"""
|
|
365
|
+
Determine if a pixel is on the edge based on neighbor count.
|
|
366
|
+
|
|
367
|
+
Parameters
|
|
368
|
+
----------
|
|
369
|
+
x, y : float
|
|
370
|
+
Coordinates of the pixel being checked.
|
|
371
|
+
x_pos, y_pos : array-like
|
|
372
|
+
Arrays of x and y positions of all pixels.
|
|
373
|
+
module_ids : array-like
|
|
374
|
+
Array of module IDs corresponding to each pixel.
|
|
375
|
+
pixel_spacing : float
|
|
376
|
+
Center-to-center spacing between pixels.
|
|
377
|
+
module_gap : float
|
|
378
|
+
Additional gap between modules.
|
|
379
|
+
shape : int
|
|
380
|
+
Pixel shape type (0: circular, 1/3: hexagonal, 2: square).
|
|
381
|
+
current_module_id : int
|
|
382
|
+
Module ID of the current pixel.
|
|
383
|
+
|
|
384
|
+
Returns
|
|
385
|
+
-------
|
|
386
|
+
bool
|
|
387
|
+
True if the pixel is an edge pixel, False otherwise.
|
|
388
|
+
"""
|
|
389
|
+
# Determine the maximum number of neighbors based on the pixel shape
|
|
390
|
+
if shape == 0: # Circular
|
|
391
|
+
max_neighbors = 8
|
|
392
|
+
elif shape in (1, 3): # Hexagonal
|
|
393
|
+
max_neighbors = 6
|
|
394
|
+
elif shape == 2: # Square
|
|
395
|
+
max_neighbors = 4
|
|
396
|
+
else:
|
|
397
|
+
raise ValueError(f"Unsupported pixel shape: {shape}")
|
|
398
|
+
|
|
399
|
+
neighbor_count = _count_neighbors(
|
|
400
|
+
x, y, x_pos, y_pos, module_ids, pixel_spacing, module_gap, current_module_id
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
# A pixel is an edge pixel if it has fewer neighbors than the maximum
|
|
404
|
+
return neighbor_count < max_neighbors
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def _create_pixel_patches(
|
|
408
|
+
x_pos,
|
|
409
|
+
y_pos,
|
|
410
|
+
diameter,
|
|
411
|
+
module_number,
|
|
412
|
+
module_gap,
|
|
413
|
+
spacing,
|
|
414
|
+
shape,
|
|
415
|
+
pixels_on,
|
|
416
|
+
pixel_ids,
|
|
417
|
+
pixels_id_to_print,
|
|
418
|
+
telescope_model_name,
|
|
419
|
+
):
|
|
420
|
+
"""Create matplotlib patches for different pixel types.
|
|
421
|
+
|
|
422
|
+
This function creates the matplotlib patches (shapes) for all pixels in the
|
|
423
|
+
layout, categorizing them into "on", "edge", and "off" pixels based on their
|
|
424
|
+
status and position.
|
|
425
|
+
|
|
426
|
+
Parameters
|
|
427
|
+
----------
|
|
428
|
+
x_pos, y_pos : array-like
|
|
429
|
+
X and Y coordinates of the pixel centers
|
|
430
|
+
diameter : float
|
|
431
|
+
Diameter of the pixels
|
|
432
|
+
module_number : array-like
|
|
433
|
+
Module numbers for each pixel
|
|
434
|
+
module_gap : float
|
|
435
|
+
Gap between modules
|
|
436
|
+
spacing : float
|
|
437
|
+
Pixel spacing
|
|
438
|
+
shape : array-like
|
|
439
|
+
Shape types for each pixel
|
|
440
|
+
pixels_on : array-like
|
|
441
|
+
Status indicating if each pixel is "on"
|
|
442
|
+
pixel_ids : array-like
|
|
443
|
+
Unique IDs for each pixel
|
|
444
|
+
pixels_id_to_print : int
|
|
445
|
+
Number of pixel IDs to print on the plot
|
|
446
|
+
telescope_model_name : str
|
|
447
|
+
Name of the telescope model
|
|
448
|
+
|
|
449
|
+
Returns
|
|
450
|
+
-------
|
|
451
|
+
tuple
|
|
452
|
+
Three lists of patches for "on", "edge", and "off" pixels
|
|
453
|
+
"""
|
|
454
|
+
on_pixels, edge_pixels, off_pixels = [], [], []
|
|
455
|
+
|
|
456
|
+
array_element_type = names.get_array_element_type_from_name(telescope_model_name)
|
|
457
|
+
font_size = 2 if "SCT" in array_element_type else 4
|
|
458
|
+
|
|
459
|
+
for i, (x, y) in enumerate(zip(x_pos, y_pos)):
|
|
460
|
+
patch = _create_patch(x, y, diameter, shape)
|
|
461
|
+
|
|
462
|
+
if pixels_on[i]:
|
|
463
|
+
if _is_edge_pixel(
|
|
464
|
+
x, y, x_pos, y_pos, module_number, spacing, module_gap, shape, module_number[i]
|
|
465
|
+
):
|
|
466
|
+
edge_pixels.append(patch)
|
|
467
|
+
else:
|
|
468
|
+
on_pixels.append(patch)
|
|
469
|
+
else:
|
|
470
|
+
off_pixels.append(patch)
|
|
471
|
+
|
|
472
|
+
if pixel_ids[i] < pixels_id_to_print:
|
|
473
|
+
plt.text(x, y, pixel_ids[i], ha="center", va="center", fontsize=font_size)
|
|
474
|
+
|
|
475
|
+
return on_pixels, edge_pixels, off_pixels
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def _count_neighbors(x, y, x_pos, y_pos, module_ids, pixel_spacing, module_gap, current_module_id):
|
|
479
|
+
"""
|
|
480
|
+
Count the number of neighboring pixels within the appropriate distance.
|
|
481
|
+
|
|
482
|
+
Parameters
|
|
483
|
+
----------
|
|
484
|
+
x, y : float
|
|
485
|
+
Coordinates of the pixel being checked.
|
|
486
|
+
x_pos, y_pos : array-like
|
|
487
|
+
Arrays of x and y positions of all pixels.
|
|
488
|
+
module_ids : array-like
|
|
489
|
+
Array of module IDs corresponding to each pixel.
|
|
490
|
+
pixel_spacing : float
|
|
491
|
+
Center-to-center spacing between pixels.
|
|
492
|
+
module_gap : float
|
|
493
|
+
Additional gap between modules.
|
|
494
|
+
current_module_id : int
|
|
495
|
+
Module ID of the current pixel.
|
|
496
|
+
|
|
497
|
+
Returns
|
|
498
|
+
-------
|
|
499
|
+
int
|
|
500
|
+
Number of neighboring pixels.
|
|
501
|
+
"""
|
|
502
|
+
count = 0
|
|
503
|
+
tolerance = 1e-6
|
|
504
|
+
|
|
505
|
+
for x2, y2, module_id2 in zip(x_pos, y_pos, module_ids):
|
|
506
|
+
# Skip the pixel itself
|
|
507
|
+
if x == x2 and y == y2:
|
|
508
|
+
continue
|
|
509
|
+
|
|
510
|
+
# Calculate the distance between the current pixel and the potential neighbor
|
|
511
|
+
dist = np.sqrt((x - x2) ** 2 + (y - y2) ** 2)
|
|
512
|
+
|
|
513
|
+
# Determine max distance based on whether pixels are in same module
|
|
514
|
+
max_distance = (
|
|
515
|
+
pixel_spacing + (0 if current_module_id == module_id2 else module_gap) + tolerance
|
|
516
|
+
) * 1.2
|
|
517
|
+
|
|
518
|
+
if dist <= max_distance:
|
|
519
|
+
count += 1
|
|
520
|
+
|
|
521
|
+
return count
|
|
522
|
+
|
|
523
|
+
|
|
524
|
+
def _configure_plot(
|
|
525
|
+
ax,
|
|
526
|
+
x_pos,
|
|
527
|
+
y_pos,
|
|
528
|
+
rotation=0 * u.deg,
|
|
529
|
+
title=None,
|
|
530
|
+
xtitle=None,
|
|
531
|
+
ytitle=None,
|
|
532
|
+
):
|
|
533
|
+
"""Configure the plot with titles, labels, and limits.
|
|
534
|
+
|
|
535
|
+
Parameters
|
|
536
|
+
----------
|
|
537
|
+
ax : matplotlib.axes.Axes
|
|
538
|
+
The axes to configure
|
|
539
|
+
x_pos, y_pos : array-like
|
|
540
|
+
Arrays of x and y positions of pixels
|
|
541
|
+
rotation : Astropy quantity in degrees, optional
|
|
542
|
+
Rotation angle in degrees, default 0
|
|
543
|
+
title : str, optional
|
|
544
|
+
Plot title
|
|
545
|
+
xtitle : str, optional
|
|
546
|
+
X-axis label
|
|
547
|
+
ytitle : str, optional
|
|
548
|
+
Y-axis label
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
Returns
|
|
552
|
+
-------
|
|
553
|
+
None
|
|
554
|
+
The function modifies the plot axes in place.
|
|
555
|
+
"""
|
|
556
|
+
# First set the aspect ratio
|
|
557
|
+
ax.set_aspect("equal")
|
|
558
|
+
|
|
559
|
+
# Calculate the axis limits
|
|
560
|
+
x_min, x_max = min(x_pos), max(x_pos)
|
|
561
|
+
y_min, y_max = min(y_pos), max(y_pos)
|
|
562
|
+
|
|
563
|
+
# Add some padding
|
|
564
|
+
x_padding = (x_max - x_min) * 0.1
|
|
565
|
+
y_padding = (y_max - y_min) * 0.1
|
|
566
|
+
|
|
567
|
+
# Set limits with padding
|
|
568
|
+
ax.set_xlim(x_min - x_padding, x_max + x_padding)
|
|
569
|
+
ax.set_ylim(y_min - y_padding, y_max + y_padding)
|
|
570
|
+
|
|
571
|
+
plt.grid(True)
|
|
572
|
+
ax.set_axisbelow(True)
|
|
573
|
+
|
|
574
|
+
plt.xlabel(xtitle or "Horizontal scale [cm]", fontsize=18, labelpad=0)
|
|
575
|
+
plt.ylabel(ytitle or "Vertical scale [cm]", fontsize=18, labelpad=0)
|
|
576
|
+
ax.set_title(
|
|
577
|
+
title or "Pixel layout",
|
|
578
|
+
fontsize=15,
|
|
579
|
+
y=1.02,
|
|
580
|
+
)
|
|
581
|
+
plt.tick_params(axis="both", which="major", labelsize=15)
|
|
582
|
+
|
|
583
|
+
_add_coordinate_axes(ax, rotation)
|
|
584
|
+
x_min = min(x_pos) - (max(x_pos) - min(x_pos)) * 0.05
|
|
585
|
+
y_min = min(y_pos) - (max(y_pos) - min(y_pos)) * 0.05
|
|
586
|
+
ax.text(x_min, y_min, "For an observer facing the camera", fontsize=10, ha="left", va="bottom")
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
def _add_coordinate_axes(ax, rotation=0 * u.deg):
|
|
590
|
+
"""Add coordinate system axes to the plot."""
|
|
591
|
+
# Setup dimensions and positions
|
|
592
|
+
x_min, x_max = ax.get_xlim()
|
|
593
|
+
y_min, y_max = ax.get_ylim()
|
|
594
|
+
plot_size = min(x_max - x_min, y_max - y_min)
|
|
595
|
+
axis_length = plot_size * 0.08
|
|
596
|
+
|
|
597
|
+
x_origin = x_max - axis_length * 1.0
|
|
598
|
+
y_origin_az = y_min + axis_length * 2.5
|
|
599
|
+
y_origin_pix = y_min + axis_length * 1.2
|
|
600
|
+
|
|
601
|
+
arrow_style = {
|
|
602
|
+
"head_width": axis_length * 0.15,
|
|
603
|
+
"head_length": axis_length * 0.15,
|
|
604
|
+
"width": axis_length * 0.02,
|
|
605
|
+
}
|
|
606
|
+
arrow_length = 0.6
|
|
607
|
+
is_sst = abs(rotation - (90.0 * u.deg)).value < 1.0
|
|
608
|
+
az_direction = 1 if is_sst else -1
|
|
609
|
+
|
|
610
|
+
def add_arrow_label(ox, oy, dx, dy, label, offset, color="black", ha="center", va="center"):
|
|
611
|
+
"""Adding arrows with label."""
|
|
612
|
+
ax.arrow(ox, oy, dx, dy, fc=color, ec=color, **arrow_style)
|
|
613
|
+
if np.sqrt(dx**2 + dy**2) > 0: # If not zero vector
|
|
614
|
+
dir_unit = np.sqrt(dx**2 + dy**2)
|
|
615
|
+
ax.text(
|
|
616
|
+
ox + dx + dx / dir_unit * axis_length * offset,
|
|
617
|
+
oy + dy + dy / dir_unit * axis_length * offset,
|
|
618
|
+
label,
|
|
619
|
+
ha=ha,
|
|
620
|
+
va=va,
|
|
621
|
+
color=color,
|
|
622
|
+
fontsize=10,
|
|
623
|
+
fontweight="bold",
|
|
624
|
+
)
|
|
625
|
+
|
|
626
|
+
# Az-Alt axes
|
|
627
|
+
az_dx = az_direction * axis_length * arrow_length
|
|
628
|
+
add_arrow_label(
|
|
629
|
+
x_origin,
|
|
630
|
+
y_origin_az,
|
|
631
|
+
az_dx,
|
|
632
|
+
0,
|
|
633
|
+
"Az",
|
|
634
|
+
0.25,
|
|
635
|
+
"red",
|
|
636
|
+
ha="left" if az_direction > 0 else "right",
|
|
637
|
+
)
|
|
638
|
+
add_arrow_label(
|
|
639
|
+
x_origin, y_origin_az, 0, -axis_length * arrow_length, "Alt", 0.25, "red", va="top"
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
# Pixel coordinate axes
|
|
643
|
+
rot_angle = rotation.to(u.rad).value
|
|
644
|
+
x_direction = -1 if is_sst else 1
|
|
645
|
+
x_dir = x_direction * axis_length * arrow_length * np.cos(rot_angle)
|
|
646
|
+
y_dir = x_direction * axis_length * arrow_length * np.sin(rot_angle)
|
|
647
|
+
add_arrow_label(x_origin, y_origin_pix, x_dir, y_dir, "$\\mathrm{x}_\\mathrm{pix}$", 0.45)
|
|
648
|
+
|
|
649
|
+
y_dx = axis_length * arrow_length * np.sin(rot_angle)
|
|
650
|
+
y_dy = -axis_length * arrow_length * np.cos(rot_angle)
|
|
651
|
+
add_arrow_label(x_origin, y_origin_pix, y_dx, y_dy, "$\\mathrm{y}_\\mathrm{pix}$", 0.45)
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
def _add_legend(ax, on_pixels, off_pixels):
|
|
655
|
+
"""Add legend to the plot."""
|
|
656
|
+
legend_objects = [leg_h.PixelObject(), leg_h.EdgePixelObject()]
|
|
657
|
+
legend_labels = ["Pixel", "Edge pixel"]
|
|
658
|
+
|
|
659
|
+
# Choose handler based on pixel shape
|
|
660
|
+
is_hex = isinstance(on_pixels[0], mpatches.RegularPolygon)
|
|
661
|
+
legend_handler_map = {
|
|
662
|
+
leg_h.PixelObject: leg_h.HexPixelHandler() if is_hex else leg_h.SquarePixelHandler(),
|
|
663
|
+
leg_h.EdgePixelObject: leg_h.HexEdgePixelHandler()
|
|
664
|
+
if is_hex
|
|
665
|
+
else leg_h.SquareEdgePixelHandler(),
|
|
666
|
+
leg_h.OffPixelObject: leg_h.HexOffPixelHandler()
|
|
667
|
+
if is_hex
|
|
668
|
+
else leg_h.SquareOffPixelHandler(),
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
if off_pixels:
|
|
672
|
+
legend_objects.append(leg_h.OffPixelObject())
|
|
673
|
+
legend_labels.append("Disabled pixel")
|
|
674
|
+
|
|
675
|
+
ax.legend(
|
|
676
|
+
legend_objects,
|
|
677
|
+
legend_labels,
|
|
678
|
+
handler_map=legend_handler_map,
|
|
679
|
+
prop={"size": 11},
|
|
680
|
+
loc="upper right",
|
|
681
|
+
)
|