diff --git a/CDB/stations/DTS_ConfigDb.json b/CDB/stations/DTS_ConfigDb.json index 741d9dc910e6ff35d04e993dab21dbd3eb08cc01..bd797a7eabcfff3050d9650f334edb11c3999376 100644 --- a/CDB/stations/DTS_ConfigDb.json +++ b/CDB/stations/DTS_ConfigDb.json @@ -64,6 +64,207 @@ ], "OPC_Time_Out": [ "5.0" + ], + "HBAT_reference_ETRS": [ + "3839371.416", "430339.901", "5057958.886", + "3839368.919", "430335.979", "5057961.1", + "3839365.645", "430339.299", "5057963.288", + "3839368.142", "430343.221", "5057961.074", + "3839374.094", "430299.513", "5057960.017", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0", + "0", "0", "0" + ], + "HBAT_PQR_rotation_angle_deg": [ + "45.73", + "45.73", + "45.73", + "45.73", + "54.40", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ], + "HBAT_PQR_to_ETRS_rotation_matrix": [ + "-0.11660087", "-0.79095632", "0.60065992", + " 0.99317077", "-0.09529842", "0.06730545", + " 0.00400627", " 0.60440575", "0.79666658" ] } } diff --git a/CDB/stations/dummy_positions_ConfigDb.json b/CDB/stations/dummy_positions_ConfigDb.json index 5f998a8102a8ceaad66b7a7a46ed293aa2223b67..1608917b748c4d7466f726153610be09981c7510 100644 --- a/CDB/stations/dummy_positions_ConfigDb.json +++ b/CDB/stations/dummy_positions_ConfigDb.json @@ -103,23 +103,108 @@ "3826577.066", "461022.948", "5064892.786", "3826577.066", "461022.948", "5064892.786" ], - "HBAT_antenna_itrf_offsets": [ - "-1.847", "-1.180", " 1.493", - "-1.581", " 0.003", " 1.186", - "-1.315", " 1.185", " 0.880", - "-1.049", " 2.367", " 0.573", - "-0.882", "-1.575", " 0.804", - "-0.616", "-0.393", " 0.498", - "-0.350", " 0.789", " 0.191", - "-0.083", " 1.971", "-0.116", - " 0.083", "-1.971", " 0.116", - " 0.350", "-0.789", "-0.191", - " 0.616", " 0.393", "-0.498", - " 0.882", " 1.575", "-0.804", - " 1.049", "-2.367", "-0.573", - " 1.315", "-1.185", "-0.880", - " 1.581", "-0.003", "-1.186", - " 1.847", " 1.180", "-1.493" + "HBAT_PQR_rotation_angles_deg": [ + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24", + "24" + ], + "HBAT_PQR_to_ETRS_rotation_matrix": [ + "-0.1195951054", "-0.7919544517", "0.5987530018", + " 0.9928227484", "-0.0954186800", "0.0720990002", + " 0.0000330969", " 0.6030782884", "0.7976820024" ] } } diff --git a/tangostationcontrol/requirements.txt b/tangostationcontrol/requirements.txt index f8bad33a66897ce01bfb5084430a9e7af8ce621b..a93c35d3d5a643afaf0d78f53cdd2c36be65f8f2 100644 --- a/tangostationcontrol/requirements.txt +++ b/tangostationcontrol/requirements.txt @@ -12,3 +12,4 @@ psutil >= 5.8.0 # BSD docker >= 5.0.3 # Apache 2 python-logstash-async >= 2.3.0 # MIT python-casacore >= 3.3.1 # GPL2 +etrs-itrs@git+https://github.com/brentjens/etrs-itrs # license pending diff --git a/tangostationcontrol/tangostationcontrol/beam/geo.py b/tangostationcontrol/tangostationcontrol/beam/geo.py new file mode 100644 index 0000000000000000000000000000000000000000..033a6c4e4293573d05cb3fa09bcab5071c88a97b --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/beam/geo.py @@ -0,0 +1,33 @@ +import etrsitrs +import numpy + +""" + LOFAR station positions are measured in ETRS89, which are the coordinates of the position as it would be in 1989. + + These coordinates are carthesian (X, Y, Z), with (0, 0, 0) being the center of the Earth. + + The ETRS89 positions differ from the current due to tectonic movements. Periodically, these differences are modelled + as position offsets and velocities. For example, ITRF2005 represents these offsets updated to 2005. We can + furthermore extrapolate these models to later dates, such as 2015.5. + + By periodically extrapolating to later dates, or by using later models, we can obtain more precise positions of our + antennas without having to remeasure them. + + The ETRSitrs package does all the transformation calculations for us. +""" + +def ETRS_to_ITRF(ETRS_coordinates: numpy.array, ITRF_reference_frame: str = "ITRF2005", ITRF_reference_epoch: float = 2015.5) -> numpy.array: + """ Convert an array of coordinate triples from ETRS to ITRF, in the given reference frame and epoch. """ + + # fetch converter + ETRS_to_ITRF_fn = etrsitrs.convert_fn("ETRF2000", ITRF_reference_frame, ITRF_reference_epoch) + + if ETRS_coordinates.ndim == 1: + # convert a single coordinate triple + ITRF_coordinates = ETRS_to_ITRF_fn(ETRS_coordinates) + else: + # convert each coordinate triple + ITRF_coordinates = numpy.apply_along_axis(ETRS_to_ITRF_fn, 1, ETRS_coordinates) + + # return computed ITRF coordinates + return ITRF_coordinates diff --git a/tangostationcontrol/tangostationcontrol/beam/hba_tile.py b/tangostationcontrol/tangostationcontrol/beam/hba_tile.py new file mode 100644 index 0000000000000000000000000000000000000000..cede7be17c1c12773931e19ec81fd7d1a666b72b --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/beam/hba_tile.py @@ -0,0 +1,68 @@ +import numpy +from math import sin, cos + +class HBATAntennaOffsets(object): + """ + This class helps calculate the absolute offsets of the antennas within a tile, + based on their relative orientation to ETRS. + + These offsets are Known in LOFAR1 as "iHBADeltas". + + Within LOFAR, we use a relative "PQR" coordinate system for each station: + + * The origin is the same as the ETRS reference position of the station, + * "Q" points to true North as seen from CS002LBA, the center of LOFAR, + * "P" points (roughly) east, + * "R" points (roughly) up, + * All antennas are positioned in the same plane (R=0 with an error < 3cm). + + With the PQR->ETRS rotation matrix (or "ROTATION_MATRIX" in LOFAR1), + we can thus convert from PQR to (relative) ETRS coordinates. + + Below, the rotation of the HBA tiles is provided with respect to this PQR frame. + Along with the PQR->ETRS rotation matrix, this allows us to calculate the + ` relative positions of each antenna element within a tile in ETRS space. + + The relative ITRF positions are subsequently equal to the relative ETRS positions. + + For reference, see: + https://git.astron.nl/RD/lofar-referentie-vlak/-/blob/master/data/dts/dts.ipynb + https://git.astron.nl/ro/lofar/-/blob/master/MAC/Deployment/data/Coordinates/calc_hba_deltas.py + https://github.com/brentjens/lofar-antenna-positions/blob/master/lofarantpos/db.py#L208 + """ + + """ Model of the HBAT1 tile, as offsets of each antenna with respect to the reference center, in metres. """ + HBAT1_BASE_ANTENNA_OFFSETS = numpy.array( + [[-1.5, +1.5, 0.0], [-0.5, +1.5, 0.0], [+0.5, +1.5, 0.0], [+1.5, +1.5, 0.0], + [-1.5, +0.5, 0.0], [-0.5, +0.5, 0.0], [+0.5, +0.5, 0.0], [+1.5, +0.5, 0.0], + [-1.5, -0.5, 0.0], [-0.5, -0.5, 0.0], [+0.5, -0.5, 0.0], [+1.5, -0.5, 0.0], + [-1.5, -1.5, 0.0], [-0.5, -1.5, 0.0], [+0.5, -1.5, 0.0], [+1.5, -1.5, 0.0]]) * 1.25 + + @staticmethod + def rotation_matrix(rad: float) -> numpy.array: + """ Return a rotation matrix for coordinates for a given number of radians. """ + + rotation_matrix = numpy.array( + [[ cos(rad), sin(rad), 0], + [-sin(rad), cos(rad), 0], + [ 0, 0, 1]]) + + return rotation_matrix + + @staticmethod + def ITRF_offsets(base_antenna_offsets: numpy.array, PQR_rotation: float, PQR_to_ETRS_rotation_matrix: numpy.array) -> numpy.array: + """ Return the antenna offsets in ITRF, given: + + :param: base_antenna_offsets: antenna offsets within an unrotated tile (16x3). + :param: PQR_rotation: rotation of the tile(s) in PQR space (radians). + :param: PQR_to_ETRS_rotation_matrix: rotation matrix for PQR -> ETRS conversion (3x3). + """ + + # Offsets in PQR are derived by rotating the base tile by the specified number of radians + PQR_offsets = numpy.inner(base_antenna_offsets, HBATAntennaOffsets.rotation_matrix(PQR_rotation)) + + # The PQR->ETRS mapping is a rotation as well + ETRS_offsets = numpy.inner(PQR_offsets, PQR_to_ETRS_rotation_matrix) + + # The ITRF offsets are the same as the ETRS offsets + return ETRS_offsets diff --git a/tangostationcontrol/tangostationcontrol/devices/beam.py b/tangostationcontrol/tangostationcontrol/devices/beam.py index 3709bc5602dda655892b6d318e59c2eef77de716..adb4dd553a3c930207007e68b2ff0fa47ee154b6 100644 --- a/tangostationcontrol/tangostationcontrol/devices/beam.py +++ b/tangostationcontrol/tangostationcontrol/devices/beam.py @@ -105,13 +105,13 @@ class Beam(lofar_device): # Retrieve positions from RECV device HBAT_reference_itrf = self.recv_proxy.HBAT_reference_itrf_R - HBAT_antenna_itrf_offsets = self.recv_proxy.HBAT_antenna_itrf_offsets_R + HBAT_antenna_itrf_offsets = self.recv_proxy.HBAT_antenna_itrf_offsets_R.reshape(96,16,3) # a delay calculator for each tile self.HBAT_delay_calculators = [delay_calculator(reference_itrf) for reference_itrf in HBAT_reference_itrf] # absolute positions of each antenna element - self.HBAT_antenna_positions = [reference_itrf + HBAT_antenna_itrf_offsets for reference_itrf in HBAT_reference_itrf] + self.HBAT_antenna_positions = [HBAT_reference_itrf[tile] + HBAT_antenna_itrf_offsets[tile] for tile in range(96)] # Create a thread object to update HBAT beam weights self.HBAT_beam_tracker = BeamTracker(self) diff --git a/tangostationcontrol/tangostationcontrol/devices/recv.py b/tangostationcontrol/tangostationcontrol/devices/recv.py index b853b60406511930ae262458564bb52eff111fe1..78dbece6bd2be4d56bd877e77b2ba2067c0570c3 100644 --- a/tangostationcontrol/tangostationcontrol/devices/recv.py +++ b/tangostationcontrol/tangostationcontrol/devices/recv.py @@ -17,9 +17,13 @@ from tango import DebugIt from tango.server import command from tango.server import device_property, attribute from tango import AttrWriteType, DevState, DevVarFloatArray + import numpy +from math import pi # Additional import +from tangostationcontrol.beam.hba_tile import HBATAntennaOffsets +from tangostationcontrol.beam.geo import ETRS_to_ITRF from tangostationcontrol.common.entrypoint import entry from tangostationcontrol.common.lofar_logging import device_logging_to_python from tangostationcontrol.clients.attribute_wrapper import attribute_wrapper @@ -37,6 +41,9 @@ class RECV(opcua_device): # ----------------- # Device Properties # ----------------- + + # ----- Default settings + ANT_mask_RW_default = device_property( dtype='DevVarBooleanArray', mandatory=False, @@ -67,6 +74,13 @@ class RECV(opcua_device): default_value=1 ) + translator_default_settings = [ + 'ANT_mask_RW', + 'RCU_mask_RW' + ] + + # ----- Calibration values + HBAT_bf_delay_step_delays = device_property( dtype="DevVarFloatArray", mandatory=False, @@ -80,39 +94,64 @@ class RECV(opcua_device): 14.9781E-9, 15.5063E-9 ],dtype=numpy.float64) ) - - HBAT_reference_itrf = device_property( + + HBAT_signal_input_delays = device_property( dtype='DevVarFloatArray', - mandatory=False + mandatory=False, + default_value = numpy.zeros((96,32), dtype=numpy.float64) ) - HBAT_antenna_itrf_offsets = device_property( + HBAT_base_antenna_offsets = device_property( + doc="Offsets of the antennas in a HBAT, with respect to its reference center (16x3).", dtype='DevVarFloatArray', - mandatory=False + mandatory=False, + default_value = HBATAntennaOffsets.HBAT1_BASE_ANTENNA_OFFSETS.flatten() ) - HBAT_signal_input_delays = device_property( + # ----- Position information + + HBAT_reference_ITRF = device_property( + doc="ITRF position (XYZ) of each HBAT (leave empty to auto-derive from ETRS)", dtype='DevVarFloatArray', - mandatory=False, - default_value = numpy.zeros((96,32), dtype=numpy.float64) + mandatory=False + ) + + HBAT_reference_ETRS = device_property( + doc="ETRS position (XYZ) of each HBAT", + dtype='DevVarFloatArray', + mandatory=False ) ITRF_Reference_Frame = device_property( + doc="Reference frame in which the ITRF coordinates are provided, or are to be computed from ETRS89", dtype='DevString', mandatory=False, default_value = "ITRF2005" ) ITRF_Reference_Epoch = device_property( + doc="Reference epoch in which the ITRF coordinates are provided, or are to be extrapolated from ETRS89", dtype='DevFloat', mandatory=False, default_value = 2015.5 ) - translator_default_settings = [ - 'ANT_mask_RW', - 'RCU_mask_RW' - ] + HBAT_PQR_rotation_angles_deg = device_property( + doc='Rotation of each tile in the PQ plane ("horizontal") in degrees.', + dtype='DevVarFloatArray', + mandatory=False, + default_value = [0.0] * 96 + ) + + HBAT_PQR_to_ETRS_rotation_matrix = device_property( + doc="Field-specific rotation matrix to convert PQR offsets to ETRS/ITRF offsets.", + dtype='DevVarFloatArray', + mandatory=False, + default_value = numpy.array([ # PQR->ETRS rotation matrix for the core stations + [-0.1195951054, -0.7919544517, 0.5987530018], + [ 0.9928227484, -0.0954186800, 0.0720990002], + [ 0.0000330969, 0.6030782884, 0.7976820024]]).flatten() + ) # ---------- # Attributes @@ -161,13 +200,38 @@ class RECV(opcua_device): RECVTR_monitor_rate_RW = attribute_wrapper(comms_annotation=["RECVTR_monitor_rate_RW" ],datatype=numpy.int64 , access=AttrWriteType.READ_WRITE) RECVTR_translator_busy_R = attribute_wrapper(comms_annotation=["RECVTR_translator_busy_R" ],datatype=numpy.bool_ ) - HBAT_antenna_itrf_offsets_R = attribute(access=AttrWriteType.READ, - dtype=((numpy.float,),), max_dim_x=3, max_dim_y=16, - fget=lambda self: numpy.array(self.HBAT_antenna_itrf_offsets).reshape(16,3)) + # ----- Position information + + HBAT_antenna_ITRF_offsets_R = attribute(access=AttrWriteType.READ, + doc='Offsets of the antennas within a tile, in ITRF ("iHBADeltas"). True shape: 96x16x3.', + dtype=((numpy.float,),), max_dim_x=48, max_dim_y=96) + + HBAT_reference_ITRF_R = attribute(access=AttrWriteType.READ, + doc='Absolute reference position of each tile, in ITRF', + dtype=((numpy.float,),), max_dim_x=3, max_dim_y=96) + + def read_HBAT_antenna_ITRF_offsets_R(self): + base_antenna_offsets = numpy.array(self.HBAT_base_antenna_offsets).reshape(16,3) + PQR_to_ETRS_rotation_matrix = numpy.array(self.HBAT_PQR_to_ETRS_rotation_matrix).reshape(3,3) + + # each tile has its own rotation angle, resulting in different offsets per tile + all_offsets = numpy.array( + [HBATAntennaOffsets.ITRF_offsets( + base_antenna_offsets, + self.HBAT_PQR_rotation_angles_deg[tile] * pi / 180, + PQR_to_ETRS_rotation_matrix) + for tile in range(96)]) + + return all_offsets.reshape(96,48) + + def read_HBAT_reference_ITRF_R(self): + # provide ITRF coordinates if they were configured + if self.HBAT_reference_ITRF: + return numpy.array(self.HBAT_reference_ITRF).reshape(96,3) - HBAT_reference_itrf_R = attribute(access=AttrWriteType.READ, - dtype=((numpy.float,),), max_dim_x=3, max_dim_y=96, - fget=lambda self: numpy.array(self.HBAT_reference_itrf).reshape(96,3)) + # calculate them from ETRS coordinates if not, using the configured ITRF reference + ETRS_coordinates = numpy.array(self.HBAT_reference_ETRS).reshape(96,3) + return ETRS_to_ITRF(ETRS_coordinates, self.ITRF_Reference_Frame, self.ITRF_Reference_Epoch) # ---------- # Summarising Attributes diff --git a/tangostationcontrol/tangostationcontrol/test/beam/test_geo.py b/tangostationcontrol/tangostationcontrol/test/beam/test_geo.py new file mode 100644 index 0000000000000000000000000000000000000000..858b3f32e954d19271f8a0dc6fc3cba7b92f47e2 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/test/beam/test_geo.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the LOFAR 2.0 Station Software +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +from tangostationcontrol.beam.geo import ETRS_to_ITRF + +from tangostationcontrol.test import base + +import numpy.testing + +class TestETRS_to_ITRF(base.TestCase): + def test_convert_single_coordinate(self): + """ Convert a single coordinate. """ + ETRS_coords = numpy.array([1.0, 1.0, 1.0]) + ITRF_coords = ETRS_to_ITRF(ETRS_coords, "ITRF2005", 2015.5) + + self.assertEqual(ETRS_coords.shape, ITRF_coords.shape) + + def test_convert_array(self): + """ Convert an array of coordinates. """ + ETRS_coords = numpy.array([ [1.0, 1.0, 1.0], [2.0, 2.0, 2.0] ]) + ITRF_coords = ETRS_to_ITRF(ETRS_coords, "ITRF2005", 2015.5) + + self.assertEqual(ETRS_coords.shape, ITRF_coords.shape) + + def test_verify_CS001_LBA(self): + """ Verify if the calculated CS001LBA phase center matches those calculated in LOFAR1. """ + + # See CLBA in MAC/Deployment/data/Coordinates/ETRF_FILES/CS001/CS001-antenna-positions-ETRS.csv + CS001_LBA_ETRS = [3826923.942, 460915.117, 5064643.229] + + # Convert to ITRF + CS001_LBA_ITRF = ETRS_to_ITRF(numpy.array(CS001_LBA_ETRS), "ITRF2005", 2015.5) + + # verify against LOFAR1 (MAC/Deployment/data/StaticMetaData/AntennaFields/CS001-AntennaField.conf) + LOFAR1_CS001_LBA_ITRF = [3826923.50275, 460915.488115, 5064643.517] + + numpy.testing.assert_almost_equal(CS001_LBA_ITRF, LOFAR1_CS001_LBA_ITRF, decimal=1.5) diff --git a/tangostationcontrol/tangostationcontrol/test/beam/test_hba_tile.py b/tangostationcontrol/tangostationcontrol/test/beam/test_hba_tile.py new file mode 100644 index 0000000000000000000000000000000000000000..d698264f845cde35c5af63612e040832120e2455 --- /dev/null +++ b/tangostationcontrol/tangostationcontrol/test/beam/test_hba_tile.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# +# This file is part of the LOFAR 2.0 Station Software +# +# +# +# Distributed under the terms of the APACHE license. +# See LICENSE.txt for more info. + +from tangostationcontrol.beam.hba_tile import HBATAntennaOffsets + +from tangostationcontrol.test import base + +from math import pi +import numpy.testing + +class TestHBATAntennaOffsets(base.TestCase): + def test_verify_CS001_HBA0(self): + """ Verify if the calculated HBAT Antenna Offsets match those calculated in LOFAR1. """ + + CS001_HBA0_rotation_angle_deg = 24 + CS001_PQR_to_ETRS_rotation_matrix = numpy.array([ + [-0.1195951054, -0.7919544517, 0.5987530018], + [ 0.9928227484, -0.0954186800, 0.0720990002], + [ 0.0000330969, 0.6030782884, 0.7976820024]]) + + # recalculate the ITRF offsets + ITRF_offsets = HBATAntennaOffsets.ITRF_offsets( + HBATAntennaOffsets.HBAT1_BASE_ANTENNA_OFFSETS, + CS001_HBA0_rotation_angle_deg * pi / 180, + CS001_PQR_to_ETRS_rotation_matrix) + + # verify against LOFAR1 (MAC/Deployment/data/StaticMetaData/iHBADeltas/CS001-iHBADeltas.conf) + LOFAR1_CS001_HBA0_ITRF_offsets = numpy.array([ + [-1.847, -1.180, 1.493], + [-1.581, 0.003, 1.186], + [-1.315, 1.185, 0.880], + [-1.049, 2.367, 0.573], + [-0.882, -1.575, 0.804], + [-0.616, -0.393, 0.498], + [-0.350, 0.789, 0.191], + [-0.083, 1.971, -0.116], + [ 0.083, -1.971, 0.116], + [ 0.350, -0.789, -0.191], + [ 0.616, 0.393, -0.498], + [ 0.882, 1.575, -0.804], + [ 1.049, -2.367, -0.573], + [ 1.315, -1.185, -0.880], + [ 1.581, -0.003, -1.186], + [ 1.847, 1.180, -1.493]]) + + numpy.testing.assert_almost_equal(ITRF_offsets, LOFAR1_CS001_HBA0_ITRF_offsets, decimal=3)