Coverage for C: \ Users \ peaco \ OneDrive \ Documents \ GitHub \ mt_metadata \ mt_metadata \ transfer_functions \ io \ emtfxml \ metadata \ run.py: 92%

77 statements  

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

1# ===================================================== 

2# Imports 

3# ===================================================== 

4from typing import Annotated 

5 

6import numpy as np 

7import pandas as pd 

8from loguru import logger 

9from pydantic import Field, field_validator 

10 

11from mt_metadata.base import MetadataBase 

12from mt_metadata.base.helpers import element_to_string 

13from mt_metadata.common import Comment 

14from mt_metadata.common.mttime import MTime 

15from mt_metadata.transfer_functions.io.emtfxml.metadata import helpers 

16 

17from . import Dipole, Instrument, Magnetometer 

18 

19 

20# ===================================================== 

21class Run(MetadataBase): 

22 errors: Annotated[ 

23 str | None, 

24 Field( 

25 default=None, 

26 description="Any field errors", 

27 alias=None, 

28 json_schema_extra={ 

29 "units": None, 

30 "required": False, 

31 "examples": ["moose ate cables"], 

32 }, 

33 ), 

34 ] 

35 

36 run: Annotated[ 

37 str, 

38 Field( 

39 default="", 

40 description="Run name", 

41 alias=None, 

42 json_schema_extra={ 

43 "units": None, 

44 "required": True, 

45 "examples": ["mt001a"], 

46 }, 

47 ), 

48 ] 

49 

50 sampling_rate: Annotated[ 

51 float | None, 

52 Field( 

53 default=None, 

54 description="Sample rate of the run", 

55 alias=None, 

56 json_schema_extra={ 

57 "units": "samples per second", 

58 "required": False, 

59 "examples": ["1"], 

60 }, 

61 ), 

62 ] 

63 

64 start: Annotated[ 

65 MTime | str | float | int | np.datetime64 | pd.Timestamp, 

66 Field( 

67 default_factory=lambda: MTime(time_stamp=None), 

68 description="Date time when the data collection started", 

69 alias=None, 

70 json_schema_extra={ 

71 "units": None, 

72 "required": True, 

73 "examples": ["2020-01-01T12:00:00"], 

74 }, 

75 ), 

76 ] 

77 

78 end: Annotated[ 

79 MTime | str | float | int | np.datetime64 | pd.Timestamp, 

80 Field( 

81 default_factory=lambda: MTime(time_stamp=None), 

82 description="Date time when the data collection ended", 

83 alias=None, 

84 json_schema_extra={ 

85 "units": None, 

86 "required": True, 

87 "examples": ["2020-05-01T12:00:00"], 

88 }, 

89 ), 

90 ] 

91 comments: Annotated[ 

92 Comment | str | None, 

93 Field( 

94 default_factory=Comment, # type: ignore 

95 description="Comments about the run", 

96 alias=None, 

97 json_schema_extra={ 

98 "units": None, 

99 "required": False, 

100 "examples": ["Comment(text='This is a comment')"], 

101 }, 

102 ), 

103 ] 

104 

105 instrument: Annotated[ 

106 Instrument, 

107 Field( 

108 default_factory=Instrument, # type: ignore 

109 description="Instrument used for the run", 

110 alias=None, 

111 json_schema_extra={ 

112 "units": None, 

113 "required": False, 

114 "examples": ["Instrument(name='MT Sensor', type='magnetometer')"], 

115 }, 

116 ), 

117 ] 

118 

119 magnetometer: Annotated[ 

120 list[Magnetometer], 

121 Field( 

122 default_factory=list, 

123 description="List of magnetometers used in the run", 

124 alias=None, 

125 json_schema_extra={ 

126 "units": None, 

127 "required": False, 

128 "examples": ["Magnetometer(name='Magnetometer 1', type='fluxgate')"], 

129 }, 

130 ), 

131 ] 

132 dipole: Annotated[ 

133 list[Dipole], 

134 Field( 

135 default_factory=list, 

136 description="List of dipoles used in the run", 

137 alias=None, 

138 json_schema_extra={ 

139 "units": None, 

140 "required": False, 

141 "examples": ["Dipole(name='Dipole 1', type='fluxgate')"], 

142 }, 

143 ), 

144 ] 

145 

146 @field_validator("start", "end", mode="before") 

147 @classmethod 

148 def validate_start( 

149 cls, field_value: MTime | float | int | np.datetime64 | pd.Timestamp | str 

150 ): 

151 return MTime(time_stamp=field_value) 

152 

153 @field_validator("comments", mode="before") 

154 @classmethod 

155 def validate_comments(cls, field_value: Comment | str | None): 

156 if isinstance(field_value, str): 

157 return Comment(value=field_value) 

158 return field_value 

159 

160 def read_dict(self, input_dict: dict) -> None: 

161 """ 

162 Field notes are odd so have a special reader to do it piece by 

163 painstaking piece. 

164 

165 :param input_dict: input dictionary containing run data 

166 :type input_dict: dict 

167 :return: None 

168 :rtype: None 

169 

170 """ 

171 

172 self.run = input_dict["run"] 

173 self.instrument.from_dict({"instrument": input_dict["instrument"]}) 

174 self.sampling_rate = input_dict["sampling_rate"] 

175 self.start = input_dict["start"] 

176 self.end = input_dict["end"] 

177 try: 

178 if isinstance(input_dict["comments"], list): 

179 self.comments.from_dict({"comments": input_dict["comments"][0]}) 

180 else: 

181 self.comments.from_dict({"comments": input_dict["comments"]}) 

182 except KeyError: 

183 logger.debug("run has no comments") 

184 self.errors = input_dict["errors"] 

185 

186 try: 

187 if isinstance(input_dict["magnetometer"], list): 

188 self.magnetometer = [] 

189 for mag in input_dict["magnetometer"]: 

190 m = Magnetometer() 

191 m.from_dict({"magnetometer": mag}) 

192 self.magnetometer.append(m) 

193 else: 

194 self.magnetometer = [] 

195 m = Magnetometer() 

196 m.from_dict({"magnetometer": input_dict["magnetometer"]}) 

197 self.magnetometer.append(m) 

198 except KeyError: 

199 logger.debug("run has no magnetotmeter information") 

200 

201 try: 

202 if isinstance(input_dict["dipole"], list): 

203 self.dipole = [] 

204 for mag in input_dict["dipole"]: 

205 m = Dipole() 

206 m.from_dict({"dipole": mag}) 

207 self.dipole.append(m) 

208 else: 

209 m = Dipole() 

210 m.from_dict({"dipole": input_dict["dipole"]}) 

211 self.dipole.append(m) 

212 except KeyError: 

213 logger.debug("run has no dipole information") 

214 

215 def to_xml(self, string=False, required=True): 

216 """ 

217 

218 :param string: DESCRIPTION, defaults to True 

219 :type string: TYPE, optional 

220 :param required: DESCRIPTION, defaults to False 

221 :type required: TYPE, optional 

222 :return: DESCRIPTION 

223 :rtype: TYPE 

224 

225 """ 

226 element = helpers.to_xml( 

227 self, 

228 string=False, 

229 required=required, 

230 order=[ 

231 "instrument", 

232 "magnetometer", 

233 "dipole", 

234 "comments", 

235 "errors", 

236 "sampling_rate", 

237 "start", 

238 "end", 

239 ], 

240 ) 

241 element.attrib = {"run": self.run} 

242 element.tag = "field_notes" 

243 

244 element.find("SamplingRate").attrib["units"] = "Hz" 

245 

246 if string: 

247 return element_to_string(element) 

248 return element