dapps 0.0.3__tar.gz → 0.0.4__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.
- {dapps-0.0.3 → dapps-0.0.4}/PKG-INFO +16 -11
- {dapps-0.0.3 → dapps-0.0.4}/README.md +15 -10
- dapps-0.0.4/VERSION +1 -0
- {dapps-0.0.3 → dapps-0.0.4}/examples/spectrum_dapp.py +25 -12
- {dapps-0.0.3 → dapps-0.0.4}/src/dapp/dapp.py +1 -1
- {dapps-0.0.3 → dapps-0.0.4}/src/e3interface/defs/e3.asn +1 -1
- {dapps-0.0.3 → dapps-0.0.4}/src/spectrum/spectrum_dapp.py +40 -31
- {dapps-0.0.3 → dapps-0.0.4}/src/visualization/dashboard.py +14 -22
- {dapps-0.0.3 → dapps-0.0.4}/src/visualization/iq.py +4 -4
- {dapps-0.0.3 → dapps-0.0.4}/src/visualization/templates/index.html +115 -28
- dapps-0.0.3/VERSION +0 -1
- {dapps-0.0.3 → dapps-0.0.4}/.github/workflows/mirror.yml +0 -0
- {dapps-0.0.3 → dapps-0.0.4}/.github/workflows/tag-and-release.yml +0 -0
- {dapps-0.0.3 → dapps-0.0.4}/.gitignore +0 -0
- {dapps-0.0.3 → dapps-0.0.4}/LICENSE +0 -0
- {dapps-0.0.3 → dapps-0.0.4}/MANIFEST.in +0 -0
- {dapps-0.0.3 → dapps-0.0.4}/docker/Dockerfile +0 -0
- {dapps-0.0.3 → dapps-0.0.4}/docker/build.sh +0 -0
- {dapps-0.0.3 → dapps-0.0.4}/docs/dapp_uml.txt +0 -0
- {dapps-0.0.3 → dapps-0.0.4}/docs/gui_uml.txt +0 -0
- {dapps-0.0.3 → dapps-0.0.4}/e3protocol/README.md +0 -0
- {dapps-0.0.3 → dapps-0.0.4}/e3protocol/build_asn.sh +0 -0
- {dapps-0.0.3 → dapps-0.0.4}/e3protocol/client/client.c +0 -0
- {dapps-0.0.3 → dapps-0.0.4}/e3protocol/dump_packets.sh +0 -0
- {dapps-0.0.3 → dapps-0.0.4}/e3protocol/server/server.py +0 -0
- {dapps-0.0.3 → dapps-0.0.4}/e3protocol/wrapper/e3.c +0 -0
- {dapps-0.0.3 → dapps-0.0.4}/e3protocol/wrapper/e3.h +0 -0
- {dapps-0.0.3 → dapps-0.0.4}/pyproject.toml +0 -0
- {dapps-0.0.3 → dapps-0.0.4}/src/dapp/__init__.py +0 -0
- {dapps-0.0.3 → dapps-0.0.4}/src/e3interface/__init__.py +0 -0
- {dapps-0.0.3 → dapps-0.0.4}/src/e3interface/e3_connector.py +0 -0
- {dapps-0.0.3 → dapps-0.0.4}/src/e3interface/e3_interface.py +0 -0
- {dapps-0.0.3 → dapps-0.0.4}/src/e3interface/e3_logging.py +0 -0
- {dapps-0.0.3 → dapps-0.0.4}/src/spectrum/__init__.py +0 -0
- {dapps-0.0.3 → dapps-0.0.4}/src/visualization/__init__.py +0 -0
- {dapps-0.0.3 → dapps-0.0.4}/src/visualization/energy.py +0 -0
- {dapps-0.0.3 → dapps-0.0.4}/src/visualization/grafana.py +0 -0
- {dapps-0.0.3 → dapps-0.0.4}/src/visualization/influx.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dapps
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.4
|
|
4
4
|
Summary: A library for creating and connecting dApps to Open RAN units.
|
|
5
5
|
Project-URL: Homepage, https://openrangym.com/o-ran-frameworks/dapps
|
|
6
6
|
Project-URL: Package, https://pypi.org/project/dapps/
|
|
@@ -48,21 +48,24 @@ A complete tutorial on how to deploy a dApp can be found on the [OpenRAN Gym web
|
|
|
48
48
|
|
|
49
49
|
## Installation
|
|
50
50
|
|
|
51
|
-
### Python package installation
|
|
51
|
+
### Python package installation (recommended)
|
|
52
52
|
|
|
53
53
|
```
|
|
54
54
|
pip3 install dapps[all]
|
|
55
55
|
```
|
|
56
56
|
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
The dApp can be found in the `examples` directory or it can be downloaded from this repository.
|
|
58
|
+
|
|
59
|
+
### Manual installation (use only for library development)
|
|
60
|
+
|
|
61
|
+
Clone the repository and install it manually with:
|
|
59
62
|
|
|
60
63
|
```
|
|
61
64
|
hatch build
|
|
62
65
|
pip3 install dist/*.tar.gz
|
|
63
66
|
```
|
|
64
67
|
|
|
65
|
-
### Launch the Spectrum Sharing dApp
|
|
68
|
+
### Launch the Spectrum Sharing dApp example
|
|
66
69
|
|
|
67
70
|
OAI should start _before_ running the dApp
|
|
68
71
|
|
|
@@ -70,23 +73,25 @@ OAI should start _before_ running the dApp
|
|
|
70
73
|
python3 examples/spectrum_dapp.py
|
|
71
74
|
```
|
|
72
75
|
|
|
73
|
-
This dApp implements a spectrum sharing use case discussed in [our paper](https://arxiv.org/pdf/2501.16502).
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
The dApp can be controlled through the following command-line arguments:
|
|
76
|
+
This dApp implements a spectrum sharing use case discussed in [our paper](https://doi.org/10.1016/j.comnet.2025.111342) ([here for the archive version](https://arxiv.org/pdf/2501.16502)).
|
|
77
|
+
The dApp parameters can be controlled through the following command-line arguments:
|
|
77
78
|
|
|
78
|
-
- `--ota` (store_true, default: False): Specify if this is OTA or on Colosseum
|
|
79
79
|
- `--link` (str, default: 'zmq', choices: [layer.value for layer in E3LinkLayer]): Specify the link layer to be used
|
|
80
80
|
- `--transport` (str, default: 'ipc', choices: [layer.value for layer in E3TransportLayer]): Specify the transport layer to be used
|
|
81
81
|
- `--save-iqs` (store_true, default: False): Specify if this is data collection run or not. In the first case I/Q samples will be saved
|
|
82
82
|
- `--control` (store_true, default: False): Set whether to perform control of PRB
|
|
83
|
+
- `--noise-floor-threshold` (int, default: None): Set the noise floor threshold for determining the presence of incumbents and for detecting the PRBs affected.
|
|
84
|
+
- `--ota` (store_true, default: False): Specify if the setup used is OTA or on Colosseum for determining the noise floor threshold. If the `noise_floor_threshold` parameter is specified, this parameter is ignored
|
|
83
85
|
- `--energy-gui` (store_true, default: False): Set whether to enable the energy GUI
|
|
84
86
|
- `--iq-plotter-gui` (store_true, default: False): Set whether to enable the IQ Plotter GUI
|
|
85
87
|
- `--demo-gui` (store_true, default: False): Set whether to enable the Demo GUI
|
|
88
|
+
- `--num-prbs` (int, default: 106): Number of PRBs
|
|
89
|
+
- `--num-subcarrier-spacing` (int, default: 30): Subcarrier spacing in kHz (FR1 is 30)
|
|
90
|
+
- `--e` (store_true, default: False): Set if 3/4 sampling for FFT size is set on the gNB (-E option on OAI)
|
|
91
|
+
- `--center-freq` (float, default: 3.6192e9): Center frequency in Hz
|
|
86
92
|
- `--timed` (store_true, default: False): Run with a 5-minute time limit
|
|
87
93
|
- `--model` (str, default: ''): Path to the CNN model file to be used
|
|
88
94
|
- `--time-window` (int, default: 5): Number of input vectors to pass to the CNN model
|
|
89
|
-
- `--input-vector` (int, default: 1536): Number of I/Q samples per input vector
|
|
90
95
|
- `--moving-avg-window` (int, default: 30): Window size (in samples) for the moving average used to detect energy peaks in the spectrum
|
|
91
96
|
- `--extraction-window` (int, default: 600): Number of samples to retain after detecting an energy peak
|
|
92
97
|
|
|
@@ -4,21 +4,24 @@ A complete tutorial on how to deploy a dApp can be found on the [OpenRAN Gym web
|
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
7
|
-
### Python package installation
|
|
7
|
+
### Python package installation (recommended)
|
|
8
8
|
|
|
9
9
|
```
|
|
10
10
|
pip3 install dapps[all]
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
The dApp can be found in the `examples` directory or it can be downloaded from this repository.
|
|
14
|
+
|
|
15
|
+
### Manual installation (use only for library development)
|
|
16
|
+
|
|
17
|
+
Clone the repository and install it manually with:
|
|
15
18
|
|
|
16
19
|
```
|
|
17
20
|
hatch build
|
|
18
21
|
pip3 install dist/*.tar.gz
|
|
19
22
|
```
|
|
20
23
|
|
|
21
|
-
### Launch the Spectrum Sharing dApp
|
|
24
|
+
### Launch the Spectrum Sharing dApp example
|
|
22
25
|
|
|
23
26
|
OAI should start _before_ running the dApp
|
|
24
27
|
|
|
@@ -26,23 +29,25 @@ OAI should start _before_ running the dApp
|
|
|
26
29
|
python3 examples/spectrum_dapp.py
|
|
27
30
|
```
|
|
28
31
|
|
|
29
|
-
This dApp implements a spectrum sharing use case discussed in [our paper](https://arxiv.org/pdf/2501.16502).
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
The dApp can be controlled through the following command-line arguments:
|
|
32
|
+
This dApp implements a spectrum sharing use case discussed in [our paper](https://doi.org/10.1016/j.comnet.2025.111342) ([here for the archive version](https://arxiv.org/pdf/2501.16502)).
|
|
33
|
+
The dApp parameters can be controlled through the following command-line arguments:
|
|
33
34
|
|
|
34
|
-
- `--ota` (store_true, default: False): Specify if this is OTA or on Colosseum
|
|
35
35
|
- `--link` (str, default: 'zmq', choices: [layer.value for layer in E3LinkLayer]): Specify the link layer to be used
|
|
36
36
|
- `--transport` (str, default: 'ipc', choices: [layer.value for layer in E3TransportLayer]): Specify the transport layer to be used
|
|
37
37
|
- `--save-iqs` (store_true, default: False): Specify if this is data collection run or not. In the first case I/Q samples will be saved
|
|
38
38
|
- `--control` (store_true, default: False): Set whether to perform control of PRB
|
|
39
|
+
- `--noise-floor-threshold` (int, default: None): Set the noise floor threshold for determining the presence of incumbents and for detecting the PRBs affected.
|
|
40
|
+
- `--ota` (store_true, default: False): Specify if the setup used is OTA or on Colosseum for determining the noise floor threshold. If the `noise_floor_threshold` parameter is specified, this parameter is ignored
|
|
39
41
|
- `--energy-gui` (store_true, default: False): Set whether to enable the energy GUI
|
|
40
42
|
- `--iq-plotter-gui` (store_true, default: False): Set whether to enable the IQ Plotter GUI
|
|
41
43
|
- `--demo-gui` (store_true, default: False): Set whether to enable the Demo GUI
|
|
44
|
+
- `--num-prbs` (int, default: 106): Number of PRBs
|
|
45
|
+
- `--num-subcarrier-spacing` (int, default: 30): Subcarrier spacing in kHz (FR1 is 30)
|
|
46
|
+
- `--e` (store_true, default: False): Set if 3/4 sampling for FFT size is set on the gNB (-E option on OAI)
|
|
47
|
+
- `--center-freq` (float, default: 3.6192e9): Center frequency in Hz
|
|
42
48
|
- `--timed` (store_true, default: False): Run with a 5-minute time limit
|
|
43
49
|
- `--model` (str, default: ''): Path to the CNN model file to be used
|
|
44
50
|
- `--time-window` (int, default: 5): Number of input vectors to pass to the CNN model
|
|
45
|
-
- `--input-vector` (int, default: 1536): Number of I/Q samples per input vector
|
|
46
51
|
- `--moving-avg-window` (int, default: 30): Window size (in samples) for the moving average used to detect energy peaks in the spectrum
|
|
47
52
|
- `--extraction-window` (int, default: 600): Number of samples to retain after detecting an energy peak
|
|
48
53
|
|
dapps-0.0.4/VERSION
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.0.4
|
|
@@ -37,27 +37,36 @@ def main(args, time_to_wait: float = 60.0):
|
|
|
37
37
|
)
|
|
38
38
|
exit(-1)
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
40
|
+
# This value really depends on the RF conditions and the RU used and should be carefully calibrated
|
|
41
|
+
if args.noise_floor_threshold:
|
|
42
|
+
print(f'Using custom configuration')
|
|
43
|
+
noise_floor_threshold = args.noise_floor_threshold
|
|
44
|
+
else:
|
|
45
|
+
if args.ota:
|
|
46
|
+
print(f'Using OTA configuration')
|
|
47
|
+
noise_floor_threshold = 20
|
|
48
|
+
else: # Colosseum
|
|
49
|
+
print(f'Using Colosseum configuration')
|
|
50
|
+
noise_floor_threshold = 53
|
|
51
|
+
|
|
52
|
+
print(f'Threshold is {noise_floor_threshold}')
|
|
48
53
|
|
|
49
54
|
classifier = None
|
|
50
55
|
if args.model:
|
|
56
|
+
import math
|
|
57
|
+
ofdm_symbol_size = args.num_prbs * 12 # 12 subcarriers per PRB always fixed by LTE/NR standard
|
|
58
|
+
fft_size = 2 ** math.ceil(math.log2(ofdm_symbol_size)) # Next power of 2
|
|
51
59
|
classifier = Classifier(
|
|
52
60
|
time_window = args.time_window,
|
|
53
|
-
input_vector =
|
|
61
|
+
input_vector = fft_size,
|
|
54
62
|
moving_avg_window = args.moving_avg_window,
|
|
55
63
|
extraction_window = args.extraction_window,
|
|
56
64
|
model_path = args.model
|
|
57
65
|
)
|
|
58
66
|
|
|
59
67
|
dapp = SpectrumSharingDApp(noise_floor_threshold=noise_floor_threshold, save_iqs=args.save_iqs, control=args.control, link=args.link, transport=args.transport,
|
|
60
|
-
energyGui=args.energy_gui, iqPlotterGui=args.iq_plotter_gui, dashboard=args.demo_gui, classifier=classifier
|
|
68
|
+
energyGui=args.energy_gui, iqPlotterGui=args.iq_plotter_gui, dashboard=args.demo_gui, classifier=classifier, center_freq=args.center_freq,
|
|
69
|
+
num_prbs=args.num_prbs, e_sampling=args.e, num_subcarrier_spacing= args.num_subcarrier_spacing)
|
|
61
70
|
|
|
62
71
|
dapp.setup_connection()
|
|
63
72
|
|
|
@@ -76,18 +85,22 @@ def main(args, time_to_wait: float = 60.0):
|
|
|
76
85
|
|
|
77
86
|
if __name__ == "__main__":
|
|
78
87
|
parser = argparse.ArgumentParser(description="Example of a dApp for Spectrum Sharing")
|
|
79
|
-
parser.add_argument('--ota', action='store_true', default=False, help="Specify if this is OTA or on Colosseum")
|
|
80
88
|
parser.add_argument('--link', type=str, default='zmq', choices=[layer.value for layer in E3LinkLayer], help="Specify the link layer to be used")
|
|
81
89
|
parser.add_argument('--transport', type=str, default='ipc', choices=[layer.value for layer in E3TransportLayer], help="Specify the transport layer to be used")
|
|
82
90
|
parser.add_argument('--save-iqs', action='store_true', default=False, help="Specify if this is data collection run or not. In the first case I/Q samples will be saved")
|
|
83
91
|
parser.add_argument('--control', action='store_true', default=False, help="Set whether to perform control of PRB")
|
|
92
|
+
parser.add_argument('--noise-floor-threshold', type=int, default=None, help="Set the noise floor threshold for determining the presence of incumbents and for detecting the PRBs affected.")
|
|
93
|
+
parser.add_argument('--ota', action='store_true', default=False, help="Specify if the setup used is OTA or on Colosseum for determining the noise floor threshold. If the `noise_floor_threshold` parameter is specified, this parameter is ignored")
|
|
84
94
|
parser.add_argument('--energy-gui', action='store_true', default=False, help="Set whether to enable the energy GUI")
|
|
85
95
|
parser.add_argument('--iq-plotter-gui', action='store_true', default=False, help="Set whether to enable the IQ Plotter GUI")
|
|
86
96
|
parser.add_argument('--demo-gui', action='store_true', default=False, help="Set whether to enable the Demo GUI")
|
|
97
|
+
parser.add_argument('--num-prbs', type=int, default=106, help="Number of PRBs")
|
|
98
|
+
parser.add_argument('--num-subcarrier-spacing', type=int, default=30, help="Subcarrier spacing in kHz (FR1 is 30)")
|
|
99
|
+
parser.add_argument('--e', action='store_true', default=False, help="Set if 3/4 sampling for FFT size is set on the gNB (-E option on OAI)")
|
|
100
|
+
parser.add_argument('--center-freq', type=float, default=3.6192e9, help="Center frequency in Hz")
|
|
87
101
|
parser.add_argument('--timed', action='store_true', default=False, help="Run with a 5-minute time limit")
|
|
88
102
|
parser.add_argument('--model', type=str, default='', help="Path to the CNN model file to be used")
|
|
89
103
|
parser.add_argument('--time-window', type=int, default=5, help="Number of input vectors to pass to the CNN model.")
|
|
90
|
-
parser.add_argument('--input-vector', type=int, default=1536, help="Number of I/Q samples per input vector.")
|
|
91
104
|
parser.add_argument('--moving-avg-window', type=int, default=30, help="Window size (in samples) for the moving average used to detect energy peaks in the spectrum.")
|
|
92
105
|
parser.add_argument('--extraction-window', type=int, default=600, help="Number of samples to retain after detecting an energy peak.")
|
|
93
106
|
|
|
@@ -19,7 +19,7 @@ LOG_DIR = ('.' if os.geteuid() != 0 else '') + '/logs/'
|
|
|
19
19
|
class DApp(ABC):
|
|
20
20
|
e3_interface: E3Interface
|
|
21
21
|
|
|
22
|
-
def __init__(self, id: int = 1, link: str = 'posix', transport:str = '
|
|
22
|
+
def __init__(self, id: int = 1, link: str = 'posix', transport:str = 'ipc', callbacks: list = [], **kwargs):
|
|
23
23
|
super().__init__()
|
|
24
24
|
self.dapp_id = id
|
|
25
25
|
self.e3_interface = E3Interface(link=link, transport=transport)
|
|
@@ -15,26 +15,36 @@ from e3interface.e3_logging import dapp_logger, LOG_DIR
|
|
|
15
15
|
|
|
16
16
|
class SpectrumSharingDApp(DApp):
|
|
17
17
|
|
|
18
|
-
### Configuration ###
|
|
19
|
-
# gNB runs with BW = 40 MHz
|
|
18
|
+
### Default Configuration ###
|
|
19
|
+
# gNB runs with BW = ~40 MHz
|
|
20
20
|
# Center frequency = 3.6192 GHz
|
|
21
|
-
# OAI sampling frequency 46.08e6
|
|
21
|
+
# OAI sampling frequency 46.08e6 # not useful for visualization
|
|
22
22
|
# No SRS for now, hence in gNB config file set do_SRS = 0
|
|
23
|
-
# gNB->frame_parms.ofdm_symbol_size = 1536
|
|
24
|
-
# gNB->frame_parms.
|
|
25
|
-
# Noise floor threshold needs to be calibrated
|
|
26
|
-
# We receive the symbols
|
|
27
|
-
|
|
28
|
-
def __init__(self, id: int = 1,
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
self.
|
|
34
|
-
self.
|
|
35
|
-
self.
|
|
36
|
-
self.
|
|
37
|
-
self.
|
|
23
|
+
# gNB->frame_parms.ofdm_symbol_size = 1536 # FFT size (even though is called ofdm_symbol_size) for USRP, with -E flag (3/4 sampling)
|
|
24
|
+
# gNB->frame_parms.ofdm_symbol_size = 2048 # FFT size (even though is called ofdm_symbol_size) for RUs, with no -E flag
|
|
25
|
+
# Noise floor threshold needs to be calibrated according to the RU
|
|
26
|
+
# We receive the symbols, average them over 63 frames and do PRB thresholding.
|
|
27
|
+
|
|
28
|
+
def __init__(self, id: int = 1, link: str = 'posix', transport: str = 'ipc', noise_floor_threshold: int = 53, save_iqs: bool = False, control: bool = False,
|
|
29
|
+
center_freq: float = 3.6192e9, num_prbs: int = 106, num_subcarrier_spacing: int = 30, e_sampling: bool = False, **kwargs):
|
|
30
|
+
super().__init__(id=id, link=link, transport=transport, **kwargs)
|
|
31
|
+
|
|
32
|
+
# gNB radio configuration used to compute exact values
|
|
33
|
+
self.num_consecutive_subcarriers_for_prb: int = 12 # Always fixed by the LTE/NR standard to 12
|
|
34
|
+
self.num_prbs = num_prbs
|
|
35
|
+
self.num_subcarrier_spacing = num_subcarrier_spacing # subcarrier spacing in kHz (FR1 is 30)
|
|
36
|
+
self.ofdm_symbol_size = num_prbs * self.num_consecutive_subcarriers_for_prb
|
|
37
|
+
self.bw = self.num_prbs * self.num_consecutive_subcarriers_for_prb * self.num_subcarrier_spacing * 1e3 # Bandwidth in Hz
|
|
38
|
+
self.center_freq = center_freq # Center frequency in Hz
|
|
39
|
+
self.fft_size = 2 ** int(np.ceil(np.log2(self.ofdm_symbol_size))) # Next power of 2 to include guard bands
|
|
40
|
+
if e_sampling:
|
|
41
|
+
self.fft_size = int(self.fft_size * 3 / 4) # 3/4 of sampling for USRPs (-E flag)
|
|
42
|
+
self.first_carrier_offset = self.fft_size - (self.ofdm_symbol_size // 2)
|
|
43
|
+
|
|
44
|
+
# dApp configuration
|
|
45
|
+
# The first 76 PRB usually have channels that should not be nulled (but we should investigate more options to enable this)
|
|
46
|
+
self.prb_thrs = 75 # This avoids blacklisting PRBs where the BWP is scheduled (it’s a workaround since the UE and gNB would not be able to communicate anymore, a cleaner fix is to move the BWP if needed or things like that)
|
|
47
|
+
self.average_over_frames = 63
|
|
38
48
|
self.noise_floor_threshold = noise_floor_threshold
|
|
39
49
|
self.save_iqs = save_iqs
|
|
40
50
|
self.e3_interface.add_callback(self.get_iqs_from_ran)
|
|
@@ -50,25 +60,24 @@ class SpectrumSharingDApp(DApp):
|
|
|
50
60
|
self.dashboard = kwargs.get('dashboard', False)
|
|
51
61
|
|
|
52
62
|
self.control_count = 1
|
|
53
|
-
self.abs_iq_av = np.zeros(self.
|
|
63
|
+
self.abs_iq_av = np.zeros(self.fft_size)
|
|
54
64
|
|
|
55
65
|
if self.energyGui:
|
|
56
66
|
from visualization.energy import EnergyPlotter
|
|
57
67
|
self.sig_queue = multiprocessing.Queue()
|
|
58
|
-
self.energyPlotter = EnergyPlotter(self.
|
|
68
|
+
self.energyPlotter = EnergyPlotter(self.fft_size, bw=self.bw, center_freq=self.center_freq)
|
|
59
69
|
|
|
60
70
|
if self.iqPlotterGui:
|
|
61
71
|
from visualization.iq import IQPlotter
|
|
62
72
|
self.iq_queue = multiprocessing.Queue()
|
|
63
|
-
|
|
64
|
-
self.iqPlotter = IQPlotter(buffer_size=500, iq_size=iq_size, bw=self.bw, center_freq=self.center_freq)
|
|
73
|
+
self.iqPlotter = IQPlotter(buffer_size=500, fft_size=self.fft_size, bw=self.bw, center_freq=self.center_freq)
|
|
65
74
|
|
|
66
75
|
if self.dashboard:
|
|
67
76
|
from visualization.dashboard import Dashboard
|
|
68
77
|
self.demo_queue = multiprocessing.Queue()
|
|
69
|
-
iq_size = self.FFT_SIZE * 2 # double the size of ofdm_symbol_size since real and imaginary parts are interleaved
|
|
70
78
|
classifier = kwargs.get('classifier', None)
|
|
71
|
-
self.demo = Dashboard(buffer_size=100,
|
|
79
|
+
self.demo = Dashboard(buffer_size=100, ofdm_symbol_size=self.ofdm_symbol_size, first_carrier_offset=self.first_carrier_offset,
|
|
80
|
+
bw=self.bw, center_freq=self.center_freq, num_prbs=num_prbs, classifier=classifier)
|
|
72
81
|
|
|
73
82
|
def get_iqs_from_ran(self, data):
|
|
74
83
|
dapp_logger.debug(f'Triggered callback')
|
|
@@ -83,7 +92,7 @@ class SpectrumSharingDApp(DApp):
|
|
|
83
92
|
|
|
84
93
|
iq_arr = np.frombuffer(data, dtype=np.int16)
|
|
85
94
|
dapp_logger.debug(f"Shape of iq_arr {iq_arr.shape}")
|
|
86
|
-
|
|
95
|
+
|
|
87
96
|
if self.iqPlotterGui:
|
|
88
97
|
self.iq_queue.put(iq_arr)
|
|
89
98
|
|
|
@@ -100,9 +109,9 @@ class SpectrumSharingDApp(DApp):
|
|
|
100
109
|
self.control_count += 1
|
|
101
110
|
dapp_logger.debug(f"Control count is: {self.control_count}")
|
|
102
111
|
|
|
103
|
-
if self.control_count == self.
|
|
104
|
-
abs_iq_av_db = 20 * np.log10(1 + (self.abs_iq_av/(self.
|
|
105
|
-
abs_iq_av_db_offset_correct = np.append(abs_iq_av_db[self.
|
|
112
|
+
if self.control_count == self.average_over_frames:
|
|
113
|
+
abs_iq_av_db = 20 * np.log10(1 + (self.abs_iq_av/(self.average_over_frames)))
|
|
114
|
+
abs_iq_av_db_offset_correct = np.append(abs_iq_av_db[self.first_carrier_offset:self.fft_size],abs_iq_av_db[0:self.first_carrier_offset])
|
|
106
115
|
dapp_logger.info(f'--- AVG VALUES ----')
|
|
107
116
|
dapp_logger.info(f'abs_iq_av_db_offset_correct: {abs_iq_av_db_offset_correct.mean()}')
|
|
108
117
|
dapp_logger.info(f'--- MAX VALUES ----')
|
|
@@ -111,11 +120,11 @@ class SpectrumSharingDApp(DApp):
|
|
|
111
120
|
# dapp_logger.info(f'Last 20 max values (abs_iq_av_db_offset_correct): {last_5_max_abs_iq_av_db_offset_correct}')
|
|
112
121
|
|
|
113
122
|
# PRB blocking based on the noise floor threshold
|
|
114
|
-
f_ind = np.arange(self.
|
|
123
|
+
f_ind = np.arange(self.fft_size)
|
|
115
124
|
blklist_sub_carrier = f_ind[abs_iq_av_db_offset_correct > self.noise_floor_threshold]
|
|
116
125
|
np.sort(blklist_sub_carrier)
|
|
117
126
|
dapp_logger.info(f'blklist_sub_carrier: {blklist_sub_carrier}')
|
|
118
|
-
prb_blk_list = np.unique((np.floor(blklist_sub_carrier/self.
|
|
127
|
+
prb_blk_list = np.unique((np.floor(blklist_sub_carrier/self.num_consecutive_subcarriers_for_prb))).astype(np.uint16)
|
|
119
128
|
dapp_logger.info(f'prb_blk_list: {prb_blk_list}')
|
|
120
129
|
prb_blk_list = prb_blk_list[prb_blk_list > self.prb_thrs]
|
|
121
130
|
# prb_blk_list = np.array([76, 77, 78, 79, 80, 81, 82, 83], dtype=np.uint16) # 76, 77, 78, 79, 80, 81, 82, 83
|
|
@@ -137,7 +146,7 @@ class SpectrumSharingDApp(DApp):
|
|
|
137
146
|
self.demo_queue.put(("prb_list", prb_blk_list))
|
|
138
147
|
|
|
139
148
|
# Reset the variables
|
|
140
|
-
self.abs_iq_av = np.zeros(self.
|
|
149
|
+
self.abs_iq_av = np.zeros(self.fft_size)
|
|
141
150
|
self.control_count = 1
|
|
142
151
|
|
|
143
152
|
def _control_loop(self):
|
|
@@ -14,18 +14,21 @@ except ModuleNotFoundError:
|
|
|
14
14
|
import numpy as np
|
|
15
15
|
|
|
16
16
|
class Dashboard:
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
# The default values of the dashboard are based on the default configuration
|
|
18
|
+
def __init__(self, buffer_size: int = 100, ofdm_symbol_size: int = 1272, bw: float = 38.16e6, center_freq: float = 3.6192e9,
|
|
19
|
+
num_prbs: int = 106, first_carrier_offset: int = 900, classifier = None):
|
|
19
20
|
# Flask app setup
|
|
20
21
|
self.app = Flask(__name__)
|
|
21
22
|
self.app.config['TEMPLATES_AUTO_RELOAD'] = True
|
|
22
23
|
self.socketio = SocketIO(self.app)
|
|
23
24
|
|
|
24
25
|
# Parameters
|
|
25
|
-
self.
|
|
26
|
-
self.
|
|
27
|
-
self.
|
|
28
|
-
self.
|
|
26
|
+
self.buffer_size = buffer_size
|
|
27
|
+
self.ofdm_symbol_size = ofdm_symbol_size
|
|
28
|
+
self.bw = bw
|
|
29
|
+
self.center_freq = center_freq
|
|
30
|
+
self.num_prbs = num_prbs
|
|
31
|
+
self.first_carrier_offset = first_carrier_offset
|
|
29
32
|
|
|
30
33
|
# Route setup
|
|
31
34
|
self.app.add_url_rule("/", view_func=self.index)
|
|
@@ -51,20 +54,12 @@ class Dashboard:
|
|
|
51
54
|
self.run_thread.start()
|
|
52
55
|
|
|
53
56
|
def handle_initial_connection(self):
|
|
54
|
-
num_prbs = 106
|
|
55
|
-
iqs_to_show = int(
|
|
56
|
-
self.iq_size / 2
|
|
57
|
-
) # / 2 since the size received is real and imaginary part
|
|
58
|
-
# Each PRB corresponds to 12 subcarriers
|
|
59
|
-
# Parameter first_carrier_offset (currently set to 900) is the first subcarrier.
|
|
60
|
-
# So PRB 0 is [900-911], 1 is [912-923], etc. They wrap around fft_size (now at 1536)
|
|
61
|
-
# The carrier frequency is in the middle PRB (including DC leak on some radios), this means it falls at (first_carrier_offset + 106*12/2) % fft_size? where % is the modulo operation
|
|
62
|
-
# The first 76 PRB usually have channels that should not be nulled (but we should investigate more options to enable this)
|
|
63
|
-
# So if you want the spectrum waterfall to correspond to the PRB masking plot, you can rotate them as per bullet 2 above and drop [635-899]
|
|
64
|
-
iqs_to_show -= 264 # is the size of the array to drop
|
|
65
57
|
data_buffer = {
|
|
66
|
-
"magnitude": np.zeros((self.
|
|
67
|
-
"
|
|
58
|
+
"magnitude": np.zeros((self.buffer_size, self.ofdm_symbol_size)).tolist(),
|
|
59
|
+
"center_freq": self.center_freq,
|
|
60
|
+
"bw": self.bw,
|
|
61
|
+
"num_prbs": self.num_prbs,
|
|
62
|
+
"first_carrier_offset": self.first_carrier_offset,
|
|
68
63
|
"predicted_label": self.classifier is not None
|
|
69
64
|
}
|
|
70
65
|
self.socketio.emit("initialize_plot", data_buffer)
|
|
@@ -84,8 +79,6 @@ class Dashboard:
|
|
|
84
79
|
plot, payload = message
|
|
85
80
|
if plot == "iq_data":
|
|
86
81
|
iq_data = payload
|
|
87
|
-
if iq_data.size != self.iq_size:
|
|
88
|
-
raise ValueError(f'Expected IQ data of size {self.iq_size}, but got {iq_data.size}')
|
|
89
82
|
|
|
90
83
|
self.sampling_counter += 1
|
|
91
84
|
if self.sampling_counter >= self.sampling_threshold:
|
|
@@ -105,7 +98,6 @@ class Dashboard:
|
|
|
105
98
|
def stop(self):
|
|
106
99
|
"""Stops the server and kills the thread."""
|
|
107
100
|
if self.run_thread and self.run_thread.is_alive():
|
|
108
|
-
self.socketio.stop()
|
|
109
101
|
self.run_thread.join()
|
|
110
102
|
|
|
111
103
|
if __name__ == "__main__":
|
|
@@ -17,19 +17,19 @@ import os
|
|
|
17
17
|
print(os.environ.get('DISPLAY'))
|
|
18
18
|
|
|
19
19
|
class IQPlotter:
|
|
20
|
-
def __init__(self, buffer_size=100,
|
|
20
|
+
def __init__(self, buffer_size=100, fft_size=1536, bw=38.16e6, center_freq=3.6192e9):
|
|
21
21
|
"""
|
|
22
22
|
Initialize the IQ plotter with buffer size and IQ data dimensions.
|
|
23
23
|
buffer_size: Number of columns (timesteps) to store and plot.
|
|
24
|
-
|
|
24
|
+
fft_size: Size of the FFT.
|
|
25
25
|
bw: Sampling frequency.
|
|
26
26
|
center_freq: Center frequency of the signal.
|
|
27
27
|
"""
|
|
28
28
|
self.buffer_size = buffer_size
|
|
29
|
-
self.iq_size =
|
|
29
|
+
self.iq_size = fft_size * 2 # double the size of fft_size since real and imaginary parts are interleaved
|
|
30
30
|
self.bw = bw
|
|
31
31
|
self.center_freq = center_freq
|
|
32
|
-
self.iq_shape = (iq_size // 2, buffer_size) # Half the size after splitting real and imaginary parts
|
|
32
|
+
self.iq_shape = (self.iq_size // 2, buffer_size) # Half the size after splitting real and imaginary parts
|
|
33
33
|
|
|
34
34
|
# Initialize the buffer
|
|
35
35
|
self.buffer = np.zeros(self.iq_shape)
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<head>
|
|
5
5
|
<meta charset="UTF-8">
|
|
6
6
|
<title>Real-Time Plots</title>
|
|
7
|
-
<script src="https://cdn.plot.ly/plotly-
|
|
7
|
+
<script src="https://cdn.plot.ly/plotly-3.0.1.min.js" charset="utf-8"></script>
|
|
8
8
|
<script src="//cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
|
|
9
9
|
</head>
|
|
10
10
|
|
|
@@ -18,27 +18,38 @@
|
|
|
18
18
|
z: [],
|
|
19
19
|
type: 'heatmap',
|
|
20
20
|
colorscale: "Viridis",
|
|
21
|
+
zmin: 0,
|
|
22
|
+
zmax: 100,
|
|
21
23
|
colorbar: {
|
|
22
24
|
title:{
|
|
23
25
|
text: "Magnitude [dB]",
|
|
24
26
|
side: 'right'
|
|
25
27
|
},
|
|
26
|
-
orientation: '
|
|
28
|
+
orientation: 'v'
|
|
27
29
|
}
|
|
28
30
|
}];
|
|
29
31
|
|
|
30
32
|
var waterfallLayout = {
|
|
31
|
-
title:
|
|
33
|
+
title: {
|
|
34
|
+
text: "Sensed Spectrum",
|
|
35
|
+
font: { size: 16 }
|
|
36
|
+
},
|
|
32
37
|
xaxis: {
|
|
33
|
-
title:
|
|
38
|
+
title: {
|
|
39
|
+
text: "OFDM Symbol Subcarriers",
|
|
40
|
+
font: { size: 14 }
|
|
41
|
+
}
|
|
34
42
|
},
|
|
35
43
|
yaxis: {
|
|
36
|
-
title:
|
|
44
|
+
title: {
|
|
45
|
+
text: "Time samples",
|
|
46
|
+
font: { size: 14 }
|
|
47
|
+
},
|
|
37
48
|
autorange: 'reversed'
|
|
38
49
|
},
|
|
39
50
|
annotations: [
|
|
40
51
|
{
|
|
41
|
-
x:
|
|
52
|
+
x: 0, // Will be updated dynamically
|
|
42
53
|
y: 1.15,
|
|
43
54
|
xref: 'x',
|
|
44
55
|
yref: 'paper',
|
|
@@ -49,21 +60,21 @@
|
|
|
49
60
|
}
|
|
50
61
|
},
|
|
51
62
|
{
|
|
52
|
-
x:
|
|
63
|
+
x: 0, // Will be updated dynamically
|
|
53
64
|
y: 1.1,
|
|
54
65
|
xref: 'x',
|
|
55
66
|
yref: 'paper',
|
|
56
|
-
text:
|
|
67
|
+
text: `0.0000`,
|
|
57
68
|
showarrow: false,
|
|
58
69
|
font: {
|
|
59
70
|
size: 12
|
|
60
71
|
}
|
|
61
72
|
}, {
|
|
62
|
-
x:
|
|
73
|
+
x: 0, // Will be updated dynamically
|
|
63
74
|
y: 1.1,
|
|
64
75
|
xref: 'x',
|
|
65
76
|
yref: 'paper',
|
|
66
|
-
text:
|
|
77
|
+
text: `0.0000`,
|
|
67
78
|
showarrow: false,
|
|
68
79
|
font: {
|
|
69
80
|
size: 12
|
|
@@ -71,33 +82,33 @@
|
|
|
71
82
|
},
|
|
72
83
|
|
|
73
84
|
{
|
|
74
|
-
x:
|
|
85
|
+
x: 0, // Will be updated dynamically
|
|
75
86
|
y: 1.1,
|
|
76
87
|
xref: 'x',
|
|
77
88
|
yref: 'paper',
|
|
78
|
-
text:
|
|
89
|
+
text: `0.0000`,
|
|
79
90
|
showarrow: false,
|
|
80
91
|
font: {
|
|
81
92
|
size: 12
|
|
82
93
|
}
|
|
83
94
|
},
|
|
84
95
|
{
|
|
85
|
-
x:
|
|
96
|
+
x: 0, // Will be updated dynamically
|
|
86
97
|
y: 1.1,
|
|
87
98
|
xref: 'x',
|
|
88
99
|
yref: 'paper',
|
|
89
|
-
text:
|
|
100
|
+
text: `0.0000`,
|
|
90
101
|
showarrow: false,
|
|
91
102
|
font: {
|
|
92
103
|
size: 12
|
|
93
104
|
}
|
|
94
105
|
},
|
|
95
106
|
{
|
|
96
|
-
x:
|
|
107
|
+
x: 0, // Will be updated dynamically
|
|
97
108
|
y: 1.1,
|
|
98
109
|
xref: 'x',
|
|
99
110
|
yref: 'paper',
|
|
100
|
-
text:
|
|
111
|
+
text: `0.0000`,
|
|
101
112
|
showarrow: false,
|
|
102
113
|
font: {
|
|
103
114
|
size: 12
|
|
@@ -145,12 +156,21 @@
|
|
|
145
156
|
align: 'right'
|
|
146
157
|
}
|
|
147
158
|
],
|
|
148
|
-
title:
|
|
159
|
+
title: {
|
|
160
|
+
text: "PRB Status Grid",
|
|
161
|
+
font: { size: 16 }
|
|
162
|
+
},
|
|
149
163
|
xaxis: {
|
|
150
|
-
title:
|
|
164
|
+
title: {
|
|
165
|
+
text: "PRB Index",
|
|
166
|
+
font: { size: 14 }
|
|
167
|
+
}
|
|
151
168
|
},
|
|
152
169
|
yaxis: {
|
|
153
|
-
title:
|
|
170
|
+
title: {
|
|
171
|
+
text: "Status",
|
|
172
|
+
font: { size: 14 }
|
|
173
|
+
},
|
|
154
174
|
showticklabels: false
|
|
155
175
|
},
|
|
156
176
|
height: 200,
|
|
@@ -172,11 +192,41 @@
|
|
|
172
192
|
var waterfall_buffer;
|
|
173
193
|
var prbs_buffer;
|
|
174
194
|
var predictedLabel;
|
|
195
|
+
var start_ignore;
|
|
196
|
+
var end_ignore;
|
|
197
|
+
var initialized = false;
|
|
175
198
|
|
|
176
199
|
var socket = io.connect('http://' + document.domain + ':' + location.port);
|
|
177
200
|
socket.on('initialize_plot', function (data) {
|
|
201
|
+
var ofdm_symbol_size = data.magnitude[0].length;
|
|
202
|
+
var first_carrier_offset = data.first_carrier_offset;
|
|
203
|
+
var freq_step = data.bw / ofdm_symbol_size;
|
|
204
|
+
var start_freq = data.center_freq - (ofdm_symbol_size / 2) * freq_step;
|
|
205
|
+
var freq_labels = [];
|
|
206
|
+
for (let i = 0; i < 5; i++) {
|
|
207
|
+
let pos = Math.floor(i * ofdm_symbol_size / 4);
|
|
208
|
+
let f = (start_freq + pos * freq_step) / 1e9;
|
|
209
|
+
freq_labels.push(f.toFixed(4));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Update x values for annotations (frequency markers) with proper edge margins
|
|
213
|
+
var left_margin = Math.max(25, ofdm_symbol_size * 0.02); // At least 25 or 2% of width
|
|
214
|
+
var right_margin = Math.max(65, ofdm_symbol_size * 0.02); // At least 65 or 2% of width
|
|
215
|
+
|
|
216
|
+
waterfallLayout.annotations[0].x = ofdm_symbol_size / 2 + ofdm_symbol_size / 50; // slightly centered to the right
|
|
217
|
+
waterfallLayout.annotations[1].x = left_margin;
|
|
218
|
+
waterfallLayout.annotations[2].x = ofdm_symbol_size / 4;
|
|
219
|
+
waterfallLayout.annotations[3].x = ofdm_symbol_size / 2;
|
|
220
|
+
waterfallLayout.annotations[4].x = ofdm_symbol_size * 3 / 4;
|
|
221
|
+
waterfallLayout.annotations[5].x = ofdm_symbol_size - right_margin;
|
|
222
|
+
waterfallLayout.annotations[1].text = `${freq_labels[0]}`;
|
|
223
|
+
waterfallLayout.annotations[2].text = `${freq_labels[1]}`;
|
|
224
|
+
waterfallLayout.annotations[3].text = `${freq_labels[2]}`;
|
|
225
|
+
waterfallLayout.annotations[4].text = `${freq_labels[3]}`;
|
|
226
|
+
waterfallLayout.annotations[5].text = `${freq_labels[4]}`;
|
|
227
|
+
|
|
178
228
|
waterfall_buffer = data.magnitude;
|
|
179
|
-
waterfall[0].z
|
|
229
|
+
waterfall[0].z = waterfall_buffer;
|
|
180
230
|
prbs_buffer = [new Array(data.num_prbs).fill(0)];
|
|
181
231
|
prbs[0].z = prbs_buffer;
|
|
182
232
|
Plotly.newPlot('magnitude', waterfall, waterfallLayout);
|
|
@@ -184,29 +234,66 @@
|
|
|
184
234
|
if(data.predicted_label){
|
|
185
235
|
Plotly.newPlot("predicted_label", [], classifierLayout);
|
|
186
236
|
}
|
|
237
|
+
|
|
238
|
+
// Dynamic preprocessing based on first_carrier_offset
|
|
239
|
+
// The carrier frequency is in the middle PRB (including DC leak on some radios)
|
|
240
|
+
console.log("Initial values:", {
|
|
241
|
+
ofdm_symbol_size: ofdm_symbol_size,
|
|
242
|
+
first_carrier_offset: first_carrier_offset,
|
|
243
|
+
num_prbs: data.num_prbs
|
|
244
|
+
});
|
|
245
|
+
const num_consecutive_subcarriers_for_prb = 12; // Always 12 by LTE / NR standard
|
|
246
|
+
// Calculate the middle PRB position
|
|
247
|
+
var middle_prb_offset = data.num_prbs * num_consecutive_subcarriers_for_prb / 2;
|
|
248
|
+
console.log("Calculated middle_prb_offset:", middle_prb_offset);
|
|
249
|
+
|
|
250
|
+
// Calculate slice points dynamically
|
|
251
|
+
// We want to show the spectrum centered around the middle PRB
|
|
252
|
+
// The interval to consider is from the middle prb offset to the first carrier offset
|
|
253
|
+
start_ignore = middle_prb_offset;
|
|
254
|
+
end_ignore = first_carrier_offset;
|
|
255
|
+
|
|
256
|
+
console.log("Slice points:", {
|
|
257
|
+
start_ignore: start_ignore,
|
|
258
|
+
end_ignore: end_ignore
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
initialized = true;
|
|
187
262
|
});
|
|
188
263
|
|
|
189
264
|
socket.on('update_plot', function (data) {
|
|
265
|
+
if (!initialized) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
190
269
|
if ('magnitude' in data) {
|
|
191
270
|
// console.log("Magnitude is: ", data.magnitude)
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
//
|
|
195
|
-
let
|
|
271
|
+
|
|
272
|
+
var magnitude_length = data.magnitude.length;
|
|
273
|
+
// Dynamic slicing based on calculated positions
|
|
274
|
+
let part1 = data.magnitude.slice(0, start_ignore);
|
|
275
|
+
// Ignore the middle section (guards symbols)
|
|
276
|
+
let part3 = data.magnitude.slice(end_ignore);
|
|
196
277
|
let magnitudeToShow = part3.concat(part1).reverse();
|
|
278
|
+
|
|
279
|
+
// console.log("Parts lengths:", {
|
|
280
|
+
// // part1_length: part1.length,
|
|
281
|
+
// // part3_length: part3.length,
|
|
282
|
+
// magnitudeToShow_length: magnitudeToShow.length
|
|
283
|
+
// });
|
|
284
|
+
|
|
197
285
|
// Update waterfall plot
|
|
198
286
|
waterfall_buffer.pop();
|
|
199
287
|
waterfall_buffer.unshift(magnitudeToShow);
|
|
200
288
|
Plotly.restyle('magnitude', { z: [waterfall_buffer] });
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
console.log("PRBs are: ", data.prb_list);
|
|
289
|
+
} else if ('prb_list' in data) {
|
|
290
|
+
// console.log("PRBs are: ", data.prb_list);
|
|
204
291
|
// We get a list of indexes to replace each time
|
|
205
292
|
prbs_buffer[0].fill(0);
|
|
206
293
|
data.prb_list.forEach(index => prbs_buffer[0][index] = 1);
|
|
207
294
|
Plotly.restyle('prbs', { z: [prbs_buffer] });
|
|
208
295
|
} else if ('predicted_label' in data) {
|
|
209
|
-
console.log("RFI Predicted is ", data.predicted_label);
|
|
296
|
+
// console.log("RFI Predicted is ", data.predicted_label);
|
|
210
297
|
Plotly.relayout('predicted_label', {
|
|
211
298
|
'title.text': `RFI Predicted: ${data.predicted_label}`
|
|
212
299
|
});
|
dapps-0.0.3/VERSION
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.0.3
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|