readerbotda 1.3.2__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.
- readerbotda-1.3.2/PKG-INFO +163 -0
- readerbotda-1.3.2/README.md +143 -0
- readerbotda-1.3.2/ReaderBOTDA/__init__.py +0 -0
- readerbotda-1.3.2/ReaderBOTDA/converter.py +16 -0
- readerbotda-1.3.2/ReaderBOTDA/plotter/__init__.py +0 -0
- readerbotda-1.3.2/ReaderBOTDA/plotter/abc.py +35 -0
- readerbotda-1.3.2/ReaderBOTDA/plotter/bokeh.py +74 -0
- readerbotda-1.3.2/ReaderBOTDA/plotter/plotly.py +255 -0
- readerbotda-1.3.2/ReaderBOTDA/reader.py +261 -0
- readerbotda-1.3.2/ReaderBOTDA/settings.py +126 -0
- readerbotda-1.3.2/pyproject.toml +24 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: readerbotda
|
|
3
|
+
Version: 1.3.2
|
|
4
|
+
Summary: Pacchetto per lettura e visualizzazione file di log sensore BOTDA Cohaerentia
|
|
5
|
+
Author: Marco Brunero
|
|
6
|
+
Author-email: marco.brunero@cohaerentia.com
|
|
7
|
+
Requires-Python: >3.10
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Requires-Dist: bokeh (>=3.7.2,<4.0.0)
|
|
13
|
+
Requires-Dist: dacite (>=1.9.2,<2.0.0)
|
|
14
|
+
Requires-Dist: numpy (>=2.2.4,<3.0.0)
|
|
15
|
+
Requires-Dist: plotly (>=6.0.1,<7.0.0)
|
|
16
|
+
Requires-Dist: progress (>=1.6,<2.0)
|
|
17
|
+
Requires-Dist: typer (>=0.15.2,<0.16.0)
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
|
|
20
|
+
# Reader BOTDA
|
|
21
|
+
|
|
22
|
+
Pacchetto per lettura e visualizzazione delle misure salvate da software per sensore BOTDA.
|
|
23
|
+
|
|
24
|
+
## Installazione
|
|
25
|
+
|
|
26
|
+
Da cartella locale dove è stato scaricato il pacchetto è possibile installare tramite:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install .
|
|
30
|
+
```
|
|
31
|
+
|
|
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
|
+
## Utilizzo
|
|
37
|
+
|
|
38
|
+
Per prima cosa è necessario caricare il necessario dai due moduli di lettura e visualizzazione:
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from ReaderBOTDA.reader import Profile, multipleProfile, Raw
|
|
42
|
+
from ReaderBOTDA.plotter.plotly import Plotly
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Per il resto del codice utilizzeremo sempre lo stesso oggetto `plotter`, che va inizializzato e poi fornito quando si leggono i files.
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
plotter = Plotly(theme='plotly')
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Si può scegliere un tema che verrà usato per tutte le misure generate dal Plotter. [doc link](https://plotly.com/python/templates/)
|
|
52
|
+
|
|
53
|
+
> Available templates: ['ggplot2', 'seaborn', 'simple_white', 'plotly', 'plotly_white', 'plotly_dark', 'presentation', 'xgridoff', 'ygridoff', 'gridon', 'none']
|
|
54
|
+
|
|
55
|
+
L'idea è che chiunque potrebbe creare una nuova classe parente della classe `Plotter` in modo che non venga utilizzato il pacchetto `plotly` ma quello che si preferirà (bokeh, matplotlib...). La nuova classe dovrà avere una definizione di tutti i metodi astratti della classe `Plotter`.
|
|
56
|
+
|
|
57
|
+
### Misura Singola
|
|
58
|
+
|
|
59
|
+
Lettura di singola misura da un singolo file "profilo".
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
misura = Profile('data/profiles/2021-11-08_16-51-16.652_rawarray.json',plotter=plotter)
|
|
63
|
+
|
|
64
|
+
fig = misura.plot() #misura.plot(title='Titolo custom')
|
|
65
|
+
plotter.show(fig)
|
|
66
|
+
fig.write_html("prova.html", full_html=False, include_plotlyjs='cdn')
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Misure multiple in una cartella
|
|
70
|
+
|
|
71
|
+
Lettura di tutti i file di tipo profilo in una cartella:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from datetime import datetime
|
|
75
|
+
folder = 'data/profiles/'
|
|
76
|
+
misure = multipleProfile(folder, plotter = plotter)
|
|
77
|
+
misure = multipleProfile(folder, plotter = plotter, n_measure=10)
|
|
78
|
+
misure = multipleProfile(folder, plotter = plotter, n_measure=10, start_measure=5)
|
|
79
|
+
misure.plot()
|
|
80
|
+
misure.plot(startTime=datetime(2021,11,8,16,51,18))
|
|
81
|
+
misure.plot(startTime=datetime(2021,11,8,16,51,18),stopTime=datetime(2021,11,8,16,51,21))
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
E' possibile limitare la lettura a `n_measure` file consecutivi; in questo caso è anche possibile utilizzare il parametro `start_measure` per leggere `n_measure` a partire da `start_measure`.
|
|
85
|
+
Nei plot è quindi possibile indicare un range temporale utilizzando i parametri opzionali `startTime` e/o `stopTime`.
|
|
86
|
+
|
|
87
|
+
Infine è possibile calcolare e visualizzare media e deviazione standard dei profili misurati:
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
misure.calcStatistics(plot=True)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
#### Leggere solo un subset
|
|
94
|
+
|
|
95
|
+
Sono disponibili i parametri opzionali:
|
|
96
|
+
|
|
97
|
+
- `start_measure`, che può essere un intero o un `datetime.datetime`
|
|
98
|
+
- `stop_measure`, che può essere un `datetime.datetime`
|
|
99
|
+
- `n_measure`, che può essere un intero
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
misure = multipleProfile(folder, plotter = plotter, n_measure=2, start_measure=1)
|
|
103
|
+
|
|
104
|
+
from datetime import datetime
|
|
105
|
+
start_datetime = datetime(2023,2,21,13,56,0)
|
|
106
|
+
stop_datetime = datetime(2023,2,21,13,57,0)
|
|
107
|
+
|
|
108
|
+
misure = multipleProfile(folder, plotter = plotter,start_measure=start_datetime,stop_measure=stop_datetime)
|
|
109
|
+
misure = multipleProfile(folder, plotter = plotter,start_measure=start_datetime,n_measure=2)
|
|
110
|
+
misure = multipleProfile(folder, plotter = plotter,stop_measure=stop_datetime)
|
|
111
|
+
misure = multipleProfile(folder, plotter = plotter,start_measure=2,stop_measure=stop_datetime)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
#### Calcolo correlazione
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
correlazioni_max = misure.calcCorrelations(type='max',reference='previous',range=(20,200))
|
|
118
|
+
correlazioni_bfs = misure.calcCorrelations(type='bfs',reference='previous')
|
|
119
|
+
correlazioni_bfs = misure.calcCorrelations(type='bfs',reference='first')
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Si utilizza la funzione [`np.corrcoef`](https://numpy.org/doc/stable/reference/generated/numpy.corrcoef.html) per calcolo correlazione tra varie misure, dell'array dei massimi o dell'array dei bfs. Il riferimento su cui è calcolata la correlazione può essere la misura precedente o la primissima misura del set. E' inoltre possibile indicare un range, espresso in campioni, su cui limitare il calcolo. La funzione `calcCorrelations` ritorna un `np.array`.
|
|
123
|
+
|
|
124
|
+
### Misura Raw
|
|
125
|
+
|
|
126
|
+
Lettura di file di debug che contiene intera matrice BGS e test dei metodi disponibili.
|
|
127
|
+
|
|
128
|
+
```python
|
|
129
|
+
raw = Raw(filename='data/raw/2021-11-08_16-51-16.652_rawmatrix.json', plotter=plotter)
|
|
130
|
+
raw.plot2d(title='prova titolo')
|
|
131
|
+
raw.plotMax()
|
|
132
|
+
raw.plot3d()
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
E' possibile plottare tutti i profili BGS e indicare uno specifico indice da mettere in primo piano:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
fig = raw.plotBGS(index=125)
|
|
139
|
+
plotter.show(fig)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Plotter Bokeh
|
|
143
|
+
|
|
144
|
+
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()`.
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
from ReaderBOTDA.plotter.bokeh import Bokeh
|
|
148
|
+
from bokeh.io import output_notebook
|
|
149
|
+
output_notebook()
|
|
150
|
+
plotter = Bokeh(theme='night_sky')
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
I temi a disposizione di default sono:
|
|
154
|
+
>caliber, dark_minimal, light_minimal, night_sky, contrast
|
|
155
|
+
|
|
156
|
+
A questo punto è possibile chiamare i vari metodi come già mostrato per plotter Plotly:
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
misura = Profile('data/profiles/2021-11-08_16-51-16.652_rawarray.json',plotter=plotter)
|
|
160
|
+
fig = misura.plot()
|
|
161
|
+
plotter.show(fig)
|
|
162
|
+
```
|
|
163
|
+
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Reader BOTDA
|
|
2
|
+
|
|
3
|
+
Pacchetto per lettura e visualizzazione delle misure salvate da software per sensore BOTDA.
|
|
4
|
+
|
|
5
|
+
## Installazione
|
|
6
|
+
|
|
7
|
+
Da cartella locale dove è stato scaricato il pacchetto è possibile installare tramite:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install .
|
|
11
|
+
```
|
|
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
|
+
## Utilizzo
|
|
18
|
+
|
|
19
|
+
Per prima cosa è necessario caricare il necessario dai due moduli di lettura e visualizzazione:
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
from ReaderBOTDA.reader import Profile, multipleProfile, Raw
|
|
23
|
+
from ReaderBOTDA.plotter.plotly import Plotly
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Per il resto del codice utilizzeremo sempre lo stesso oggetto `plotter`, che va inizializzato e poi fornito quando si leggono i files.
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
plotter = Plotly(theme='plotly')
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Si può scegliere un tema che verrà usato per tutte le misure generate dal Plotter. [doc link](https://plotly.com/python/templates/)
|
|
33
|
+
|
|
34
|
+
> Available templates: ['ggplot2', 'seaborn', 'simple_white', 'plotly', 'plotly_white', 'plotly_dark', 'presentation', 'xgridoff', 'ygridoff', 'gridon', 'none']
|
|
35
|
+
|
|
36
|
+
L'idea è che chiunque potrebbe creare una nuova classe parente della classe `Plotter` in modo che non venga utilizzato il pacchetto `plotly` ma quello che si preferirà (bokeh, matplotlib...). La nuova classe dovrà avere una definizione di tutti i metodi astratti della classe `Plotter`.
|
|
37
|
+
|
|
38
|
+
### Misura Singola
|
|
39
|
+
|
|
40
|
+
Lettura di singola misura da un singolo file "profilo".
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
misura = Profile('data/profiles/2021-11-08_16-51-16.652_rawarray.json',plotter=plotter)
|
|
44
|
+
|
|
45
|
+
fig = misura.plot() #misura.plot(title='Titolo custom')
|
|
46
|
+
plotter.show(fig)
|
|
47
|
+
fig.write_html("prova.html", full_html=False, include_plotlyjs='cdn')
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Misure multiple in una cartella
|
|
51
|
+
|
|
52
|
+
Lettura di tutti i file di tipo profilo in una cartella:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from datetime import datetime
|
|
56
|
+
folder = 'data/profiles/'
|
|
57
|
+
misure = multipleProfile(folder, plotter = plotter)
|
|
58
|
+
misure = multipleProfile(folder, plotter = plotter, n_measure=10)
|
|
59
|
+
misure = multipleProfile(folder, plotter = plotter, n_measure=10, start_measure=5)
|
|
60
|
+
misure.plot()
|
|
61
|
+
misure.plot(startTime=datetime(2021,11,8,16,51,18))
|
|
62
|
+
misure.plot(startTime=datetime(2021,11,8,16,51,18),stopTime=datetime(2021,11,8,16,51,21))
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
E' possibile limitare la lettura a `n_measure` file consecutivi; in questo caso è anche possibile utilizzare il parametro `start_measure` per leggere `n_measure` a partire da `start_measure`.
|
|
66
|
+
Nei plot è quindi possibile indicare un range temporale utilizzando i parametri opzionali `startTime` e/o `stopTime`.
|
|
67
|
+
|
|
68
|
+
Infine è possibile calcolare e visualizzare media e deviazione standard dei profili misurati:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
misure.calcStatistics(plot=True)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
#### Leggere solo un subset
|
|
75
|
+
|
|
76
|
+
Sono disponibili i parametri opzionali:
|
|
77
|
+
|
|
78
|
+
- `start_measure`, che può essere un intero o un `datetime.datetime`
|
|
79
|
+
- `stop_measure`, che può essere un `datetime.datetime`
|
|
80
|
+
- `n_measure`, che può essere un intero
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
misure = multipleProfile(folder, plotter = plotter, n_measure=2, start_measure=1)
|
|
84
|
+
|
|
85
|
+
from datetime import datetime
|
|
86
|
+
start_datetime = datetime(2023,2,21,13,56,0)
|
|
87
|
+
stop_datetime = datetime(2023,2,21,13,57,0)
|
|
88
|
+
|
|
89
|
+
misure = multipleProfile(folder, plotter = plotter,start_measure=start_datetime,stop_measure=stop_datetime)
|
|
90
|
+
misure = multipleProfile(folder, plotter = plotter,start_measure=start_datetime,n_measure=2)
|
|
91
|
+
misure = multipleProfile(folder, plotter = plotter,stop_measure=stop_datetime)
|
|
92
|
+
misure = multipleProfile(folder, plotter = plotter,start_measure=2,stop_measure=stop_datetime)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
#### Calcolo correlazione
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
correlazioni_max = misure.calcCorrelations(type='max',reference='previous',range=(20,200))
|
|
99
|
+
correlazioni_bfs = misure.calcCorrelations(type='bfs',reference='previous')
|
|
100
|
+
correlazioni_bfs = misure.calcCorrelations(type='bfs',reference='first')
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Si utilizza la funzione [`np.corrcoef`](https://numpy.org/doc/stable/reference/generated/numpy.corrcoef.html) per calcolo correlazione tra varie misure, dell'array dei massimi o dell'array dei bfs. Il riferimento su cui è calcolata la correlazione può essere la misura precedente o la primissima misura del set. E' inoltre possibile indicare un range, espresso in campioni, su cui limitare il calcolo. La funzione `calcCorrelations` ritorna un `np.array`.
|
|
104
|
+
|
|
105
|
+
### Misura Raw
|
|
106
|
+
|
|
107
|
+
Lettura di file di debug che contiene intera matrice BGS e test dei metodi disponibili.
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
raw = Raw(filename='data/raw/2021-11-08_16-51-16.652_rawmatrix.json', plotter=plotter)
|
|
111
|
+
raw.plot2d(title='prova titolo')
|
|
112
|
+
raw.plotMax()
|
|
113
|
+
raw.plot3d()
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
E' possibile plottare tutti i profili BGS e indicare uno specifico indice da mettere in primo piano:
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
fig = raw.plotBGS(index=125)
|
|
120
|
+
plotter.show(fig)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Plotter Bokeh
|
|
124
|
+
|
|
125
|
+
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()`.
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from ReaderBOTDA.plotter.bokeh import Bokeh
|
|
129
|
+
from bokeh.io import output_notebook
|
|
130
|
+
output_notebook()
|
|
131
|
+
plotter = Bokeh(theme='night_sky')
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
I temi a disposizione di default sono:
|
|
135
|
+
>caliber, dark_minimal, light_minimal, night_sky, contrast
|
|
136
|
+
|
|
137
|
+
A questo punto è possibile chiamare i vari metodi come già mostrato per plotter Plotly:
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
misura = Profile('data/profiles/2021-11-08_16-51-16.652_rawarray.json',plotter=plotter)
|
|
141
|
+
fig = misura.plot()
|
|
142
|
+
plotter.show(fig)
|
|
143
|
+
```
|
|
File without changes
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from ReaderBOTDA.settings import parseSettingsDict, Settings
|
|
2
|
+
from ReaderBOTDA.reader import Profile
|
|
3
|
+
import h5py
|
|
4
|
+
from os import path
|
|
5
|
+
from glob import glob
|
|
6
|
+
|
|
7
|
+
def convert_to_h5(folder: str, putput_filename: str):
|
|
8
|
+
|
|
9
|
+
filelist = glob(path.join(folder,'*.json'))
|
|
10
|
+
|
|
11
|
+
if filelist:
|
|
12
|
+
with h5py.File("mytestfile.hdf5", "w") as f:
|
|
13
|
+
f.create_group("bfs")
|
|
14
|
+
for file in filelist:
|
|
15
|
+
temp = Profile(filename=file)
|
|
16
|
+
|
|
File without changes
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
|
|
3
|
+
class Plotter(ABC):
|
|
4
|
+
|
|
5
|
+
@abstractmethod
|
|
6
|
+
def single_plot(self,position, profile, title: str = None):
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
@abstractmethod
|
|
10
|
+
def multiple_plot(self,position, profiles, timestamps, title: str = ''):
|
|
11
|
+
pass
|
|
12
|
+
|
|
13
|
+
@abstractmethod
|
|
14
|
+
def statistics(self,position,mean,std,title:str = ''):
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
@abstractmethod
|
|
18
|
+
def raw2d_plot(self,position, frequency, BGS, title: str = None):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
@abstractmethod
|
|
22
|
+
def rawBGS_plot(self,frequency, BGS, index: int = None, title: str = None):
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def raw3d_plot(self,position, frequency, BGS, title: str = None):
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
@abstractmethod
|
|
30
|
+
def max_plot(self,position,max,title: str = None):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
@abstractmethod
|
|
34
|
+
def max_stat_plot(self,max_mean,max_std, title:str = ''):
|
|
35
|
+
pass
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
from .abc import Plotter
|
|
2
|
+
|
|
3
|
+
from bokeh.plotting import figure
|
|
4
|
+
from bokeh.models.layouts import Column
|
|
5
|
+
from bokeh.plotting.figure import Figure as bokehFigure
|
|
6
|
+
from bokeh.plotting import show as bokehShow
|
|
7
|
+
from bokeh.models import Legend
|
|
8
|
+
from bokeh.layouts import gridplot
|
|
9
|
+
from bokeh.palettes import Bokeh8
|
|
10
|
+
from bokeh.io import curdoc
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
class Bokeh(Plotter):
|
|
15
|
+
def __init__(self,theme:str='caliber'):
|
|
16
|
+
curdoc().theme = theme
|
|
17
|
+
#TODO: capire come prendere la palette del tema scelto, invece che usare sempre Bokeh8.
|
|
18
|
+
self.palette = Bokeh8
|
|
19
|
+
|
|
20
|
+
def single_plot(self, position, profile, title: str = None) -> bokehFigure:
|
|
21
|
+
fig = figure(title=title, x_axis_label='Position (m)', y_axis_label='BFS (MHz)',sizing_mode='stretch_width')
|
|
22
|
+
fig.line(position, profile)
|
|
23
|
+
return fig
|
|
24
|
+
|
|
25
|
+
def multiple_plot(self, position, profiles, timestamps, title: str = '') -> bokehFigure:
|
|
26
|
+
fig = figure(title=title, x_axis_label='Position (m)', y_axis_label='BFS (MHz)',sizing_mode='stretch_width')
|
|
27
|
+
fig.add_layout(Legend(), 'right')
|
|
28
|
+
for i,(time,color) in enumerate(zip(timestamps, self.palette)):
|
|
29
|
+
fig.line(x=position,y=profiles[:,i],
|
|
30
|
+
color=color,
|
|
31
|
+
legend_label=time.strftime("%m/%d/%Y, %H:%M:%S"))
|
|
32
|
+
return fig
|
|
33
|
+
|
|
34
|
+
def statistics(self, position, mean: np.ndarray,
|
|
35
|
+
std: np.ndarray, title:str = '') -> Column:
|
|
36
|
+
f1 = figure(title=title,y_axis_label='Mean BFS (MHz)')
|
|
37
|
+
f1.line(x=position, y=mean)
|
|
38
|
+
f1.varea(x=position, y1=mean+std, y2=mean-std,fill_alpha=0.3)
|
|
39
|
+
f2 = figure(x_axis_label='Position (m)', y_axis_label='Std Dev (MHz)', y_range=(0,10))
|
|
40
|
+
f2.line(x=position, y=std)
|
|
41
|
+
return gridplot([f1,f2],ncols=1,height=150,sizing_mode='stretch_width')
|
|
42
|
+
|
|
43
|
+
def max_plot(self, position, max, title: str = None) -> bokehFigure:
|
|
44
|
+
fig = figure(title=title, x_axis_label='Position (m)', y_axis_label='Max BGS Amplitude',sizing_mode='stretch_width')
|
|
45
|
+
fig.line(x=position,y=max)
|
|
46
|
+
return fig
|
|
47
|
+
|
|
48
|
+
def max_stat_plot(self,max_mean: np.ndarray, max_std: np.ndarray, title:str = '')-> bokehFigure:
|
|
49
|
+
fig = figure(title=title,y_axis_label='Max BGS (Volts)',sizing_mode='stretch_width')
|
|
50
|
+
fig.line(y=max_mean)
|
|
51
|
+
fig.varea(y1=max_mean+max_std, y2=max_mean-max_std,fill_alpha=0.3)
|
|
52
|
+
return fig
|
|
53
|
+
|
|
54
|
+
def rawBGS_plot(self, frequency, BGS, index: int = None, title: str = None) -> bokehFigure:
|
|
55
|
+
fig = figure(title=title,x_axis_label='Frequency (GHz)', y_axis_label='Amplitude (V)',sizing_mode='stretch_width')
|
|
56
|
+
if not index:
|
|
57
|
+
for single in np.transpose(BGS):
|
|
58
|
+
fig.line(x=frequency,y=single)
|
|
59
|
+
else:
|
|
60
|
+
for single in np.transpose(BGS):
|
|
61
|
+
fig.line(x=frequency,y=single,color='rgba(175,175,175,0.15)')
|
|
62
|
+
fig.line(x=frequency,y=BGS[:,index])
|
|
63
|
+
return fig
|
|
64
|
+
|
|
65
|
+
def raw3d_plot(self, position, frequency, BGS, title: str = None):
|
|
66
|
+
raise NameError('Function raw3d_plot not yet defined for Bokeh plotter.')
|
|
67
|
+
|
|
68
|
+
def raw2d_plot(self, position, frequency, BGS, title: str = None):
|
|
69
|
+
raise NameError('Function raw2d_plot not yet defined for Bokeh plotter.')
|
|
70
|
+
|
|
71
|
+
@staticmethod
|
|
72
|
+
def show(fig) -> None:
|
|
73
|
+
bokehShow(fig)
|
|
74
|
+
pass
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
from .abc import Plotter
|
|
2
|
+
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
import plotly.graph_objects as go
|
|
6
|
+
import plotly.io as pio
|
|
7
|
+
from plotly.subplots import make_subplots
|
|
8
|
+
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Plotly(Plotter):
|
|
14
|
+
|
|
15
|
+
def __init__(self, theme="ggplot2", colorscale: str = "YlGnBu") -> None:
|
|
16
|
+
self.theme = theme
|
|
17
|
+
self.colorscale = colorscale
|
|
18
|
+
self.firstColor = pio.templates[self.theme].layout.colorway[0]
|
|
19
|
+
super().__init__()
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
def _applyTheme(self, fig) -> go.Figure:
|
|
23
|
+
return fig.update_layout(
|
|
24
|
+
template=self.theme,
|
|
25
|
+
scene_xaxis=dict(separatethousands=False),
|
|
26
|
+
scene_yaxis=dict(separatethousands=False),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
def single_plot(self, position, profile, title: str = None) -> go.Figure:
|
|
30
|
+
fig = go.Figure(
|
|
31
|
+
data=go.Scatter(x=position, y=profile, name=title),
|
|
32
|
+
layout=dict(
|
|
33
|
+
title=title,
|
|
34
|
+
xaxis=dict(title=dict(text="Position (m)")),
|
|
35
|
+
yaxis=dict(title=dict(text="BFS (MHz)")),
|
|
36
|
+
),
|
|
37
|
+
)
|
|
38
|
+
return self._applyTheme(fig)
|
|
39
|
+
|
|
40
|
+
def multiple_plot(
|
|
41
|
+
self,
|
|
42
|
+
position: np.ndarray,
|
|
43
|
+
profiles: np.ndarray,
|
|
44
|
+
timestamps: List[datetime],
|
|
45
|
+
title: str = "",
|
|
46
|
+
) -> go.Figure:
|
|
47
|
+
fig = go.Figure(
|
|
48
|
+
layout=dict(
|
|
49
|
+
title=title,
|
|
50
|
+
xaxis=dict(title=dict(text="Position (m)")),
|
|
51
|
+
yaxis=dict(title=dict(text="BFS (MHz)")),
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
for i, time in enumerate(timestamps):
|
|
55
|
+
fig.add_trace(
|
|
56
|
+
go.Scatter(
|
|
57
|
+
x=position,
|
|
58
|
+
y=profiles[:, i],
|
|
59
|
+
mode="lines",
|
|
60
|
+
name=time.strftime("%m/%d/%Y, %H:%M:%S"),
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
return self._applyTheme(fig)
|
|
64
|
+
|
|
65
|
+
def statistics(
|
|
66
|
+
self, position: np.ndarray, mean: np.ndarray, std: np.ndarray, title: str = ""
|
|
67
|
+
) -> go.Figure:
|
|
68
|
+
|
|
69
|
+
fig = make_subplots(rows=2, cols=1, column_titles=[title])
|
|
70
|
+
fig.add_trace(
|
|
71
|
+
go.Scatter(
|
|
72
|
+
x=position,
|
|
73
|
+
y=mean,
|
|
74
|
+
line=dict(color=self.firstColor),
|
|
75
|
+
mode="lines",
|
|
76
|
+
showlegend=False,
|
|
77
|
+
),
|
|
78
|
+
row=1,
|
|
79
|
+
col=1,
|
|
80
|
+
)
|
|
81
|
+
fig.add_trace(
|
|
82
|
+
go.Scatter(
|
|
83
|
+
name="Upper Bound",
|
|
84
|
+
x=position,
|
|
85
|
+
y=mean + std,
|
|
86
|
+
mode="lines",
|
|
87
|
+
marker=dict(color="#444"),
|
|
88
|
+
line=dict(width=0),
|
|
89
|
+
showlegend=False,
|
|
90
|
+
),
|
|
91
|
+
row=1,
|
|
92
|
+
col=1,
|
|
93
|
+
)
|
|
94
|
+
fig.add_trace(
|
|
95
|
+
go.Scatter(
|
|
96
|
+
name="Lower Bound",
|
|
97
|
+
x=position,
|
|
98
|
+
y=mean - std,
|
|
99
|
+
marker=dict(color=self.firstColor),
|
|
100
|
+
line=dict(width=0, color=self.firstColor),
|
|
101
|
+
mode="lines",
|
|
102
|
+
# fillcolor='rgba(68, 68, 68, 0.3)',
|
|
103
|
+
fill="tonexty",
|
|
104
|
+
showlegend=False,
|
|
105
|
+
),
|
|
106
|
+
row=1,
|
|
107
|
+
col=1,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
fig.update_yaxes(title_text="BFS (MHz)", row=1, col=1)
|
|
111
|
+
fig.update_layout(hovermode="x")
|
|
112
|
+
|
|
113
|
+
fig.add_trace(
|
|
114
|
+
go.Scatter(
|
|
115
|
+
name="std",
|
|
116
|
+
x=position,
|
|
117
|
+
y=std,
|
|
118
|
+
line=dict(color=self.firstColor),
|
|
119
|
+
showlegend=False,
|
|
120
|
+
),
|
|
121
|
+
row=2,
|
|
122
|
+
col=1,
|
|
123
|
+
)
|
|
124
|
+
fig.update_yaxes(title_text="Std Dev (MHz)", range=[0, 10], row=2, col=1)
|
|
125
|
+
fig.update_xaxes(title_text="Position (m)", matches="x", row=2, col=1)
|
|
126
|
+
return self._applyTheme(fig)
|
|
127
|
+
|
|
128
|
+
def max_stat_plot(
|
|
129
|
+
self, max_mean: np.ndarray, max_std: np.ndarray, title: str = ""
|
|
130
|
+
) -> go.Figure:
|
|
131
|
+
|
|
132
|
+
fig = go.Figure(layout=dict(title=title))
|
|
133
|
+
fig.add_trace(
|
|
134
|
+
go.Scatter(
|
|
135
|
+
y=max_mean,
|
|
136
|
+
line=dict(color=self.firstColor),
|
|
137
|
+
mode="lines",
|
|
138
|
+
showlegend=False,
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
fig.add_trace(
|
|
142
|
+
go.Scatter(
|
|
143
|
+
name="Upper Bound",
|
|
144
|
+
y=max_mean + max_std,
|
|
145
|
+
mode="lines",
|
|
146
|
+
marker=dict(color="#444"),
|
|
147
|
+
line=dict(width=0),
|
|
148
|
+
showlegend=False,
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
fig.add_trace(
|
|
152
|
+
go.Scatter(
|
|
153
|
+
name="Lower Bound",
|
|
154
|
+
y=max_mean - max_std,
|
|
155
|
+
marker=dict(color=self.firstColor),
|
|
156
|
+
line=dict(width=0, color=self.firstColor),
|
|
157
|
+
mode="lines",
|
|
158
|
+
# fillcolor='rgba(68, 68, 68, 0.3)',
|
|
159
|
+
fill="tonexty",
|
|
160
|
+
showlegend=False,
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
fig.update_yaxes(title_text="Max BGS (Volts)")
|
|
165
|
+
|
|
166
|
+
return self._applyTheme(fig)
|
|
167
|
+
|
|
168
|
+
def raw2d_plot(self, position, frequency, BGS, title: str = None) -> go.Figure:
|
|
169
|
+
fig = go.Figure(
|
|
170
|
+
data=go.Contour(
|
|
171
|
+
z=BGS,
|
|
172
|
+
x=position,
|
|
173
|
+
y=frequency,
|
|
174
|
+
showscale=False,
|
|
175
|
+
colorscale=self.colorscale,
|
|
176
|
+
line_width=0,
|
|
177
|
+
),
|
|
178
|
+
layout=dict(
|
|
179
|
+
title=title,
|
|
180
|
+
xaxis=dict(title=dict(text="Position (m)")),
|
|
181
|
+
yaxis=dict(title=dict(text="Frequency (GHz)")),
|
|
182
|
+
),
|
|
183
|
+
)
|
|
184
|
+
return self._applyTheme(fig)
|
|
185
|
+
|
|
186
|
+
def raw3d_plot(self, position, frequency, BGS, title: str = None):
|
|
187
|
+
hovertemplate = "Pos: %{x:.2f} m, Freq: %{y:.3f} GHz, Ampl: %{z:.3f} V"
|
|
188
|
+
fig = go.Figure(
|
|
189
|
+
data=go.Surface(
|
|
190
|
+
z=BGS, x=position, y=frequency, hovertemplate=hovertemplate
|
|
191
|
+
),
|
|
192
|
+
layout=dict(
|
|
193
|
+
title=title,
|
|
194
|
+
scene=dict(
|
|
195
|
+
xaxis_title="Position (m)",
|
|
196
|
+
yaxis_title="Frequency (GHz)",
|
|
197
|
+
zaxis_title="Amplitude (V)",
|
|
198
|
+
),
|
|
199
|
+
),
|
|
200
|
+
)
|
|
201
|
+
return self._applyTheme(fig)
|
|
202
|
+
|
|
203
|
+
def rawBGS_plot(
|
|
204
|
+
self, frequency, BGS, index: int = None, title: str = None
|
|
205
|
+
) -> go.Figure:
|
|
206
|
+
fig = go.Figure(
|
|
207
|
+
layout=dict(
|
|
208
|
+
title=title,
|
|
209
|
+
hovermode=False,
|
|
210
|
+
xaxis=dict(title=dict(text="Frequency (GHz)")),
|
|
211
|
+
yaxis=dict(title=dict(text="Amplitude (V)")),
|
|
212
|
+
)
|
|
213
|
+
)
|
|
214
|
+
if not index:
|
|
215
|
+
for single in np.transpose(BGS):
|
|
216
|
+
fig.add_trace(
|
|
217
|
+
go.Scatter(x=frequency, y=single, mode="lines", showlegend=False)
|
|
218
|
+
)
|
|
219
|
+
else:
|
|
220
|
+
for single in np.transpose(BGS):
|
|
221
|
+
fig.add_trace(
|
|
222
|
+
go.Scatter(
|
|
223
|
+
x=frequency,
|
|
224
|
+
y=single,
|
|
225
|
+
line=dict(color="rgba(175,175,175,0.15)"),
|
|
226
|
+
mode="lines",
|
|
227
|
+
showlegend=False,
|
|
228
|
+
)
|
|
229
|
+
)
|
|
230
|
+
fig.add_trace(
|
|
231
|
+
go.Scatter(
|
|
232
|
+
x=frequency,
|
|
233
|
+
y=BGS[:, index],
|
|
234
|
+
line=dict(color=self.firstColor),
|
|
235
|
+
mode="lines",
|
|
236
|
+
showlegend=False,
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
return self._applyTheme(fig)
|
|
240
|
+
|
|
241
|
+
def max_plot(self, position, max, title: str = None) -> go.Figure:
|
|
242
|
+
fig = go.Figure(
|
|
243
|
+
data=go.Scatter(x=position, y=max, name=title),
|
|
244
|
+
layout=dict(
|
|
245
|
+
title=title,
|
|
246
|
+
xaxis=dict(title=dict(text="Position (m)")),
|
|
247
|
+
yaxis=dict(title=dict(text="Max BGS Amplitude")),
|
|
248
|
+
),
|
|
249
|
+
)
|
|
250
|
+
return self._applyTheme(fig)
|
|
251
|
+
|
|
252
|
+
@staticmethod
|
|
253
|
+
def show(fig: go.Figure) -> None:
|
|
254
|
+
fig.show()
|
|
255
|
+
pass
|
|
@@ -0,0 +1,261 @@
|
|
|
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)
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from json import load
|
|
3
|
+
from typing import Dict, Union, Optional
|
|
4
|
+
from dacite import from_dict
|
|
5
|
+
from enum import Enum
|
|
6
|
+
|
|
7
|
+
class EstimationMode(str, Enum):
|
|
8
|
+
WEIGHTED = 'Weighted Correlation'
|
|
9
|
+
CORRELATION = 'Correlation'
|
|
10
|
+
LORFIT = 'Lorentzian fitting'
|
|
11
|
+
POLYFIT = 'Polyfit'
|
|
12
|
+
|
|
13
|
+
class DynamicRange(int, Enum):
|
|
14
|
+
'''Dynamic Range della picoscope espresso in mV o V.'''
|
|
15
|
+
mV10 = 0
|
|
16
|
+
mV20 = 1
|
|
17
|
+
mV50 = 2
|
|
18
|
+
mV100 = 3
|
|
19
|
+
mV200 = 4
|
|
20
|
+
mV500 = 5
|
|
21
|
+
V1 = 6
|
|
22
|
+
V2 = 7
|
|
23
|
+
V5 = 8
|
|
24
|
+
V10 = 9
|
|
25
|
+
V20 = 10
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class Pulse:
|
|
29
|
+
Width_ns: float
|
|
30
|
+
RepRate_kHz: float
|
|
31
|
+
Amplitude_V: float
|
|
32
|
+
Offset_V: float
|
|
33
|
+
EDFAoutput: float
|
|
34
|
+
|
|
35
|
+
def __str__(self):
|
|
36
|
+
return f"Width: {self.Width_ns} ns,\tRep Rate: {self.RepRate_kHz} kHz,\tAmplitude: {self.Amplitude_V} V,\tOffset: {self.Offset_V} V,\tEDFA Pump: {self.EDFAoutput} mA."
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class Clock:
|
|
40
|
+
Power: float
|
|
41
|
+
StartMHz: float
|
|
42
|
+
StopMHz: float
|
|
43
|
+
StepMHz: int
|
|
44
|
+
|
|
45
|
+
def __str__(self):
|
|
46
|
+
return f"Power: {self.Power} dBm,\tStart Freq: {self.StartMHz} MHz,\tStop Freq: {self.StopMHz} MHz,\tStep Freq: {self.StepMHz} MHz."
|
|
47
|
+
|
|
48
|
+
@dataclass
|
|
49
|
+
class Scope:
|
|
50
|
+
Offset: float
|
|
51
|
+
Average: int
|
|
52
|
+
DynamicRange: Union[int,DynamicRange]
|
|
53
|
+
TriggerDelay_m: float
|
|
54
|
+
TriggerLevel_V: float
|
|
55
|
+
|
|
56
|
+
def __post_init__(self):
|
|
57
|
+
self.DynamicRange = DynamicRange(self.DynamicRange)
|
|
58
|
+
|
|
59
|
+
def __str__(self):
|
|
60
|
+
return f"Averages: {self.Average},\tRange: {self.DynamicRange.name},\tOffset: {self.Offset} V,\tTrigger Delay: {self.TriggerDelay_m} m,\tTrigger Level: {self.TriggerLevel_V} V."
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class Cable:
|
|
64
|
+
Length: float
|
|
65
|
+
RefractiveIndex: float
|
|
66
|
+
CoeffStrainGHz: float
|
|
67
|
+
CoeffTemperatureGHz: float
|
|
68
|
+
ReferenceBFS_GHz: float
|
|
69
|
+
|
|
70
|
+
def __str__(self):
|
|
71
|
+
return f"Length: {self.Length} m,\tRefractive Index: {self.RefractiveIndex},\tStrain Coeff: {self.CoeffStrainGHz} GHz/microstrain,\tTemperature Coeff: {self.CoeffTemperatureGHz} GHz/°C,\tReference BFS: {self.ReferenceBFS_GHz} GHz."
|
|
72
|
+
|
|
73
|
+
@dataclass
|
|
74
|
+
class Brillouin:
|
|
75
|
+
EstimationMode: Union[str,int,EstimationMode]
|
|
76
|
+
SplineRes: float
|
|
77
|
+
Subset: float
|
|
78
|
+
RemoveBackground: bool
|
|
79
|
+
RemovePulseGhost: bool
|
|
80
|
+
CalibrationLength: float
|
|
81
|
+
peak_2nd_thr: Optional[float]
|
|
82
|
+
polyfit_lorFWHM: Optional[float]
|
|
83
|
+
polyfit_prefilter_bw: Optional[float]
|
|
84
|
+
polyfit_prefilter_taps: Optional[int]
|
|
85
|
+
|
|
86
|
+
def __post_init__(self):
|
|
87
|
+
if isinstance(self.EstimationMode, EstimationMode):
|
|
88
|
+
pass
|
|
89
|
+
self.EstimationMode = EstimationMode(self.EstimationMode)
|
|
90
|
+
|
|
91
|
+
def __str__(self):
|
|
92
|
+
if self.EstimationMode == EstimationMode.POLYFIT:
|
|
93
|
+
additional_settings = f"Lorentzian FWHM: {self.polyfit_lorFWHM} MHz,\tPrefilter BW: {self.polyfit_prefilter_bw},\tPrefilter taps: {self.polyfit_prefilter_taps}."
|
|
94
|
+
elif self.EstimationMode == EstimationMode.WEIGHTED:
|
|
95
|
+
additional_settings = f"Spline Resolution: {self.SplineRes} MHz,\tSubset: {self.Subset} MHz,\t2nd peak thr: {self.peak_2nd_thr}."
|
|
96
|
+
else:
|
|
97
|
+
additional_settings = f"Spline Resolution: {self.SplineRes} MHz,\tSubset: {self.Subset} MHz,\t2nd peak thr: {self.peak_2nd_thr}."
|
|
98
|
+
return f"Mode: {self.EstimationMode},\t{additional_settings}"
|
|
99
|
+
|
|
100
|
+
@dataclass
|
|
101
|
+
class Settings:
|
|
102
|
+
Pulse: Pulse
|
|
103
|
+
Clock: Clock
|
|
104
|
+
Scope: Scope
|
|
105
|
+
Cable: Cable
|
|
106
|
+
Brillouin: Brillouin
|
|
107
|
+
|
|
108
|
+
def __str__(self):
|
|
109
|
+
return f"PULSE\t- {self.Pulse}\nCLOCK\t- {self.Clock}\nSCOPE\t- {self.Scope}\nCABLE\t- {self.Cable}\nBRILL\t- {self.Brillouin}"
|
|
110
|
+
|
|
111
|
+
def parseSettingsDict(dict: Dict) -> Settings:
|
|
112
|
+
dict['Brillouin']['EstimationMode'] = dict['Brillouin'].pop('Estimation mode')
|
|
113
|
+
dict['Brillouin']['CalibrationLength'] = dict['Brillouin'].pop('CalibrationLength(S)')
|
|
114
|
+
if '2nd_peak_thr' in dict['Brillouin']:
|
|
115
|
+
dict['Brillouin']['peak_2nd_thr'] = dict['Brillouin'].pop('2nd_peak_thr')
|
|
116
|
+
return from_dict(data_class=Settings, data=dict)
|
|
117
|
+
|
|
118
|
+
if __name__ == '__main__':
|
|
119
|
+
#filename='data/2021/profiles/2021-11-08_16-51-16.652_rawarray.json'
|
|
120
|
+
filename='data/2023_09/rawarray/2023-09-26_10-33-20.210_rawarray.json'
|
|
121
|
+
#filename='C:/Users/marbr/OneDrive/Cohaerentia/00 - Sensori/Brillouin/BOTDA/Misure/2023_12 - polyfit/gialla_15MHz/rawarray/2023-12-11_10-30-23.093_rawarray.json'
|
|
122
|
+
with open(filename) as file:
|
|
123
|
+
text = load(file)
|
|
124
|
+
setting_test = text['Settings']
|
|
125
|
+
settings = parseSettingsDict(setting_test)
|
|
126
|
+
print(settings)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "readerbotda"
|
|
3
|
+
version = "1.3.2"
|
|
4
|
+
description = "Pacchetto per lettura e visualizzazione file di log sensore BOTDA Cohaerentia"
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "Marco Brunero",email = "marco.brunero@cohaerentia.com"}
|
|
7
|
+
]
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">3.10"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"dacite (>=1.9.2,<2.0.0)",
|
|
12
|
+
"plotly (>=6.0.1,<7.0.0)",
|
|
13
|
+
"numpy (>=2.2.4,<3.0.0)",
|
|
14
|
+
"bokeh (>=3.7.2,<4.0.0)",
|
|
15
|
+
"typer (>=0.15.2,<0.16.0)",
|
|
16
|
+
"progress (>=1.6,<2.0)"
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[tool.poetry.scripts]
|
|
20
|
+
botda-converter = "readBOTDA:app"
|
|
21
|
+
|
|
22
|
+
[build-system]
|
|
23
|
+
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
|
24
|
+
build-backend = "poetry.core.masonry.api"
|