Skip to content

Commit 8f03819

Browse files
committed
fix camera coordinates & sky spherical offset calc
1 parent e43e32b commit 8f03819

File tree

1 file changed

+118
-45
lines changed

1 file changed

+118
-45
lines changed

dl1_data_handler/reader.py

Lines changed: 118 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626
from astropy import units as u
2727
from astropy.coordinates.earth import EarthLocation
28-
from astropy.coordinates import SkyCoord
28+
from astropy.coordinates import AltAz, SkyCoord
2929
from astropy.table import (
3030
Table,
3131
unique,
@@ -34,6 +34,7 @@
3434
)
3535
from astropy.time import Time
3636

37+
from ctapipe.coordinates import CameraFrame
3738
from ctapipe.core import Component, QualityQuery
3839
from ctapipe.core.traits import (
3940
Bool,
@@ -51,13 +52,6 @@
5152
from ctapipe.io import read_table
5253
from dl1_data_handler.image_mapper import ImageMapper
5354

54-
# Reference (dummy) location to insert in the SkyCoord object as the default location
55-
#: Area averaged position of LST-1, MAGIC-1 and MAGIC-2 (using 23**2 and 17**2 m2)
56-
REFERENCE_LOCATION = EarthLocation(
57-
lon=-17.890879 * u.deg,
58-
lat=28.761579 * u.deg,
59-
height=2199 * u.m, # MC obs-level
60-
)
6155
# Reference (dummy) time to insert in the SkyCoord object as the default time
6256
LST_EPOCH = Time("2018-10-01T00:00:00", scale="utc")
6357

@@ -227,7 +221,6 @@ def __init__(
227221
parent=None,
228222
**kwargs,
229223
):
230-
231224
super().__init__(config=config, parent=parent, **kwargs)
232225

233226
# Register the destructor to close all open files properly
@@ -576,14 +569,11 @@ def _construct_stereo_example_identifiers(self):
576569
simulation_info = []
577570
example_identifiers = []
578571
for file_idx, (filename, f) in enumerate(self.files.items()):
579-
if self.process_type == ProcessType.Simulation:
580-
# Read simulation information for each observation
581-
simulation_info.append(read_table(f, "/configuration/simulation/run"))
582-
# Construct the shower simulation table
583-
simshower_table = read_table(f, "/simulation/event/subarray/shower")
584572
# Read the trigger table.
585573
trigger_table = read_table(f, "/dl1/event/subarray/trigger")
586574
if self.process_type == ProcessType.Simulation:
575+
# Construct the shower simulation table
576+
simshower_table = read_table(f, "/simulation/event/subarray/shower")
587577
# The shower simulation table is joined with the subarray trigger table.
588578
trigger_table = join(
589579
left=trigger_table,
@@ -627,7 +617,7 @@ def _construct_stereo_example_identifiers(self):
627617
right=tel_pointing,
628618
keys=["obs_id", "tel_id"],
629619
)
630-
table_per_type = self._transform_to_spherical_offsets(
620+
table_per_type = self._transform_to_cam_coord_offsets(
631621
table_per_type
632622
)
633623
# Apply the multiplicity cut based on the telescope type
@@ -663,6 +653,24 @@ def _multiplicity_cut_subarray(table, key_colnames):
663653
events.add_column(
664654
true_shower_primary_class, name="true_shower_primary_class"
665655
)
656+
# Read simulation information for each observation
657+
simulation_info_table = read_table(f, "/configuration/simulation/run")
658+
# Append the simulation information to the list of simulation information
659+
simulation_info.append(simulation_info_table)
660+
# Assuming min_az = max_az and min_alt = max_alt
661+
fix_array_pointing = simulation_info_table.copy()
662+
fix_array_pointing.keep_columns(["obs_id", "min_az", "min_alt"])
663+
fix_array_pointing.rename_column("min_az", "pointing_azimuth")
664+
fix_array_pointing.rename_column("min_alt", "pointing_altitude")
665+
# Join the prediction table with the telescope pointing table
666+
events = join(
667+
left=events,
668+
right=fix_array_pointing,
669+
keys=["obs_id"],
670+
)
671+
# TODO: use keep_order for astropy v7.0.0
672+
events.sort(["obs_id", "event_id"])
673+
events = self._transform_to_sky_spher_offsets(events)
666674
# Appending the events to the list of example identifiers
667675
example_identifiers.append(events)
668676

@@ -741,9 +749,9 @@ def _transform_to_log_energy(self, table):
741749
table.add_column(np.log10(table["true_energy"]), name="log_true_energy")
742750
return table
743751

744-
def _transform_to_spherical_offsets(self, table) -> Table:
752+
def _transform_to_cam_coord_offsets(self, table) -> Table:
745753
"""
746-
Transform Alt/Az coordinates to spherical offsets w.r.t. the telescope pointing.
754+
Transform Alt/Az coordinates to camera coordinate offsets w.r.t. the telescope pointing.
747755
748756
This method converts the Alt/Az coordinates in the provided table to spherical offsets
749757
w.r.t. the telescope pointing. It also calculates the angular separation between the
@@ -758,34 +766,98 @@ def _transform_to_spherical_offsets(self, table) -> Table:
758766
--------
759767
table : astropy.table.Table
760768
A Table with the spherical offsets and the angular separation added as new columns.
761-
The telescope pointing columns are removed from the table.
769+
"""
770+
771+
tel_tables = []
772+
for tel_id in self.tel_ids:
773+
tel_table = table.copy()
774+
tel_table.keep_columns(
775+
[
776+
"obs_id",
777+
"tel_id",
778+
"true_alt",
779+
"true_az",
780+
"telescope_pointing_azimuth",
781+
"telescope_pointing_altitude",
782+
]
783+
)
784+
tel_table = tel_table[tel_table["tel_id"] == tel_id]
785+
# Set the telescope pointing of the SkyOffsetSeparation tranform to the fix pointing
786+
tel_ground_frame = self.subarray.tel_coords[
787+
self.subarray.tel_ids_to_indices(tel_id)
788+
]
789+
fix_tel_pointing = SkyCoord(
790+
tel_table["telescope_pointing_azimuth"],
791+
tel_table["telescope_pointing_altitude"],
792+
location=tel_ground_frame.to_earth_location(),
793+
obstime=LST_EPOCH,
794+
)
795+
camera_frame = CameraFrame(
796+
focal_length=self.subarray.tel[tel_id].optics.equivalent_focal_length,
797+
rotation=self.subarray.tel[tel_id].camera.geometry.pix_rotation,
798+
telescope_pointing=fix_tel_pointing,
799+
)
800+
# Transform the true Alt/Az coordinates to camera coordinates
801+
true_direction = SkyCoord(
802+
tel_table["true_az"],
803+
tel_table["true_alt"],
804+
location=tel_ground_frame.to_earth_location(),
805+
obstime=LST_EPOCH,
806+
)
807+
true_cam_position = true_direction.transform_to(camera_frame)
808+
true_cam_distance = np.sqrt(
809+
true_cam_position.x**2 + true_cam_position.y**2
810+
)
811+
tel_table.add_column(true_cam_position.x, name="cam_coord_offset_x")
812+
tel_table.add_column(true_cam_position.y, name="cam_coord_offset_y")
813+
tel_table.add_column(true_cam_distance, name="cam_coord_distance")
814+
tel_tables.append(tel_table)
815+
tel_tables = vstack(tel_tables)
816+
table = join(
817+
left=table,
818+
right=tel_tables,
819+
keys=["obs_id", "event_id", "tel_id"],
820+
)
821+
# TODO: use keep_order for astropy v7.0.0
822+
table.sort(["obs_id", "event_id", "tel_id"])
823+
return table
824+
825+
def _transform_to_sky_spher_offsets(self, table) -> Table:
826+
"""
827+
Transform Alt/Az coordinates to sky spherical offsets w.r.t. the telescope pointing.
828+
829+
This method converts the Alt/Az coordinates in the provided table to sky spherical offsets
830+
w.r.t. the telescope pointing. It also calculates the angular separation between the
831+
true and telescope pointing directions.
832+
833+
Parameters:
834+
-----------
835+
table : astropy.table.Table
836+
A Table containing the true Alt/Az coordinates and telescope pointing.
837+
838+
Returns:
839+
--------
840+
table : astropy.table.Table
841+
A Table with the spherical offsets and the angular separation added as new columns.
762842
"""
763843
# Set the telescope pointing of the SkyOffsetSeparation tranform to the fix pointing
764-
fix_pointing = SkyCoord(
765-
table["telescope_pointing_azimuth"],
766-
table["telescope_pointing_altitude"],
767-
frame="altaz",
768-
location=REFERENCE_LOCATION,
844+
fix_array_pointing = SkyCoord(
845+
table["pointing_azimuth"],
846+
table["pointing_altitude"],
847+
location=self.subarray.reference_location,
769848
obstime=LST_EPOCH,
770849
)
771850
true_direction = SkyCoord(
772851
table["true_az"],
773852
table["true_alt"],
774-
frame="altaz",
775-
location=REFERENCE_LOCATION,
853+
location=self.subarray.reference_location,
776854
obstime=LST_EPOCH,
777855
)
778856
sky_offset = fix_pointing.spherical_offsets_to(true_direction)
779857
angular_separation = fix_pointing.separation(true_direction)
780-
table.add_column(sky_offset[0], name="spherical_offset_az")
781-
table.add_column(sky_offset[1], name="spherical_offset_alt")
782-
table.add_column(angular_separation, name="angular_separation")
783-
table.remove_columns(
784-
[
785-
"telescope_pointing_azimuth",
786-
"telescope_pointing_altitude",
787-
]
788-
)
858+
table.add_column(sky_offset[0], name="sky_offset_lon")
859+
table.add_column(sky_offset[1], name="sky_offset_lat")
860+
table.add_column(angular_separation, name="sky_angular_separation")
789861
return table
790862

791863
def get_parameters(self, batch, dl1b_parameter_list) -> np.array:
@@ -901,7 +973,9 @@ def generate_stereo_batch(self, batch_indices) -> Table:
901973
batch = self._append_features(batch)
902974
# Add blank inputs for missing telescopes in the batch
903975
if self.process_type == ProcessType.Simulation:
904-
batch_grouped = batch.group_by(["obs_id", "event_id", "true_shower_primary_class"])
976+
batch_grouped = batch.group_by(
977+
["obs_id", "event_id", "true_shower_primary_class"]
978+
)
905979
elif self.process_type == ProcessType.Observation:
906980
batch_grouped = batch.group_by(["obs_id", "event_id"])
907981
for group_element in batch_grouped.groups:
@@ -927,26 +1001,27 @@ def generate_stereo_batch(self, batch_indices) -> Table:
9271001
if "features" in group_element.colnames:
9281002
blank_input_row["features"] = blank_input
9291003
if "mono_feature_vectors" in group_element.colnames:
930-
blank_input_row["mono_feature_vectors"] = (
931-
blank_mono_feature_vectors
932-
)
1004+
blank_input_row[
1005+
"mono_feature_vectors"
1006+
] = blank_mono_feature_vectors
9331007
if "stereo_feature_vectors" in group_element.colnames:
934-
blank_input_row["stereo_feature_vectors"] = (
935-
blank_stereo_feature_vectors
936-
)
1008+
blank_input_row[
1009+
"stereo_feature_vectors"
1010+
] = blank_stereo_feature_vectors
9371011
batch.add_row(blank_input_row)
9381012
# Sort the batch with the new rows of blank inputs
9391013
batch.sort(["obs_id", "event_id", "tel_type_id", "tel_id"])
9401014
return batch
9411015

9421016
def __destructor(self):
9431017
"""Destructor to ensure all opened HDF5 files are properly closed."""
944-
if hasattr(self, "files"): # Ensure self.files exists before attempting to close
1018+
if hasattr(
1019+
self, "files"
1020+
): # Ensure self.files exists before attempting to close
9451021
for file_name in list(self.files.keys()):
9461022
if self.files[file_name].isopen: # Check if file is still open
9471023
self.files[file_name].close()
9481024

949-
9501025
@abstractmethod
9511026
def _append_features(self, batch) -> Table:
9521027
pass
@@ -1078,7 +1153,6 @@ def __init__(
10781153
parent=None,
10791154
**kwargs,
10801155
):
1081-
10821156
super().__init__(
10831157
input_url_signal=input_url_signal,
10841158
input_url_background=input_url_background,
@@ -1337,7 +1411,6 @@ def __init__(
13371411
parent=None,
13381412
**kwargs,
13391413
):
1340-
13411414
super().__init__(
13421415
input_url_signal=input_url_signal,
13431416
input_url_background=input_url_background,

0 commit comments

Comments
 (0)