-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathparseGHG.py
More file actions
168 lines (155 loc) · 7.44 KB
/
Copy pathparseGHG.py
File metadata and controls
168 lines (155 loc) · 7.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
import os
import re
import yaml
import json
import zipfile
import datetime
import xmltodict
import configparser
import pandas as pd
from io import TextIOWrapper
from dataclasses import dataclass,field
try:
from . import readSystemConfig
except:
import readSystemConfig
## Written by June Skeeter 11/23/2025
# This script can parse a zipped .ghg file to all sub-components and return relevant data values
# Requires a ghg file output by a LICOR logger
# self.modes:
# 1 - Parse Metadata
# 2 - Read data and dump to a timestamped pandas dataframe
# saveTo: self.mode must == 2, save a GHG file to specified directory with timestamp in name following format output by card convert
# depth:
# base - only files in "root" of ghg, sufficient for most needs
# full - includes subfolders, which gives access to eddypro and config files where present
# Key elements:
# Metadata - dict of header information
# Data - numpy array or pandas timestamped dataframe depending on self.mode
# Timestamp - numpy array in POSIX format from logger time
def load():
c = os.path.dirname(os.path.abspath(__file__))
pth = os.path.join(c,'config_files','defaultMetadata.yml')
with open(pth,'r') as f:
defaults = yaml.safe_load(f)
return(defaults)
@dataclass
class Metadata:
log: bool = False
verbose: bool = False
mode: int = 1
Metadata: dict = field(default_factory=load)
Contents: dict = field(default_factory=lambda:{'data':None,
'metadata':None,
'biometdata':None,
'biometmetadata':None,
'system_config':{},
'eddypro':{}})
class parseGHG(Metadata):
def __init__(self,**kwds):
super().__init__(**kwds)
def parse(self,file,saveTo=None,depth='base',):
try:
with zipfile.ZipFile(file, 'r') as ghgZip:
subFiles=ghgZip.namelist()
if self.verbose == True:
print(f'Contents of {file}: \n\n'+'\n'.join(f for f in subFiles))
fn = os.path.commonprefix([s for s in subFiles if len(os.path.split(s)[0])==0])
self.Metadata['Timestamp'] = pd.to_datetime(datetime.datetime.strptime(
re.search('([0-9]{4}\-[0-9]{2}\-[0-9]{2}T[0-9]{6})',
fn).group(0),'%Y-%m-%dT%H%M%S')).strftime('%Y-%m-%dT%H%M')
# Get all possible contents of ghg file, for now only concerned with .data and .metadata, can expand to biomet and config/calibration files later
for self.file in subFiles:
self.name = self.file.replace(fn,'').replace('.','').lstrip('-')
with ghgZip.open(self.file) as f:
if self.file.endswith('.data') or self.file.endswith('.status'):
self.readDATA(f)
elif self.file.endswith('.metadata') or (self.file.endswith('eddypro') and depth == 'full'):
self.ini2dict(TextIOWrapper(f, 'utf-8'))
elif self.file.endswith('.conf') and depth == 'full':
self.Contents[self.name] = readSystemConfig.pareseConfig(f.readline().decode('ascii'))
elif self.file.endswith('.json') and depth == 'full':
self.Contents[self.name] = json.load(TextIOWrapper(f, 'utf-8'))
elif self.file.endswith('.log') and depth == 'full':
self.Contents[self.name] = TextIOWrapper(f, 'utf-8').read()
elif self.file.endswith('.xml') and depth == 'full':
self.Contents[self.name] = xmltodict.parse(f)
elif self.file.endswith('.csv') and depth == 'full':
if 'full_output' in self.file:
self.readEP(f,header=[0,1],skiprows=[0])
elif 'biomet' in self.file:
self.readEP(f,header=[0,1])
else:
self.readEP(f,header=[0])
t = os.path.split(self.name)
if len(t[0])>0 and len(t[1])>0 and self.name in self.Contents.keys():
tmp = self.Contents.pop(self.name)
if t[0] not in self.Contents.keys():
self.Contents[t[0]] = {}
self.Contents[t[0]][t[1]] = tmp
except:
self.mode = 0
print('unable to extract file')
if self.mode >1:
self.Data = {}
for l,t in {'data':['Date','Time'],
'biometdata':['DATE','TIME'],
'li7700status':['SECONDS','NANOSECONDS']}.items():
if l in self.Contents.keys():
self.Data[l] = {}
df = self.Contents[l].pop('Data')
if t[0].lower()=='date':
df.index = pd.to_datetime(df[t[0]]+' '+df[t[1]],format='%Y-%m-%d %H:%M:%S:%f')
else:
df.index = pd.to_datetime(df[t[0]]+df[t[1]]*1e-9,unit='s')
self.Data[l] = df.copy()
def readDATA(self,f):
i = 0
d = {}
while i == 0 or len(Line)==2:
Line = f.readline().decode('ascii').rstrip().lstrip()
Line = Line.split('\t')
if len(Line)==2:
d[Line[0].replace(':','')]=Line[1]
else:
# Header = {f'col_{i}':c for i,c in enumerate(Line)}
d['Header'] = Line
i += 1
if self.mode > 1:
df = pd.read_csv(f,header=None,sep='\t')
df.columns=Line
d['Data'] = df.copy()
if self.Metadata['Timezone'] is None:
self.Metadata['Timezone'] = d['Timezone']
if self.Metadata['Logger'] is None:
self.Metadata['Logger'] = d['Model'].split(' ')[0]
self.Contents[self.name] = d
def ini2dict(self,f):
cfg = configparser.ConfigParser()
cfg.read_file(f)
d = cfg._sections
self.Contents[self.name] = d
if 'Station' in d.keys():
self.Metadata['SerialNo'] = d['Station']['logger_id']
if d['Station']['station_name'] != '':
self.Metadata['StationName'] = d['Station']['station_name']
elif d['Site']['site_name'] != '':
self.Metadata['StationName'] = d['Site']['site_name']
else:
self.Metadata['StationName'] = None
self.Metadata['Program'] = 'Smartflux'+d['Station']['logger_sw_version']
elif 'Site' in d.keys():
self.Metadata['StationName'] = d['Site']['site_name']
if 'Timing' in d.keys():
self.Metadata['Frequency'] = str(1/float(d['Timing']['acquisition_frequency'])) + 's'
def readEP(self,f,header=None,skiprows=None):
df = pd.read_csv(f,header=header,skiprows=skiprows,encoding='utf-8')
if len(header) < 2:
df.index=['value']
d = df.to_dict()
elif len(header) == 2:
d = {}
for c in df.columns:
d[str(c[0])]={'unit':str(c[1]),
'value':df[c].tolist()[0]}
self.Contents[self.name] = d