readerbotda 1.3.2__tar.gz → 1.4.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: readerbotda
3
- Version: 1.3.2
3
+ Version: 1.4.0
4
4
  Summary: Pacchetto per lettura e visualizzazione file di log sensore BOTDA Cohaerentia
5
5
  Author: Marco Brunero
6
6
  Author-email: marco.brunero@cohaerentia.com
@@ -11,6 +11,8 @@ Classifier: Programming Language :: Python :: 3.12
11
11
  Classifier: Programming Language :: Python :: 3.13
12
12
  Requires-Dist: bokeh (>=3.7.2,<4.0.0)
13
13
  Requires-Dist: dacite (>=1.9.2,<2.0.0)
14
+ Requires-Dist: h5py (>=3.14.0,<4.0.0)
15
+ Requires-Dist: nbformat (>=5.10.4,<6.0.0)
14
16
  Requires-Dist: numpy (>=2.2.4,<3.0.0)
15
17
  Requires-Dist: plotly (>=6.0.1,<7.0.0)
16
18
  Requires-Dist: progress (>=1.6,<2.0)
@@ -23,16 +25,12 @@ Pacchetto per lettura e visualizzazione delle misure salvate da software per sen
23
25
 
24
26
  ## Installazione
25
27
 
26
- Da cartella locale dove è stato scaricato il pacchetto è possibile installare tramite:
28
+ Tramite `pip`:
27
29
 
28
30
  ```bash
29
- pip install .
31
+ pip install readerbotda
30
32
  ```
31
33
 
32
- In questo modo i files dovrebbero essere ancora editabili e la versione installata dovrebbe automaticamente aggiornarsi. Nota bene: se si sta usando un notebook, è necessario riavviare il kernel, non è sufficiente re importare il pacchetto.
33
-
34
- Dovrebbe anche essere possibile installare direttamente tramite link al repository privato ma devo ancora guardare come.
35
-
36
34
  ## Utilizzo
37
35
 
38
36
  Per prima cosa è necessario caricare il necessario dai due moduli di lettura e visualizzazione:
@@ -56,7 +54,7 @@ L'idea è che chiunque potrebbe creare una nuova classe parente della classe `Pl
56
54
 
57
55
  ### Misura Singola
58
56
 
59
- Lettura di singola misura da un singolo file "profilo".
57
+ Lettura di singola misura da un singolo file "profilo" di tipo json.
60
58
 
61
59
  ```python
62
60
  misura = Profile('data/profiles/2021-11-08_16-51-16.652_rawarray.json',plotter=plotter)
@@ -68,7 +66,7 @@ fig.write_html("prova.html", full_html=False, include_plotlyjs='cdn')
68
66
 
69
67
  ### Misure multiple in una cartella
70
68
 
71
- Lettura di tutti i file di tipo profilo in una cartella:
69
+ Lettura di tutti i file di tipo profilo in una cartella, contenente files di tipo json:
72
70
 
73
71
  ```python
74
72
  from datetime import datetime
