pytrms 0.2.1__py3-none-any.whl → 0.9.0__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.
@@ -0,0 +1,481 @@
1
+ import os.path
2
+ from functools import partial, lru_cache
3
+ from itertools import islice
4
+
5
+ import h5py
6
+ import numpy as np
7
+ import pandas as pd
8
+
9
+ from .._base import itype
10
+
11
+ __all__ = ['IoniTOFReader', 'GroupNotFoundError']
12
+
13
+ def convert_labview_to_posix(lv_time_utc, utc_offset_sec):
14
+ '''Create a `pandas.Timestamp` from LabView time.'''
15
+ # change epoch from 01.01.1904 to 01.01.1970:
16
+ posix_time = lv_time_utc - 2082844800
17
+ # the tz must be specified in isoformat like '+02:30'..
18
+ tz_sec = int(utc_offset_sec)
19
+ tz_designator = '{0}{1:02d}:{2:02d}'.format(
20
+ '+' if tz_sec >= 0 else '-', tz_sec // 3600, tz_sec % 3600 // 60)
21
+
22
+ return pd.Timestamp(posix_time, unit='s', tz=tz_designator)
23
+
24
+ class GroupNotFoundError(KeyError):
25
+ pass
26
+
27
+
28
+ class IoniTOFReader:
29
+
30
+ @property
31
+ @lru_cache
32
+ def time_of_meas(self):
33
+ """The pandas.Timestamp of the 0th measurement cycle."""
34
+ return next(self.iter_index('abs_time')) - next(self.iter_index('rel_time'))
35
+
36
+ @property
37
+ @lru_cache
38
+ def time_of_file(self):
39
+ """The pandas.Timestamp of the 0th file cycle."""
40
+ # ..which is *not* the 1st file-cycle, but the (unrecorded) one before..
41
+ file0 = next(self.iter_index('abs_time')) - pd.Timedelta(self.single_spec_duration_ms, 'ms')
42
+ # ..and should never pre-pone the measurement time:
43
+ return max(file0, self.time_of_meas)
44
+
45
+ @property
46
+ @lru_cache
47
+ def time_of_file_creation(self):
48
+ """The pandas.Timestamp of the file creation."""
49
+ return convert_labview_to_posix(float(self.hf.attrs['FileCreatedTime_UTC']), self.utc_offset_sec)
50
+
51
+ @property
52
+ @lru_cache
53
+ def utc_offset_sec(self):
54
+ """The pandas.Timestamp of the 0th file cycle."""
55
+ return int(self.hf.attrs['UTC_Offset'])
56
+
57
+ @property
58
+ def inst_type(self):
59
+ return str(self.hf.attrs.get('InstrumentType', [b'',])[0].decode('latin-1'))
60
+
61
+ @property
62
+ def sub_type(self):
63
+ return str(self.hf.attrs.get('InstSubType', [b'',])[0].decode('latin-1'))
64
+
65
+ @property
66
+ def serial_nr(self):
67
+ return str(self.hf.attrs.get('InstSerial#', [b'???',])[0].decode('latin-1'))
68
+
69
+ @serial_nr.setter
70
+ def serial_nr(self, number):
71
+ path = self.filename
72
+ self.hf.close()
73
+ try:
74
+ hf = h5py.File(path, 'r+')
75
+ hf.attrs['InstSerial#'] = np.array([str(number).encode('latin-1')], dtype='S')
76
+ hf.flush()
77
+ hf.close()
78
+ except OSError:
79
+ # well it didn't work..
80
+ pass
81
+ finally:
82
+ self.hf = h5py.File(path, 'r', swmr=True)
83
+
84
+ @property
85
+ def number_of_timebins(self):
86
+ return int(self.hf['SPECdata/Intensities'].shape[1])
87
+
88
+ @property
89
+ def timebin_width_ps(self):
90
+ return float(self.hf.attrs.get('Timebin width (ps)'))
91
+
92
+ @property
93
+ def poisson_deadtime_ns(self):
94
+ return float(self.hf.attrs.get('PoissonDeadTime (ns)'))
95
+
96
+ @property
97
+ def pulsing_period_ns(self):
98
+ return float(self.hf.attrs.get('Pulsing Period (ns)'))
99
+
100
+ @property
101
+ def start_delay_ns(self):
102
+ return float(self.hf.attrs.get('Start Delay (ns)'))
103
+
104
+ @property
105
+ def single_spec_duration_ms(self):
106
+ return float(self.hf.attrs.get('Single Spec Duration (ms)'))
107
+
108
+ def __init__(self, path):
109
+ self.hf = h5py.File(path, 'r', swmr=True)
110
+ self.filename = os.path.abspath(self.hf.filename)
111
+
112
+ table_locs = {
113
+ 'primary_ions': '/PTR-PrimaryIons',
114
+ 'transmission': '/PTR-Transmission',
115
+ }
116
+
117
+ def get_table(self, table_name):
118
+ try:
119
+ grp = self.hf.get(IoniTOFReader.table_locs[table_name])
120
+ assert grp is not None, "missing dataset in hdf5 file"
121
+ except KeyError as exc:
122
+ raise KeyError(str(exc) + f", possible values: {list(IoniTOFReader.table_locs.keys())}")
123
+
124
+ rv = []
125
+ for i, name in enumerate(s.decode('latin-1') for s in grp['Descriptions']):
126
+ # Note: the dataset is 10 x 2 x 10 by default, but we remove all empty rows...
127
+ if not len(name):
128
+ continue
129
+
130
+ dset = grp['Masses_Factors'][i]
131
+ # ...and columns:
132
+ filled = np.all(dset, axis=0)
133
+ masses = dset[0, filled]
134
+ values = dset[1, filled]
135
+ rv.append(itype.table_setting_t(name, list(zip(masses, values))))
136
+
137
+ return rv
138
+
139
+ def read_addtraces(self, matches=None, index='abs_cycle'):
140
+ """Reads all /AddTraces into a DataFrame.
141
+
142
+ - 'index' one of abs_cycle|abs_time|rel_cycle|rel_time
143
+ """
144
+ if matches is not None:
145
+ if callable(matches):
146
+ filter_fun = matches
147
+ elif isinstance(matches, str):
148
+ filter_fun = lambda x: matches.lower() in x.lower()
149
+ else:
150
+ raise ValueError(repr(matches))
151
+ locs = list(filter(filter_fun, self._locate_datainfo()))
152
+ else:
153
+ locs = self._locate_datainfo()
154
+
155
+ if not len(locs):
156
+ raise ValueError(f"no match for {matches} in {self._locate_datainfo()}")
157
+
158
+ rv = pd.concat((self._read_datainfo(loc) for loc in locs), axis='columns')
159
+ rv.index = list(self.iter_index(index))
160
+
161
+ # de-duplicate trace-columns to prevent issues...
162
+ return rv.loc[:, ~rv.columns.duplicated()]
163
+
164
+ def read_calctraces(self, index='abs_cycle'):
165
+ """Reads the calculated traces into a DataFrame.
166
+
167
+ - 'index' one of abs_cycle|abs_time|rel_cycle|rel_time
168
+ """
169
+ return self.read_addtraces('CalcTraces', index)
170
+
171
+ @lru_cache
172
+ def read_traces(self, kind='conc', index='abs_cycle', force_original=False):
173
+ """Reads the peak-traces of the given 'kind' into a DataFrame.
174
+
175
+ If the traces have been post-processed in the Ionicon Viewer,
176
+ those will be used, unless `force_original=True`.
177
+
178
+ - 'kind' one of raw|corr|conc
179
+ - 'index' one of abs_cycle|abs_time|rel_cycle|rel_time
180
+ - 'force_original' ignore the post-processed data
181
+ """
182
+ if force_original:
183
+ return self._read_original_traces(kind, index)
184
+ else:
185
+ try:
186
+ return self._read_processed_traces(kind, index)
187
+ except GroupNotFoundError:
188
+ return self._read_original_traces(kind, index)
189
+
190
+ def read_all(self, kind='conc', index='abs_cycle', force_original=False):
191
+ """Reads all traces into a DataFrame.
192
+
193
+ If the traces have been post-processed in the Ionicon Viewer,
194
+ those will be used, unless `force_original=True`.
195
+
196
+ - 'kind' one of raw|corr|conc
197
+ - 'index' one of abs_cycle|abs_time|rel_cycle|rel_time
198
+ - 'force_original' ignore the post-processed data
199
+ """
200
+ # ...and throw it all together:
201
+ return pd.concat([
202
+ self.read_traces(kind, index, force_original),
203
+ self.read_addtraces(None, index),
204
+ ], axis='columns')
205
+
206
+ def iter_index(self, kind='abs_cycle'):
207
+ lut = {
208
+ 'rel_cycle': (0, lambda a: iter(a.astype('int', copy=False))),
209
+ 'abs_cycle': (1, lambda a: iter(a.astype('int', copy=False))),
210
+ 'abs_time': (2, lambda a: map(partial(convert_labview_to_posix, utc_offset_sec=self.utc_offset_sec), a)),
211
+ 'rel_time': (3, lambda a: map(partial(pd.Timedelta, unit='s'), a)),
212
+ }
213
+ try:
214
+ _N, convert2iterator = lut[kind.lower()]
215
+ except KeyError as exc:
216
+ msg = "Unknown index-type! `kind` must be one of {0}.".format(', '.join(lut.keys()))
217
+ raise KeyError(msg) from exc
218
+
219
+ return convert2iterator(self.hf['SPECdata/Times'][:, _N])
220
+
221
+ @lru_cache
222
+ def make_index(self, kind='abs_cycle'):
223
+ return pd.Index(self.iter_index(kind))
224
+
225
+ def __len__(self):
226
+ return self.hf['SPECdata/Intensities'].shape[0]
227
+
228
+ def iter_specdata(self, start=None, stop=None):
229
+ has_mc_segments = False # self.hf.get('MassCal') is not None
230
+
231
+ add_data_dicts = {ad_info.split('/')[1]: self.read_addtraces(ad_info)
232
+ for ad_info in self._locate_datainfo()
233
+ if ad_info.startswith('AddTraces')}
234
+
235
+ for i in islice(range(len(self)), start, stop):
236
+ tc = itype.timecycle_t(*self.hf['SPECdata/Times'][i])
237
+ iy = self.hf['SPECdata/Intensities'][i]
238
+ if has_mc_segments:
239
+ raise NotImplementedError("new style mass-cal")
240
+ else:
241
+ mc_map = self.hf['CALdata/Mapping']
242
+ mc_pars = self.hf['CALdata/Spectrum'][i]
243
+ mc_segs = mc_pars.reshape((1, mc_pars.size))
244
+ mc = itype.masscal_t(0, mc_map[:, 0], mc_map[:, 1], mc_pars, mc_segs)
245
+ ad = dict()
246
+ for ad_info, ad_frame in add_data_dicts.items():
247
+ ad_series = ad_frame.iloc[i]
248
+ unit = ''
249
+ view = 1
250
+ ad[ad_info] = [itype.add_data_item_t(val, name, unit, view)
251
+ for name, val in ad_series.items()]
252
+ yield itype.fullcycle_t(tc, iy, mc, ad)
253
+
254
+ def list_file_structure(self):
255
+ """Lists all hdf5 group- and dataset-names."""
256
+ # this walks all h5 objects in alphabetic order:
257
+ obj_names = set()
258
+ self.hf.visit(lambda obj_name: obj_names.add(obj_name))
259
+
260
+ return sorted(obj_names)
261
+
262
+ def list_addtrace_groups(self):
263
+ """Lists the recorded additional trace-groups."""
264
+ return sorted(self._locate_datainfo())
265
+
266
+ def __repr__(self):
267
+ return "<%s (%s) [no. %s] %s>" % (self.__class__.__name__,
268
+ self.inst_type, self.serial_nr, self.hf.filename)
269
+
270
+ @lru_cache
271
+ def _locate_datainfo(self):
272
+ """Lookup groups with data-info traces."""
273
+ dataloc = set()
274
+ infoloc = set()
275
+
276
+ def func(object_name):
277
+ nonlocal dataloc
278
+ nonlocal infoloc
279
+ if object_name.endswith('/Data'):
280
+ dataloc |= {object_name[:-5], }
281
+ if object_name.endswith('/Info'):
282
+ infoloc |= {object_name[:-5], }
283
+ return None
284
+
285
+ # use the above 'visit'-function that appends matched sections...
286
+ self.hf.visit(func)
287
+
288
+ # ...and return only groups with both /Data and /Info datasets:
289
+ return dataloc.intersection(infoloc)
290
+
291
+ def traces(self):
292
+ """Returns a 'pandas.DataFrame' with all traces concatenated."""
293
+ return self.read_all(kind='conc', index='abs_cycle', force_original=False)
294
+
295
+ # TODO :: optimize: gib eine 'smarte' Series zurueck, die sich die aufgerufenen
296
+ # columns merkt! diese haelt die ganze erste Zeile des datensatzes.
297
+ # ab dem zweiten durchgang kann die Series auf diese columns
298
+ # reduziert werden
299
+ # uuuuuuuuuund so geht's:
300
+ # - defaultdict ~> factory checkt parIDs! ~> sonst KeyError
301
+ # |__ wird dann bei bedarf ge-populated
302
+ #
303
+ # das sourcefile / measurement soll sich wie ein pd.DataFrame "anfuehlen":
304
+
305
+ # das loest das Problem, aus einer "Matrix2 gezielt eine Zeile oder eine "Spalte"
306
+ # oder alles (d.h. iterieren ueber Zeilen) zu selektieren und zwar intuitiv!!
307
+
308
+ # IDEE: die .traces "tun so, als waren sie ein DataFrame"
309
+ # (keine inheritance, nur ein paar methoden werden durch effizientere ersetzt):
310
+ # wir brauchen:
311
+ # 1. _len_getter ~> reace condi vermeiden!
312
+ # 2. |__ index_getter
313
+ # 3. _column_getter
314
+ # 4. _row_getter
315
+ # 5. parID_resolver ~> keys() aus ParID.txt zu addtrace-group + column!
316
+
317
+ ###################################################################################
318
+ # #
319
+ # ENDZIEL: times u. Automation fuer die "letzte" Zeile an die Datenbank schicken! #
320
+ # #
321
+ ###################################################################################
322
+
323
+ def __getitem__(self, key):
324
+ index = self.make_index()
325
+ if isinstance(key, str):
326
+ return pd.Series(self._get_datacolumn(key), name=key, index=index)
327
+ else:
328
+ return pd.DataFrame({k: self._get_datacolumn(k) for k in key}, index=index)
329
+
330
+ @lru_cache
331
+ def _build_datainfo(self):
332
+ """Parse all "Data-Info" groups and build a lookup-table.
333
+ """
334
+ lut = dict()
335
+ for group_name in self._locate_datainfo():
336
+ info = self.hf[group_name + '/Info']
337
+ for column, label in enumerate(info[:] if info.ndim == 1 else info[0,:]):
338
+ if hasattr(label, 'decode'):
339
+ label = label.decode('latin1')
340
+ lut[label] = group_name + '/Data', column
341
+
342
+ return lut
343
+
344
+ def _get_datacolumn(self, key):
345
+ lut = self._build_datainfo()
346
+ if key not in lut and not key.endswith('_Act') and not key.endswith('_Set'):
347
+ # fallback to act-value (which is typically wanted):
348
+ key = key + '_Act'
349
+
350
+ dset_name, column = lut[key] # may raise KeyError
351
+
352
+ return self.hf[dset_name][:,column]
353
+
354
+ def loc(self, label):
355
+ if isinstance(label, int):
356
+ return self.iloc[self.make_index('abs_cycle')[label]]
357
+ else:
358
+ return self.iloc[self.make_index('abs_time')[label]]
359
+
360
+ def iloc(self, offset):
361
+ # build a row of all trace-data...
362
+ lut = self._build_datainfo()
363
+ name = self.make_index()[offset]
364
+ data = {key: self.hf[h5_loc][offset,col] for key, [h5_loc, col] in lut.items()}
365
+
366
+ return pd.Series(data, name=name)
367
+
368
+ @lru_cache
369
+ def _read_datainfo(self, group, prefix=''):
370
+ """Parse a "Data-Info" group into a pd.DataFrame.
371
+
372
+ - 'group' a hdf5 group or a string-location to a group
373
+ - 'prefix' names an optional sub-group
374
+ """
375
+ if isinstance(group, str):
376
+ group = self.hf[group]
377
+ data = group[prefix + 'Data']
378
+ info = group[prefix + 'Info']
379
+ if info.ndim > 1:
380
+ labels = info[0,:]
381
+ else:
382
+ labels = info[:]
383
+
384
+ if hasattr(labels[0], 'decode'):
385
+ labels = [b.decode('latin1') for b in labels]
386
+
387
+ # TODO :: wir haben hier diese doesigen Set/Act werte drin, was wollen wir??
388
+ # if keys[0].endswith('[Set]'):
389
+ # rv = {key[:-5]: (value, unit)
390
+ # for key, value, unit in zip(keys, values, units)
391
+ # if key.endswith('[Set]')}
392
+ # else:
393
+ # rv = {key: (value, unit)
394
+ # for key, value, unit in zip(keys, values, units)}
395
+
396
+ # siehe auch:
397
+
398
+ #def datainfo2df(h5group, selection=slice(None)):
399
+ # """
400
+ # Split a Data-Info-group into `pd.DataFrame`s for set- and act-values, respectively.
401
+ #
402
+ # Note, that the column names are inferred from the Info-dataset and that
403
+ # the columns of act- and set-dataframe need not overlap!
404
+ #
405
+ # `h5group` - a HDF5-group containing datasets "Data" and "Info"
406
+ # `selection` - [slice, optional] load only a part of the TimeCycle-data
407
+ # """
408
+ # from collections import namedtuple
409
+ # _trace = namedtuple('Trace', ['set', 'act'])
410
+ #
411
+ # names = (info.decode('latin-1') for info in h5group['Info'][0])
412
+ # units = (info.decode('latin-1') for info in h5group['Info'][1])
413
+ #
414
+ # df = pd.DataFrame(h5group['Data'][selection], columns=names)
415
+ #
416
+ # set_cols = [col for col in df.columns if col.endswith('_Set')]
417
+ # act_cols = [col for col in df.columns if col.endswith('_Act')]
418
+ #
419
+ # set_values = df[set_cols]
420
+ # act_values = df[act_cols]
421
+ #
422
+ # set_values.columns = [col.replace('_Set', '') for col in set_values.columns]
423
+ # act_values.columns = [col.replace('_Act', '') for col in act_values.columns]
424
+ #
425
+ # return _trace(set_values, act_values)
426
+
427
+
428
+ return pd.DataFrame(data, columns=labels)
429
+
430
+ def _read_processed_traces(self, kind, index):
431
+ # error conditions:
432
+ # 1) 'kind' is not recognized -> ValueError
433
+ # 2) no 'PROCESSED/TraceData' group -> GroupNotFoundError
434
+ # 3) expected group not found -> KeyError (file is not supported yet)
435
+ lut = {
436
+ 'con': 'Concentrations',
437
+ 'raw': 'Raw',
438
+ 'cor': 'Corrected',
439
+ }
440
+ tracedata = self.hf.get('PROCESSED/TraceData')
441
+ if tracedata is None:
442
+ raise GroupNotFoundError()
443
+
444
+ try:
445
+ prefix = lut[kind[:3].lower()]
446
+ except KeyError as exc:
447
+ msg = ("Unknown trace-type! `kind` must be one of 'raw', 'corrected' or 'concentration'.")
448
+ raise ValueError(msg) from exc
449
+
450
+ try:
451
+ data = self._read_datainfo(tracedata, prefix=prefix) # may raise KeyError
452
+ pt = self._read_datainfo(tracedata, prefix='PeakTable') # may raise KeyError
453
+ labels = [b.decode('latin1') for b in pt['label']]
454
+ except KeyError as exc:
455
+ raise KeyError(f'unknown group {exc}. filetype is not supported yet.') from exc
456
+
457
+ mapper = dict(zip(data.columns, labels))
458
+ data.rename(columns=mapper)
459
+ data.index = list(self.iter_index(index))
460
+
461
+ return data
462
+
463
+ def _read_original_traces(self, kind, index):
464
+ lut = {
465
+ 'con': 'TraceConcentration',
466
+ 'raw': 'TraceRaw',
467
+ 'cor': 'TraceCorrected',
468
+ }
469
+ tracedata = self.hf['TRACEdata']
470
+ try:
471
+ loc = lut[kind[:3].lower()]
472
+ data = tracedata[loc]
473
+ except KeyError as exc:
474
+ msg = ("Unknown trace-type! `kind` must be one of 'raw', 'corrected' or 'concentration'.")
475
+ raise ValueError(msg) from exc
476
+
477
+ info = self.hf['TRACEdata/TraceInfo']
478
+ labels = [b.decode('latin1') for b in info[1,:]]
479
+
480
+ return pd.DataFrame(data, columns=labels, index=list(self.iter_index(index)))
481
+
pytrms/tracebuffer.py CHANGED
@@ -7,7 +7,7 @@ from threading import Thread, Condition
7
7
 
8
8
  import pandas as pd
9
9
 
10
- from .helpers import convert_labview_to_posix
10
+ from .helpers import convert_labview_to_posix, PTRConnectionError
11
11
 
12
12
 
13
13
  def parse(response, trace='raw'):
@@ -28,7 +28,7 @@ class TraceBuffer(Thread):
28
28
  poll = 0.2 # seconds
29
29
 
30
30
  class State(Enum):
31
- UNKNOWN = -1
31
+ CONNECTING = -1
32
32
  IDLE = 0
33
33
  ACTIVE = 1
34
34
 
@@ -39,21 +39,46 @@ class TraceBuffer(Thread):
39
39
  self.daemon = True
40
40
  self.client = client
41
41
  self.queue = queue.Queue()
42
- self.state = TraceBuffer.State.UNKNOWN
42
+ self.state = TraceBuffer.State.CONNECTING
43
43
  self._cond = Condition()
44
44
 
45
- @property
46
- def is_idle(self):
47
- while self.state == TraceBuffer.State.UNKNOWN:
48
- time.sleep(0.01)
45
+ def is_connected(self):
46
+ return self.state != TraceBuffer.State.CONNECTING
49
47
 
48
+ def is_idle(self):
50
49
  return self.state == TraceBuffer.State.IDLE
51
50
 
51
+ def wait_for_connection(self, timeout=5):
52
+ '''
53
+ will raise a PTRConnectionError if not connected after `timeout`
54
+ '''
55
+ waited = 0
56
+ dt = 0.01
57
+ while not self.is_connected():
58
+ waited += dt
59
+ if waited > timeout:
60
+ raise PTRConnectionError('no connection to instrument')
61
+ time.sleep(dt)
62
+
52
63
  def run(self):
53
- last = -753 # the year Rome was founded
64
+ last = -753 # the year Rome was founded is never a valid cycle
54
65
  while True:
66
+ #TODO :: das kann ja gar nicht funktionieren!?!?
67
+ #if not self.is_connected():
68
+ # time.sleep(self.poll)
69
+ # continue
70
+
71
+ time.sleep(self.poll)
72
+
55
73
  with self._cond: # .acquire()`s the underlying lock
56
74
  raw = self.client.get_traces()
75
+ #try:
76
+ #except PTRConnectionError as exc:
77
+ # print(exc)
78
+ # break
79
+ if not len(raw):
80
+ continue
81
+
57
82
  jsonized = json.loads(raw)
58
83
  ts = jsonized['TimeCycle']['AbsTime']
59
84
  oc = jsonized['TimeCycle']['OverallCycle']
@@ -0,0 +1,19 @@
1
+ Metadata-Version: 2.1
2
+ Name: pytrms
3
+ Version: 0.9.0
4
+ Summary: Python bundle for proton-transfer reaction mass-spectrometry (PTR-MS).
5
+ License: GPL-2.0
6
+ Author: Moritz Koenemann
7
+ Author-email: moritz.koenemann@ionicon.com
8
+ Requires-Python: >=3.10,<4.0
9
+ Classifier: License :: OSI Approved :: GNU General Public License v2 (GPLv2)
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Requires-Dist: h5py (>=3.12.1,<4.0.0)
15
+ Requires-Dist: matplotlib (>=3.9.2,<4.0.0)
16
+ Requires-Dist: paho-mqtt (>=1.6.1,<1.7.0)
17
+ Requires-Dist: pandas (>=2.2.3,<3.0.0)
18
+ Requires-Dist: pyModbusTCP (>=0.3.0,<0.4.0)
19
+ Requires-Dist: requests (>=2.32.3,<3.0.0)
@@ -0,0 +1,29 @@
1
+ pytrms/__init__.py,sha256=U4ixhTjfFkiVlLXV4JXcRq-eLFkdKlGlU4e52Zlbpsg,970
2
+ pytrms/_base/__init__.py,sha256=GBALqAy1kUPMc0CWnWRmn_Cg_HGKGCeE-V2rdZEmN8A,836
3
+ pytrms/_base/ioniclient.py,sha256=f4xWW3PL0p1gP7pczBaDEva3WUUb_J73n7yTIY5pW4s,826
4
+ pytrms/_base/mqttclient.py,sha256=iua-AK7S_3rH1hsuLepqIjf7ivF5Ihb6T9OJNafJXXE,4233
5
+ pytrms/_version.py,sha256=fXD69jGmDocNcc-uipbNIhjWxC7EuyhPUQ6BrBvK0Wg,854
6
+ pytrms/clients/__init__.py,sha256=RejfXqq7zSKLMZbUHdxtBvLiyeshLX-JQzgthmpYeMw,1404
7
+ pytrms/clients/db_api.py,sha256=exgE4iUHdACprztYYs330NzYI88f6TwF8PjtBzohgjk,6658
8
+ pytrms/clients/dirigent.py,sha256=9WZPfqXxLmFCcT2RC8wxm7uixe4n-Y8vbsdpzc61tsI,5742
9
+ pytrms/clients/ioniclient.py,sha256=5s3GPDr6AKH4Ia0Jix6UI8Xie_t3muKxr218MNRk2Yw,2853
10
+ pytrms/clients/modbus.py,sha256=E1IfoHJm2gVHo-GcKDWba359ii5e0Ymaplv-DO88FqM,19223
11
+ pytrms/clients/mqtt.py,sha256=QRJgC10GWFKzT3r4yNGOafXshgHlA5poGEzouEporEU,30231
12
+ pytrms/clients/ssevent.py,sha256=OziYX9amJunVzTN0MiQmLg2mICHdnVC0LXT5o_ZcwcE,2546
13
+ pytrms/compose/__init__.py,sha256=gRkwGezTsuiMLA4jr5hhQsY7XX_FonBeWcvDfDuMFnY,30
14
+ pytrms/compose/composition.py,sha256=c3ae3PgQg0b741RhRU6oQMAiM7B2xRvbweGp0tCZatc,7906
15
+ pytrms/data/IoniTofPrefs.ini,sha256=e1nU7Hyuh0efpgfN-G5-ERAFC6ZeehUiotsM4VtZIT0,1999
16
+ pytrms/data/ParaIDs.csv,sha256=eWQxmHFfeTSxFfMcFpqloiZWDyK4VLZaV9zCnzLHNYs,27996
17
+ pytrms/helpers.py,sha256=pNLYWlHM1n7vOkCi2vwESMG5baIJ5v2OD4z94XpXU1k,108
18
+ pytrms/instrument.py,sha256=OIFTbS6fuhos6oYMsrA_qdgvs_7T-H-sOSl1a0qpxZ8,3977
19
+ pytrms/measurement.py,sha256=RqACqedT0JQDkv5cmKxcXVSgl1n0Fbp1mQj81aOsZkg,5507
20
+ pytrms/peaktable.py,sha256=b3KkILn5DctEfTi1-tOC9LZv1yrRcoH23VaE5JEqyk4,17536
21
+ pytrms/plotting/__init__.py,sha256=vp5mAa3npo4kP5wvXXNDxKnryFK693P4PSwC-BxEUH8,66
22
+ pytrms/plotting/plotting.py,sha256=t7RXUhjOwXchtYXVgb0rJPD_9aVMDCT6DCAGu8TZQfE,702
23
+ pytrms/readers/__init__.py,sha256=F1ZX4Jv7NcY8nuSWnIbwjYnNFC2JsTnEp0BnfBHUMSM,76
24
+ pytrms/readers/ionitof_reader.py,sha256=vhwVfhJ-ly8I_02okpgxoVqnB0QRDuqkufGZY21vFi4,18583
25
+ pytrms/tracebuffer.py,sha256=30HfMJtxZ4Dnf4mPeyinXqESOA-0Xpu9FR4jaBvb_nA,3912
26
+ pytrms-0.9.0.dist-info/LICENSE,sha256=GJsa-V1mEVHgVM6hDJGz11Tk3k0_7PsHTB-ylHb3Fns,18431
27
+ pytrms-0.9.0.dist-info/METADATA,sha256=sTHS7ZvxCdgcaTj_hCI_Vb8erA1nIUBCs4JMGb06mRg,770
28
+ pytrms-0.9.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
29
+ pytrms-0.9.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry 1.0.8
2
+ Generator: poetry-core 1.9.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any