readerbotda 1.3.2__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.
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
ReaderBOTDA/reader.py ADDED
@@ -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,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,12 @@
1
+ ReaderBOTDA/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ ReaderBOTDA/converter.py,sha256=zUvh4RkJBppgp9nWOSlWxzTX66_4Tw7TyZqp2RwmMZ4,474
3
+ ReaderBOTDA/plotter/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ ReaderBOTDA/plotter/abc.py,sha256=drgVF3XWaulBME4okI9a1bg47yWMWEHmz8ld4F6XezY,870
5
+ ReaderBOTDA/plotter/bokeh.py,sha256=ggHbjSnliPTGrZxaDCORxJVhfdqOjA9lHZh_YybnU6Q,3336
6
+ ReaderBOTDA/plotter/plotly.py,sha256=S_HjHIRk15GKpNnyW_-bvxEBoEy0JLlcSZshOSabe4Q,7832
7
+ ReaderBOTDA/reader.py,sha256=zPmK1mDNTKvLYcgBoYzSAVUTAQ3fEt4K58PttM3qDQ4,10853
8
+ ReaderBOTDA/settings.py,sha256=tELeDj6rBqkl4lVPKrDv4xDDnQPfUajo-qJzPhOSldc,4409
9
+ readerbotda-1.3.2.dist-info/entry_points.txt,sha256=X6SGuG_wL9yRN0RQUdIndutNf1hDi1RRv6KbFSO1Y9A,49
10
+ readerbotda-1.3.2.dist-info/METADATA,sha256=7aN65jftgu-TlVOq4ok_HoEly_8ZxKq0giPXaD5MVnw,6298
11
+ readerbotda-1.3.2.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
12
+ readerbotda-1.3.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 2.1.2
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ botda-converter=readBOTDA:app
3
+