rfmetadata 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
rfmetadata/__init__.py ADDED
@@ -0,0 +1,12 @@
1
+ """
2
+
3
+ **RF Metadata Displayer**
4
+
5
+ """
6
+
7
+ from rfmetadata.version import version as __version__
8
+
9
+ __all__ = (
10
+ "__version__",
11
+ "main",
12
+ )
rfmetadata/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from rfmetadata.main import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
rfmetadata/main.py ADDED
@@ -0,0 +1,31 @@
1
+ '''
2
+ Steps:
3
+ >>> pwd
4
+ >>> python -m venv .venv
5
+ >>> source ./venv/bin/activate
6
+ >>> pip install -e .
7
+ >>> rfmetadata
8
+
9
+ '''
10
+ import sys
11
+ from PySide6 import QtWidgets
12
+ from rfmetadata.windows.main_window import MainWindow
13
+ import argparse
14
+ from . import __version__ # FIXME dynamic versioning ????? how ????
15
+
16
+ def main()-> None:
17
+
18
+ parser = argparse.ArgumentParser()
19
+ parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
20
+ args = parser.parse_args()
21
+
22
+ app = QtWidgets.QApplication()
23
+
24
+ widget = MainWindow()
25
+ widget.show()
26
+
27
+ sys.exit(app.exec())
28
+
29
+ # this is important so that it does not run from pytest
30
+ if __name__ == "__main__":
31
+ main()
rfmetadata/readme.md ADDED
@@ -0,0 +1,44 @@
1
+ # This is an overview on how rfmetadata is working Alan Check ????
2
+
3
+ ## Folders
4
+
5
+ The work is separated into folder:
6
+
7
+ ### database folder
8
+ This folder is responsible for the modules that query the database. It contains the files:
9
+ 1- database_query.py: This file contains the databasemanager class which queries the database using the method search_power_frequency() and returns the result.
10
+
11
+ ### widgets folder:
12
+ This folder contains all the different widgets that will be added into the main window later. It contains the files:
13
+ 1- search_layout.py: This module creates the widget for the search box at the top of the main window.
14
+ 2- data_graph_layout.py: This module creates the widget for the 3d graph that draws the searched data.
15
+
16
+ we create different widgets now here and easily add them to the main window later.
17
+
18
+ ### windows folder:
19
+ This folder contains the different windows of our application. It contains the files:
20
+ 1- main_window.py: which is the main app gui, it contains the search box currently at the top.
21
+ 2- query_result_window.py: which is the window that pops up when you press search. It will contain your result in a clean table view.
22
+
23
+ I separated them, and made the result pop up in a different window. This way our main gui is much cleaner and not crammed with results.
24
+ And the user gets a beautiful clean window for the result.
25
+
26
+ ### signal_manager folder:
27
+ This folder contains:
28
+ 1- data_signal_manager.py: This module creates two signal managers objects that i use to send (the result of the query to the data_graph_layout.py to be drawn) and (the type of the type of the graph taken from the mainwindow).
29
+
30
+ ## main
31
+
32
+ In the main.py, I added an arg parser for the "--version" . This way rfsink --version will give the user the current version of our application.
33
+ And called the main_window class to create our main gui.
34
+
35
+ ## how execute the RF-Sink Desktop application
36
+ * Create and activate a virtual environment
37
+ * Go to the root folder of the project (rf-surveillance)
38
+
39
+ ```
40
+ $ pip install -e .
41
+ $ rfsink
42
+ ```
43
+
44
+
File without changes
@@ -0,0 +1,26 @@
1
+ from PySide6.QtCore import QObject, Signal
2
+
3
+
4
+ class GraphSignalManager(QObject):
5
+ data_signal = Signal(list)
6
+
7
+ def __init__(self)->None:
8
+ super().__init__()
9
+
10
+ signal_manager = GraphSignalManager()
11
+
12
+ class GraphTypeSignalManager(QObject):
13
+ data_signal = Signal(str)
14
+
15
+ def __init__(self)->None:
16
+ super().__init__()
17
+
18
+ graph_type_manager = GraphTypeSignalManager()
19
+
20
+ class TableTypeSignalManager(QObject):
21
+ data_signal = Signal(bool)
22
+
23
+ def __init__(self)->None:
24
+ super().__init__()
25
+
26
+ table_type_manager = TableTypeSignalManager()
rfmetadata/version.py ADDED
@@ -0,0 +1 @@
1
+ version = '1.0.0'
File without changes
@@ -0,0 +1,155 @@
1
+ from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas
2
+ from matplotlib.figure import Figure
3
+ from rfmetadata.signal_manager.data_signal_manager import signal_manager, graph_type_manager
4
+ import matplotlib.dates as mdates
5
+ from datetime import datetime
6
+ import numpy as np
7
+ from scipy.interpolate import griddata
8
+ from matplotlib import cm
9
+ from matplotlib.colors import Normalize
10
+
11
+
12
+ class DataGraph3D(FigureCanvas):
13
+ def __init__(self, figure:Figure=None)->None:
14
+ self.figure = Figure()
15
+ super().__init__(figure)
16
+
17
+ self.min_power = 20.0
18
+ self.max_power = 60.0
19
+
20
+ self.type = "scatter"
21
+
22
+ self.data = []
23
+ self.freq = []
24
+ self.pow = []
25
+ self.time = []
26
+
27
+
28
+ self.ax = self.figure.add_subplot(projection='3d')
29
+ self.ax.set_title("Data Graph")
30
+
31
+ signal_manager.data_signal.connect(self.receive_data)
32
+ graph_type_manager.data_signal.connect(self.set_type)
33
+
34
+
35
+
36
+ def receive_data(self, data):
37
+ self.data = data
38
+ self.freq.clear()
39
+ self.pow.clear()
40
+ self.time.clear()
41
+
42
+ for row_data in self.data:
43
+ self.freq.append(row_data[1])
44
+ self.pow.append(row_data[2])
45
+ dt = datetime.strptime(row_data[3], "%d-%m-%Y %H:%M:%S")
46
+ self.time.append(mdates.date2num(dt))
47
+
48
+ self.draw_graph()
49
+
50
+ def set_type(self, type):
51
+ self.type = type
52
+ self.draw_graph()
53
+
54
+
55
+ def draw_graph(self):
56
+
57
+ if not (self.freq and self.pow and self.time):
58
+ return
59
+
60
+
61
+ if self.type == "scatter":
62
+ self.figure.clear()
63
+ self.ax = self.figure.add_subplot(projection='3d')
64
+
65
+ self.ax.set_title("Data Graph")
66
+
67
+ self.ax.set_title("Data Graph")
68
+ self.ax.set_xlabel("Frequency")
69
+ self.ax.set_ylabel("Time")
70
+ self.ax.set_zlabel("Power")
71
+ self.ax.yaxis.set_major_formatter(mdates.DateFormatter('%d-%m %H:%M'))
72
+ self.ax.set_zlim(self.min_power, self.max_power)
73
+ self.ax.scatter(self.freq, self.time, self.pow)
74
+ self.draw()
75
+
76
+ elif self.type == "meshgrid":
77
+ self.figure.clear()
78
+
79
+ self.ax = self.figure.add_subplot(projection='3d')
80
+
81
+ self.ax.set_title("Data Graph")
82
+
83
+ # Preparing data as numpy arrays
84
+ freq = np.array(self.freq)
85
+ time = np.array(self.time)
86
+ power = np.array(self.pow)
87
+
88
+ # Building a grid
89
+ freq_lin = np.linspace(freq.min(), freq.max(), 100)
90
+ time_lin = np.linspace(time.min(), time.max(), 100)
91
+ FREQ, TIME = np.meshgrid(freq_lin, time_lin)
92
+
93
+ # Interpolating power data onto grid
94
+ POWER = griddata(
95
+ points=(freq, time),
96
+ values=power,
97
+ xi=(FREQ, TIME),
98
+ method='nearest'
99
+ )
100
+
101
+ POWER = np.nan_to_num(POWER, nan=np.nanmin(power))
102
+
103
+
104
+ surf = self.ax.plot_surface(
105
+ FREQ, TIME, POWER,
106
+ cmap='inferno',
107
+ linewidth=0.5,
108
+ antialiased=True
109
+ )
110
+
111
+ self.ax.set_title("Data Graph")
112
+ self.ax.set_xlabel("Frequency")
113
+ self.ax.set_ylabel("Time")
114
+ self.ax.set_zlabel("Power")
115
+
116
+ self.ax.yaxis.set_major_formatter(mdates.DateFormatter('%d-%m %H:%M'))
117
+
118
+ self.ax.set_zlim(self.min_power, self.max_power)
119
+
120
+ self.draw()
121
+
122
+ elif self.type == "line":
123
+ self.figure.clear()
124
+ self.ax = self.figure.add_subplot(111)
125
+
126
+ # Normalizing frequency values for colormap
127
+ norm = Normalize(vmin=min(self.freq), vmax=max(self.freq))
128
+ cmap = cm.get_cmap('viridis')
129
+
130
+ # Plotting each segment with color based on frequency
131
+ for i in range(len(self.pow) - 1):
132
+ x_vals = [self.time[i], self.time[i+1]]
133
+ y_vals = [self.pow[i], self.pow[i+1]]
134
+ freq_avg = (self.freq[i] + self.freq[i+1]) / 2
135
+ color = cmap(norm(freq_avg))
136
+
137
+ self.ax.plot(x_vals, y_vals, color=color, linewidth=2)
138
+
139
+ # Formatting time axis
140
+ self.ax.set_xlabel('Time')
141
+ self.ax.set_ylabel('Power')
142
+ self.ax.set_title('Power vs Time (Colored by Frequency)')
143
+
144
+ # Adding colorbar
145
+ sm = cm.ScalarMappable(norm=norm, cmap=cmap)
146
+ sm.set_array([]) # Required to register mappable
147
+ cbar = self.figure.colorbar(sm, ax=self.ax)
148
+ cbar.set_label("Frequency")
149
+
150
+ self.ax.xaxis.set_major_formatter(mdates.DateFormatter('%d-%m %H:%M'))
151
+ self.figure.autofmt_xdate()
152
+
153
+ self.draw()
154
+
155
+
@@ -0,0 +1,36 @@
1
+ from PySide6.QtWidgets import QWidget, QTableWidget, QTableWidgetItem, QHBoxLayout
2
+ from PySide6.QtCore import Qt
3
+ from rfmetadata.signal_manager.data_signal_manager import signal_manager
4
+
5
+ class TableResultsWidget(QTableWidget):
6
+ def __init__(self):
7
+ super().__init__()
8
+
9
+ self.result = ""
10
+ signal_manager.data_signal.connect(self.set_result)
11
+ # Configure table
12
+ self.setAlternatingRowColors(True)
13
+ self.horizontalHeader().setStretchLastSection(True)
14
+
15
+ self.setColumnCount(4)
16
+ self.setRowCount(20)
17
+ self.setHorizontalHeaderLabels(["ID", "frequency", "power", "datetime"])
18
+
19
+ def set_result(self, data):
20
+ self.result = data
21
+ self.setup_ui(self.result)
22
+
23
+ def setup_ui(self, query):
24
+ self.setRowCount(len(query))
25
+ for row, row_data in enumerate(query):
26
+ for col, data in enumerate(row_data):
27
+ item = QTableWidgetItem(str(data))
28
+ item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
29
+ self.setItem(row, col, item)
30
+
31
+
32
+
33
+
34
+
35
+
36
+
@@ -0,0 +1,109 @@
1
+ from PySide6.QtWidgets import QGroupBox, QGridLayout, QLabel, QPushButton, QDoubleSpinBox, QSpacerItem, QSizePolicy
2
+ from PySide6.QtCore import QLocale, Qt
3
+ from rfmetadata.windows.query_result_window import show_query_results
4
+ from rfmetadata.signal_manager.data_signal_manager import signal_manager
5
+ import requests
6
+
7
+ class SearchGroupBox(QGroupBox):
8
+
9
+ def __init__(self, parent=None):
10
+
11
+ self.min_power = 20.0
12
+ self.max_power = 60.0
13
+ self.min_frequency = 103.0
14
+ self.max_frequency = 109.0
15
+
16
+ super().__init__(parent)
17
+ self.setup_ui()
18
+
19
+ def setup_ui(self):
20
+
21
+ layout = QGridLayout()
22
+ self.setLayout(layout)
23
+
24
+ self.min_power_spin = QDoubleSpinBox()
25
+ self.min_power_spin.setRange(self.min_power,self.max_power)
26
+ self.min_power_spin.setValue(20.0)
27
+ self.min_power_spin.setSingleStep(0.01)
28
+ self.min_power_spin.setDecimals(2)
29
+ self.min_power_spin.setLocale(QLocale(QLocale.English))
30
+
31
+
32
+ self.max_power_spin = QDoubleSpinBox()
33
+ self.max_power_spin.setRange(self.min_power,self.max_power)
34
+ self.max_power_spin.setValue(self.max_power)
35
+ self.max_power_spin.setSingleStep(0.01)
36
+ self.max_power_spin.setDecimals(2)
37
+ self.max_power_spin.setLocale(QLocale(QLocale.English))
38
+
39
+
40
+ self.min_frequency_spin = QDoubleSpinBox()
41
+ self.min_frequency_spin.setRange(self.min_frequency,self.max_frequency)
42
+ self.min_frequency_spin.setValue(self.min_frequency)
43
+ self.min_frequency_spin.setSingleStep(0.01)
44
+ self.min_frequency_spin.setDecimals(2)
45
+ self.min_frequency_spin.setLocale(QLocale(QLocale.English))
46
+
47
+
48
+ self.max_frequency_spin = QDoubleSpinBox()
49
+ self.max_frequency_spin.setRange(self.min_frequency,self.max_frequency)
50
+ self.max_frequency_spin.setValue(self.max_frequency)
51
+ self.max_frequency_spin.setSingleStep(0.01)
52
+ self.max_frequency_spin.setDecimals(2)
53
+ self.max_frequency_spin.setLocale(QLocale(QLocale.English))
54
+
55
+
56
+ self.power_label = QLabel("Power:")
57
+ self.min_power_label = QLabel("Min:",alignment=(Qt.AlignmentFlag.AlignRight|Qt.AlignmentFlag.AlignCenter))
58
+ self.max_power_label = QLabel("Max:",alignment=(Qt.AlignmentFlag.AlignRight|Qt.AlignmentFlag.AlignCenter))
59
+
60
+ self.frequency_label = QLabel("Frequency:")
61
+ self.min_frequency_label = QLabel("Min:",alignment=(Qt.AlignmentFlag.AlignRight|Qt.AlignmentFlag.AlignCenter))
62
+ self.max_frequency_label = QLabel("Max:",alignment=(Qt.AlignmentFlag.AlignRight|Qt.AlignmentFlag.AlignCenter))
63
+
64
+ self.error_message = QLabel("")
65
+ self.error_message.setStyleSheet("QLabel { color : red}")
66
+
67
+ self.button = QPushButton("Search")
68
+
69
+ layout.addWidget(self.power_label,0,0)
70
+ layout.addItem(QSpacerItem(200, 0, QSizePolicy.Fixed, QSizePolicy.Minimum))
71
+ layout.addWidget(self.min_power_label,0,1)
72
+ layout.addWidget(self.min_power_spin,0,2)
73
+ layout.addWidget(self.max_power_label,0,3)
74
+ layout.addWidget(self.max_power_spin,0,4)
75
+
76
+ layout.addWidget(self.frequency_label,1,0)
77
+ layout.addItem(QSpacerItem(200, 0, QSizePolicy.Fixed, QSizePolicy.Minimum))
78
+ layout.addWidget(self.min_frequency_label,1,1)
79
+ layout.addWidget(self.min_frequency_spin,1,2)
80
+ layout.addWidget(self.max_frequency_label,1,3)
81
+ layout.addWidget(self.max_frequency_spin,1,4)
82
+
83
+
84
+ layout.addWidget(self.error_message)
85
+ layout.addWidget(self.button,2,0)
86
+
87
+
88
+ self.button.clicked.connect(self.search_database)
89
+
90
+ def search_database(self):
91
+ min_power = self.min_power_spin.value()
92
+ max_power = self.max_power_spin.value()
93
+ min_frequency = self.min_frequency_spin.value()
94
+ max_frequency = self.max_frequency_spin.value()
95
+
96
+ if min_frequency > max_frequency or min_power > max_power:
97
+ self.error_message.setText("min values can't be larger than max values !")
98
+
99
+ else:
100
+ self.error_message.setText("")
101
+ result = requests.get(f'http://127.0.0.1:5000/search/{min_power}/{max_power}/{min_frequency}/{max_frequency}')
102
+
103
+ if result:
104
+ signal_manager.data_signal.emit(result.json())
105
+ show_query_results(result.json())
106
+ else:
107
+ self.error_message.setText("No results !")
108
+
109
+
File without changes
@@ -0,0 +1,135 @@
1
+ from PySide6 import QtWidgets
2
+ from PySide6.QtWidgets import QMainWindow, QVBoxLayout, QHBoxLayout, QWidget
3
+ from rfmetadata.widgets.search_layout import SearchGroupBox
4
+ from rfmetadata.widgets.data_graph_layout import DataGraph3D
5
+ from PySide6.QtGui import QAction, QActionGroup
6
+ from rfmetadata.signal_manager.data_signal_manager import graph_type_manager, table_type_manager
7
+
8
+ from rfmetadata.widgets.query_result_layout import TableResultsWidget
9
+
10
+ class MainWindow(QMainWindow):
11
+ def __init__(self):
12
+ super().__init__()
13
+
14
+ self.result_window= "in_window"
15
+
16
+ self.setup_ui()
17
+
18
+ def setup_ui(self):
19
+ central_widget = QWidget()
20
+ self.setCentralWidget(central_widget)
21
+
22
+ layout = QVBoxLayout(central_widget)
23
+
24
+ self.search = SearchGroupBox("Select minimum and maximum values")
25
+ self.search.setMaximumHeight(200)
26
+ layout.addWidget(self.search)
27
+
28
+ self.graph = DataGraph3D()
29
+ self.graph.setMinimumWidth(410)
30
+ self.graph.setMinimumHeight(300)
31
+
32
+ self.table = TableResultsWidget()
33
+ self.table.setMinimumWidth(500)
34
+
35
+ result_widget = QWidget()
36
+ result_widget_layout = QHBoxLayout(result_widget)
37
+
38
+ if self.result_window == "in_window":
39
+ result_widget_layout.addWidget(self.table)
40
+
41
+ result_widget_layout.addWidget(self.graph)
42
+
43
+ layout.addWidget(result_widget)
44
+
45
+
46
+ self.setWindowTitle("RF Sink Viewer")
47
+ self.resize(800, 600)
48
+
49
+ self.create_menu()
50
+
51
+
52
+ def create_menu(self):
53
+ global menubar
54
+ menubar = self.menuBar()
55
+
56
+ file_menu = menubar.addMenu("File")
57
+
58
+ exit_action = QAction("Exit", self)
59
+ exit_action.triggered.connect(self.close)
60
+ file_menu.addAction(exit_action)
61
+
62
+ help_menu = menubar.addMenu("Help")
63
+
64
+ about_action = QAction("About", self)
65
+ about_action.triggered.connect(self._show_about)
66
+ help_menu.addAction(about_action)
67
+
68
+
69
+ # parrent menu
70
+ tool_menu = menubar.addMenu("Tools")
71
+ select_graph_menu = tool_menu.addMenu("Graph")
72
+
73
+ graph_group = QActionGroup(self)
74
+ graph_group.setExclusive(True)
75
+
76
+ graph_option_a = QAction("3D Scatter Plot", self, checkable=True)
77
+ graph_option_a.setChecked(True)
78
+ graph_option_a.triggered.connect(self.set_scatter)
79
+
80
+ graph_option_b = QAction("3D Mesh Grid", self, checkable=True)
81
+ graph_option_b.triggered.connect(self.set_meshgrid)
82
+
83
+ graph_option_c = QAction("2D Line", self, checkable=True)
84
+ graph_option_c.triggered.connect(self.set_line)
85
+
86
+ graph_group.addAction(graph_option_a)
87
+ graph_group.addAction(graph_option_b)
88
+ graph_group.addAction(graph_option_c)
89
+
90
+ select_graph_menu.addAction(graph_option_a)
91
+ select_graph_menu.addAction(graph_option_b)
92
+ select_graph_menu.addAction(graph_option_c)
93
+
94
+ select_table_menu = tool_menu.addMenu("Table view")
95
+
96
+ table_group = QActionGroup(self)
97
+ table_group.setExclusive(True)
98
+
99
+ table_option_a = QAction("In Window", self, checkable=True)
100
+ table_option_a.setChecked(True)
101
+ table_option_a.triggered.connect(self.set_in_window)
102
+
103
+ table_option_b = QAction("New Window", self, checkable=True)
104
+ table_option_b.triggered.connect(self.set_new_window)
105
+
106
+ table_group.addAction(table_option_a)
107
+ table_group.addAction(table_option_b)
108
+
109
+ select_table_menu.addAction(table_option_a)
110
+ select_table_menu.addAction(table_option_b)
111
+
112
+
113
+ def _show_about(self):
114
+ QtWidgets.QMessageBox.about(self, "About", "RF Sink Viewer using PySide6\nTips:\n1-")
115
+
116
+ def set_scatter(self):
117
+ graph_type_manager.data_signal.emit("scatter")
118
+
119
+ def set_meshgrid(self):
120
+ graph_type_manager.data_signal.emit("meshgrid")
121
+
122
+ def set_line(self):
123
+ graph_type_manager.data_signal.emit("line")
124
+
125
+ def set_in_window(self):
126
+ self.result_window = "in_window"
127
+ table_type_manager.data_signal.emit(False)
128
+ menubar.clear()
129
+ self.setup_ui()
130
+
131
+ def set_new_window(self):
132
+ self.result_window = "new_window"
133
+ table_type_manager.data_signal.emit(True)
134
+ menubar.clear()
135
+ self.setup_ui()
@@ -0,0 +1,56 @@
1
+ from PySide6.QtWidgets import QMainWindow, QTableWidget, QTableWidgetItem
2
+ from PySide6.QtCore import Qt
3
+ from rfmetadata.signal_manager.data_signal_manager import table_type_manager
4
+
5
+ global window_type
6
+ window_type = False
7
+
8
+ class TableResultsWindow(QMainWindow):
9
+ def __init__(self, query):
10
+ super().__init__()
11
+ self.setWindowTitle("Query Results")
12
+
13
+ # Create table view
14
+ self.table = QTableWidget()
15
+ self.table.setColumnCount(4)
16
+ self.table.setRowCount(len(query))
17
+ self.table.setHorizontalHeaderLabels(["ID", "frequency", "power", "datetime"])
18
+
19
+
20
+ for row, row_data in enumerate(query):
21
+ for col, data in enumerate(row_data):
22
+ item = QTableWidgetItem(str(data))
23
+ item.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
24
+ self.table.setItem(row, col, item)
25
+
26
+
27
+
28
+ # Configure table
29
+ self.table.setAlternatingRowColors(True)
30
+ self.table.horizontalHeader().setStretchLastSection(True)
31
+ self.table.resizeColumnsToContents()
32
+
33
+ self.setCentralWidget(self.table)
34
+ self.resize(800, 600)
35
+
36
+ table_type_manager.data_signal.connect(self.set_type)
37
+
38
+ def set_type(self, type):
39
+ global window_type
40
+ window_type = type
41
+
42
+ # Usage:
43
+ def show_query_results(query):
44
+
45
+ global window_type
46
+
47
+ global result_window
48
+ result_window = TableResultsWindow(query)
49
+
50
+ if window_type:
51
+
52
+ result_window.show()
53
+ else:
54
+ pass
55
+
56
+
@@ -0,0 +1,2 @@
1
+ # Authors
2
+ **Holm Consulting**