tonik 0.1.10__py3-none-any.whl → 0.1.11__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/__init__.py CHANGED
@@ -3,7 +3,7 @@ from os import PathLike
3
3
  from typing import Optional
4
4
 
5
5
  from .storage import Storage, Path
6
- from .utils import generate_test_data
6
+ from .utils import generate_test_data, get_labels
7
7
 
8
8
 
9
9
  def get_data(filename: Optional[PathLike] = None) -> str:
tonik/api.py CHANGED
@@ -10,7 +10,7 @@ import datashader as dsh
10
10
  import numpy as np
11
11
  import pandas as pd
12
12
  import uvicorn
13
- from cftime import date2num, num2date
13
+ from cftime import date2num, num2pydate
14
14
  from fastapi import FastAPI, HTTPException, Query
15
15
  from fastapi.middleware.cors import CORSMiddleware
16
16
  from fastapi.responses import HTMLResponse, StreamingResponse
@@ -37,6 +37,7 @@ class TonikAPI:
37
37
  self.app.get("/", response_class=HTMLResponse)(self.root)
38
38
  self.app.get("/feature")(self.feature)
39
39
  self.app.get("/inventory")(self.inventory)
40
+ self.app.get("/labels")(self.labels)
40
41
 
41
42
  def root(self):
42
43
  with open(get_data("package_data/index.html"), "r", encoding="utf-8") as file:
@@ -101,11 +102,12 @@ class TonikAPI:
101
102
  dates = np.tile(dates, freq.size)
102
103
  df = pd.DataFrame(
103
104
  {'dates': dates, 'freqs': freqs, 'feature': vals})
105
+ df.dates = pd.to_datetime(df.dates.values).tz_localize('UTC')
104
106
  output = df.to_csv(index=False,
105
107
  columns=['dates', 'freqs', 'feature'])
106
108
  else:
107
109
  df = pd.DataFrame(data=feat.to_pandas(), columns=[feat.name])
108
- df['dates'] = df.index
110
+ df['dates'] = df.index.tz_localize('UTC')
109
111
  if resolution != 'full':
110
112
  try:
