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 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,11 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ ItIf/ItIf.py
5
+ ItIf/__init__.py
6
+ ItIf.egg-info/PKG-INFO
7
+ ItIf.egg-info/SOURCES.txt
8
+ ItIf.egg-info/dependency_links.txt
9
+ ItIf.egg-info/entry_points.txt
10
+ ItIf.egg-info/requires.txt
11
+ ItIf.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ItIf = ItIf:start
@@ -0,0 +1,4 @@
1
+ numpy
2
+ pandas
3
+ matplotlib
4
+ PyQt6
@@ -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.
@@ -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
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+