Coverage for C: \ Users \ peaco \ OneDrive \ Documents \ GitHub \ mth5 \ mth5 \ io \ zen \ z3d_schedule.py: 89%

70 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-27 20:09 -0800

1# -*- coding: utf-8 -*- 

2""" 

3Z3D Schedule File Parser 

4 

5This module provides functionality for parsing schedule information from Z3D files. 

6The Z3DSchedule class extracts and processes schedule metadata stored at offset 512 

7in Z3D files, providing access to various recording parameters and timing information. 

8 

9Created on Wed Aug 24 11:24:57 2022 

10@author: jpeacock 

11""" 

12 

13from __future__ import annotations 

14 

15from pathlib import Path 

16from typing import Any, BinaryIO 

17 

18from loguru import logger 

19from mt_metadata.common import MTime 

20 

21 

22class Z3DSchedule: 

23 """ 

24 Parser for Z3D file schedule information and metadata. 

25 

26 Reads schedule information from Z3D files and creates object attributes 

27 for each metadata entry. Schedule data is stored at byte offset 512 in 

28 Z3D files and contains recording parameters, timing information, and 

29 instrument configuration settings. 

30 

31 The class preserves the original capitalization from the Z3D file format 

32 and provides automatic parsing of key-value pairs in the schedule section. 

33 

34 Parameters 

35 ---------- 

36 fn : str | pathlib.Path, optional 

37 Full path to Z3D file to read schedule information from. 

38 Can be string path or pathlib.Path object. 

39 fid : BinaryIO, optional 

40 Open file object for reading Z3D file in binary mode. 

41 Example: open('file.z3d', 'rb') 

42 **kwargs : Any 

43 Additional keyword arguments to set as object attributes. 

44 

45 Attributes 

46 ---------- 

47 AutoGain : str | None 

48 Auto gain setting for the recording channel ['Y' or 'N']. 

49 Comment : str | None 

50 User comments or notes for the schedule configuration. 

51 Date : str | None 

52 Date when the schedule action was started in YYYY-MM-DD format. 

53 Duty : str | None 

54 Duty cycle percentage of the transmitter (0-100). 

55 FFTStacks : str | None 

56 Number of FFT stacks used by the transmitter. 

57 Filename : str | None 

58 Original filename that the ZEN instrument assigns to the recording. 

59 Gain : str | None 

60 Gain setting for the recording channel (e.g., '1.0000'). 

61 Log : str | None 

62 Data logging enabled flag ['Y' or 'N']. 

63 NewFile : str | None 

64 Create new file for recording flag ['Y' or 'N']. 

65 Period : str | None 

66 Base period setting for the transmitter in seconds. 

67 RadioOn : str | None 

68 Radio communication enabled flag ['Y', 'N', or 'X']. 

69 SR : str | None 

70 Sampling rate in Hz (originally 'S/R' in file, converted to 'SR'). 

71 SamplesPerAcq : str | None 

72 Number of samples per acquisition for transmitter mode. 

73 Sleep : str | None 

74 Sleep mode enabled flag ['Y' or 'N']. 

75 Sync : str | None 

76 GPS synchronization enabled flag ['Y' or 'N']. 

77 Time : str | None 

78 Time when the schedule action started in HH:MM:SS format (GPS time). 

79 initial_start : MTime 

80 Parsed start time as MTime object with GPS time flag enabled. 

81 Combines Date and Time attributes for timestamp calculation. 

82 fn : str | pathlib.Path | None 

83 Path to the Z3D file being processed. 

84 fid : BinaryIO | None 

85 File object for reading the Z3D file. 

86 meta_string : bytes | None 

87 Raw schedule metadata string read from the file. 

88 _header_len : int 

89 Length of Z3D file header in bytes (512). 

90 _schedule_metadata_len : int 

91 Length of schedule metadata section in bytes (512). 

92 logger : Logger 

93 Loguru logger instance for debugging and status messages. 

94 

95 Notes 

96 ----- 

97 The Z3D file format stores schedule information at a fixed offset of 512 bytes 

98 from the beginning of the file. The schedule section is also 512 bytes long 

99 and contains key-value pairs in the format "Schedule.key = value". 

100 

101 All schedule values are stored as strings to preserve the original format 

102 from the Z3D file. Boolean-like values use 'Y'/'N' convention. 

103 

104 The initial_start attribute automatically converts Date and Time into a 

105 GPS-corrected MTime object for accurate timestamp handling. 

106 

107 Examples 

108 -------- 

109 Read schedule from file path: 

110 

111 >>> from mth5.io.zen import Z3DSchedule 

112 >>> from pathlib import Path 

113 >>> 

114 >>> # Using filename 

115 >>> schedule = Z3DSchedule(fn="recording.z3d") 

116 >>> schedule.read_schedule() 

117 >>> print(f"Sampling rate: {schedule.SR} Hz") 

118 >>> print(f"Start time: {schedule.initial_start}") 

119 

120 Read schedule from file object: 

121 

122 >>> with open("recording.z3d", "rb") as fid: 

123 ... schedule = Z3DSchedule(fid=fid) 

124 ... schedule.read_schedule() 

125 ... print(f"Date: {schedule.Date}, Time: {schedule.Time}") 

126 ... print(f"GPS Sync: {schedule.Sync}") 

127 

128 Access schedule attributes: 

129 

130 >>> schedule = Z3DSchedule() 

131 >>> schedule.read_schedule(fn="recording.z3d") 

132 >>> 

133 >>> # Check recording configuration 

134 >>> if schedule.Sync == 'Y': 

135 ... print("GPS synchronization enabled") 

136 >>> if schedule.Log == 'Y': 

137 ... print("Data logging enabled") 

138 >>> 

139 >>> # Get numeric values (stored as strings) 

140 >>> sample_rate = float(schedule.SR) if schedule.SR else 0 

141 >>> gain_value = float(schedule.Gain) if schedule.Gain else 1.0 

142 """ 

