1313import copy
1414import logging
1515from collections import namedtuple
16+ from dataclasses import dataclass , field
1617from datetime import datetime
1718from pathlib import Path
1819from typing import TYPE_CHECKING , Any
2122import pandas as pd
2223from dateutil import tz
2324
24- from turn_by_turn .constants import PLANES
25+ from turn_by_turn .constants import PLANES , MetaDict
2526from turn_by_turn .errors import PTCFormatError
2627from turn_by_turn .structures import TbtData , TransverseData
2728
@@ -66,21 +67,27 @@ def read_tbt(file_path: str | Path) -> TbtData:
6667 lines = lines [header_length :]
6768
6869 # parameters
69- bpms , particles , column_indices , n_turns , n_particles = _read_from_first_turn (lines )
70+ params = _read_from_first_turn (lines )
7071
7172 # read into dict first for speed then convert to DFs
7273 matrices = [
73- {p : {bpm : np .zeros (n_turns ) for bpm in bpms } for p in PLANES } for _ in range (n_particles )
74+ {p : {bpm : np .zeros (params . n_turns ) for bpm in params . bpms } for p in PLANES } for _ in range (params . n_particles )
7475 ]
75- matrices = _read_data (lines , matrices , column_indices )
76- for bunch in range (n_particles ):
76+ matrices = _read_data (lines , matrices , params . column_indices )
77+ for bunch in range (params . n_particles ):
7778 matrices [bunch ] = TransverseData (
7879 X = pd .DataFrame (matrices [bunch ]["X" ]).transpose (),
7980 Y = pd .DataFrame (matrices [bunch ]["Y" ]).transpose (),
8081 )
8182
8283 LOGGER .debug (f"Read Tbt matrices from: '{ file_path .absolute ()} '" )
83- return TbtData (matrices = matrices , date = date , bunch_ids = particles , nturns = n_turns )
84+
85+ meta : MetaDict = {
86+ "date" : date ,
87+ "file" : file_path ,
88+ "source_datatype" : "ptc" ,
89+ }
90+ return TbtData (matrices = matrices , nturns = params .n_turns , bunch_ids = params .particles , meta = meta )
8491
8592
8693def _read_header (lines : Sequence [str ]) -> tuple [datetime , int ]:
@@ -105,19 +112,23 @@ def _read_header(lines: Sequence[str]) -> tuple[datetime, int]:
105112 return datetime .strptime (f"{ date_str [DATE ]} { date_str [TIME ]} " , TIME_FORMAT ), idx_line
106113
107114
108- def _read_from_first_turn (
109- lines : Sequence [str ],
110- ) -> tuple [list [str ], list [int ], dict [Any , Any ], int , int ]:
115+ @dataclass (slots = True )
116+ class TbTParams :
117+ """ Parameters read from the first turn of the file. """
118+ bpms : list [str ] = field (default_factory = list )
119+ particles : list [int ] = field (default_factory = list )
120+ column_indices : dict [Any , Any ] | None = None
121+ n_turns : int = 0
122+ n_particles : int = 0
123+
124+
125+ def _read_from_first_turn (lines : Sequence [str ]) -> TbTParams :
111126 """
112127 Reads the BPMs, particles, column indices and number of turns and particles from the matrices of
113128 the first turn.
114129 """
115130 LOGGER .debug ("Reading first turn to define boundary parameters." )
116- bpms = []
117- particles = []
118- column_indices = None
119- n_turns = 0
120- n_particles = 0
131+ data = TbTParams ()
121132 first_segment = True
122133
123134 for line in lines :
@@ -126,37 +137,38 @@ def _read_from_first_turn(
126137 continue
127138
128139 if parts [0 ] == NAMES : # read column names
129- if column_indices is not None :
140+ if data . column_indices is not None :
130141 raise KeyError (f"{ NAMES } are defined twice in tbt file!" )
131- column_indices = _parse_column_names_to_indices (parts [1 :])
142+ data . column_indices = _parse_column_names_to_indices (parts [1 :])
132143 continue
133144
134145 if parts [0 ] == SEGMENTS : # read segments, append to bunch_id
135146 segment = Segment (* parts [1 :])
136147 if segment .name == SEGMENT_MARKER [0 ]: # start of first segment
137- n_turns = int (segment .turns ) - 1
138- n_particles = int (segment .particles )
148+ data . n_turns = int (segment .turns ) - 1
149+ data . n_particles = int (segment .particles )
139150
140151 elif segment .name == SEGMENT_MARKER [1 ]: # end of first segment
141152 break
142153
143154 else :
144155 first_segment = False
145- bpms .append (segment .name )
156+ data . bpms .append (segment .name )
146157
147158 elif first_segment :
148- if column_indices is None :
159+ if data . column_indices is None :
149160 LOGGER .error ("Columns not defined in Tbt file" )
150161 raise PTCFormatError
151162
152- new_data = _parse_data (column_indices , parts )
163+ new_data = _parse_data (data . column_indices , parts )
153164 particle = int (float (new_data [COLPARTICLE ]))
154- particles .append (particle )
165+ data . particles .append (particle )
155166
156- if len (particles ) == 0 :
157- LOGGER .error ("No matrices found in TbT file" )
158- raise PTCFormatError
159- return bpms , particles , column_indices , n_turns , n_particles
167+ if len (data .particles ) == 0 :
168+ msg = "No particles found in TbT file"
169+ LOGGER .error (msg )
170+ raise PTCFormatError (msg )
171+ return data
160172
161173
162174def _read_data (
0 commit comments