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
« 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
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.
9Created on Wed Aug 24 11:24:57 2022
10@author: jpeacock
11"""
13from __future__ import annotations
15from pathlib import Path
16from typing import Any, BinaryIO
18from loguru import logger
19from mt_metadata.common import MTime
22class Z3DSchedule:
23 """
24 Parser for Z3D file schedule information and metadata.
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.
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.
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.
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.
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".
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.
104 The initial_start attribute automatically converts Date and Time into a
105 GPS-corrected MTime object for accurate timestamp handling.
107 Examples
108 --------
109 Read schedule from file path:
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}")
120 Read schedule from file object:
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}")
128 Access schedule attributes:
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 """
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.
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
167 # Z3D file format constants
168 self._schedule_metadata_len: int = 512
169 self._header_len: int = 512
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
189 # Parsed start time with GPS correction
190 self.initial_start: MTime = MTime(time_stamp=None)
192 # Set any additional attributes from kwargs
193 for key, value in kwargs.items():
194 setattr(self, key, value)
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.
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.
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').
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.
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
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").
239 If both Date and Time are present, they are combined into an MTime object
240 with GPS time correction applied automatically.
242 Examples
243 --------
244 Read from file path:
246 >>> schedule = Z3DSchedule()
247 >>> schedule.read_schedule(fn="recording.z3d")
248 >>> print(f"Sampling rate: {schedule.SR}")
250 Read from open file object:
252 >>> with open("recording.z3d", "rb") as fid:
253 ... schedule = Z3DSchedule()
254 ... schedule.read_schedule(fid=fid)
255 ... print(f"GPS sync: {schedule.Sync}")
257 Read using instance attributes:
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
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
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)
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
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