Coverage for C: \ Users \ peaco \ OneDrive \ Documents \ GitHub \ mth5 \ mth5 \ io \ reader.py: 79%

38 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-10 00:01 -0800

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

2""" 

3Universal reader for magnetotelluric time series data files. 

4 

5This module provides a plugin-like system for reading various MT data formats 

6and returning appropriate :class:`mth5.timeseries` objects. The reader 

7automatically detects file types and dispatches to the correct parser. 

8 

9Plugin Structure 

10---------------- 

11If you are writing your own reader, implement the following structure: 

12 

13 * Class object that reads the given file format 

14 * A reader function named read_{file_type} (e.g., read_nims) 

15 * Return value should be a :class:`mth5.timeseries.MTTS` or 

16 :class:`mth5.timeseries.RunTS` object plus extra metadata as a 

17 dictionary with keys formatted as {level.attribute} 

18 

19Example Implementation 

20---------------------- 

21.. code-block:: python 

22 

23 class NewFile: 

24 def __init__(self, fn): 

25 self.fn = fn 

26 

27 def read_header(self): 

28 return header_information 

29 

30 def read_newfile(self): 

31 ex, ey, hx, hy, hz = read_in_channels_as_MTTS 

32 return RunTS([ex, ey, hx, hy, hz]) 

33 

34 def read_newfile(fn): 

35 new_file_obj = NewFile(fn) 

36 run_obj = new_file_obj.read_newfile() 

37 return run_obj, extra_metadata 

38 

39Then add your reader to the readers dictionary for automatic detection. 

40 

41See Also 

42-------- 

43Existing readers in `mth5.io` for implementation guidance. 

44 

45Created on Wed Aug 26 10:32:45 2020 

46 

47:author: Jared Peacock 

48:license: MIT 

49""" 

50 

51# ============================================================================= 

52# Imports 

53# ============================================================================= 

54from __future__ import annotations 

55 

56from pathlib import Path 

57from typing import Any, Callable 

58 

59from loguru import logger 

60 

61from mth5.io import lemi, metronix, miniseed, nims, phoenix, usgs_ascii, zen 

62 

63 

64# ============================================================================= 

65# Reader registry for MT data formats 

66# ============================================================================= 

67readers: dict[str, dict[str, Any]] = { 

68 "zen": {"file_types": ["z3d"], "reader": zen.read_z3d}, 

69 "nims": {"file_types": ["bin", "bnn"], "reader": nims.read_nims}, 

70 "usgs_ascii": { 

71 "file_types": ["asc", "zip"], 

72 "reader": usgs_ascii.read_ascii, 

73 }, 

74 "miniseed": { 

75 "file_types": ["miniseed", "ms", "mseed"], 

76 "reader": miniseed.read_miniseed, 

77 }, 

78 "lemi424": { 

79 "file_types": ["txt"], 

80 "reader": lemi.read_lemi424, 

81 }, 

82 "phoenix": { 

83 "file_types": ["bin", "td_30", "td_150", "td_24k"], 

84 "reader": phoenix.read_phoenix, 

85 }, 

86 "metronix": { 

87 "file_types": ["atss"], 

88 "reader": metronix.read_atss, 

89 }, 

90} 

91 

92 

93def get_reader(extension: str) -> tuple[str, Callable]: 

94 """ 

95 Get the appropriate reader function for a file extension. 

96 

97 Searches the reader registry to find the correct parser function 

98 for the given file extension. Handles ambiguous extensions by 

99 issuing warnings when multiple readers might apply. 

100 

101 Parameters 

102 ---------- 

103 extension : str 

104 File extension (without the dot) to find a reader for 

105 

106 Returns 

107 ------- 

108 tuple[str, Callable] 

109 Tuple containing: 

110 - Reader name (str): Identifier for the reader type 

111 - Reader function (Callable): Function to parse files of this type 

112 

113 Raises 

114 ------ 

115 ValueError 

116 If no reader is found for the given file extension 

117 

118 Examples 

119 -------- 

120 >>> reader_name, reader_func = get_reader("z3d") 

121 >>> print(reader_name) # "zen" 

122 >>> data = reader_func("/path/to/file.z3d") 

123 

124 Notes 

125 ----- 

126 Some extensions like "bin" are ambiguous and could match multiple 

127 readers (NIMS or Phoenix). A warning is issued in such cases. 

128 """ 