111
113
  current_resolution = pd.Timedelta(
@@ -138,7 +140,7 @@ class TonikAPI:
138
140
  agg = cvs.raster(source=feat)
139
141
  freq_dim = feat.dims[0]
140
142
  freq, d, spec = agg.coords[freq_dim].values, agg.coords['datetime'].values, agg.data
141
- dates = num2date(
143
+ dates = num2pydate(
142
144
  d, units='hours since 1970-01-01 00:00:00.0', calendar='gregorian')
143
145
  return freq, dates, spec
144
146
 
@@ -156,8 +158,25 @@ class TonikAPI:
156
158
  return sg.to_dict()
157
159
  else:
158
160
  dir_contents = os.listdir(c.path)
161
+ if 'labels.json' in dir_contents:
162
+ dir_contents.remove('labels.json')
159
163
  return [fn.replace('.nc', '').replace('.zarr', '') for fn in dir_contents]
160
164
 
165
+ def labels(self, group: str, subdir: SubdirType = None, starttime: Optional[str] = None, endtime: Optional[str] = None):
166
+ _st = self.preprocess_datetime(starttime)
167
+ _et = self.preprocess_datetime(endtime)
168
+ sg = Storage(group, rootdir=self.rootdir,
169
+ starttime=_st, endtime=_et, create=False)
170
+ try:
171
+ c = sg.get_substore(*subdir)
172
+ except TypeError:
173
+ c = sg
174
+ except FileNotFoundError:
175
+ msg = "Directory {} not found.".format(
176
+ '/'.join([sg.path] + subdir))
177
+ raise HTTPException(status_code=404, detail=msg)
178
+ return c.get_labels()
179
+
161
180
 
162
181
  def main(argv=None):
163
182
  parser = ArgumentParser()
tonik/storage.py CHANGED
@@ -1,3 +1,4 @@
1
+ import json
1
2
  import logging
2
3
  import logging.config
3
4
  import os
@@ -157,6 +158,30 @@ class Path(object):
157
158
  with xr.open_dataset(filename, group='original', engine=self.engine) as ds:
158
159
  return ds[feature].sizes
159
160
 
161
+ def save_labels(self, labels):
162
+ """
163
+ Save all labels. Labels are stored in a list of dictionaries with the following keys:
164
+ {
165
+ 'time': 'time, or the beginning of the time window',
166
+ 'timeEnd': 'end of the time window [optional]',
167
+ 'title': 'title of the label',
168
+ 'text': 'A more detailed description of the label',
169
+ 'tags': 'tags to sort labels',
170
+ 'id': 'unique id of the label'
171
+ }
172
+ """
173
+ filename = os.path.join(self.path, 'labels.json')
174
+ with open(filename, 'w') as f:
175
+ json.dump(labels, f)
176
+
177
+ def get_labels(self):
178
+ """
179
+ Load all labels.
180
+ """
181
+ filename = os.path.join(self.path, 'labels.json')
182
+ with open(filename) as f:
183
+ return json.load(f)
184
+
160
185
 
161
186
  class Storage(Path):
162
187
  """
@@ -238,7 +263,10 @@ class Storage(Path):
238
263
  if name.endswith('.zarr'):
239
264
  return name.replace('.zarr', '')
240
265
  elif os.path.isdir(path):
241
- return {name: [Storage.directory_tree_to_dict(os.path.join(path, child)) for child in sorted(os.listdir(path))]}
266
+ dir_contents = os.listdir(path)
267
+ if 'labels.json' in dir_contents:
268
+ dir_contents.remove('labels.json')
269
+ return {name: [Storage.directory_tree_to_dict(os.path.join(path, child)) for child in sorted(dir_contents)]}
242
270
  else:
243
271
  if name.endswith('.nc'):
244
272
  return name.replace('.nc', '')
tonik/utils.py CHANGED
@@ -1,4 +1,5 @@
1
- from datetime import datetime
1
+ from typing import List
2
+ from datetime import datetime, timezone, timedelta
2
3
 
3
4
  import numpy as np
4
5
  import pandas as pd
@@ -89,3 +90,100 @@ def merge_arrays(xds_old: xr.DataArray, xds_new: xr.DataArray,
89
90
  freq=f'{resolution}h')
90
91
  xda_new = xda_new.reindex(datetime=new_dates)
91
92
  return xda_new
93
+
94
+
95
+ def extract_consecutive_integers(nums: List[int]) -> List[List[int]]:
96
+ """
97
+ Extract consecutive integers from a list of integers.
98
+ """
99
+ if not len(nums) > 0:
100
+ return []
101
+
102
+ nums.sort() # Sort the array
103
+ result = []
104
+ temp = [nums[0]] # Initialize the first group with the first number
105
+
106
+ for i in range(1, len(nums)):
107
+ if nums[i] == nums[i - 1] + 1: # Check if consecutive
108
+ temp.append(nums[i])
109
+ else:
110
+ result.append(temp) # Add the current group to the result
111
+ temp = [nums[i]] # Start a new group
112
+
113
+ result.append(temp) # Add the last group
114
+ return result
115
+
116
+
117
+ def get_labels(xda: xr.DataArray, threshold: float) -> dict:
118
+ """
119
+ Generate labels for time windows where the values are exceeding the threshold.
120
+ """
121
+ # add labels to time windows where the values are exceeding the 85th percentile
122
+ labels = {xda.name: []}
123
+ idx = np.where(xda > threshold)[0]
124
+ window_list = extract_consecutive_integers(idx)
125
+ ids = 0
126
+ for _w in window_list:
127
+ timeEnd = None
128
+ # Convert to unix time milliseconds
129
+ timeStart = xda.datetime.isel(dict(datetime=_w[0])).values.astype(
130
+ 'datetime64[ms]').astype(int)
131
+ timeStart = int(timeStart)
132
+ if len(_w) > 1:
133
+ timeEnd = xda.datetime.isel(
134
+ dict(datetime=_w[-1])).values.astype('datetime64[ms]').astype(int)
135
+ timeEnd = int(timeEnd)
136
+ label = dict(time=timeStart,
137
+ timeEnd=timeEnd,
138
+ title='Greater 85th percentile',
139
+ description='Values exceed 85th percentile',
140
+ tags=['anomaly'],
141
+ id=ids)
142
+ ids += 1
143
+ labels[xda.name].append(label)
144
+ return labels
145
+
146
+
147
+ def main():
148
+ from tonik import Storage
149
+ import logging
150
+
151
+ logger = logging.getLogger(__name__)
152
+ logger.info("Generating test data")
153
+ rootdir = '/tmp'
154
+ g = Storage('volcanoes', rootdir=rootdir)
155
+ st1 = g.get_substore('Mt Doom', 'MDR', '00', 'BHZ')
156
+ st2 = g.get_substore('Misty Mountain', 'MMS', '10', 'HHZ')
157
+
158
+ # Get start time of the current 10 minute window
159
+ tstart = datetime.now(timezone.utc).timestamp()
160
+ tstart -= tstart % 600
161
+ tstart = datetime.fromtimestamp(tstart, timezone.utc)
162
+ tstart -= timedelta(days=30)
163
+ tstart = tstart.replace(tzinfo=None)
164
+ logger.info(f"Start time: {tstart}")
165
+
166
+ # Generate test data
167
+ xdf_1D = generate_test_data(dim=1, tstart=tstart, add_nans=False, seed=42)
168
+ xdf_1D_1 = generate_test_data(
169
+ dim=1, tstart=tstart, add_nans=False, seed=24)
170
+ xdf_2D = generate_test_data(
171
+ dim=2, tstart=tstart, add_nans=False, seed=1234)
172
+ xdf_2D_1 = generate_test_data(
173
+ dim=2, tstart=tstart, add_nans=False, seed=4321)
174
+ logger.info("Saving test data to " + st1.path)
175
+ st1.save(xdf_1D)
176
+ st1.save(xdf_2D)
177
+ logger.info("Saving test data to " + st2.path)
178
+ st2.save(xdf_1D_1)
179
+ st2.save(xdf_2D_1)
180
+
181
+ # add labels to RSAM time windows where the values are exceeding the 85th percentile
182
+ logger.info("Adding labels to DSAR data")
183
+ labels = get_labels(xdf_1D.dsar, float(xdf_1D.dsar.quantile(0.85)))
184
+ logger.info("Saving labels to " + st1.path + '/labels.json')
185
+ st1.save_labels(labels)
186
+
187
+
188
+ if __name__ == '__main__':
189
+ main()
tonik/xarray2netcdf.py CHANGED
@@ -103,7 +103,7 @@ def _create_h5_Structure(defaultGroupName, featureName, h5f, xArray, starttime,
103
103
  coordinates = rootGrp.create_variable(timedim, (timedim,), float)
104
104
  coordinates.attrs['units'] = 'hours since 1970-01-01 00:00:00.0'
105
105
  coordinates.attrs['calendar'] = 'gregorian'
106
- rootGrp.attrs['starttime'] = str(starttime)
106
+ rootGrp.attrs['archive_starttime'] = str(starttime)
107
107
  for label, size in xArray.sizes.items():
108
108
  if not np.issubdtype(xArray[label].dtype, np.datetime64):
109
109
  rootGrp.dimensions[label] = size
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.3
1
+ Metadata-Version: 2.4
2
2
  Name: tonik
3
- Version: 0.1.10
3
+ Version: 0.1.11
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
@@ -10,6 +10,7 @@ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
10
10
  Classifier: Operating System :: OS Independent
11
11
  Classifier: Programming Language :: Python :: 3
12
12
  Requires-Python: >=3.7
13
+ Requires-Dist: dask<=2024.10.0
13
14
  Requires-Dist: datashader>=0.14
14
15
  Requires-Dist: fastapi>=0.112
15
16
  Requires-Dist: h5netcdf>=1.1
@@ -30,8 +31,8 @@ Description-Content-Type: text/markdown
30
31
 
31
32
  # Tonik
32
33
 
33
- Tonik provides you with a solution to store and retrieve scientific data as well as serving it through an API.
34
- For visualisations, the API can serve large requests very quickly by downsampling the data to the requested resolution on demand.
34
+ Tonik provides you with a solution to store and retrieve scientific time-series data as well as serving it through an API.
35
+ For visualisations, the API can serve large requests very quickly by downsampling the data to the requested resolution on demand. The API was optimised to visualise time-series and data labels with [Grafana](https://grafana.com/oss/grafana/).
35
36
 
36
37
  ## Requirements
37
38
  * h5py
@@ -54,10 +55,6 @@ pip install -U tonik
54
55
 
55
56
  Learn more about tonik in its official [documentation](https://tsc-tools.github.io/tonik)
56
57
 
57
- ## Contributing
58
-
59
- You can find information about contributing to tonik at our [Contributing page]
60
-
61
58
  ## Get in touch
62
59
 
63
- Report bugs, suggest features, view the source code, and ask questions [on GitHub](https://github.com/tsc-tools/tonik).
60
+ Report bugs, suggest features, view the source code, and ask questions [on GitHub](https://github.com/tsc-tools/tonik/issues).
@@ -0,0 +1,12 @@
1
+ tonik/__init__.py,sha256=dov-nMeGFBzLspmj4rWKjC4r736vmaPDgMEkHSUfP98,523
2
+ tonik/api.py,sha256=xUH-fr-xUwc_a21QYz11Dk3YjB2nRuCclmALvU64UJM,7592
3
+ tonik/storage.py,sha256=IklM_atZD4rebUsnXsUj5JldSHU2LqmuqME03PHp_UI,10441
4
+ tonik/utils.py,sha256=9eSVKIbs8TIZlJCz_-B7FrvOUQCQHO3K52v4Heus-uE,6135
5
+ tonik/xarray2netcdf.py,sha256=gDNT6nxnRbXPeRqZ3URW5oXY3Nfh3TCrfueE-eUrIoY,5181
6
+ tonik/xarray2zarr.py,sha256=xJjKcFZF0oz6gw47apuCiXFtW5HgWqnZgiIuEVQHhBI,2363
7
+ tonik/package_data/index.html,sha256=GKDClUhIam_fAYbNfzAolORhSCG3ae1wW3VjWCg4PMk,2732
8
+ tonik-0.1.11.dist-info/METADATA,sha256=xEhlRUeS79ZRxwL_-ksmf5do2OrOapKBealQRPUSFpE,2005
9
+ tonik-0.1.11.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
+ tonik-0.1.11.dist-info/entry_points.txt,sha256=mT3B4eBE8SHlAeMhFnZGor9-YkVtoWM1NVHVuypJ-uY,74
11
+ tonik-0.1.11.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
12
+ tonik-0.1.11.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.25.0
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,2 +1,3 @@
1
1
  [console_scripts]
2
+ test_data = tonik.utils:main
2
3
  tonik_api = tonik.api:main
@@ -1,12 +0,0 @@
1
- tonik/__init__.py,sha256=ZBVGh4dm_l9xwiBGb33O5QV9MfZeNiEd3DBDAm6DiHk,511
2
- tonik/api.py,sha256=JaKHzavqYDjZo2DypTHgAaM01Y4EOkn6UF3ZVm50F5k,6694
3
- tonik/storage.py,sha256=GNJ6w9VHOeTR_ZJMZ-Ipqe3nFK2I91fkHYwg1k9bEuo,9470
4
- tonik/utils.py,sha256=3nSRU_GnV6arP4e63YHn4oEV8XbqzVAW8FCvQVIwGdg,2757
5
- tonik/xarray2netcdf.py,sha256=RUsPwPDEOnUIMGt7_9F1VDyg83zu8x-Qs3jmZs3Dq0o,5173
6
- tonik/xarray2zarr.py,sha256=xJjKcFZF0oz6gw47apuCiXFtW5HgWqnZgiIuEVQHhBI,2363
7
- tonik/package_data/index.html,sha256=GKDClUhIam_fAYbNfzAolORhSCG3ae1wW3VjWCg4PMk,2732
8
- tonik-0.1.10.dist-info/METADATA,sha256=gDvofXsP2pnRFDcwYzCd67dZ6BC0GGBDtdC_sn71k7o,1940
9
- tonik-0.1.10.dist-info/WHEEL,sha256=1yFddiXMmvYK7QYTqtRNtX66WJ0Mz8PYEiEUoOUUxRY,87
10
- tonik-0.1.10.dist-info/entry_points.txt,sha256=VnGfC5qAzpntEHAb5pooUEpYABSgOfQoNhCEtLDJyf8,45
11
- tonik-0.1.10.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
12
- tonik-0.1.10.dist-info/RECORD,,