ItIf 0.9__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.
- ItIf-0.9/ItIf/ItIf.py +599 -0
- ItIf-0.9/ItIf/__init__.py +1 -0
- ItIf-0.9/ItIf.egg-info/PKG-INFO +23 -0
- ItIf-0.9/ItIf.egg-info/SOURCES.txt +11 -0
- ItIf-0.9/ItIf.egg-info/dependency_links.txt +1 -0
- ItIf-0.9/ItIf.egg-info/entry_points.txt +2 -0
- ItIf-0.9/ItIf.egg-info/requires.txt +4 -0
- ItIf-0.9/ItIf.egg-info/top_level.txt +1 -0
- ItIf-0.9/LICENSE +18 -0
- ItIf-0.9/PKG-INFO +23 -0
- ItIf-0.9/README.md +10 -0
- ItIf-0.9/pyproject.toml +23 -0
- ItIf-0.9/setup.cfg +4 -0
ItIf-0.9/ItIf/ItIf.py
ADDED
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
# Author: Luis Bonah
|
|
5
|
+
# Description: Program to create CSV files for the AWG
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
import json
|
|
11
|
+
import traceback as tb
|
|
12
|
+
import numpy as np
|
|
13
|
+
import pandas as pd
|
|
14
|
+
import threading
|
|
15
|
+
import matplotlib
|
|
16
|
+
from matplotlib import gridspec, figure
|
|
17
|
+
from matplotlib.backends.backend_qtagg import FigureCanvas, NavigationToolbar2QT
|
|
18
|
+
|
|
19
|
+
from PyQt6.QtCore import *
|
|
20
|
+
from PyQt6.QtWidgets import *
|
|
21
|
+
from PyQt6.QtGui import *
|
|
22
|
+
|
|
23
|
+
QLocale.setDefault(QLocale("en_EN"))
|
|
24
|
+
|
|
25
|
+
homefolder = os.path.join(os.path.expanduser("~"), ".itif")
|
|
26
|
+
if not os.path.isdir(homefolder):
|
|
27
|
+
os.mkdir(homefolder)
|
|
28
|
+
|
|
29
|
+
OPTIONFILE = os.path.join(homefolder, "default.json")
|
|
30
|
+
PYQT_MAX = 2147483647
|
|
31
|
+
|
|
32
|
+
### Functions for the FFT conversion
|
|
33
|
+
def zeropadding(xs, ys):
|
|
34
|
+
if not len(xs):
|
|
35
|
+
return([], [])
|
|
36
|
+
num_add = int(2**np.ceil(np.log2(len(xs)))-len(xs))
|
|
37
|
+
dt = xs[1] - xs[0] if len(xs) > 1 else 0
|
|
38
|
+
xs = np.pad(xs, (0, num_add), "linear_ramp", end_values=(0, xs[-1] + num_add*dt))
|
|
39
|
+
ys = np.pad(ys, (0, num_add), "constant", constant_values=(0, 0))
|
|
40
|
+
return(xs, ys)
|
|
41
|
+
|
|
42
|
+
def calc_range(ys, margin=0.1):
|
|
43
|
+
if not len(ys):
|
|
44
|
+
return((0, 1))
|
|
45
|
+
ymin = np.min(ys)
|
|
46
|
+
ymax = np.max(ys)
|
|
47
|
+
ydiff = ymax-ymin
|
|
48
|
+
|
|
49
|
+
return((ymin-margin*ydiff, ymax+margin*ydiff))
|
|
50
|
+
|
|
51
|
+
WINDOWFUNCTIONS = ("hanning", "blackman", "hamming", "bartlett", "boxcar")
|
|
52
|
+
def calc_window(windowtype, ys):
|
|
53
|
+
functions = {
|
|
54
|
+
"hanning": np.hanning,
|
|
55
|
+
"blackman": np.blackman,
|
|
56
|
+
"hamming": np.hamming,
|
|
57
|
+
"bartlett": np.bartlett,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if windowtype in functions:
|
|
61
|
+
return(functions[windowtype](len(ys)))
|
|
62
|
+
|
|
63
|
+
return(np.ones(len(ys)))
|
|
64
|
+
|
|
65
|
+
def calc_fft(data, config):
|
|
66
|
+
if data is None:
|
|
67
|
+
return([], [], [], [])
|
|
68
|
+
|
|
69
|
+
time_xs, time_ys = data[:, 0], data[:, 1]
|
|
70
|
+
fft_min, fft_max = config["windowstart"], config["windowstop"]
|
|
71
|
+
|
|
72
|
+
mask = (time_xs > fft_min) & (time_xs < fft_max)
|
|
73
|
+
xs, ys = time_xs[mask], time_ys[mask]
|
|
74
|
+
|
|
75
|
+
if config["zeropad"]:
|
|
76
|
+
xs, ys = zeropadding(xs, ys)
|
|
77
|
+
|
|
78
|
+
window = calc_window(config["windowfunction"], ys)
|
|
79
|
+
|
|
80
|
+
N = len(xs)
|
|
81
|
+
if N:
|
|
82
|
+
spec_xs = np.fft.fftfreq(N, (min(xs)-max(xs))/N)
|
|
83
|
+
spec_ys = np.fft.fft(ys*window)
|
|
84
|
+
|
|
85
|
+
# Only positive frequencies
|
|
86
|
+
mask = (spec_xs > 0)
|
|
87
|
+
spec_xs = spec_xs[mask]
|
|
88
|
+
spec_ys = abs(spec_ys[mask])
|
|
89
|
+
else:
|
|
90
|
+
spec_xs = []
|
|
91
|
+
spec_ys = []
|
|
92
|
+
|
|
93
|
+
return(time_xs, time_ys, spec_xs, spec_ys)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
### GUI
|
|
97
|
+
class QSpinBox(QSpinBox):
|
|
98
|
+
def __init__(self, *args, **kwargs):
|
|
99
|
+
super().__init__(*args, **kwargs)
|
|
100
|
+
self.setRange(0, PYQT_MAX)
|
|
101
|
+
|
|
102
|
+
def setRange(self, min, max):
|
|
103
|
+
min = min if not min is None else -np.inf
|
|
104
|
+
max = max if not max is None else +np.inf
|
|
105
|
+
return super().setRange(min, max)
|
|
106
|
+
|
|
107
|
+
class QDoubleSpinBox(QDoubleSpinBox):
|
|
108
|
+
def __init__(self, *args, **kwargs):
|
|
109
|
+
super().__init__(*args, **kwargs)
|
|
110
|
+
self.setDecimals(20)
|
|
111
|
+
self.setRange(0, PYQT_MAX)
|
|
112
|
+
|
|
113
|
+
try:
|
|
114
|
+
self.setStepType(QAbstractSpinBox.StepType.AdaptiveDecimalStepType)
|
|
115
|
+
except:
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
def textFromValue(self, value):
|
|
119
|
+
return(f"{value:.10f}".rstrip("0").rstrip("."))
|
|
120
|
+
|
|
121
|
+
def valueFromText(self, text):
|
|
122
|
+
return(np.float64(text))
|
|
123
|
+
|
|
124
|
+
def setRange(self, min, max):
|
|
125
|
+
min = min if not min is None else -np.inf
|
|
126
|
+
max = max if not max is None else +np.inf
|
|
127
|
+
return super().setRange(min, max)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class MainWindow(QMainWindow):
|
|
132
|
+
updateconfig = pyqtSignal(tuple)
|
|
133
|
+
redrawplot = pyqtSignal()
|
|
134
|
+
|
|
135
|
+
def __init__(self, parent=None):
|
|
136
|
+
global config
|
|
137
|
+
|
|
138
|
+
super().__init__(parent)
|
|
139
|
+
self.setAcceptDrops(True)
|
|
140
|
+
self.timer = None
|
|
141
|
+
self.update_data_thread = None
|
|
142
|
+
self.update_data_lock = threading.RLock()
|
|
143
|
+
|
|
144
|
+
self.data = None
|
|
145
|
+
self.spec_data = None
|
|
146
|
+
self.fname = None
|
|
147
|
+
|
|
148
|
+
config = self.config = Config(self.updateconfig, {
|
|
149
|
+
"savefigure_kwargs": {
|
|
150
|
+
"dpi": 600,
|
|
151
|
+
},
|
|
152
|
+
"readfile_kwargs": {
|
|
153
|
+
"usecols": (3, 4),
|
|
154
|
+
"skip_header": 6,
|
|
155
|
+
"delimiter": ",",
|
|
156
|
+
},
|
|
157
|
+
"savefile_kwargs": {
|
|
158
|
+
"delimiter": "\t",
|
|
159
|
+
},
|
|
160
|
+
"mpltoolbar": True,
|
|
161
|
+
"windowfunction": WINDOWFUNCTIONS[0],
|
|
162
|
+
"windowstart": 0,
|
|
163
|
+
"windowstop": 0,
|
|
164
|
+
"zeropad": True,
|
|
165
|
+
"rescale": True,
|
|
166
|
+
"asksavename": False,
|
|
167
|
+
"xvaluesunit": 1E6,
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
self.gui()
|
|
171
|
+
self.setWindowTitle("Time Signal to Frequency Spectrum")
|
|
172
|
+
self.readoptions(OPTIONFILE, ignore=True)
|
|
173
|
+
self.show()
|
|
174
|
+
|
|
175
|
+
def dragEnterEvent(self, event):
|
|
176
|
+
if event.mimeData().hasUrls():
|
|
177
|
+
event.accept()
|
|
178
|
+
else:
|
|
179
|
+
event.ignore()
|
|
180
|
+
|
|
181
|
+
def dropEvent(self, event):
|
|
182
|
+
files = [url.toLocalFile() for url in event.mimeData().urls()]
|
|
183
|
+
|
|
184
|
+
option_files = []
|
|
185
|
+
data_files = []
|
|
186
|
+
|
|
187
|
+
for file in files:
|
|
188
|
+
if file.endswith(".json"):
|
|
189
|
+
option_files.append(file)
|
|
190
|
+
else:
|
|
191
|
+
data_files.append(file)
|
|
192
|
+
|
|
193
|
+
if len(data_files) == 1:
|
|
194
|
+
self.open_file(data_files[0])
|
|
195
|
+
elif len(data_files) > 1:
|
|
196
|
+
self.notification("Only drop a single data file.")
|
|
197
|
+
|
|
198
|
+
if len(option_files) == 1:
|
|
199
|
+
self.readoptions(option_files[0])
|
|
200
|
+
elif len(option_files) > 1:
|
|
201
|
+
self.notification("Only drop a single option file.")
|
|
202
|
+
|
|
203
|
+
def gui_menu(self):
|
|
204
|
+
filemenu = self.menuBar().addMenu(f"File")
|
|
205
|
+
|
|
206
|
+
filemenu.addAction(QQ(QAction, parent=self, text="&Open File", shortcut="Ctrl+O", tooltip="Open a new file", change=lambda x: self.open_file()))
|
|
207
|
+
filemenu.addAction(QQ(QAction, parent=self, text="&Save File", shortcut="Ctrl+S", tooltip="Save data to file", change=lambda x: self.save_file()))
|
|
208
|
+
filemenu.addSeparator()
|
|
209
|
+
filemenu.addAction(QQ(QAction, parent=self, text="&Load Options", tooltip="Load option file", change=lambda x: self.readoptions()))
|
|
210
|
+
filemenu.addAction(QQ(QAction, parent=self, text="&Save Options", tooltip="Save options to file", change=lambda x: self.saveoptions()))
|
|
211
|
+
filemenu.addSeparator()
|
|
212
|
+
filemenu.addAction(QQ(QAction, parent=self, text="&Save as default", tooltip="Save options as default options", change=lambda x: self.saveoptions(OPTIONFILE)))
|
|
213
|
+
|
|
214
|
+
actionmenu = self.menuBar().addMenu(f"Actions")
|
|
215
|
+
actionmenu.addAction(QQ(QAction, parent=self, text="&Reset", shortcut="Ctrl+R", tooltip="Reset the plots and span selection", change=lambda x: self.onreset()))
|
|
216
|
+
actionmenu.addSeparator()
|
|
217
|
+
actionmenu.addAction(QQ(QAction, "mpltoolbar", parent=self, text="&Show MPL Toolbar", tooltip="Show or hide matplotlib toolbar", checkable=True))
|
|
218
|
+
actionmenu.addSeparator()
|
|
219
|
+
actionmenu.addAction(QQ(QAction, parent=self, text="&Save Figure", tooltip="Save the figure", change=lambda x: self.savefigure()))
|
|
220
|
+
|
|
221
|
+
def gui(self):
|
|
222
|
+
self.gui_menu()
|
|
223
|
+
|
|
224
|
+
layout = QVBoxLayout()
|
|
225
|
+
|
|
226
|
+
self.fig = figure.Figure()
|
|
227
|
+
gs = gridspec.GridSpec(4, 1, height_ratios = [0.25, 1, 0.5, 1], hspace = 0, wspace=0)
|
|
228
|
+
self.plotcanvas = FigureCanvas(self.fig)
|
|
229
|
+
self.plotcanvas.setMinimumHeight(200)
|
|
230
|
+
self.plotcanvas.setMinimumWidth(200)
|
|
231
|
+
layout.addWidget(self.plotcanvas)
|
|
232
|
+
|
|
233
|
+
self.config.register(["windowfunction", "windowstart", "windowstop", "zeropad"], self.update_data)
|
|
234
|
+
self.redrawplot.connect(self.fig.canvas.draw_idle)
|
|
235
|
+
|
|
236
|
+
self.title_ax = self.fig.add_subplot(gs[0, :])
|
|
237
|
+
self.title_ax.axis("off")
|
|
238
|
+
self.title_ax.set_title("Press 'Open File' to load data")
|
|
239
|
+
|
|
240
|
+
self.ax0 = self.fig.add_subplot(gs[1, :])
|
|
241
|
+
self.timesignal_line = self.ax0.plot([], [], color="#FF0266", label="Time series")[0]
|
|
242
|
+
self.ax0.legend(loc = "upper right")
|
|
243
|
+
self.span = matplotlib.widgets.SpanSelector(self.ax0, self.onselect, interactive=True, drag_from_anywhere=True, direction="horizontal")
|
|
244
|
+
self.config.register(["windowstart", "windowstop"], self.setspan)
|
|
245
|
+
|
|
246
|
+
tmp_ax = self.fig.add_subplot(gs[2, :])
|
|
247
|
+
tmp_ax.axis("off")
|
|
248
|
+
|
|
249
|
+
self.ax1 = self.fig.add_subplot(gs[3, :])
|
|
250
|
+
self.spectrum_line = self.ax1.plot([], [], color="#0336FF", label="Frequency Spectrum")[0]
|
|
251
|
+
self.ax1.legend(loc = "upper right")
|
|
252
|
+
|
|
253
|
+
self.mpltoolbar = NavigationToolbar2QT(self.plotcanvas, self)
|
|
254
|
+
self.mpltoolbar.setVisible(self.config["mpltoolbar"])
|
|
255
|
+
self.config.register("mpltoolbar", lambda: self.mpltoolbar.setVisible(self.config["mpltoolbar"]))
|
|
256
|
+
self.addToolBar(self.mpltoolbar)
|
|
257
|
+
|
|
258
|
+
self.notificationarea = QLabel()
|
|
259
|
+
self.notificationarea.setWordWrap(True)
|
|
260
|
+
self.notificationarea.setHidden(True)
|
|
261
|
+
layout.addWidget(self.notificationarea)
|
|
262
|
+
|
|
263
|
+
button_layout = QGridLayout()
|
|
264
|
+
button_layout.setColumnStretch(2, 2)
|
|
265
|
+
|
|
266
|
+
button_layout.addWidget(QLabel("Window Function: "), 0, 0)
|
|
267
|
+
button_layout.addWidget(QQ(QComboBox, "windowfunction", options=WINDOWFUNCTIONS), 0, 1)
|
|
268
|
+
|
|
269
|
+
button_layout.addWidget(QLabel("Window Start: "), 1, 0)
|
|
270
|
+
button_layout.addWidget(QQ(QDoubleSpinBox, "windowstart", range=(None, None)), 1, 1)
|
|
271
|
+
|
|
272
|
+
button_layout.addWidget(QLabel("Window Stop: "), 2, 0)
|
|
273
|
+
button_layout.addWidget(QQ(QDoubleSpinBox, "windowstop", range=(None, None)), 2, 1)
|
|
274
|
+
|
|
275
|
+
button_layout.addWidget(QQ(QCheckBox, "zeropad", text="Zeropad"), 0, 2)
|
|
276
|
+
button_layout.addWidget(QQ(QCheckBox, "rescale", text="Rescale"), 1, 2)
|
|
277
|
+
|
|
278
|
+
button_layout.addWidget(QQ(QPushButton, text="Reset", change=self.onreset), 0, 3)
|
|
279
|
+
button_layout.addWidget(QQ(QPushButton, text="Open File", change=self.open_file), 1, 3)
|
|
280
|
+
button_layout.addWidget(QQ(QPushButton, text="Save File", change=self.save_file), 2, 3)
|
|
281
|
+
|
|
282
|
+
layout.addLayout(button_layout)
|
|
283
|
+
|
|
284
|
+
widget = QWidget()
|
|
285
|
+
self.setCentralWidget(widget)
|
|
286
|
+
widget.setLayout(layout)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def open_file(self, fname=None):
|
|
290
|
+
if fname is None:
|
|
291
|
+
fname, _ = QFileDialog.getOpenFileName(None, 'Choose File to open',"")
|
|
292
|
+
if not fname:
|
|
293
|
+
self.notification("No file was selected. Keeping current data.")
|
|
294
|
+
return
|
|
295
|
+
|
|
296
|
+
self.data = np.genfromtxt(fname, **self.config["readfile_kwargs"])
|
|
297
|
+
self.fname = fname
|
|
298
|
+
self.update_data()
|
|
299
|
+
|
|
300
|
+
def save_file(self, fname=None):
|
|
301
|
+
if self.data is None:
|
|
302
|
+
self.notification("No data to save.")
|
|
303
|
+
return
|
|
304
|
+
|
|
305
|
+
if self.config["asksavename"]:
|
|
306
|
+
fname, _ = QFileDialog.getSaveFileName(None, 'Choose File to Save to',"")
|
|
307
|
+
else:
|
|
308
|
+
fname = self.fname
|
|
309
|
+
|
|
310
|
+
if not fname:
|
|
311
|
+
self.notification("No filename specified for saving.")
|
|
312
|
+
return
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
data = self.spec_data.copy()
|
|
316
|
+
data[:,0] /= self.config["xvaluesunit"]
|
|
317
|
+
|
|
318
|
+
header = f"Window: {self.config['windowfunction']} from {self.config['windowstart']} to {self.config['windowstop']}\nZeropadding: {self.config['zeropad']}"
|
|
319
|
+
basename, extension = os.path.splitext(fname)
|
|
320
|
+
np.savetxt(basename + "FFT" + extension, data, header=header, **self.config["savefile_kwargs"])
|
|
321
|
+
self.notification(f"Saved data successfully to '{fname}'")
|
|
322
|
+
|
|
323
|
+
def notification(self, text):
|
|
324
|
+
self.notificationarea.setText(text)
|
|
325
|
+
self.notificationarea.setHidden(False)
|
|
326
|
+
|
|
327
|
+
if self.timer:
|
|
328
|
+
self.timer.stop()
|
|
329
|
+
self.timer = QTimer(self)
|
|
330
|
+
self.timer.setSingleShot(True)
|
|
331
|
+
self.timer.timeout.connect(lambda: self.notificationarea.setHidden(True))
|
|
332
|
+
self.timer.start(5000)
|
|
333
|
+
|
|
334
|
+
def saveoptions(self, filename=None):
|
|
335
|
+
if filename is None:
|
|
336
|
+
filename, _ = QFileDialog.getSaveFileName(self, "Save options to file")
|
|
337
|
+
if not filename:
|
|
338
|
+
return
|
|
339
|
+
|
|
340
|
+
with open(filename, "w+") as optionfile:
|
|
341
|
+
json.dump(self.config, optionfile, indent=2)
|
|
342
|
+
self.notification("Options have been saved")
|
|
343
|
+
|
|
344
|
+
def readoptions(self, filename=None, ignore=False):
|
|
345
|
+
if filename is None:
|
|
346
|
+
filename, _ = QFileDialog.getOpenFileName(self, "Read options from file")
|
|
347
|
+
if not filename:
|
|
348
|
+
return
|
|
349
|
+
|
|
350
|
+
if not os.path.isfile(filename):
|
|
351
|
+
if not ignore:
|
|
352
|
+
self.notification(f"Option file '{filename}' does not exist.")
|
|
353
|
+
return
|
|
354
|
+
|
|
355
|
+
with open(filename, "r") as optionfile:
|
|
356
|
+
values = json.load(optionfile)
|
|
357
|
+
self.config.update(values)
|
|
358
|
+
self.notification("Options have been loaded")
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def savefigure(self):
|
|
362
|
+
fname, _ = QFileDialog.getSaveFileName(None, 'Choose File to Save to',"")
|
|
363
|
+
if not fname:
|
|
364
|
+
return
|
|
365
|
+
|
|
366
|
+
self.fig.savefig(fname, **config["savefigure_kwargs"])
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def update_data(self, force_rescale=False):
|
|
370
|
+
thread = threading.Thread(target=self.update_data_core, args=(force_rescale, ))
|
|
371
|
+
with self.update_data_lock:
|
|
372
|
+
thread.start()
|
|
373
|
+
self.update_data_thread = thread.ident
|
|
374
|
+
return(thread)
|
|
375
|
+
|
|
376
|
+
def update_data_core(self, force_rescale):
|
|
377
|
+
with self.update_data_lock:
|
|
378
|
+
ownid = threading.current_thread().ident
|
|
379
|
+
|
|
380
|
+
try:
|
|
381
|
+
breakpoint(ownid, self.update_data_thread)
|
|
382
|
+
|
|
383
|
+
xs, ys, spec_xs, spec_ys = calc_fft(self.data, self.config)
|
|
384
|
+
breakpoint(ownid, self.update_data_thread)
|
|
385
|
+
self.spec_data = np.vstack((spec_xs, spec_ys)).T
|
|
386
|
+
self.timesignal_line.set_data(xs, ys)
|
|
387
|
+
self.spectrum_line.set_data(spec_xs, spec_ys)
|
|
388
|
+
breakpoint(ownid, self.update_data_thread)
|
|
389
|
+
|
|
390
|
+
if config["rescale"] or force_rescale:
|
|
391
|
+
self.ax0.set_xlim(calc_range(xs, margin=0))
|
|
392
|
+
self.ax0.set_ylim(calc_range(ys))
|
|
393
|
+
|
|
394
|
+
self.ax1.set_xlim(calc_range(spec_xs, margin=0))
|
|
395
|
+
self.ax1.set_ylim(calc_range(spec_ys))
|
|
396
|
+
breakpoint(ownid, self.update_data_thread)
|
|
397
|
+
|
|
398
|
+
if self.fname:
|
|
399
|
+
self.title_ax.set_title(f"{os.path.basename(self.fname)}", ha="center")
|
|
400
|
+
else:
|
|
401
|
+
self.title_ax.set_title("Press 'Open File' to load data", ha="center")
|
|
402
|
+
breakpoint(ownid, self.update_data_thread)
|
|
403
|
+
|
|
404
|
+
self.redrawplot.emit()
|
|
405
|
+
except BreakpointError as E:
|
|
406
|
+
pass
|
|
407
|
+
|
|
408
|
+
def onselect(self, xmin, xmax):
|
|
409
|
+
self.config["windowstart"] = xmin
|
|
410
|
+
self.config["windowstop"] = xmax
|
|
411
|
+
|
|
412
|
+
def setspan(self):
|
|
413
|
+
self.span.extents = self.config["windowstart"], self.config["windowstop"]
|
|
414
|
+
|
|
415
|
+
def onreset(self):
|
|
416
|
+
self.span.set_visible(False)
|
|
417
|
+
self.span.onselect(0, 0)
|
|
418
|
+
self.update_data(force_rescale=True)
|
|
419
|
+
|
|
420
|
+
class Config(dict):
|
|
421
|
+
def __init__(self, signal, init_values={}):
|
|
422
|
+
super().__init__(init_values)
|
|
423
|
+
self.signal = signal
|
|
424
|
+
self.signal.connect(self.callback)
|
|
425
|
+
self.callbacks = pd.DataFrame(columns=["id", "key", "widget", "function"], dtype="object").astype({"id": np.uint})
|
|
426
|
+
|
|
427
|
+
def __setitem__(self, key, value, widget=None):
|
|
428
|
+
super().__setitem__(key, value)
|
|
429
|
+
self.signal.emit((key, value, widget))
|
|
430
|
+
|
|
431
|
+
def callback(self, args):
|
|
432
|
+
key, value, widget = args
|
|
433
|
+
if widget:
|
|
434
|
+
callbacks_widget = self.callbacks.query(f"key == @key and widget != @widget")
|
|
435
|
+
else:
|
|
436
|
+
callbacks_widget = self.callbacks.query(f"key == @key")
|
|
437
|
+
for i, row in callbacks_widget.iterrows():
|
|
438
|
+
row["function"]()
|
|
439
|
+
|
|
440
|
+
def register(self, keys, function):
|
|
441
|
+
if not isinstance(keys, (tuple, list)):
|
|
442
|
+
keys = [keys]
|
|
443
|
+
for key in keys:
|
|
444
|
+
id = 0
|
|
445
|
+
df = self.callbacks
|
|
446
|
+
df.loc[len(df), ["id", "key", "function"]] = id, key, function
|
|
447
|
+
|
|
448
|
+
def register_widget(self, key, widget, function):
|
|
449
|
+
ids = set(self.callbacks["id"])
|
|
450
|
+
id = 1
|
|
451
|
+
while id in ids:
|
|
452
|
+
id += 1
|
|
453
|
+
df = self.callbacks
|
|
454
|
+
df.loc[len(df), ["id", "key", "function", "widget"]] = id, key, function, widget
|
|
455
|
+
widget.destroyed.connect(lambda x, id=id: self.unregister_widget(id))
|
|
456
|
+
|
|
457
|
+
def unregister_widget(self, id):
|
|
458
|
+
self.callbacks.drop(self.callbacks[self.callbacks["id"] == id].index, inplace=True)
|
|
459
|
+
|
|
460
|
+
def QQ(widgetclass, config_key=None, **kwargs):
|
|
461
|
+
widget = widgetclass()
|
|
462
|
+
|
|
463
|
+
if "range" in kwargs:
|
|
464
|
+
widget.setRange(*kwargs["range"])
|
|
465
|
+
if "maxWidth" in kwargs:
|
|
466
|
+
widget.setMaximumWidth(kwargs["maxWidth"])
|
|
467
|
+
if "maxHeight" in kwargs:
|
|
468
|
+
widget.setMaximumHeight(kwargs["maxHeight"])
|
|
469
|
+
if "minWidth" in kwargs:
|
|
470
|
+
widget.setMinimumWidth(kwargs["minWidth"])
|
|
471
|
+
if "minHeight" in kwargs:
|
|
472
|
+
widget.setMinimumHeight(kwargs["minHeight"])
|
|
473
|
+
if "color" in kwargs:
|
|
474
|
+
widget.setColor(kwargs["color"])
|
|
475
|
+
if "text" in kwargs:
|
|
476
|
+
widget.setText(kwargs["text"])
|
|
477
|
+
if "options" in kwargs:
|
|
478
|
+
options = kwargs["options"]
|
|
479
|
+
if isinstance(options, dict):
|
|
480
|
+
for key, value in options.items():
|
|
481
|
+
widget.addItem(key, value)
|
|
482
|
+
else:
|
|
483
|
+
for option in kwargs["options"]:
|
|
484
|
+
widget.addItem(option)
|
|
485
|
+
if "width" in kwargs:
|
|
486
|
+
widget.setFixedWidth(kwargs["width"])
|
|
487
|
+
if "tooltip" in kwargs:
|
|
488
|
+
widget.setToolTip(kwargs["tooltip"])
|
|
489
|
+
if "placeholder" in kwargs:
|
|
490
|
+
widget.setPlaceholderText(kwargs["placeholder"])
|
|
491
|
+
if "singlestep" in kwargs:
|
|
492
|
+
widget.setSingleStep(kwargs["singlestep"])
|
|
493
|
+
if "wordwrap" in kwargs:
|
|
494
|
+
widget.setWordWrap(kwargs["wordwrap"])
|
|
495
|
+
if "align" in kwargs:
|
|
496
|
+
widget.setAlignment(kwargs["align"])
|
|
497
|
+
if "rowCount" in kwargs:
|
|
498
|
+
widget.setRowCount(kwargs["rowCount"])
|
|
499
|
+
if "columnCount" in kwargs:
|
|
500
|
+
widget.setColumnCount(kwargs["columnCount"])
|
|
501
|
+
if "move" in kwargs:
|
|
502
|
+
widget.move(*kwargs["move"])
|
|
503
|
+
if "default" in kwargs:
|
|
504
|
+
widget.setDefault(kwargs["default"])
|
|
505
|
+
if "textFormat" in kwargs:
|
|
506
|
+
widget.setTextFormat(kwargs["textFormat"])
|
|
507
|
+
if "checkable" in kwargs:
|
|
508
|
+
widget.setCheckable(kwargs["checkable"])
|
|
509
|
+
if "shortcut" in kwargs:
|
|
510
|
+
widget.setShortcut(kwargs["shortcut"])
|
|
511
|
+
if "parent" in kwargs:
|
|
512
|
+
widget.setParent(kwargs["parent"])
|
|
513
|
+
if "completer" in kwargs:
|
|
514
|
+
widget.setCompleter(kwargs["completer"])
|
|
515
|
+
if "hidden" in kwargs:
|
|
516
|
+
widget.setHidden(kwargs["hidden"])
|
|
517
|
+
if "visible" in kwargs:
|
|
518
|
+
widget.setVisible(kwargs["visible"])
|
|
519
|
+
if "stylesheet" in kwargs:
|
|
520
|
+
widget.setStyleSheet(kwargs["stylesheet"])
|
|
521
|
+
if "enabled" in kwargs:
|
|
522
|
+
widget.setEnabled(kwargs["enabled"])
|
|
523
|
+
if "items" in kwargs:
|
|
524
|
+
for item in kwargs["items"]:
|
|
525
|
+
widget.addItem(item)
|
|
526
|
+
if "readonly" in kwargs:
|
|
527
|
+
widget.setReadOnly(kwargs["readonly"])
|
|
528
|
+
if "prefix" in kwargs:
|
|
529
|
+
widget.setPrefix(kwargs["prefix"])
|
|
530
|
+
|
|
531
|
+
if widgetclass in [QSpinBox, QDoubleSpinBox]:
|
|
532
|
+
setter = widget.setValue
|
|
533
|
+
changer = widget.valueChanged.connect
|
|
534
|
+
getter = widget.value
|
|
535
|
+
elif widgetclass == QCheckBox:
|
|
536
|
+
setter = widget.setChecked
|
|
537
|
+
changer = widget.stateChanged.connect
|
|
538
|
+
getter = widget.isChecked
|
|
539
|
+
elif widgetclass == QPlainTextEdit:
|
|
540
|
+
setter = widget.setPlainText
|
|
541
|
+
changer = widget.textChanged.connect
|
|
542
|
+
getter = widget.toPlainText
|
|
543
|
+
elif widgetclass == QLineEdit:
|
|
544
|
+
setter = widget.setText
|
|
545
|
+
changer = widget.textChanged.connect
|
|
546
|
+
getter = widget.text
|
|
547
|
+
elif widgetclass == QAction:
|
|
548
|
+
setter = widget.setChecked
|
|
549
|
+
changer = widget.triggered.connect
|
|
550
|
+
getter = widget.isChecked
|
|
551
|
+
elif widgetclass == QPushButton:
|
|
552
|
+
setter = widget.setDefault
|
|
553
|
+
changer = widget.clicked.connect
|
|
554
|
+
getter = widget.isDefault
|
|
555
|
+
elif widgetclass == QToolButton:
|
|
556
|
+
setter = widget.setChecked
|
|
557
|
+
changer = widget.clicked.connect
|
|
558
|
+
getter = widget.isChecked
|
|
559
|
+
elif widgetclass == QComboBox:
|
|
560
|
+
setter = widget.setCurrentText
|
|
561
|
+
changer = widget.currentTextChanged.connect
|
|
562
|
+
getter = widget.currentText
|
|
563
|
+
else:
|
|
564
|
+
return widget
|
|
565
|
+
|
|
566
|
+
if "value" in kwargs:
|
|
567
|
+
setter(kwargs["value"])
|
|
568
|
+
if config_key:
|
|
569
|
+
setter(config[config_key])
|
|
570
|
+
changer(lambda x=None, key=config_key: config.__setitem__(key, getter(), widget))
|
|
571
|
+
config.register_widget(config_key, widget, lambda: setter(config[config_key]))
|
|
572
|
+
if "change" in kwargs:
|
|
573
|
+
changer(kwargs["change"])
|
|
574
|
+
if "changes" in kwargs:
|
|
575
|
+
for change in kwargs["changes"]:
|
|
576
|
+
changer(change)
|
|
577
|
+
|
|
578
|
+
return widget
|
|
579
|
+
|
|
580
|
+
class BreakpointError(Exception):
|
|
581
|
+
pass
|
|
582
|
+
|
|
583
|
+
def breakpoint(ownid, lastid):
|
|
584
|
+
if ownid != lastid:
|
|
585
|
+
raise BreakpointError()
|
|
586
|
+
|
|
587
|
+
def except_hook(cls, exception, traceback):
|
|
588
|
+
sys.__excepthook__(cls, exception, traceback)
|
|
589
|
+
window.notification(f"{exception}\n{''.join(tb.format_tb(traceback))}")
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
def start():
|
|
593
|
+
sys.excepthook = except_hook
|
|
594
|
+
app = QApplication(sys.argv)
|
|
595
|
+
window = MainWindow()
|
|
596
|
+
sys.exit(app.exec())
|
|
597
|
+
|
|
598
|
+
if __name__ == '__main__':
|
|
599
|
+
start()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .ItIf import *
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: ItIf
|
|
3
|
+
Version: 0.9
|
|
4
|
+
Summary: ItIf provides a convenient and interactive way to transform time signal to frequncy spectra via FFT
|
|
5
|
+
Author-email: Luis Bonah <bonah@ph1.uni-koeln.de>
|
|
6
|
+
Keywords: FFT,Spectroscopy
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.7
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
|
|
14
|
+
# I(t) to I(f)
|
|
15
|
+
|
|
16
|
+
This package provides an graphical user interface to convert a timesignel (I(t)) to a frequency spectrum (I(f)) via Fast Fourier transform (FFT).
|
|
17
|
+
Different window functions can be applied, the window position and duration can be changed interactively and zeropadding can be applied.
|
|
18
|
+
|
|
19
|
+
The default options are chosen to fit our usecase in the [Labastro Group Cologne](https://astro.uni-koeln.de/schlemmer).
|
|
20
|
+
If your dataformats are different, please modify the default options file accordingly.
|
|
21
|
+
To do this, start the program and save the default values via *File > Save as default*. This creates the file "~/.itif./default.json".
|
|
22
|
+
|
|
23
|
+
This file is in simple json format and can be edited with your editor of choice.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ItIf
|
ItIf-0.9/LICENSE
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Copyright 2023 Luis Bonah
|
|
2
|
+
|
|
3
|
+
As this programs GUI is based on PyQt6, which is GNU GPL v3 licensed, this program is also licensed under GNU GPL v3 (See the bottom paragraph).
|
|
4
|
+
|
|
5
|
+
Copyright (C) 2020
|
|
6
|
+
|
|
7
|
+
This program is free software: you can redistribute it and/or modify
|
|
8
|
+
it under the terms of the GNU General Public License as published by
|
|
9
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
10
|
+
(at your option) any later version.
|
|
11
|
+
|
|
12
|
+
This program is distributed in the hope that it will be useful,
|
|
13
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
14
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
15
|
+
GNU General Public License for more details.
|
|
16
|
+
|
|
17
|
+
You should have received a copy of the GNU General Public License
|
|
18
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
ItIf-0.9/PKG-INFO
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: ItIf
|
|
3
|
+
Version: 0.9
|
|
4
|
+
Summary: ItIf provides a convenient and interactive way to transform time signal to frequncy spectra via FFT
|
|
5
|
+
Author-email: Luis Bonah <bonah@ph1.uni-koeln.de>
|
|
6
|
+
Keywords: FFT,Spectroscopy
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Requires-Python: >=3.7
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
|
|
14
|
+
# I(t) to I(f)
|
|
15
|
+
|
|
16
|
+
This package provides an graphical user interface to convert a timesignel (I(t)) to a frequency spectrum (I(f)) via Fast Fourier transform (FFT).
|
|
17
|
+
Different window functions can be applied, the window position and duration can be changed interactively and zeropadding can be applied.
|
|
18
|
+
|
|
19
|
+
The default options are chosen to fit our usecase in the [Labastro Group Cologne](https://astro.uni-koeln.de/schlemmer).
|
|
20
|
+
If your dataformats are different, please modify the default options file accordingly.
|
|
21
|
+
To do this, start the program and save the default values via *File > Save as default*. This creates the file "~/.itif./default.json".
|
|
22
|
+
|
|
23
|
+
This file is in simple json format and can be edited with your editor of choice.
|
ItIf-0.9/README.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# I(t) to I(f)
|
|
2
|
+
|
|
3
|
+
This package provides an graphical user interface to convert a timesignel (I(t)) to a frequency spectrum (I(f)) via Fast Fourier transform (FFT).
|
|
4
|
+
Different window functions can be applied, the window position and duration can be changed interactively and zeropadding can be applied.
|
|
5
|
+
|
|
6
|
+
The default options are chosen to fit our usecase in the [Labastro Group Cologne](https://astro.uni-koeln.de/schlemmer).
|
|
7
|
+
If your dataformats are different, please modify the default options file accordingly.
|
|
8
|
+
To do this, start the program and save the default values via *File > Save as default*. This creates the file "~/.itif./default.json".
|
|
9
|
+
|
|
10
|
+
This file is in simple json format and can be edited with your editor of choice.
|
ItIf-0.9/pyproject.toml
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ItIf"
|
|
7
|
+
version = "0.9"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name="Luis Bonah", email="bonah@ph1.uni-koeln.de" },
|
|
10
|
+
]
|
|
11
|
+
description = "ItIf provides a convenient and interactive way to transform time signal to frequncy spectra via FFT"
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
requires-python = ">=3.7"
|
|
14
|
+
dependencies = ['numpy', 'pandas', 'matplotlib', 'PyQt6']
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
|
18
|
+
"Operating System :: OS Independent",
|
|
19
|
+
]
|
|
20
|
+
keywords = ["FFT", "Spectroscopy"]
|
|
21
|
+
|
|
22
|
+
[project.scripts]
|
|
23
|
+
ItIf = "ItIf:start"
|
ItIf-0.9/setup.cfg
ADDED