Add 'wavestaff.py'
commit
65119499ae
@ -0,0 +1,176 @@
|
||||
import os
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
|
||||
def read_wavestaff_file(csv_name,
|
||||
m=None,
|
||||
b=None,
|
||||
length=2000,
|
||||
raw=False,
|
||||
abs_time=True):
|
||||
"""Read data from Wave Logger ASCII data file.
|
||||
|
||||
The Wave Logger III stores water level observations as unsigned 12-bit
|
||||
integers (uint12), ranging from 0 to 4095. The input variables 'm' and 'b'
|
||||
are the slope and intercept of the function:
|
||||
|
||||
eta = m * x + b
|
||||
|
||||
where 'x' is the raw data value. If 'm' and 'b' are not provided, the
|
||||
nominal factory calibration will be applied, based on length of the staff.
|
||||
E.g. if the staff length is 2000 mm, then x = 0 when the probe is dry, and
|
||||
x = 4095 when the depth at the staff is 2 m. This gives:
|
||||
|
||||
m = 2000 / 4095
|
||||
= 0.488
|
||||
|
||||
Args:
|
||||
csv_name: path to Wave Logger ASCII data file
|
||||
m: calibrated slope (mm / uint12)
|
||||
b: calibrated zero offset (mm)
|
||||
length: length of wave staff probe (mm)
|
||||
raw: return raw uint12 values instead of calibrated units
|
||||
abs_time: use absolute date and time (otherwise use relative seconds)
|
||||
|
||||
Returns:
|
||||
pandas dataframe of all data blocks
|
||||
"""
|
||||
|
||||
meta, blocks = get_file_info(csv_name)
|
||||
|
||||
master = pd.DataFrame()
|
||||
for block in blocks:
|
||||
raw_data = pd.read_csv(
|
||||
csv_name,
|
||||
skiprows=block['start_line'],
|
||||
nrows=block['nrows'],
|
||||
header=None,
|
||||
usecols=range(13))
|
||||
|
||||
# Get water level observations (m)
|
||||
val = raw_data.iloc[:, :-1].values.flatten()
|
||||
|
||||
data = {}
|
||||
if raw:
|
||||
# Use raw values
|
||||
data['eta (raw)'] = val
|
||||
elif bool(m) & bool(b):
|
||||
# Calculate eta based on calibration values
|
||||
data['eta (mm)'] = val * m + b
|
||||
else:
|
||||
# Calculate eta based on staff length
|
||||
data['eta, uncalibrated (mm)'] = val * length / 4095
|
||||
|
||||
# Get temperature (degrees C)
|
||||
temperature = raw_data.iloc[:, -1].values / 16
|
||||
data['temperature (C)'] = np.repeat(temperature, 12)
|
||||
|
||||
# Generate timestamps
|
||||
dt = 1 / meta['frequency']
|
||||
t_seconds = np.arange(len(val)) * dt
|
||||
|
||||
if abs_time:
|
||||
# Add start time if absolute date and time is required
|
||||
t = pd.TimedeltaIndex(t_seconds, 's') + block['start_time']
|
||||
index_name = 'datetime'
|
||||
|
||||
else:
|
||||
# Use seconds if relative time is specified
|
||||
t = t_seconds
|
||||
index_name = 'time (s)'
|
||||
|
||||
# Put all results into dataframe
|
||||
master = master.append(pd.DataFrame(data, index=t))
|
||||
master.index.name = index_name
|
||||
|
||||
return master
|
||||
|
||||
|
||||
def parse_header(line):
|
||||
"""Parse header line from Wave Logger ASCII data file.
|
||||
|
||||
Args:
|
||||
line: header string from top of file or burst block
|
||||
|
||||
Returns:
|
||||
start time (datetime object), and metadata (dict)
|
||||
"""
|
||||
|
||||
descriptions = {
|
||||
'y': 'year',
|
||||
'm': 'month',
|
||||
'd': 'day',
|
||||
'H': 'hour',
|
||||
'M': 'minute',
|
||||
'S': 'second',
|
||||
'F': 'frequency',
|
||||
'L': 'burst_length',
|
||||
'I': 'burst_interval',
|
||||
'N': 'newfile_interval',
|
||||
'A': 'scale',
|
||||
'C': 'cycle_count',
|
||||
'T': 'logger_type',
|
||||
'R': 'reserved',
|
||||
}
|
||||
|
||||
items = [s for s in line.split(',') if s]
|
||||
meta = {}
|
||||
for i, s in enumerate(items):
|
||||
if i < 3:
|
||||
# Use lowercase keys for year, month, day
|
||||
key = s[0].lower()
|
||||
else:
|
||||
# Use uppercase keys for other items
|
||||
key = s[0]
|
||||
|
||||
# Add metadata value
|
||||
meta[descriptions[key]] = int(s[1:])
|
||||
|
||||
# Get start time
|
||||
start_time = pd.datetime(
|
||||
2000 + meta['year'],
|
||||
meta['month'],
|
||||
meta['day'],
|
||||
meta['hour'],
|
||||
meta['minute'],
|
||||
meta['second'],
|
||||
)
|
||||
|
||||
return start_time, meta
|
||||
|
||||
|
||||
def get_file_info(csv_name):
|
||||
"""Get file info from Wave Logger ASCII data file.
|
||||
|
||||
Args:
|
||||
csv_name: path to Wave Logger ASCII data file
|
||||
|
||||
Returns:
|
||||
file metadata (dict), and data blocks (list of dicts)
|
||||
"""
|
||||
|
||||
blocks = []
|
||||
# Get start and end lines for all data blocks
|
||||
with open(csv_name, 'r') as f:
|
||||
header, *lines = f.read().splitlines()
|
||||
for i, line in enumerate(lines, start=2):
|
||||
if len(blocks) > 0 & (line.startswith('Y') |
|
||||
(i == len(lines) + 1)):
|
||||
# Add end line and line count for previous block
|
||||
b = blocks[-1]
|
||||
b['end_line'] = i - 2
|
||||
b['nrows'] = b['end_line'] - b['start_line'] + 1
|
||||
|
||||
if line.startswith('Y'):
|
||||
# Add start line for current block
|
||||
block = {}
|
||||
block['start_line'] = i
|
||||
# Get start time for current block
|
||||
block['start_time'] = parse_header(line)[0]
|
||||
blocks.append(block)
|
||||
|
||||
# Get metadata from file header
|
||||
meta = parse_header(header)[-1]
|
||||
|
||||
return meta, blocks
|
Loading…
Reference in New Issue