Source code for mth5.io.nims.header
# -*- coding: utf-8 -*-
"""
Created on Thu Sep 1 12:57:32 2022
@author: jpeacock
"""
# =============================================================================
# Imports
# =============================================================================
from pathlib import Path
import dateutil
import logging
from mt_metadata.utils.mttime import MTime
# =============================================================================
[docs]class NIMSHeader(object):
"""
class to hold the NIMS header information.
A typical header looks like
.. code-block::
'''
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
>>>user field>>>>>>>>>>>>>>>>>>>>>>>>>>>>
SITE NAME: Budwieser Spring
STATE/PROVINCE: CA
COUNTRY: USA
>>> The following code in double quotes is REQUIRED to start the NIMS <<
>>> The next 3 lines contain values required for processing <<<<<<<<<<<<
>>> The lines after that are optional <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
"300b" <-- 2CHAR EXPERIMENT CODE + 3 CHAR SITE CODE + RUN LETTER
1105-3; 1305-3 <-- SYSTEM BOX I.D.; MAG HEAD ID (if different)
106 0 <-- N-S Ex WIRE LENGTH (m); HEADING (deg E mag N)
109 90 <-- E-W Ey WIRE LENGTH (m); HEADING (deg E mag N)
1 <-- N ELECTRODE ID
3 <-- E ELECTRODE ID
2 <-- S ELECTRODE ID
4 <-- W ELECTRODE ID
Cu <-- GROUND ELECTRODE INFO
GPS INFO: 01/10/19 16:16:42 1616.7000 3443.6088 115.7350 W 946.6
OPERATOR: KP
COMMENT: N/S CRS: .95/.96 DCV: 3.5 ACV:1
E/W CRS: .85/.86 DCV: 1.5 ACV: 1
Redeployed site for run b b/c possible animal disturbance
'''
"""
def __init__(self, fn=None):
self.logger = logging.getLogger(
f"{__name__}.{self.__class__.__name__}"
)
self.fn = fn
self._max_header_length = 1000
self.header_dict = None
self.site_name = None
self.state_province = None
self.country = None
self.box_id = None
self.mag_id = None
self.ex_length = None
self.ex_azimuth = None
self.ey_length = None
self.ey_azimuth = None
self.n_electrode_id = None
self.s_electrode_id = None
self.e_electrode_id = None
self.w_electrode_id = None
self.ground_electrode_info = None
self.header_gps_stamp = None
self.header_gps_longitude = None
self.header_gps_longitude = None
self.header_gps_elevation = None
self.operator = None
self.comments = None
self.run_id = None
self.data_start_seek = 0
@property
def fn(self):
"""Full path to NIMS file"""
return self._fn
@fn.setter
def fn(self, value):
if value is not None:
self._fn = Path(value)
else:
self._fn = None
@property
def station(self):
"""Station ID"""
if self.run_id is not None:
return self.run_id[0:-1]
@property
def file_size(self):
"""Size of the file"""
if self.fn is not None:
return self.fn.stat().st_size
[docs] def read_header(self, fn=None):
"""
read header information
:param fn: full path to file to read
:type fn: string or :class:`pathlib.Path`
:raises: :class:`mth5.io.nims.NIMSError` if something is not right.
"""
if fn is not None:
self.fn = fn
if not self.fn.exists():
msg = f"Could not find nims file {self.fn}"
self.logger.error(msg)
raise NIMSError(msg)
self.logger.debug(f"Reading NIMS file {self.fn}")
### load in the entire file, its not too big
with open(self.fn, "rb") as fid:
header_str = fid.read(self._max_header_length)
header_list = header_str.split(b"\r")
self.header_dict = {}
last_index = len(header_list)
last_line = header_list[-1]
for ii, line in enumerate(header_list[0:-1]):
if ii == last_index:
break
if b"comments" in line.lower():
last_line = header_list[ii + 1]
last_index = ii + 1
line = line.decode()
if line.find(">") == 0:
continue
elif line.find(":") > 0:
key, value = line.split(":", 1)
self.header_dict[key.strip().lower()] = value.strip()
elif line.find("<--") > 0:
value, key = line.split("<--")
self.header_dict[key.strip().lower()] = value.strip()
### sometimes there are some spaces before the data starts
if last_line.count(b" ") > 0:
if last_line[0:1] == b" ":
last_line = last_line.strip()
else:
last_line = last_line.split()[1].strip()
data_start_byte = last_line[0:1]
### sometimes there are rogue $ around
if data_start_byte in [b"$", b"g"]:
data_start_byte = last_line[1:2]
self.data_start_seek = header_str.find(data_start_byte)
self.parse_header_dict()
[docs] def parse_header_dict(self, header_dict=None):
"""
parse the header dictionary into something useful
"""
if header_dict is not None:
self.header_dict = header_dict
assert isinstance(self.header_dict, dict)
for key, value in self.header_dict.items():
if "wire" in key:
if key.find("n") == 0:
self.ex_length = float(value.split()[0])
self.ex_azimuth = float(value.split()[1])
elif key.find("e") == 0:
self.ey_length = float(value.split()[0])
self.ey_azimuth = float(value.split()[1])
elif "system" in key:
self.box_id = value.split(";")[0].strip()
self.mag_id = value.split(";")[1].strip()
elif "gps" in key:
gps_list = value.split()
self.header_gps_stamp = MTime(
dateutil.parser.parse(
" ".join(gps_list[0:2]), dayfirst=True
)
)
self.header_gps_latitude = self._get_latitude(
gps_list[2], gps_list[3]
)
self.header_gps_longitude = self._get_longitude(
gps_list[4], gps_list[5]
)
self.header_gps_elevation = float(gps_list[6])
elif "run" in key:
self.run_id = value.replace('"', "")
else:
setattr(self, key.replace(" ", "_").replace("/", "_"), value)
def _get_latitude(self, latitude, hemisphere):
"""
Get latitude as decimal degrees
:param latitude: latitude
:type latitude: float
:param hemisphere: hemisphere id [ 'N' | 'S' ]
:type hemisphere: string
:return: latitude in decimal degrees with proper sign
:rtype: float
"""
if not isinstance(latitude, float):
latitude = float(latitude)
if hemisphere.lower() == "n":
return latitude
if hemisphere.lower() == "s":
return -1 * latitude
def _get_longitude(self, longitude, hemisphere):
"""
Get longitude as decimal degrees
:param longitude: longitude
:type longitude: float
:param hemisphere: hemisphere id [ 'N' | 'S' ]
:type hemisphere: string
:return: latitude in decimal degrees with proper sign
:rtype: float
"""
if not isinstance(longitude, float):
longitude = float(longitude)
if hemisphere.lower() == "e":
return longitude
if hemisphere.lower() == "w":
return -1 * longitude