tonik 0.0.6__py3-none-any.whl → 0.0.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tonik/api.py +43 -31
- tonik/storage.py +23 -22
- {tonik-0.0.6.dist-info → tonik-0.0.7.dist-info}/METADATA +1 -1
- tonik-0.0.7.dist-info/RECORD +11 -0
- tonik-0.0.6.dist-info/RECORD +0 -11
- {tonik-0.0.6.dist-info → tonik-0.0.7.dist-info}/WHEEL +0 -0
- {tonik-0.0.6.dist-info → tonik-0.0.7.dist-info}/entry_points.txt +0 -0
- {tonik-0.0.6.dist-info → tonik-0.0.7.dist-info}/licenses/LICENSE +0 -0
tonik/api.py
CHANGED
|
@@ -20,12 +20,12 @@ from . import get_data
|
|
|
20
20
|
|
|
21
21
|
logger = logging.getLogger(__name__)
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
class TonikAPI:
|
|
25
25
|
|
|
26
26
|
def __init__(self, rootdir) -> None:
|
|
27
27
|
self.rootdir = rootdir
|
|
28
|
-
self.app = FastAPI()
|
|
28
|
+
self.app = FastAPI()
|
|
29
29
|
|
|
30
30
|
# -- allow any origin to query API
|
|
31
31
|
self.app.add_middleware(CORSMiddleware,
|
|
@@ -55,20 +55,23 @@ class TonikAPI:
|
|
|
55
55
|
return dt
|
|
56
56
|
|
|
57
57
|
def feature(self,
|
|
58
|
-
group: str
|
|
59
|
-
name: str
|
|
60
|
-
starttime: str=None,
|
|
61
|
-
endtime: str=None,
|
|
62
|
-
resolution: str='full',
|
|
63
|
-
verticalres: int=10,
|
|
64
|
-
log: bool=False,
|
|
65
|
-
normalise: bool=False,
|
|
66
|
-
subdir: Annotated[list[str] | None, Query()]=None):
|
|
58
|
+
group: str,
|
|
59
|
+
name: str,
|
|
60
|
+
starttime: str = None,
|
|
61
|
+
endtime: str = None,
|
|
62
|
+
resolution: str = 'full',
|
|
63
|
+
verticalres: int = 10,
|
|
64
|
+
log: bool = False,
|
|
65
|
+
normalise: bool = False,
|
|
66
|
+
subdir: Annotated[list[str] | None, Query()] = None):
|
|
67
67
|
_st = self.preprocess_datetime(starttime)
|
|
68
68
|
_et = self.preprocess_datetime(endtime)
|
|
69
69
|
g = StorageGroup(group, rootdir=self.rootdir,
|
|
70
|
-
|
|
71
|
-
|
|
70
|
+
starttime=_st, endtime=_et)
|
|
71
|
+
if subdir is None:
|
|
72
|
+
c = g
|
|
73
|
+
else:
|
|
74
|
+
c = g.get_store(*subdir)
|
|
72
75
|
try:
|
|
73
76
|
feat = c(name)
|
|
74
77
|
except ValueError as e:
|
|
@@ -80,7 +83,8 @@ class TonikAPI:
|
|
|
80
83
|
nfreqs = feat.shape[0]
|
|
81
84
|
dates = feat.coords[feat.dims[1]].values
|
|
82
85
|
if resolution != 'full':
|
|
83
|
-
freq, dates, spec = self.aggregate_feature(
|
|
86
|
+
freq, dates, spec = self.aggregate_feature(
|
|
87
|
+
resolution, verticalres, feat, nfreqs, dates)
|
|
84
88
|
else:
|
|
85
89
|
spec = feat.values
|
|
86
90
|
freq = feat.coords[feat.dims[0]].values
|
|
@@ -88,44 +92,50 @@ class TonikAPI:
|
|
|
88
92
|
if log and feat.name != 'sonogram':
|
|
89
93
|
vals = 10*np.log10(vals)
|
|
90
94
|
if normalise:
|
|
91
|
-
vals = (vals - np.nanmin(vals))/
|
|
95
|
+
vals = (vals - np.nanmin(vals)) / \
|
|
96
|
+
(np.nanmax(vals) - np.nanmin(vals))
|
|
92
97
|
freqs = freq.repeat(dates.size)
|
|
93
98
|
dates = np.tile(dates, freq.size)
|
|
94
|
-
df = pd.DataFrame(
|
|
99
|
+
df = pd.DataFrame(
|
|
100
|
+
{'dates': dates, 'freqs': freqs, 'feature': vals})
|
|
95
101
|
output = df.to_csv(index=False,
|
|
96
|
-
|
|
102
|
+
columns=['dates', 'freqs', 'feature'])
|
|
97
103
|
else:
|
|
98
104
|
df = pd.DataFrame(data=feat.to_pandas(), columns=[feat.name])
|
|
99
105
|
df['dates'] = df.index
|
|
100
106
|
try:
|
|
101
|
-
|
|
107
|
+
current_resolution = pd.Timedelta(df['dates'].diff().mean())
|
|
108
|
+
if current_resolution < pd.Timedelta(resolution):
|
|
109
|
+
df = df.resample(pd.Timedelta(resolution)).mean()
|
|
102
110
|
except ValueError as e:
|
|
103
|
-
logger.warning(
|
|
111
|
+
logger.warning(
|
|
112
|
+
f"Cannot resample {feat.name} to {resolution}: e")
|
|
104
113
|
df.rename(columns={feat.name: 'feature'}, inplace=True)
|
|
105
114
|
output = df.to_csv(index=False, columns=['dates', 'feature'])
|
|
106
115
|
return StreamingResponse(iter([output]),
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
116
|
+
media_type='text/csv',
|
|
117
|
+
headers={"Content-Disposition":
|
|
118
|
+
"attachment;filename=<tonik_feature>.csv",
|
|
119
|
+
'Content-Length': str(len(output))})
|
|
112
120
|
|
|
113
121
|
def aggregate_feature(self, resolution, verticalres, feat, nfreqs, dates):
|
|
114
|
-
resolution = np.timedelta64(
|
|
122
|
+
resolution = np.timedelta64(
|
|
123
|
+
pd.Timedelta(resolution), 'ms').astype(float)
|
|
115
124
|
ndays = np.timedelta64(dates[-1] - dates[0], 'ms').astype(float)
|
|
116
|
-
canvas_x =
|
|
125
|
+
canvas_x = int(ndays/resolution)
|
|
117
126
|
canvas_y = min(nfreqs, verticalres)
|
|
118
127
|
dates = date2num(dates.astype('datetime64[us]').astype(datetime),
|
|
119
|
-
|
|
120
|
-
|
|
128
|
+
units='hours since 1970-01-01 00:00:00.0',
|
|
129
|
+
calendar='gregorian')
|
|
121
130
|
feat = feat.assign_coords({'datetime': dates})
|
|
122
131
|
cvs = dsh.Canvas(plot_width=canvas_x,
|
|
123
|
-
|
|
132
|
+
plot_height=canvas_y)
|
|
124
133
|
agg = cvs.raster(source=feat)
|
|
125
134
|
freq_dim = feat.dims[0]
|
|
126
135
|
freq, d, spec = agg.coords[freq_dim].values, agg.coords['datetime'].values, agg.data
|
|
127
|
-
dates = num2date(
|
|
128
|
-
|
|
136
|
+
dates = num2date(
|
|
137
|
+
d, units='hours since 1970-01-01 00:00:00.0', calendar='gregorian')
|
|
138
|
+
return freq, dates, spec
|
|
129
139
|
|
|
130
140
|
def inventory(self, group: str) -> dict:
|
|
131
141
|
sg = StorageGroup(group, rootdir=self.rootdir)
|
|
@@ -133,6 +143,7 @@ class TonikAPI:
|
|
|
133
143
|
|
|
134
144
|
# ta = TonikAPI('/tmp').feature()
|
|
135
145
|
|
|
146
|
+
|
|
136
147
|
def main(argv=None):
|
|
137
148
|
parser = ArgumentParser()
|
|
138
149
|
parser.add_argument("--rootdir", default='/tmp')
|
|
@@ -140,5 +151,6 @@ def main(argv=None):
|
|
|
140
151
|
ta = TonikAPI(args.rootdir)
|
|
141
152
|
uvicorn.run(ta.app, host="0.0.0.0", port=8003)
|
|
142
153
|
|
|
154
|
+
|
|
143
155
|
if __name__ == "__main__":
|
|
144
156
|
main()
|
tonik/storage.py
CHANGED
|
@@ -18,13 +18,14 @@ ERROR_LOG_FILENAME = "tonik.log"
|
|
|
18
18
|
LOGGING_CONFIG = {
|
|
19
19
|
"version": 1,
|
|
20
20
|
"disable_existing_loggers": False,
|
|
21
|
-
"formatters": {
|
|
21
|
+
"formatters": {
|
|
22
22
|
"default": { # The formatter name, it can be anything that I wish
|
|
23
|
-
|
|
23
|
+
# What to add in the message
|
|
24
|
+
"format": "%(asctime)s:%(name)s:%(process)d:%(lineno)d " "%(levelname)s %(message)s",
|
|
24
25
|
"datefmt": "%Y-%m-%d %H:%M:%S", # How to display dates
|
|
25
26
|
},
|
|
26
27
|
"json": { # The formatter name
|
|
27
|
-
|
|
28
|
+
"()": "pythonjsonlogger.jsonlogger.JsonFormatter", # The class to instantiate!
|
|
28
29
|
# Json is more complex, but easier to read, display all attributes!
|
|
29
30
|
"format": """
|
|
30
31
|
asctime: %(asctime)s
|
|
@@ -48,22 +49,23 @@ LOGGING_CONFIG = {
|
|
|
48
49
|
""",
|
|
49
50
|
"datefmt": "%Y-%m-%d %H:%M:%S", # How to display dates
|
|
50
51
|
},
|
|
51
|
-
},
|
|
52
|
+
},
|
|
52
53
|
"handlers": {
|
|
53
54
|
"logfile": { # The handler name
|
|
54
55
|
"formatter": "json", # Refer to the formatter defined above
|
|
55
56
|
"level": "ERROR", # FILTER: Only ERROR and CRITICAL logs
|
|
56
57
|
"class": "logging.handlers.RotatingFileHandler", # OUTPUT: Which class to use
|
|
57
|
-
|
|
58
|
+
# Param for class above. Defines filename to use, load it from constant
|
|
59
|
+
"filename": ERROR_LOG_FILENAME,
|
|
58
60
|
"backupCount": 2, # Param for class above. Defines how many log files to keep as it grows
|
|
59
|
-
},
|
|
61
|
+
},
|
|
60
62
|
"simple": { # The handler name
|
|
61
63
|
"formatter": "default", # Refer to the formatter defined above
|
|
62
64
|
"class": "logging.StreamHandler", # OUTPUT: Same as above, stream to console
|
|
63
65
|
"stream": "ext://sys.stdout",
|
|
64
66
|
},
|
|
65
67
|
},
|
|
66
|
-
"loggers": {
|
|
68
|
+
"loggers": {
|
|
67
69
|
"zizou": { # The name of the logger, this SHOULD match your module!
|
|
68
70
|
"level": "DEBUG", # FILTER: only INFO logs onwards from "tryceratops" logger
|
|
69
71
|
"handlers": [
|
|
@@ -92,10 +94,10 @@ class Path(object):
|
|
|
92
94
|
except FileExistsError:
|
|
93
95
|
pass
|
|
94
96
|
self.children = {}
|
|
95
|
-
|
|
97
|
+
|
|
96
98
|
def __str__(self):
|
|
97
99
|
return self.path
|
|
98
|
-
|
|
100
|
+
|
|
99
101
|
def __getitem__(self, key):
|
|
100
102
|
if key is None:
|
|
101
103
|
raise ValueError("Key cannot be None")
|
|
@@ -125,18 +127,18 @@ class Path(object):
|
|
|
125
127
|
if self.endtime <= self.starttime:
|
|
126
128
|
raise ValueError('Startime has to be smaller than endtime.')
|
|
127
129
|
|
|
128
|
-
feature = feature.lower()
|
|
129
130
|
filename = self.feature_path(feature)
|
|
130
131
|
|
|
131
|
-
logger.debug(
|
|
132
|
+
logger.debug(
|
|
133
|
+
f"Reading feature {feature} between {self.starttime} and {self.endtime}")
|
|
132
134
|
num_periods = None
|
|
133
135
|
if stack_length is not None:
|
|
134
136
|
valid_stack_units = ['W', 'D', 'h', 'T', 'min', 'S']
|
|
135
137
|
if not re.match(r'\d*\s*(\w*)', stack_length).group(1)\
|
|
136
|
-
|
|
138
|
+
in valid_stack_units:
|
|
137
139
|
raise ValueError(
|
|
138
140
|
'Stack length should be one of: {}'.
|
|
139
|
-
|
|
141
|
+
format(', '.join(valid_stack_units))
|
|
140
142
|
)
|
|
141
143
|
|
|
142
144
|
if pd.to_timedelta(stack_length) < pd.to_timedelta(interval):
|
|
@@ -146,13 +148,13 @@ class Path(object):
|
|
|
146
148
|
# Rewind starttime to account for stack length
|
|
147
149
|
self.starttime -= pd.to_timedelta(stack_length)
|
|
148
150
|
|
|
149
|
-
num_periods = (pd.to_timedelta(stack_length)/
|
|
151
|
+
num_periods = (pd.to_timedelta(stack_length) /
|
|
150
152
|
pd.to_timedelta(interval))
|
|
151
153
|
if not num_periods.is_integer():
|
|
152
154
|
raise ValueError(
|
|
153
155
|
'Stack length {} / interval {} = {}, but it needs'
|
|
154
156
|
' to be a whole number'.
|
|
155
|
-
|
|
157
|
+
format(stack_length, interval, num_periods))
|
|
156
158
|
|
|
157
159
|
xd_index = dict(datetime=slice(self.starttime, self.endtime))
|
|
158
160
|
with xr.open_dataset(filename, group='original', engine='h5netcdf') as ds:
|
|
@@ -164,8 +166,8 @@ class Path(object):
|
|
|
164
166
|
logger.debug("Stacking feature...")
|
|
165
167
|
try:
|
|
166
168
|
xdf = rq[feature].rolling(datetime=int(num_periods),
|
|
167
|
-
|
|
168
|
-
|
|
169
|
+
center=False,
|
|
170
|
+
min_periods=1).mean()
|
|
169
171
|
# Return requested timeframe to that defined in initialisation
|
|
170
172
|
self.starttime += pd.to_timedelta(stack_length)
|
|
171
173
|
xdf_new = xdf.loc[self.starttime:self.endtime]
|
|
@@ -212,12 +214,13 @@ class StorageGroup(Path):
|
|
|
212
214
|
>>> c = g.channel(site='WIZ', sensor='00', channel='HHZ')
|
|
213
215
|
>>> rsam = c("rsam")
|
|
214
216
|
"""
|
|
217
|
+
|
|
215
218
|
def __init__(self, name, rootdir=None, starttime=None, endtime=None):
|
|
216
|
-
self.stores = set()
|
|
219
|
+
self.stores = set()
|
|
217
220
|
self.starttime = starttime
|
|
218
221
|
self.endtime = endtime
|
|
219
222
|
super().__init__(name, rootdir)
|
|
220
|
-
|
|
223
|
+
|
|
221
224
|
def print_tree(self, site, indent=0, output=''):
|
|
222
225
|
output += ' ' * indent + site.path + '\n'
|
|
223
226
|
for site in site.children.values():
|
|
@@ -243,7 +246,7 @@ class StorageGroup(Path):
|
|
|
243
246
|
st.starttime = self.starttime
|
|
244
247
|
st.endtime = self.endtime
|
|
245
248
|
self.stores.add(st)
|
|
246
|
-
return st
|
|
249
|
+
return st
|
|
247
250
|
|
|
248
251
|
def from_directory(self):
|
|
249
252
|
"""
|
|
@@ -311,7 +314,5 @@ class StorageGroup(Path):
|
|
|
311
314
|
if s is not self:
|
|
312
315
|
s.endtime = time
|
|
313
316
|
|
|
314
|
-
|
|
315
317
|
starttime = property(get_starttime, set_starttime)
|
|
316
318
|
endtime = property(get_endtime, set_endtime)
|
|
317
|
-
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: tonik
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.7
|
|
4
4
|
Summary: Store time series data as HDF5 files and access them through an API.
|
|
5
5
|
Project-URL: Homepage, https://tsc-tools.github.io/tonik
|
|
6
6
|
Project-URL: Issues, https://github.com/tsc-tools/tonik/issues
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
tonik/__init__.py,sha256=p97Bbz-yujI-uNmbqn1S61lq-zfF1VPaS5c1fxs1Fa8,516
|
|
2
|
+
tonik/api.py,sha256=gnwoss7UV8FaY92xzumhcoVPjkzB695qgByHUYcLSw4,5916
|
|
3
|
+
tonik/storage.py,sha256=pJnvoGFb8uZqnpkjOsgnntW-a7dhKVlvevs725nAS54,11009
|
|
4
|
+
tonik/utils.py,sha256=nV0lK8Azasr8LUuQGXxfxef6nU3bn3dCTQnQTmWsKAY,1534
|
|
5
|
+
tonik/xarray2hdf5.py,sha256=cekO9vo9ZRlr0VndswJjPC27CEVD3TpRVKLAJ-aAO0g,4465
|
|
6
|
+
tonik/package_data/index.html,sha256=GKDClUhIam_fAYbNfzAolORhSCG3ae1wW3VjWCg4PMk,2732
|
|
7
|
+
tonik-0.0.7.dist-info/METADATA,sha256=6DhYEfnEAWSKLEZJQQRiRF_cZAGAQFK6mLmHQEYJbuE,1918
|
|
8
|
+
tonik-0.0.7.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
9
|
+
tonik-0.0.7.dist-info/entry_points.txt,sha256=VnGfC5qAzpntEHAb5pooUEpYABSgOfQoNhCEtLDJyf8,45
|
|
10
|
+
tonik-0.0.7.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
11
|
+
tonik-0.0.7.dist-info/RECORD,,
|
tonik-0.0.6.dist-info/RECORD
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
tonik/__init__.py,sha256=p97Bbz-yujI-uNmbqn1S61lq-zfF1VPaS5c1fxs1Fa8,516
|
|
2
|
-
tonik/api.py,sha256=PV41vA7FGDqt1LK0nYKc1SNF04-LtINfqkYHH_y3S4U,5645
|
|
3
|
-
tonik/storage.py,sha256=JuDq4T-45kjOeH_gu1E3Z1WdT9x0lQ7DJfz8hRZNwzw,11032
|
|
4
|
-
tonik/utils.py,sha256=nV0lK8Azasr8LUuQGXxfxef6nU3bn3dCTQnQTmWsKAY,1534
|
|
5
|
-
tonik/xarray2hdf5.py,sha256=cekO9vo9ZRlr0VndswJjPC27CEVD3TpRVKLAJ-aAO0g,4465
|
|
6
|
-
tonik/package_data/index.html,sha256=GKDClUhIam_fAYbNfzAolORhSCG3ae1wW3VjWCg4PMk,2732
|
|
7
|
-
tonik-0.0.6.dist-info/METADATA,sha256=HHDURyUqgrCa1-1wZeyQG4lYCa_ZdbzgtY5y5eVJ4lY,1918
|
|
8
|
-
tonik-0.0.6.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
|
|
9
|
-
tonik-0.0.6.dist-info/entry_points.txt,sha256=VnGfC5qAzpntEHAb5pooUEpYABSgOfQoNhCEtLDJyf8,45
|
|
10
|
-
tonik-0.0.6.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
11
|
-
tonik-0.0.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|