129 if extension in ["bin"]: 

130 logger.warning("Suggest inputing file type, bin could be nims or phoenix") 

131 for key, vdict in readers.items(): 

132 if extension.lower() in vdict["file_types"]: 

133 return key, vdict["reader"] 

134 msg = f"Could not find a reader for file type {extension}" 

135 logger.error(msg) 

136 raise ValueError(msg) 

137 

138 

139def read_file( 

140 fn: str | Path | list[str | Path], file_type: str | None = None, **kwargs: Any 

141) -> Any: 

142 """ 

143 Universal reader for magnetotelluric time series data files. 

144 

145 Automatically detects the file type based on extension and dispatches 

146 to the appropriate reader function. Supports both single files and 

147 lists of files for multi-file formats. 

148 

149 Parameters 

150 ---------- 

151 fn : str, Path, or list of str/Path 

152 Full path(s) to data file(s) to be read. For multi-file formats, 

153 pass a list of file paths. 

154 file_type : str, optional 

155 Specific reader type to use if file extension is ambiguous. 

156 Must be one of the keys in the readers registry, by default None 

157 **kwargs : dict 

158 Additional keyword arguments passed to the specific reader function. 

159 Supported arguments depend on the file format and reader. 

160 

161 Returns 

162 ------- 

163 MTTS or RunTS 

164 Time series object containing the data: 

165 - :class:`mth5.timeseries.MTTS` for single channel data 

166 - :class:`mth5.timeseries.RunTS` for multi-channel run data 

167 

168 Raises 

169 ------ 

170 IOError 

171 If any specified file does not exist 

172 KeyError 

173 If the specified file_type is not supported 

174 ValueError 

175 If no reader can be found for the file extension 

176 

177 Examples 

178 -------- 

179 Read a single Z3D file (auto-detected) 

180 

181 >>> data = read_file("/path/to/station_001.z3d") 

182 >>> print(type(data)) # <class 'mth5.timeseries.ChannelTS'> 

183 

184 Read with explicit file type for ambiguous extensions 

185 

186 >>> data = read_file("/path/to/data.bin", file_type="nims") 

187 >>> print(data.n_channels) 

188 

189 Read multiple files for a multi-file format 

190 

191 >>> files = ["/path/to/file1.asc", "/path/to/file2.asc"] 

192 >>> run_data = read_file(files, sample_rate=1.0) 

193 

194 Notes 

195 ----- 

196 Supported file types and extensions: 

197 - zen: .z3d (Zonge Z3D files) 

198 - nims: .bin, .bnn (USGS NIMS files) 

199 - usgs_ascii: .asc, .zip (USGS ASCII format) 

200 - miniseed: .miniseed, .ms, .mseed (miniSEED format) 

201 - lemi424: .txt (LEMI-424 format) 

202 - phoenix: .bin, .td_30, .td_150, .td_24k (Phoenix formats) 

203 - metronix: .atss (Metronix ADU format) 

204 

205 For ambiguous extensions like .bin, specify file_type explicitly. 

206 """ 

207 

208 if isinstance(fn, (list, tuple)): 

209 fn = [Path(ff) for ff in fn] 

210 if not fn[0].exists(): 

211 msg = f"Could not find file {fn}. Check path." 

212 logger.error(msg) 

213 raise IOError(msg) 

214 file_ext = fn[0].suffix[1:] 

215 

216 else: 

217 fn = Path(fn) 

218 if not fn.exists(): 

219 msg = f"Could not find file {fn}. Check path." 

220 logger.error(msg) 

221 raise IOError(msg) 

222 file_ext = fn.suffix[1:] 

223 

224 if file_type is not None: 

225 try: 

226 file_reader = readers[file_type]["reader"] 

227 except KeyError: 

228 msg = ( 

229 f"Reader for the file type {file_type} does not currently exist. " 

230 f"Current readers {list(readers.keys())}" 

231 ) 

232 logger.error(msg) 

233 raise KeyError(msg) 

234 else: 

235 file_type, file_reader = get_reader(file_ext) 

236 return file_reader(fn, **kwargs)