143 

144 def __init__( 

145 self, 

146 fn: str | Path | None = None, 

147 fid: BinaryIO | None = None, 

148 **kwargs: Any, 

149 ) -> None: 

150 """ 

151 Initialize Z3DSchedule parser. 

152 

153 Parameters 

154 ---------- 

155 fn : str | pathlib.Path, optional 

156 Path to Z3D file. Can be string or pathlib.Path object. 

157 fid : BinaryIO, optional 

158 Open file object for reading Z3D file in binary mode. 

159 **kwargs : Any 

160 Additional attributes to set on the instance. 

161 """ 

162 self.logger = logger 

163 self.fn: str | Path | None = fn 

164 self.fid: BinaryIO | None = fid 

165 self.meta_string: bytes | None = None 

166 

167 # Z3D file format constants 

168 self._schedule_metadata_len: int = 512 

169 self._header_len: int = 512 

170 

171 # Schedule metadata attributes (all stored as strings from Z3D format) 

172 self.AutoGain: str | None = None 

173 self.Comment: str | None = None 

174 self.Date: str | None = None 

175 self.Duty: str | None = None 

176 self.FFTStacks: str | None = None 

177 self.Filename: str | None = None 

178 self.Gain: str | None = None 

179 self.Log: str | None = None 

180 self.NewFile: str | None = None 

181 self.Period: str | None = None 

182 self.RadioOn: str | None = None 

183 self.SR: str | None = None # Sampling Rate (S/R becomes SR) 

184 self.SamplesPerAcq: str | None = None 

185 self.Sleep: str | None = None 

186 self.Sync: str | None = None 

187 self.Time: str | None = None 

188 

189 # Parsed start time with GPS correction 

190 self.initial_start: MTime = MTime(time_stamp=None) 

191 

192 # Set any additional attributes from kwargs 

193 for key, value in kwargs.items(): 

194 setattr(self, key, value) 

195 

196 def read_schedule( 

197 self, fn: str | Path | None = None, fid: BinaryIO | None = None 

198 ) -> None: 

