2@file antenna_attributes.py
3@brief `main()` shows how to use
4[read_MI_antenna_geometry()](#antenna_attributes.read_MI_antenna_geometry) and
5[read_MI_antenna_geometry()](#antenna_attributes.read_global_channel_mapping).
8from pathlib
import Path
29ALL_PUEO_ANTENNA_NAMES = (
31 "101",
"201",
"301",
"401",
32 "102",
"202",
"302",
"402",
33 "103",
"203",
"303",
"403",
34 "104",
"204",
"304",
"404",
35 "105",
"205",
"305",
"405",
36 "106",
"206",
"306",
"406",
37 "107",
"207",
"307",
"407",
38 "108",
"208",
"308",
"408",
39 "109",
"209",
"309",
"409",
40 "110",
"210",
"310",
"410",
41 "111",
"211",
"311",
"411",
42 "112",
"212",
"312",
"412",
43 "113",
"213",
"313",
"413",
44 "114",
"214",
"314",
"414",
45 "115",
"215",
"315",
"415",
46 "116",
"216",
"316",
"416",
47 "117",
"217",
"317",
"417",
48 "118",
"218",
"318",
"418",
49 "119",
"219",
"319",
"419",
50 "120",
"220",
"320",
"420",
51 "121",
"221",
"321",
"421",
52 "122",
"222",
"322",
"422",
53 "123",
"223",
"323",
"423",
54 "124",
"224",
"324",
"424",
68ANTENNA_POLARIZATION_TYPE = pl.Enum([
"H",
"V"])
73PUEO_ANTENNA_TYPE = pl.Enum([
"MI",
"LF"])
91ASSUMED_PHI_SECTOR_APERTURE_WIDTH = 50
94def read_global_channel_mapping() -> pl.DataFrame:
97 @brief Generates a Polars DataFrame from
98 [map.dat](https://github.com/PUEOCollaboration/pueo-data/blob/main/geometry/jun25/map.dat)
99 in the pueo-data repository.
101 * `map.dat` contains the channel mapping between the indices `GlobalChan`, `AntNum` and `AntIdx`
103 * That is, the dataframe contains both polarizations (V and H) of both instruments (MI and LF).
104 * `GlobalChan` is the index used by the `pueoEvent` repository to access the waveforms,
105 see its [example script](https://github.com/PUEOCollaboration/pueoEvent/tree/main/examples).
106 * The relevant line is
108 chan = pueo.GeomTool.Instance().getChanIndexFromRingPhiPol(
109 pueo.ring.ring_t.kTopRing, 10, pueo.pol.pol_t.kVertical
112 * `chan` in the line above comes from the column `GlobalChan` in the DataFrame.
116┌────────────┬────────┬────────┬──────┬─────────┬─────────┬─────────────┬───────────┬──────┬─────────┐
117│ GlobalChan ┆ AntNum ┆ AntIdx ┆ Pol ┆ AntType ┆ SurfNum ┆ SurfChanNum ┆ PhiSector ┆ Ring ┆ Comment │
118│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
119│ u32 ┆ enum ┆ u32 ┆ enum ┆ enum ┆ u8 ┆ u8 ┆ u8 ┆ u8 ┆ str │
120╞════════════╪════════╪════════╪══════╪═════════╪═════════╪═════════════╪═══════════╪══════╪═════════╡
121│ 0 ┆ 101 ┆ 0 ┆ H ┆ MI ┆ 0 ┆ 0 ┆ 1 ┆ 1 ┆ null │
122│ 1 ┆ 201 ┆ 1 ┆ H ┆ MI ┆ 0 ┆ 1 ┆ 1 ┆ 2 ┆ null │
123│ 2 ┆ 301 ┆ 2 ┆ H ┆ MI ┆ 0 ┆ 2 ┆ 1 ┆ 3 ┆ null │
124│ 3 ┆ 401 ┆ 3 ┆ H ┆ MI ┆ 0 ┆ 3 ┆ 1 ┆ 4 ┆ null │
125│ 4 ┆ 102 ┆ 4 ┆ H ┆ MI ┆ 0 ┆ 4 ┆ 2 ┆ 1 ┆ null │
126│ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … │
127│ 203 ┆ 609 ┆ 99 ┆ V ┆ LF ┆ 25 ┆ 3 ┆ 9 ┆ 6 ┆ null │
128│ 204 ┆ 713 ┆ 100 ┆ V ┆ LF ┆ 25 ┆ 4 ┆ 13 ┆ 7 ┆ null │
129│ 205 ┆ 813 ┆ 101 ┆ V ┆ LF ┆ 25 ┆ 5 ┆ 13 ┆ 8 ┆ null │
130│ 206 ┆ 817 ┆ 102 ┆ V ┆ LF ┆ 25 ┆ 6 ┆ 17 ┆ 8 ┆ null │
131│ 207 ┆ 821 ┆ 103 ┆ V ┆ LF ┆ 25 ┆ 7 ┆ 21 ┆ 8 ┆ null │
132└────────────┴────────┴────────┴──────┴─────────┴─────────┴─────────────┴───────────┴──────┴─────────┘
136 map_dot_dat_file = os.environ.get(
"PUEO_UTIL_INSTALL_DIR") / Path(
"share/pueo/geometry/jun25/map.dat")
137 all_channels: pl.DataFrame = (
142 "GlobalChan": pl.UInt32,
144 "Pol": ANTENNA_POLARIZATION_TYPE,
145 "AntType": PUEO_ANTENNA_TYPE,
147 "SurfChanNum": pl.UInt8,
148 "PhiSector": pl.UInt8,
153 pl.col(
"AntNum").cast(pl.String).cast(ALL_PUEO_ANTENNA_NAMES)
160def read_antenna_geometry(qrh_dot_dat: Path, offset_inSIM:Path |
None =
None, ant_type=
'MI') -> pl.DataFrame:
163 @brief Reads in antenna positions (Cartesian coordinates, meters) and attitudes (degrees) from
164 [qrh.dat](https://github.com/PUEOCollaboration/pueo-data/blob/main/geometry/jun25/qrh.dat)
165 in the pueo-data repository.
166 @param[in] qrh_dot_dat Path to `qrh.dat`
168 * Note that `qrh.dat` contains the **face centers** of all main-instrument (MI) antennas
169 (quad-ridged horn antennas) and NOT the **phase center**.
171 * The function will error out if, although unlikely, `qrh.dat` contains an
172 index that is not listed in #ALL_PUEO_ANTENNA_NAMES.
177┌────────┬──────┬───────────┬────────┬───────┬────────┬───────┬────────────┬──────────┬───────────┐
178│ AntNum ┆ Ring ┆ PhiSector ┆ AntIdx ┆ X[m] ┆ Y[m] ┆ Z[m] ┆ Pitch[deg] ┆ Yaw[deg] ┆ Roll[deg] │
179│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
180│ enum ┆ u8 ┆ u8 ┆ u32 ┆ f64 ┆ f64 ┆ f64 ┆ f64 ┆ f64 ┆ f64 │
181╞════════╪══════╪═══════════╪════════╪═══════╪════════╪═══════╪════════════╪══════════╪═══════════╡
182│ 101 ┆ 1 ┆ 1 ┆ 0 ┆ 1.488 ┆ 0.0 ┆ 5.114 ┆ -10.0 ┆ 0.0 ┆ 0.0 │
183│ 201 ┆ 2 ┆ 1 ┆ 1 ┆ 2.596 ┆ 0.0 ┆ 1.457 ┆ -10.0 ┆ 0.0 ┆ 0.0 │
184│ 301 ┆ 3 ┆ 1 ┆ 2 ┆ 2.596 ┆ 0.0 ┆ 0.728 ┆ -10.0 ┆ 0.0 ┆ 0.0 │
185│ 401 ┆ 4 ┆ 1 ┆ 3 ┆ 2.596 ┆ 0.0 ┆ 0.0 ┆ -10.0 ┆ 0.0 ┆ 0.0 │
186│ 102 ┆ 1 ┆ 2 ┆ 4 ┆ 1.33 ┆ 0.356 ┆ 4.476 ┆ -10.0 ┆ 15.0 ┆ 0.0 │
187│ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … ┆ … │
188│ 423 ┆ 4 ┆ 23 ┆ 91 ┆ 2.248 ┆ -1.298 ┆ 0.0 ┆ -10.0 ┆ -30.0 ┆ 0.0 │
189│ 124 ┆ 1 ┆ 24 ┆ 92 ┆ 1.33 ┆ -0.357 ┆ 4.476 ┆ -10.0 ┆ -15.0 ┆ 0.0 │
190│ 224 ┆ 2 ┆ 24 ┆ 93 ┆ 2.507 ┆ -0.672 ┆ 1.457 ┆ -10.0 ┆ -15.0 ┆ 0.0 │
191│ 324 ┆ 3 ┆ 24 ┆ 94 ┆ 2.507 ┆ -0.672 ┆ 0.728 ┆ -10.0 ┆ -15.0 ┆ 0.0 │
192│ 424 ┆ 4 ┆ 24 ┆ 95 ┆ 2.507 ┆ -0.672 ┆ 0.0 ┆ -10.0 ┆ -15.0 ┆ 0.0 │
193└────────┴──────┴───────────┴────────┴───────┴────────┴───────┴────────────┴──────────┴───────────┘
198 version = [int(i)
for i
in pl.__version__.split(
".")]
202 assert minor > 26
and minor != 31,
"Bad polars version"
208 if ant_type
in (
'MI',
'LF'):
209 antenna_phi_sector_mapping = (
210 read_global_channel_mapping()
211 .filter(pl.col(
"AntType") == ant_type, pl.col(
"Pol") ==
"V")
212 .select(
"AntNum",
"AntIdx",
"PhiSector",
"Ring")
215 print(
"Ant type is not supported!")
218 ants: pl.DataFrame = (
223 "AntNum": ALL_PUEO_ANTENNA_NAMES,
224 "Pitch[deg]": pl.Float64,
225 "Yaw[deg]": pl.Float64,
226 "Roll[deg]": pl.Float64,
229 .join(antenna_phi_sector_mapping, on=
"AntNum")
231 pl.col(
"AntNum",
"Ring",
"PhiSector",
"AntIdx"),
232 pl.col(
"X[m]",
"Y[m]",
"Z[m]",
"Pitch[deg]",
"Yaw[deg]",
"Roll[deg]")
237 offset_df = pl.read_csv(
242 new_columns=[
"Pol",
"dx[m]",
"dy[m]",
"dz[m]"],
245 dx, dy, dz = offset_df.select([
"dx[m]",
"dy[m]",
"dz[m]"]).row(0)
248 ants = ants.with_columns(
249 (pl.col(
"X[m]") + dx).alias(
"X[m]"),
250 (pl.col(
"Y[m]") + dy).alias(
"Y[m]"),
251 (pl.col(
"Z[m]") + dz).alias(
"Z[m]"),
254 return ants.sort(
"AntNum")
257def get_nominal_phase_center(face_centers: pl.DataFrame, coordinates: str =
None, ant_type=
'MI') -> pl.DataFrame:
258 """!Takes the antenna **face centers** and returns the (nominal) antenna **phase centers**
260 @param[in] face_centers The output of #read_MI_antenna_geometry
261 @param[in] coordinates can be "cylindrical" or default (Cartesian)
262 @retval phase_centers Same as `face_centers` except `X[m]`, `Y[m]`, and `Z[m]` now represent
265* Essential columns: `face_centers` **must** contain `X[m]` and `Y[m]`
266* The nominal phase center is obtained by taking the face center and moving radially inward by 30 cm
267 (cylindrical coordinates).
268* If `coordinates` is not specified then Cartesian coordinates is used, and the output schema is
281* If `coordinates` is `cylindrical`, then the ouput schema is
300 rho = (x ** 2 + y ** 2).sqrt()
302 rho = (x ** 2 + y ** 2).sqrt()
304 phi = pl.arctan2(y, x)
306 new_x = rho * phi.cos()
307 new_y = rho * phi.sin()
309 if coordinates
is None:
310 phase_centers = face_centers.with_columns(new_x.alias(
"X[m]"), new_y.alias(
"Y[m]"))
312 elif coordinates ==
"cylindrical":
313 phase_centers = face_centers.select(
314 pl.all().exclude(
"X[m]",
"Y[m]"),
316 phi.alias(
"Phi[rad]")
319 print(f
"I can't handle this coordinate system: {coordinates}")
325if __name__ ==
"__main__":
326 pl.Config().set_tbl_cols(10)
328 print(read_global_channel_mapping())
334 ants = read_antenna_geometry(
335 os.environ.get(
"PUEO_UTIL_INSTALL_DIR") / Path(
"share/pueo/geometry/jun25/qrh.dat"),
340 print(get_nominal_phase_center(ants, coordinates=
'cylindrical', ant_type=
'MI'))