@@ -123,7 +121,7 @@ Si utilizza la funzione [`np.corrcoef`](https://numpy.org/doc/stable/reference/g
123
121
 
124
122
  ### Misura Raw
125
123
 
126
- Lettura di file di debug che contiene intera matrice BGS e test dei metodi disponibili.
124
+ Lettura di file json di debug che contiene intera matrice BGS e test dei metodi disponibili.
127
125
 
128
126
  ```python
129
127
  raw = Raw(filename='data/raw/2021-11-08_16-51-16.652_rawmatrix.json', plotter=plotter)
@@ -139,6 +137,18 @@ fig = raw.plotBGS(index=125)
139
137
  plotter.show(fig)
140
138
  ```
141
139
 
140
+ ## Lettura di misure in singolo file H5
141
+
142
+ Per leggere misure salvate in un file H5, è possibile utilizzare la classe `h5Profile`:
143
+
144
+ ```python
145
+ from ReaderBOTDA.reader import h5Profile
146
+
147
+ h5 = h5Profile('data/2023_09/build1.3_profile.h5', plotter=plotter)
148
+ ```
149
+
150
+ A questo punto l'oggetto `h5Profile` si dovrebbe comportare come `multipleProfile`, quindi è possibile utilizzare i metodi `plot()`, `calcStatistics()`, `calcCorrelations()` e così via.
151
+
142
152
  ## Plotter Bokeh
143
153
 
144
154
  Oltre al plotter Plotly è disponibile anche una versione che utilizza il pacchetto `bokeh`. Non sono al momento disponibili i metodi `Bokeh.plot2d()` e `Bokeh.plot3d()`.
@@ -161,3 +171,10 @@ fig = misura.plot()
161
171
  plotter.show(fig)
162
172
  ```
163
173
 
174
+ ## TODO
175
+
176
+ 1. lettura misure raw da file H5.
177
+ 2. sistemare settings nelle ultime versioni di sw.
178
+ 3. sistemare la timezone nelle misure.
179
+ 4. aggiungere ricalcolo stima BFS a partire da dati raw.
180
+
@@ -4,16 +4,12 @@ Pacchetto per lettura e visualizzazione delle misure salvate da software per sen
4
4
 
5
5
  ## Installazione
6
6
 
7
- Da cartella locale dove è stato scaricato il pacchetto è possibile installare tramite:
7
+ Tramite `pip`:
8
8
 
9
9
  ```bash
10
- pip install .
10
+ pip install readerbotda
11
11
  ```
12
12
 
13
- In questo modo i files dovrebbero essere ancora editabili e la versione installata dovrebbe automaticamente aggiornarsi. Nota bene: se si sta usando un notebook, è necessario riavviare il kernel, non è sufficiente re importare il pacchetto.
14
-
15
- Dovrebbe anche essere possibile installare direttamente tramite link al repository privato ma devo ancora guardare come.
16
-
17
13
  ## Utilizzo
18
14
 
19
15
  Per prima cosa è necessario caricare il necessario dai due moduli di lettura e visualizzazione:
@@ -37,7 +33,7 @@ L'idea è che chiunque potrebbe creare una nuova classe parente della classe `Pl
37
33
 
38
34
  ### Misura Singola
39
35
 
40
- Lettura di singola misura da un singolo file "profilo".
36
+ Lettura di singola misura da un singolo file "profilo" di tipo json.
41
37
 
42
38
  ```python
43
39
  misura = Profile('data/profiles/2021-11-08_16-51-16.652_rawarray.json',plotter=plotter)
@@ -49,7 +45,7 @@ fig.write_html("prova.html", full_html=False, include_plotlyjs='cdn')
49
45
 
50
46
  ### Misure multiple in una cartella
51
47
 
52
- Lettura di tutti i file di tipo profilo in una cartella:
48
+ Lettura di tutti i file di tipo profilo in una cartella, contenente files di tipo json:
53
49
 
54
50
  ```python
55
51
  from datetime import datetime
@@ -104,7 +100,7 @@ Si utilizza la funzione [`np.corrcoef`](https://numpy.org/doc/stable/reference/g
104
100
 
105
101
  ### Misura Raw
106
102
 
107
- Lettura di file di debug che contiene intera matrice BGS e test dei metodi disponibili.
103
+ Lettura di file json di debug che contiene intera matrice BGS e test dei metodi disponibili.
108
104
 
109
105
  ```python
110
106
  raw = Raw(filename='data/raw/2021-11-08_16-51-16.652_rawmatrix.json', plotter=plotter)
@@ -120,6 +116,18 @@ fig = raw.plotBGS(index=125)
120
116
  plotter.show(fig)
121
117
  ```
122
118
 
119
+ ## Lettura di misure in singolo file H5
120
+
121
+ Per leggere misure salvate in un file H5, è possibile utilizzare la classe `h5Profile`:
122
+
123
+ ```python
124
+ from ReaderBOTDA.reader import h5Profile
125
+
126
+ h5 = h5Profile('data/2023_09/build1.3_profile.h5', plotter=plotter)
127
+ ```
128
+
129
+ A questo punto l'oggetto `h5Profile` si dovrebbe comportare come `multipleProfile`, quindi è possibile utilizzare i metodi `plot()`, `calcStatistics()`, `calcCorrelations()` e così via.
130
+
123
131
  ## Plotter Bokeh
124
132
 
125
133
  Oltre al plotter Plotly è disponibile anche una versione che utilizza il pacchetto `bokeh`. Non sono al momento disponibili i metodi `Bokeh.plot2d()` e `Bokeh.plot3d()`.
@@ -141,3 +149,10 @@ misura = Profile('data/profiles/2021-11-08_16-51-16.652_rawarray.json',plotter=p
141
149
  fig = misura.plot()
142
150
  plotter.show(fig)
143
151
  ```
152
+
153
+ ## TODO
154
+
155
+ 1. lettura misure raw da file H5.
156
+ 2. sistemare settings nelle ultime versioni di sw.
157
+ 3. sistemare la timezone nelle misure.
158
+ 4. aggiungere ricalcolo stima BFS a partire da dati raw.
@@ -0,0 +1,389 @@
1
+ from ReaderBOTDA.settings import parseSettingsDict, Settings
2
+ from ReaderBOTDA.plotter.abc import Plotter
3
+ from ReaderBOTDA.plotter.plotly import Plotly
4
+
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from json import load, loads
8
+ import numpy as np
9
+ import numpy.typing as npt
10
+ import h5py
11
+ from datetime import datetime, timezone # TODO usare numpy anche per questo?
12
+ from os import path, PathLike, strerror
13
+ from glob import glob
14
+ from typing import Tuple, Union, Literal
15
+ import errno
16
+ import warnings
17
+ from progress.bar import Bar
18
+
19
+
20
+ class NoFilesSelected(Exception):
21
+ def __init__(self, folder, message="No file found in folder"):
22
+ self.message = f"{message}: {folder}"
23
+ super().__init__(self.message)
24
+
25
+
26
+ # TODO da implementare nei metodi. E aggiungere anche i campi MaxMean e MaxStd
27
+ @dataclass
28
+ class Statistics:
29
+ BFSmean: npt.ArrayLike
30
+ BFSstd: npt.ArrayLike
31
+ BFSstd_mean: float
32
+
33
+
34
+ class Profile:
35
+
36
+ filename: str = ""
37
+ plotter: Plotter
38
+ settings: Settings
39
+ sw_version: str = "<1.2.0.0"
40
+ BFS: np.ndarray = np.array([])
41
+ MaxGain: np.ndarray = np.array([])
42
+ timestamp: datetime = datetime.now
43
+
44
+ def __init__(
45
+ self, filename: PathLike, plotter: Plotter = Plotly(), name: str = None
46
+ ) -> None:
47
+ """Load a single measure from a json file."""
48
+ self.filename = filename
49
+ self.plotter = plotter
50
+ with open(self.filename) as file:
51
+ text = load(file)
52
+ self.settings = parseSettingsDict(text["Settings"])
53
+ if "sw_version" in text:
54
+ self.sw_version = text["sw_version"]
55
+ if "sw_version" in text and text["sw_version"] == "":
56
+ self.sw_version = "ambiente sviluppo"
57
+ self.timestamp = datetime.strptime(
58
+ text["Time Stamp"], "%Y-%m-%d" + "T" + "%H:%M:%S.%f" + "Z"
59
+ ) # "2021-11-08T16:51:16.652Z"
60
+ if not name:
61
+ self.name = self.timestamp.strftime("%m/%d/%Y, %H:%M:%S")
62
+ else:
63
+ self.name = name
64
+
65
+ self.BFS = np.array(text["Profile"])
66
+ self._createPositionArray()
67
+ if "arrayMaxGain" in text:
68
+ self.MaxGain = np.array(text["arrayMaxGain"])
69
+
70
+ def _createPositionArray(self):
71
+ self.position = np.linspace(
72
+ 0, self.settings.Cable.Length, num=len(self.BFS), endpoint=True
73
+ )
74
+ self.spatialResolution = self.position[1]
75
+
76
+ def plot(self, title: str = None):
77
+ if not title:
78
+ title = self.name
79
+ # TODO usare xrange espresso in metri per affettare array e poi passarli a single_plot. xrange:Tuple[float,float]=None
80
+ return self.plotter.single_plot(self.position, self.BFS, title=title)
81
+
82
+ def plotMax(self, title: str = None):
83
+ if self.MaxGain.size == 0:
84
+ warnings.warn(
85
+ "Max array in not available. If possibile, load the raw file of the same measure."
86
+ )
87
+ return None
88
+ if not title:
89
+ title = self.name
90
+ return self.plotter.max_plot(self.position, self.MaxGain, title)
91
+
92
+
93
+ class multipleProfile:
94
+
95
+ statistics: Statistics
96
+ plotter: Plotter
97
+
98
+ def __init__(
99
+ self,
100
+ folder: PathLike,
101
+ plotter: Plotter = Plotly(),
102
+ n_measure: int = None,
103
+ start_measure: Union[int, datetime] = 0,
104
+ stop_measure: datetime = None,
105
+ ):
106
+
107
+ self.folder = folder
108
+ self.plotter = plotter
109
+
110
+ filelist = glob(path.join(folder, "*.json"))
111
+ if start_measure and isinstance(start_measure, datetime):
112
+ timestamps_files = [
113
+ datetime.strptime(
114
+ "_".join(path.basename(file).split("_")[:2]), "%Y-%m-%d_%H-%M-%S.%f"
115
+ )
116
+ for file in filelist
117
+ ]
118
+ primo = next(
119
+ (
120
+ x
121
+ for x, value in enumerate(timestamps_files)
122
+ if value >= start_measure
123
+ ),
124
+ 0,
125
+ )
126
+ if stop_measure:
127
+ ultimo = next(
128
+ (
129
+ x
130
+ for x, value in enumerate(timestamps_files)
131
+ if value > stop_measure
132
+ ),
133
+ len(filelist),
134
+ )
135
+ elif n_measure:
136
+ ultimo = primo + n_measure
137
+ else:
138
+ ultimo = len(filelist)
139
+ filelist = filelist[primo:ultimo]
140
+
141
+ if n_measure and (start_measure == 0 or isinstance(start_measure, int)):
142
+ filelist = filelist[start_measure : start_measure + n_measure]
143
+
144
+ if isinstance(start_measure, int) and not n_measure and stop_measure:
145
+ timestamps_files = [
146
+ datetime.strptime(
147
+ "_".join(path.basename(file).split("_")[:2]), "%Y-%m-%d_%H-%M-%S.%f"
148
+ )
149
+ for file in filelist
150
+ ]
151
+ ultimo = next(
152
+ (x for x, value in enumerate(timestamps_files) if value > stop_measure),
153
+ len(filelist),
154
+ )
155
+ filelist = filelist[start_measure:ultimo]
156
+
157
+ if len(filelist) == 0:
158
+ raise NoFilesSelected(folder=folder)
159
+
160
+ timestamps = list()
161
+ with Bar("Redaing files", max=len(filelist)) as bar:
162
+ for file in filelist:
163
+ temp = Profile(filename=file)
164
+ timestamps.append(temp.timestamp)
165
+ try:
166
+ self.BFS = np.column_stack((self.BFS, temp.BFS))
167
+ self.MaxGain = np.column_stack((self.MaxGain, temp.MaxGain))
168
+ except AttributeError:
169
+ self.BFS = temp.BFS
170
+ self.MaxGain = temp.MaxGain
171
+ bar.next()
172
+
173
+ self.timestamps = np.array(timestamps)
174
+ self.settings = temp.settings
175
+ self.sw_version = temp.sw_version
176
+ self.position = temp.position
177
+ self.calcStatistics()
178
+ self.MaxGainMean = self.MaxGain.mean(axis=1)
179
+ self.MaxGainStd = self.MaxGain.std(axis=1)
180
+
181
+ def calcCorrelations(
182
+ self,
183
+ type: Literal["max", "bfs"],
184
+ reference: Literal["first", "previous"] = "previous",
185
+ range: Tuple[float, float] = None,
186
+ ) -> np.array:
187
+ """Ritorna correlazione tra prima misura e misura n-esima.
188
+ Si può scegliere se effettuarla su matrice dei BFS o matrice dei massimi"""
189
+
190
+ if type == "max":
191
+ correlations = np.corrcoef(
192
+ self.MaxGain[range[0] : range[1]] if range else self.MaxGain,
193
+ rowvar=False,
194
+ )
195
+ else:
196
+ correlations = np.corrcoef(
197
+ self.BFS[range[0] : range[1]] if range else self.BFS, rowvar=False
198
+ )
199
+
200
+ if reference == "first":
201
+ return correlations[0, :]
202
+
203
+ indici = np.arange(1, np.shape(correlations)[0])
204
+ return np.insert(correlations[indici, indici - 1], 0, 1)
205
+
206
+ def calcStatistics(
207
+ self, plot: bool = False, range: Tuple[float, float] = None, title: str = None
208
+ ) -> Statistics:
209
+ # TODO gestione input range
210
+ self.statistics = Statistics(
211
+ BFSmean=self.BFS.mean(axis=1),
212
+ BFSstd=self.BFS.std(axis=1),
213
+ BFSstd_mean=self.BFS.std(axis=1).mean(),
214
+ )
215
+
216
+ if plot:
217
+ if not title:
218
+ title = self.folder
219
+ return self.plotter.statistics(
220
+ self.position,
221
+ self.statistics.BFSmean,
222
+ self.statistics.BFSstd,
223
+ title=title,
224
+ )
225
+ return self.statistics
226
+
227
+ def plotStatistics(self, title: str = None):
228
+ if not title:
229
+ title = self.folder
230
+ # TODO se sono state calcolate con range allora position è sbagliato
231
+ return self.plotter.statistics(
232
+ self.position, self.statistics.BFSmean, self.statistics.BFSstd, title=title
233
+ )
234
+
235
+ def plot(
236
+ self,
237
+ startTime: datetime = datetime.min,
238
+ stopTime: datetime = datetime.now(),
239
+ title: str = None,
240
+ ):
241
+ if not title:
242
+ title = self.folder
243
+ match = [
244
+ i
245
+ for i, date in enumerate(self.timestamps)
246
+ if date >= startTime and date <= stopTime
247
+ ]
248
+ return self.plotter.multiple_plot(
249
+ self.position,
250
+ self.BFS[:, match],
251
+ [self.timestamps[i] for i in match],
252
+ title=title,
253
+ )
254
+
255
+ def plotMax(self, title: str = None):
256
+ if self.MaxGain.size == 0:
257
+ warnings.warn(
258
+ "Max array in not available. If possibile, load the raw file of the same measure."
259
+ )
260
+ return None
261
+ if not title:
262
+ title = self.folder
263
+ return self.plotter.max_stat_plot(self.MaxGainMean, self.MaxGainStd, title)
264
+
265
+
266
+ class h5Profile(multipleProfile):
267
+
268
+ def __init__(
269
+ self,
270
+ filename: PathLike,
271
+ plotter: Plotter = Plotly(),
272
+ # n_measure: int = None,
273
+ # start_measure: Union[int, datetime] = 0,
274
+ # stop_measure: datetime = None,
275
+ ):
276
+ """Load multiple measures from a h5 file."""
277
+ self.filename = filename
278
+ self.plotter = plotter
279
+
280
+ if not path.isfile(filename):
281
+ raise FileNotFoundError(errno.ENOENT, strerror(errno.ENOENT), filename)
282
+
283
+ with h5py.File(filename, "r") as f:
284
+ self.filename = filename
285
+ self.folder = f.attrs["folder"]
286
+ self.sw_version = f.attrs["sw_version"]
287
+
288
+ self.position = f["position"][:]
289
+ self.BFS = np.transpose(np.squeeze(f["data"]["bfs"][:]))
290
+ self.MaxGain = np.transpose(np.squeeze(f["data"]["max_gain"][:]))
291
+ self.timestamps = [
292
+ datetime.fromtimestamp(timestamp / 1000000) # FIXME, timezone.utc)
293
+ for timestamp in f["data"]["timestamps"][:]
294
+ ]
295
+ self.settings = loads(
296
+ f.attrs["settings"]
297
+ ) # FIXME parseSettingsDict(loads(f.attrs["settings"]))
298
+
299
+ self.calcStatistics()
300
+ self.MaxGainMean = self.MaxGain.mean(axis=1)
301
+ self.MaxGainStd = self.MaxGain.std(axis=1)
302
+
303
+
304
+ class Raw:
305
+
306
+ filename: PathLike = Path("")
307
+ plotter: Plotter
308
+ settings: Settings
309
+ sw_version: str = "<1.2.0.0"
310
+ BGS: np.ndarray = np.array([])
311
+ residuo: np.ndarray = np.array([])
312
+ timestamp: datetime = datetime.now
313
+
314
+ def __init__(self, filename: Union[str, Path], plotter: Plotter = Plotly()) -> None:
315
+ """Load a single raw data from a json file."""
316
+ self.filename = filename
317
+ self.plotter = plotter
318
+ with open(self.filename) as file:
319
+ text = load(file)
320
+ self.settings = parseSettingsDict(text["Settings"])
321
+ if "sw_version" in text:
322
+ self.sw_version = text["sw_version"]
323
+ if "sw_version" in text and text["sw_version"] == "":
324
+ self.sw_version = "ambiente sviluppo"
325
+ self.BGS = np.transpose(np.array(text["Raw"]))
326
+ self.timestamp = datetime.strptime(
327
+ text["Time Stamp"], "%Y-%m-%d" + "T" + "%H:%M:%S.%f" + "Z"
328
+ ) # "2021-11-08T16:51:16.652Z"
329
+ self._createPositionArray()
330
+ self._createFrequencyArray()
331
+ try:
332
+ self.residuo = np.array(text["residuo"])
333
+ except KeyError:
334
+ self.residuo = np.zeros(np.shape(self.frequency))
335
+
336
+ def _createPositionArray(self) -> None:
337
+ """Crea array numpy delle posizioni e la risoluzione spaziale"""
338
+ self.position = np.linspace(
339
+ 0, self.settings.Cable.Length, num=self.BGS.shape[1], endpoint=True
340
+ )
341
+ self.spatialResolution = self.position[1]
342
+
343
+ def _createFrequencyArray(self) -> None:
344
+ """Crea array numpy delle frequenze, espresse in GHz"""
345
+ self.frequency = (
346
+ np.linspace(
347
+ self.settings.Clock.StartMHz,
348
+ self.settings.Clock.StartMHz
349
+ + self.settings.Clock.StepMHz * self.BGS.shape[0],
350
+ num=self.BGS.shape[0],
351
+ endpoint=True,
352
+ )
353
+ / 1000
354
+ )
355
+
356
+ def plot2d(self, title: str = None):
357
+ if not title:
358
+ title = self.filename
359
+ return self.plotter.raw2d_plot(
360
+ self.position, self.frequency, self.BGS, title=title
361
+ )
362
+
363
+ def plot3d(self, title: str = None):
364
+ if not title:
365
+ title = self.filename
366
+ return self.plotter.raw3d_plot(
367
+ self.position, self.frequency, self.BGS, title=title
368
+ )
369
+
370
+ def plotBGS(self, index: int = None, title: str = None):
371
+ """Plot 2D di tutti gli spettri BGS"""
372
+ if not title:
373
+ title = self.filename
374
+ return self.plotter.rawBGS_plot(
375
+ self.frequency, self.BGS, index=index, title=title
376
+ )
377
+
378
+ def plotMax(self, title: str = None):
379
+ if not title:
380
+ title = self.filename
381
+ return self.plotter.max_plot(self.position, self.BGS.max(axis=0), title)
382
+
383
+
384
+ if __name__ == "__main__":
385
+ """Non va se si lancia direttamente questo script a meno di non cambiare i primi due import da modules.settings a settings; uguale per plotter."""
386
+ # a = Profile(filename='data/profiles/2021-11-08_16-51-16.652_rawarray.json')
387
+ # b = multipleProfile(folder='data/profiles/')
388
+ c = Raw(filename="data/raw/2021-11-08_16-51-16.652_rawmatrix.json")
389
+ # print(b.BFS.shape)
@@ -0,0 +1,66 @@
1
+ #TODO trovare modo di integrarlo in setup.py di setuptools: https://typer.tiangolo.com/tutorial/package/
2
+
3
+ '''
4
+ Per help, digitare in terminale: python readBOTDA.py --help
5
+ e poi per help specifici su uno dei comandi: python readBOTDA.py multi --help
6
+ '''
7
+
8
+ import typer
9
+ from pathlib import Path
10
+ from ReaderBOTDA.reader import Profile, multipleProfile, Raw
11
+ from ReaderBOTDA.plotter.plotly import Plotly
12
+ from dataclasses import asdict
13
+
14
+ app = typer.Typer()
15
+ PLOTTER = Plotly(theme='plotly_dark')
16
+
17
+ @app.command()
18
+ def single(filename: str, title:str=None):
19
+ if not title:
20
+ title = filename
21
+ data = Profile(filename,PLOTTER)
22
+ PLOTTER.show(data.plot(title=title))
23
+ typer.secho("Settings:", fg=typer.colors.GREEN, bold=True)
24
+ typer.echo(str(data.settings))
25
+
26
+ @app.command()
27
+ def multi(folder: str, title: str =None, stat: bool = True):
28
+ if not title:
29
+ title = folder
30
+ data = multipleProfile(folder,PLOTTER)
31
+ typer.secho(f"Lette {len(data.timestamps)} misure nella cartella {folder}", fg=typer.colors.GREEN, bold=True)
32
+ PLOTTER.show(data.plot(title=title))
33
+ if stat:
34
+ PLOTTER.show(data.calcStatistics(plot=True,title=title))
35
+ typer.secho("Settings:", fg=typer.colors.GREEN, bold=True)
36
+ typer.echo(str(data.settings))
37
+
38
+ @app.command()
39
+ def raw(filename: str, title: str = None):
40
+
41
+ from plotly.subplots import make_subplots
42
+ if not title:
43
+ title = filename
44
+
45
+ data = Raw(filename, PLOTTER)
46
+ typer.secho("Settings:", fg=typer.colors.GREEN, bold=True)
47
+ typer.echo(str(data.settings))
48
+
49
+ fig2d = data.plot2d()
50
+ figMax = data.plotMax()
51
+ figBGS = data.plotBGS()
52
+ fig = make_subplots(rows=2, cols=2, shared_xaxes='columns', column_widths=[0.25, 0.75],row_heights=[0.75, 0.25])
53
+
54
+ fig.add_trace(fig2d.data[0], row=1, col=2)
55
+ fig.add_trace(figMax.data[0], row=2, col=2)
56
+ fig.add_traces(figBGS.data, rows=1, cols=1)
57
+ fig.update_xaxes(title_text='Position (m)',row=2,col=2)
58
+ fig.update_xaxes(title_text='Frequency (GHz)',row=1,col=1)
59
+ fig.update_yaxes(title_text='Frequency (GHz)',row=1,col=2)
60
+ fig.update_yaxes(title_text='Amplitude (V)',row=1,col=1)
61
+ fig.update_yaxes(title_text='Max Amp (V)',row=2,col=2)
62
+ fig.update_layout(title_text=title,showlegend=False)
63
+ fig.show()
64
+
65
+ if __name__ == "__main__":
66
+ app()
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "readerbotda"
3
- version = "1.3.2"
3
+ version = "1.4.0"
4
4
  description = "Pacchetto per lettura e visualizzazione file di log sensore BOTDA Cohaerentia"
5
5
  authors = [
6
6
  {name = "Marco Brunero",email = "marco.brunero@cohaerentia.com"}
@@ -13,11 +13,16 @@ dependencies = [
13
13
  "numpy (>=2.2.4,<3.0.0)",
14
14
  "bokeh (>=3.7.2,<4.0.0)",
15
15
  "typer (>=0.15.2,<0.16.0)",
16
- "progress (>=1.6,<2.0)"
16
+ "progress (>=1.6,<2.0)",
17
+ "h5py (>=3.14.0,<4.0.0)",
18
+ "nbformat (>=5.10.4,<6.0.0)"
17
19
  ]
18
20
 
19
21
  [tool.poetry.scripts]
20
- botda-converter = "readBOTDA:app"
22
+ readBOTDA = "ReaderBOTDA.script:app"
23
+
24
+ [tool.poetry.group.dev.dependencies]
25
+ ipykernel = "^6.29.5"
21
26
 
22
27
  [build-system]
23
28
  requires = ["poetry-core>=2.0.0,<3.0.0"]
@@ -1,261 +0,0 @@
1
- from ReaderBOTDA.settings import parseSettingsDict, Settings
2
- from ReaderBOTDA.plotter.abc import Plotter
3
- from ReaderBOTDA.plotter.plotly import Plotly
4
-
5
- from dataclasses import dataclass
6
- from pathlib import Path
7
- from json import load
8
- import numpy as np
9
- from datetime import datetime # TODO usare numpy anche per questo?
10
- from os import path
11
- from glob import glob
12
- from typing import Tuple, Union, Literal
13
- import warnings
14
- from progress.bar import Bar
15
-
16
- class NoFilesSelected(Exception):
17
- def __init__(self, folder, message='No file found in folder'):
18
- self.message = f'{message}: {folder}'
19
- super().__init__(self.message)
20
-
21
-
22
-
23
- #TODO da implementare nei metodi. E aggiungere anche i campi MaxMean e MaxStd
24
- @dataclass
25
- class Statistics:
26
- BFSmean: np.array([])
27
- BFSstd: np.array([])
28
- BFSstd_mean: float
29
-
30
- class Profile:
31
-
32
- filename: str = ''
33
- plotter: Plotter
34
- settings: Settings
35
- sw_version: str = '<1.2.0.0'
36
- BFS: np.ndarray = np.array([])
37
- MaxGain: np.ndarray = np.array([])
38
- timestamp: datetime = datetime.now
39
-
40
-
41
- def __init__(self, filename: Union[str,Path], plotter: Plotter = Plotly(), name: str = None) -> None:
42
- self.filename = filename
43
- self.plotter = plotter
44
- with open(self.filename) as file:
45
- text = load(file)
46
- self.settings = parseSettingsDict(text['Settings'])
47
- if 'sw_version' in text:
48
- self.sw_version = text['sw_version']
49
- if 'sw_version' in text and text['sw_version'] == '':
50
- self.sw_version = 'ambiente sviluppo'
51
- self.timestamp = datetime.strptime(
52
- text['Time Stamp'], '%Y-%m-%d'+'T'+'%H:%M:%S.%f'+'Z') # "2021-11-08T16:51:16.652Z"
53
- if not name:
54
- self.name = self.timestamp.strftime("%m/%d/%Y, %H:%M:%S")
55
- else:
56
- self.name = name
57
-
58
- self.BFS = np.array(text['Profile'])
59
- self._createPositionArray()
60
- if 'arrayMaxGain' in text:
61
- self.MaxGain = np.array(text['arrayMaxGain'])
62
-
63
- def _createPositionArray(self):
64
- self.position = np.linspace(
65
- 0, self.settings.Cable.Length, num=len(self.BFS), endpoint=True)
66
- self.spatialResolution = self.position[1]
67
-
68
- def plot(self,title:str = None):
69
- if not title:
70
- title = self.name
71
- # TODO usare xrange espresso in metri per affettare array e poi passarli a single_plot. xrange:Tuple[float,float]=None
72
- return self.plotter.single_plot(self.position,self.BFS,title=title)
73
-
74
- def plotMax(self,title: str = None):
75
- if self.MaxGain.size == 0:
76
- warnings.warn("Max array in not available. If possibile, load the raw file of the same measure.")
77
- return None
78
- if not title:
79
- title = self.name
80
- return self.plotter.max_plot(self.position, self.MaxGain, title)
81
-
82
-
83
- class multipleProfile():
84
-
85
- statistics: Statistics
86
- plotter: Plotter
87
-
88
- def __init__(self, folder: Union[str,Path], plotter: Plotter = Plotly(),
89
- n_measure: int = None,
90
- start_measure: Union[int,datetime] = 0,
91
- stop_measure: datetime = None) -> None:
92
-
93
- self.folder = folder
94
- self.plotter = plotter
95
-
96
- filelist = glob(path.join(folder,'*.json'))
97
- if start_measure and isinstance(start_measure, datetime):
98
- timestamps_files = [ datetime.strptime('_'.join(path.basename(file).split('_')[:2]), '%Y-%m-%d_%H-%M-%S.%f' ) for file in filelist]
99
- primo = next((x for x,value in enumerate(timestamps_files) if value >= start_measure),0)
100
- if stop_measure:
101
- ultimo = next((x for x,value in enumerate(timestamps_files) if value > stop_measure),len(filelist))
102
- elif n_measure:
103
- ultimo = primo + n_measure
104
- else:
105
- ultimo = len(filelist)
106
- filelist = filelist[primo:ultimo]
107
-
108
- if n_measure and (start_measure==0 or isinstance(start_measure,int)):
109
- filelist = filelist[start_measure:start_measure+n_measure]
110
-
111
- if isinstance(start_measure,int) and not n_measure and stop_measure:
112
- timestamps_files = [ datetime.strptime('_'.join(path.basename(file).split('_')[:2]), '%Y-%m-%d_%H-%M-%S.%f' ) for file in filelist]
113
- ultimo = next((x for x,value in enumerate(timestamps_files) if value > stop_measure),len(filelist))
114
- filelist = filelist[start_measure:ultimo]
115
-
116
- if len(filelist) == 0:
117
- raise NoFilesSelected(folder=folder)
118
-
119
-
120
- timestamps = list()
121
- with Bar('Redaing files',max=len(filelist)) as bar:
122
- for file in filelist:
123
- temp = Profile(filename=file)
124
- timestamps.append(temp.timestamp)
125
- try:
126
- self.BFS = np.column_stack((self.BFS, temp.BFS))
127
- self.MaxGain = np.column_stack((self.MaxGain, temp.MaxGain))
128
- except AttributeError:
129
- self.BFS = temp.BFS
130
- self.MaxGain = temp.MaxGain
131
- bar.next()
132
-
133
- self.timestamps = np.array(timestamps)
134
- self.settings = temp.settings
135
- self.sw_version = temp.sw_version
136
- self.position = temp.position
137
- self.calcStatistics()
138
- self.MaxGainMean = self.MaxGain.mean(axis=1)
139
- self.MaxGainStd = self.MaxGain.std(axis=1)
140
-
141
- def calcCorrelations(self, type:Literal['max','bfs'],
142
- reference:Literal['first','previous']='previous',
143
- range:Tuple[float,float]=None) -> np.array:
144
- '''Ritorna correlazione tra prima misura e misura n-esima.
145
- Si può scegliere se effettuarla su matrice dei BFS o matrice dei massimi'''
146
-
147
- if type == 'max':
148
- correlations = np.corrcoef(self.MaxGain[range[0]:range[1]] if range else self.MaxGain,rowvar=False)
149
- else:
150
- correlations = np.corrcoef(self.BFS[range[0]:range[1]] if range else self.BFS,rowvar=False)
151
-
152
- if reference == 'first':
153
- return correlations[0,:]
154
-
155
- indici = np.arange(1,np.shape(correlations)[0])
156
- return np.insert(correlations[indici,indici-1],0,1)
157
-
158
-
159
- def calcStatistics(self, plot:bool=False, range:Tuple[float,float]=None, title:str=None)->Statistics:
160
- # TODO gestione input range
161
- self.statistics = Statistics(BFSmean=self.BFS.mean(axis=1),
162
- BFSstd=self.BFS.std(axis=1),
163
- BFSstd_mean=self.BFS.std(axis=1).mean())
164
-
165
- if plot:
166
- if not title:
167
- title=self.folder
168
- return self.plotter.statistics(self.position,
169
- self.statistics.BFSmean,
170
- self.statistics.BFSstd, title=title)
171
- return self.statistics
172
-
173
- def plotStatistics(self, title:str=None):
174
- if not title:
175
- title=self.folder
176
- # TODO se sono state calcolate con range allora position è sbagliato
177
- return self.plotter.statistics(self.position, self.statistics, title=title)
178
-
179
- def plot(self, startTime: datetime = datetime.min, stopTime: datetime = datetime.now(), title: str = None):
180
- if not title:
181
- title=self.folder
182
- match = [i for i,date in enumerate(self.timestamps) if date >= startTime and date <= stopTime]
183
- return self.plotter.multiple_plot(self.position,self.BFS[:,match],self.timestamps[match],title=title)
184
-
185
- def plotMax(self,title: str = None):
186
- if self.MaxGain.size == 0:
187
- warnings.warn("Max array in not available. If possibile, load the raw file of the same measure.")
188
- return None
189
- if not title:
190
- title = self.folder
191
- return self.plotter.max_stat_plot(self.MaxGainMean, self.MaxGainStd, title)
192
-
193
-
194
- class Raw():
195
-
196
- filename: Union[str,Path] = Path('')
197
- plotter: Plotter
198
- settings: Settings
199
- sw_version: str = '<1.2.0.0'
200
- BGS: np.ndarray = np.array([])
201
- residuo: np.ndarray = np.array([])
202
- timestamp: datetime = datetime.now
203
-
204
- def __init__(self, filename: Union[str,Path], plotter: Plotter = Plotly()) -> None:
205
- self.filename = filename
206
- self.plotter = plotter
207
- with open(self.filename) as file:
208
- text = load(file)
209
- self.settings = parseSettingsDict(text['Settings'])
210
- if 'sw_version' in text:
211
- self.sw_version = text['sw_version']
212
- if 'sw_version' in text and text['sw_version'] == '':
213
- self.sw_version = 'ambiente sviluppo'
214
- self.BGS = np.transpose(np.array(text['Raw']))
215
- self.timestamp = datetime.strptime(
216
- text['Time Stamp'], '%Y-%m-%d'+'T'+'%H:%M:%S.%f'+'Z') # "2021-11-08T16:51:16.652Z"
217
- self._createPositionArray()
218
- self._createFrequencyArray()
219
- try:
220
- self.residuo = np.array(text['residuo'])
221
- except KeyError:
222
- self.residuo = np.zeros(np.shape(self.frequency))
223
-
224
- def _createPositionArray(self) -> None:
225
- '''Crea array numpy delle posizioni e la risoluzione spaziale'''
226
- self.position = np.linspace(
227
- 0, self.settings.Cable.Length, num=self.BGS.shape[1], endpoint=True)
228
- self.spatialResolution = self.position[1]
229
-
230
- def _createFrequencyArray(self) -> None:
231
- '''Crea array numpy delle frequenze, espresse in GHz'''
232
- self.frequency = np.linspace( self.settings.Clock.StartMHz,
233
- self.settings.Clock.StartMHz + self.settings.Clock.StepMHz * self.BGS.shape[0], num= self.BGS.shape[0], endpoint=True) / 1000
234
-
235
- def plot2d(self, title: str = None):
236
- if not title:
237
- title=self.filename
238
- return self.plotter.raw2d_plot(self.position, self.frequency, self.BGS, title=title)
239
-
240
- def plot3d(self, title: str = None):
241
- if not title:
242
- title=self.filename
243
- return self.plotter.raw3d_plot(self.position, self.frequency, self.BGS, title=title)
244
-
245
- def plotBGS(self, index: int = None, title: str = None):
246
- '''Plot 2D di tutti gli spettri BGS'''
247
- if not title:
248
- title=self.filename
249
- return self.plotter.rawBGS_plot(self.frequency, self.BGS, index=index, title=title)
250
-
251
- def plotMax(self, title: str = None):
252
- if not title:
253
- title=self.filename
254
- return self.plotter.max_plot(self.position, self.BGS.max(axis=0), title)
255
-
256
- if __name__ == '__main__':
257
- '''Non va se si lancia direttamente questo script a meno di non cambiare i primi due import da modules.settings a settings; uguale per plotter.'''
258
- #a = Profile(filename='data/profiles/2021-11-08_16-51-16.652_rawarray.json')
259
- #b = multipleProfile(folder='data/profiles/')
260
- c = Raw(filename='data/raw/2021-11-08_16-51-16.652_rawmatrix.json')
261
- # print(b.BFS.shape)