199 """ 

200 Read and parse schedule metadata from Z3D file. 

201 

202 Reads the schedule information section from a Z3D file starting at 

203 byte offset 512 (after the header) and parses key-value pairs to 

204 populate object attributes. Automatically creates an MTime object 

205 for the initial start time using GPS time correction. 

206 

207 Parameters 

208 ---------- 

209 fn : str | pathlib.Path, optional 

210 Path to Z3D file to read. Overrides instance fn if provided. 

211 Can be string path or pathlib.Path object. 

212 fid : BinaryIO, optional 

213 Open file object for reading Z3D file. Overrides instance fid if provided. 

214 Must be opened in binary mode ('rb'). 

215 

216 Raises 

217 ------ 

218 UnicodeDecodeError 

219 If schedule metadata cannot be decoded as UTF-8 text. 

220 IndexError 

221 If schedule lines don't match expected "Schedule.key = value" format. 

222 ValueError 

223 If Date/Time values cannot be parsed into valid MTime object. 

224 

225 Notes 

226 ----- 

227 The method performs the following steps: 

228 1. Determines file source (fn parameter, fid parameter, or instance attributes) 

229 2. Seeks to byte offset 512 (after Z3D header) 

230 3. Reads 512 bytes of schedule metadata 

231 4. Splits metadata into lines and parses key-value pairs 

232 5. Sets object attributes for each parsed schedule entry 

233 6. Creates MTime object from Date and Time with GPS correction 

234 

235 Schedule entries must follow the format "Schedule.key = value". 

236 The "Schedule." prefix is removed and "/" characters in keys are stripped 

237 (e.g., "S/R" becomes "SR"). 

238 

239 If both Date and Time are present, they are combined into an MTime object 

240 with GPS time correction applied automatically. 

241 

242 Examples 

243 -------- 

244 Read from file path: 

245 

246 >>> schedule = Z3DSchedule() 

247 >>> schedule.read_schedule(fn="recording.z3d") 

248 >>> print(f"Sampling rate: {schedule.SR}") 

249 

250 Read from open file object: 

251 

252 >>> with open("recording.z3d", "rb") as fid: 

253 ... schedule = Z3DSchedule() 

254 ... schedule.read_schedule(fid=fid) 

255 ... print(f"GPS sync: {schedule.Sync}") 

256 

257 Read using instance attributes: 

258 

259 >>> schedule = Z3DSchedule(fn="recording.z3d") 

260 >>> schedule.read_schedule() # Uses instance fn 

261 >>> print(f"Start time: {schedule.initial_start}") 

262 """ 

263 # Update file parameters if provided 

264 if fn is not None: 

265 self.fn = fn 

266 if fid is not None: 

267 self.fid = fid 

268 

269 # Validate file source is available 

270 if self.fn is None and self.fid is None: 

271 self.logger.warning("No Z3D file to read.") 

272 return 

273 

274 # Read schedule metadata from file 

275 if self.fn is None: 

276 # Use existing file object 

277 if self.fid is not None: 

278 self.fid.seek(self._header_len) 

279 self.meta_string = self.fid.read(self._schedule_metadata_len) 

280 elif self.fn is not None: 

281 # Open file and read, or use existing file object 

282 if self.fid is None: 

283 self.fid = open(self.fn, "rb") 

284 self.fid.seek(self._header_len) 

285 self.meta_string = self.fid.read(self._schedule_metadata_len) 

286 else: 

287 self.fid.seek(self._header_len) 

288 self.meta_string = self.fid.read(self._schedule_metadata_len) 

289 

290 # Parse schedule metadata lines 

291 meta_list = self.meta_string.split(b"\n") 

292 for m_str in meta_list: 

293 try: 

294 m_str_decoded = m_str.decode() 

295 if m_str_decoded.find("=") > 0: 

296 m_list = m_str_decoded.split("=") 

297 # Extract key after "Schedule." prefix 

298 m_key = m_list[0].split(".")[1].strip() 

299 # Clean key name (remove slashes) 

300 m_key = m_key.replace("/", "") 

301 # Extract and clean value 

302 m_value = m_list[1].strip() 

303 # Set as object attribute 

304 setattr(self, m_key, m_value) 

305 except (UnicodeDecodeError, IndexError) as e: 

306 # Skip malformed lines but log for debugging 

307 self.logger.debug(f"Skipped malformed schedule line: {m_str!r} - {e}") 

308 continue 

309 

310 # Create initial start time from Date and Time with GPS correction 

311 try: 

312 if self.Date is not None and self.Time is not None: 

313 timestamp_str = f"{self.Date}T{self.Time}" 

314 self.initial_start = MTime(time_stamp=timestamp_str, gps_time=True) 

315 except Exception as e: 

316 self.logger.warning(f"Could not parse initial start time: {e}") 

317 # Keep default MTime object if parsing fails