barsukov 1.3.3__tar.gz → 1.3.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.
Potentially problematic release.
This version of barsukov might be problematic. Click here for more details.
- {barsukov-1.3.3/src/barsukov.egg-info → barsukov-1.3.4}/PKG-INFO +6 -2
- {barsukov-1.3.3 → barsukov-1.3.4}/README.md +5 -1
- {barsukov-1.3.3 → barsukov-1.3.4}/src/barsukov/__init__.py +1 -4
- barsukov-1.3.4/src/barsukov/app/lock_in_emulator_app.py +293 -0
- barsukov-1.3.4/src/barsukov/data/Change_phase.py +160 -0
- barsukov-1.3.4/src/barsukov/data/Lock_in_emulator.py +183 -0
- barsukov-1.3.4/src/barsukov/data/__init__.py +5 -0
- barsukov-1.3.4/src/barsukov/data/constants.py +10 -0
- barsukov-1.3.4/src/barsukov/data/fft.py +132 -0
- barsukov-1.3.4/src/barsukov/data/noise.py +276 -0
- barsukov-1.3.4/src/barsukov/exp/__init__.py +3 -0
- {barsukov-1.3.3 → barsukov-1.3.4}/src/barsukov/exp/mwHP.py +11 -2
- barsukov-1.3.4/src/barsukov/exp/smKE.py +148 -0
- {barsukov-1.3.3 → barsukov-1.3.4}/src/barsukov/logger.py +4 -3
- {barsukov-1.3.3 → barsukov-1.3.4}/src/barsukov/script.py +16 -4
- {barsukov-1.3.3 → barsukov-1.3.4}/src/barsukov/time.py +1 -1
- {barsukov-1.3.3 → barsukov-1.3.4/src/barsukov.egg-info}/PKG-INFO +6 -2
- {barsukov-1.3.3 → barsukov-1.3.4}/src/barsukov.egg-info/SOURCES.txt +7 -1
- barsukov-1.3.3/src/barsukov/data/__init__.py +0 -1
- barsukov-1.3.3/src/barsukov/data/fft.py +0 -87
- barsukov-1.3.3/src/barsukov/exp/__init__.py +0 -1
- {barsukov-1.3.3 → barsukov-1.3.4}/.github/workflows/versioning.yml +0 -0
- {barsukov-1.3.3 → barsukov-1.3.4}/.gitignore +0 -0
- {barsukov-1.3.3 → barsukov-1.3.4}/MANIFEST.in +0 -0
- {barsukov-1.3.3 → barsukov-1.3.4}/pyproject.toml +0 -0
- {barsukov-1.3.3 → barsukov-1.3.4}/setup.cfg +0 -0
- {barsukov-1.3.3 → barsukov-1.3.4}/src/barsukov/exp/exp_utils.py +0 -0
- {barsukov-1.3.3 → barsukov-1.3.4}/src/barsukov/obj2file.py +0 -0
- {barsukov-1.3.3 → barsukov-1.3.4}/src/barsukov.egg-info/dependency_links.txt +0 -0
- {barsukov-1.3.3 → barsukov-1.3.4}/src/barsukov.egg-info/requires.txt +0 -0
- {barsukov-1.3.3 → barsukov-1.3.4}/src/barsukov.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: barsukov
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.4
|
|
4
4
|
Summary: Experiment Automation Package
|
|
5
5
|
Author-email: Igor Barsukov <igorb@ucr.edu>, Steven Castaneda <scast206@ucr.edu>
|
|
6
6
|
Project-URL: Homepage, https://barsukov.ucr.edu
|
|
@@ -16,6 +16,10 @@ Requires-Dist: scipy>=0.9.0
|
|
|
16
16
|
|
|
17
17
|
Barsukov is a Python library for experiment automation.
|
|
18
18
|
|
|
19
|
+
## For Developers
|
|
20
|
+
|
|
21
|
+
To push to PyPi, commit to main on Github Desktop, click the history tab, right click and create tag, format the tag v\*.\*.\* and push to the repository (ctrl+p).
|
|
22
|
+
|
|
19
23
|
## Installation
|
|
20
24
|
|
|
21
25
|
Use the package manager [pip](https://pip.pypa.io/en/stable/) to install barsukov.
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Barsukov is a Python library for experiment automation.
|
|
4
4
|
|
|
5
|
+
## For Developers
|
|
6
|
+
|
|
7
|
+
To push to PyPi, commit to main on Github Desktop, click the history tab, right click and create tag, format the tag v\*.\*.\* and push to the repository (ctrl+p).
|
|
8
|
+
|
|
5
9
|
## Installation
|
|
6
10
|
|
|
7
11
|
Use the package manager [pip](https://pip.pypa.io/en/stable/) to install barsukov.
|
|
@@ -30,4 +34,4 @@ pip install barsukov
|
|
|
30
34
|
|
|
31
35
|
## License
|
|
32
36
|
|
|
33
|
-
[MIT](https://choosealicense.com/licenses/mit/)
|
|
37
|
+
[MIT](https://choosealicense.com/licenses/mit/)
|
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
# Modules:
|
|
2
2
|
from . import time
|
|
3
3
|
from . import data
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
|
|
6
6
|
# Objects/Functions:
|
|
7
7
|
from .script import Script
|
|
8
8
|
from .logger import Logger
|
|
9
|
-
|
|
10
9
|
from .obj2file import *
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
# Equipment Objects:
|
|
14
13
|
from .exp.mwHP import mwHP
|
|
15
14
|
|
|
16
|
-
__all__ = ["time", "data", "save_object", "load_object", "Script", "Logger", "mwHP"]
|
|
17
|
-
|
|
18
15
|
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
from PyQt5 import QtWidgets
|
|
2
|
+
import pyqtgraph as pg
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
from barsukov.data import Lock_in_emulator, noise
|
|
6
|
+
|
|
7
|
+
def make_lorentzian(center, width, amp):
|
|
8
|
+
def l(x):
|
|
9
|
+
return amp / ((x - center)**2 + width**2)
|
|
10
|
+
return l
|
|
11
|
+
|
|
12
|
+
def make_gaussian(center, width, amp):
|
|
13
|
+
def g(x):
|
|
14
|
+
return amp / ((x - center)**2 + width**2)
|
|
15
|
+
return g
|
|
16
|
+
|
|
17
|
+
params = [
|
|
18
|
+
("l_center", 2, "Center:", float),
|
|
19
|
+
("l_width", 1, "Width:", float),
|
|
20
|
+
("l_amp", 1e-6, "Amplitude:", float),
|
|
21
|
+
("g_center", 4, "Center:", float),
|
|
22
|
+
("g_width", 2, "Width:", float),
|
|
23
|
+
("g_amp", 2e-6, "Amplitude:", float),
|
|
24
|
+
("jT", 300, "Temperature (K):", float),
|
|
25
|
+
("jR", 200, "Resistance (Ohms):", float),
|
|
26
|
+
("sI", 1e-3, "Current (Amps):", float),
|
|
27
|
+
("sR", 200, "Resistance (Ohms):", float),
|
|
28
|
+
("oRMS", 1e-7, "RMS:", float),
|
|
29
|
+
("rTU", 0.01, "Tau Up (s):", float),
|
|
30
|
+
("rTD", 0.01, "Tau Down (s):", float),
|
|
31
|
+
("rSU", 1e-7, "State Up:", float),
|
|
32
|
+
("rSD", 1e-7, "State Down:", float),
|
|
33
|
+
("bD", 64, "Bit Depth (bits):", int),
|
|
34
|
+
("bMIN", -21, "Minimum Measurement:", float),
|
|
35
|
+
("bMAX", 21, "Maximum Measurement:", float),
|
|
36
|
+
("xstart_input", 0, "X Start:", float),
|
|
37
|
+
("plotpoints_input", 500, "# Plot Points:", int),
|
|
38
|
+
("time_input", 120, "Sweep Time (s):", float),
|
|
39
|
+
("xstop_input", 10, "X Stop:", float),
|
|
40
|
+
("xamp_input", 0.2, "Modulation Amp:", float),
|
|
41
|
+
("f_input", 1000, "Modulation Freq (Hz):", float),
|
|
42
|
+
("TC_input", 500e-3, "Time Constant (s):", float),
|
|
43
|
+
("order_input", 4, "Filter Order:", int),
|
|
44
|
+
("dt_input", 1e-4, "Sampling Step (s):", float),
|
|
45
|
+
("buffersize_input", 10000, "Buffer Size:", int),
|
|
46
|
+
("phase_input", 0, "Phase Offset (deg):", float),
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
class MyWindow(QtWidgets.QWidget):
|
|
50
|
+
def __init__(self):
|
|
51
|
+
super().__init__()
|
|
52
|
+
self.setWindowTitle("Lock-in Amplifier Emulator")
|
|
53
|
+
self.resize(1000, 800)
|
|
54
|
+
|
|
55
|
+
#Window Area (horizontal)
|
|
56
|
+
main_layout = QtWidgets.QHBoxLayout(self)
|
|
57
|
+
self.inputs = {} # (widget, cast, method)
|
|
58
|
+
self.line_edits = {}
|
|
59
|
+
|
|
60
|
+
#Interactive Input Box Setup
|
|
61
|
+
for name, default, label, cast in params:
|
|
62
|
+
le = QtWidgets.QLineEdit(str(default))
|
|
63
|
+
#le.editingFinished.connect(self.update_plot)
|
|
64
|
+
self.line_edits[name] = le
|
|
65
|
+
self.inputs[name] = (le, lambda w=le, c=cast: c(w.text()))
|
|
66
|
+
|
|
67
|
+
#Left Area:
|
|
68
|
+
left_layout = QtWidgets.QVBoxLayout()
|
|
69
|
+
main_layout.addLayout(left_layout, 1)
|
|
70
|
+
|
|
71
|
+
# Signal Type Selector
|
|
72
|
+
signal_combo = QtWidgets.QComboBox()
|
|
73
|
+
signal_combo.addItems(["Lorentzian", "Gaussian"])
|
|
74
|
+
#signal_combo.currentIndexChanged.connect(self.update_plot)
|
|
75
|
+
self.inputs["signal_type"] = (signal_combo, lambda w: str(w.currentText()))
|
|
76
|
+
left_layout.addWidget(QtWidgets.QLabel("<b>Signal Type:</b>"))
|
|
77
|
+
left_layout.addWidget(signal_combo)
|
|
78
|
+
|
|
79
|
+
# Signal Inputs Stack
|
|
80
|
+
signal_stack = QtWidgets.QStackedWidget()
|
|
81
|
+
left_layout.addWidget(signal_stack)
|
|
82
|
+
|
|
83
|
+
#Lorentzian Inputs
|
|
84
|
+
lorentz_widget = QtWidgets.QWidget()
|
|
85
|
+
lorentz_layout = QtWidgets.QFormLayout(lorentz_widget)
|
|
86
|
+
lorentz_layout.addRow("<b>Center:</b>", self.line_edits["l_center"])
|
|
87
|
+
lorentz_layout.addRow("<b>Width:</b>", self.line_edits["l_width"])
|
|
88
|
+
lorentz_layout.addRow("<b>Amplitude:</b>", self.line_edits["l_amp"])
|
|
89
|
+
signal_stack.addWidget(lorentz_widget)
|
|
90
|
+
|
|
91
|
+
#Guassian Inputs
|
|
92
|
+
gauss_widget = QtWidgets.QWidget()
|
|
93
|
+
gauss_layout = QtWidgets.QFormLayout(gauss_widget)
|
|
94
|
+
gauss_layout.addRow("<b>Center:</b>", self.line_edits["g_center"])
|
|
95
|
+
gauss_layout.addRow("<b>Width:</b>", self.line_edits["g_width"])
|
|
96
|
+
gauss_layout.addRow("<b>Amplitude:</b>", self.line_edits["g_amp"])
|
|
97
|
+
signal_stack.addWidget(gauss_widget)
|
|
98
|
+
|
|
99
|
+
signal_combo.currentIndexChanged.connect(signal_stack.setCurrentIndex)
|
|
100
|
+
|
|
101
|
+
#Noise Options:
|
|
102
|
+
left_layout.addWidget(QtWidgets.QLabel("<b>Noise Options:</b>"))
|
|
103
|
+
|
|
104
|
+
noises = [
|
|
105
|
+
("Johnson Noise", "Temperature (K):,jT", "Resistance (Ohms):,jR"),
|
|
106
|
+
("Shot Noise", "Current (Amps):,sI", "Resistance (Ohms):,sR"),
|
|
107
|
+
("1/f Noise", "RMS:,oRMS"),
|
|
108
|
+
("Random Telegraph Noise", "Tau Up (s):,rTU", "Tau Down (s):,rTD", "State Up:,rSU", "State Down:,rSD"),
|
|
109
|
+
("Bit Noise", "Bit Depth (bits):,bD", "Minimum Measurement:,bMIN", "Maximum Measurement:,bMAX")
|
|
110
|
+
]
|
|
111
|
+
|
|
112
|
+
for noise in noises:
|
|
113
|
+
#Noise Checkbox
|
|
114
|
+
cb = QtWidgets.QCheckBox(noise[0])
|
|
115
|
+
self.inputs[noise[0]] = (cb, lambda w=cb: w.isChecked())
|
|
116
|
+
left_layout.addWidget(cb)
|
|
117
|
+
|
|
118
|
+
# Group of Noise Inputs
|
|
119
|
+
group = QtWidgets.QGroupBox(noise[0] + " Settings")
|
|
120
|
+
group.setCheckable(False)
|
|
121
|
+
group.setVisible(False)
|
|
122
|
+
form = QtWidgets.QFormLayout(group)
|
|
123
|
+
for p in noise[1:]:
|
|
124
|
+
label, name = p.split(",")
|
|
125
|
+
form.addRow("<b>"+label+"</b>", self.line_edits[name])
|
|
126
|
+
left_layout.addWidget(group)
|
|
127
|
+
|
|
128
|
+
cb.toggled.connect(group.setVisible)
|
|
129
|
+
#cb.toggled.connect(self.update_plot)
|
|
130
|
+
|
|
131
|
+
left_layout.addStretch(1)
|
|
132
|
+
|
|
133
|
+
# Simulate Button
|
|
134
|
+
simulate_button = QtWidgets.QPushButton("Simulate")
|
|
135
|
+
simulate_button.clicked.connect(self.update_plot)
|
|
136
|
+
left_layout.addWidget(simulate_button)
|
|
137
|
+
|
|
138
|
+
# Reset Button
|
|
139
|
+
reset_button = QtWidgets.QPushButton("Reset")
|
|
140
|
+
reset_button.clicked.connect(self.reset_fields)
|
|
141
|
+
left_layout.addWidget(reset_button)
|
|
142
|
+
|
|
143
|
+
# User Notes
|
|
144
|
+
left_layout.addWidget(QtWidgets.QLabel("<b>User Notes:</b>"))
|
|
145
|
+
user_notes = QtWidgets.QLabel()
|
|
146
|
+
user_notes.setText("- SweepTime ≥ SamplingStep*BufferSize*#PlotPoints\n"
|
|
147
|
+
"- Runs well up to 50 million calculations\n"
|
|
148
|
+
" EX: 500 PlotPoints * 100000 BufferSize")
|
|
149
|
+
left_layout.addWidget(user_notes)
|
|
150
|
+
|
|
151
|
+
#Right Area (vertical) - right side of window
|
|
152
|
+
right_layout = QtWidgets.QVBoxLayout()
|
|
153
|
+
main_layout.addLayout(right_layout, 4)
|
|
154
|
+
|
|
155
|
+
#Graph 1:
|
|
156
|
+
plot1 = pg.PlotWidget(title="Original Signal vs X", background='w')
|
|
157
|
+
legend1 = plot1.addLegend()
|
|
158
|
+
legend1.anchor((1,0), (1,0))
|
|
159
|
+
right_layout.addWidget(plot1)
|
|
160
|
+
|
|
161
|
+
#Input Area 1:
|
|
162
|
+
input_layout1 = QtWidgets.QGridLayout()
|
|
163
|
+
cols = 4
|
|
164
|
+
for i, p in enumerate(params[18:22]):
|
|
165
|
+
name, label = p[0], p[2]
|
|
166
|
+
row = i // cols
|
|
167
|
+
col = i % cols
|
|
168
|
+
|
|
169
|
+
h_layout = QtWidgets.QHBoxLayout()
|
|
170
|
+
h_layout.addWidget(QtWidgets.QLabel("<b>"+label+"</b>"))
|
|
171
|
+
h_layout.addWidget(self.line_edits[name])
|
|
172
|
+
input_layout1.addLayout(h_layout, row, col)
|
|
173
|
+
right_layout.addLayout(input_layout1)
|
|
174
|
+
|
|
175
|
+
#Fit Results:
|
|
176
|
+
result_layout = QtWidgets.QHBoxLayout()
|
|
177
|
+
self.result_text = QtWidgets.QLabel()
|
|
178
|
+
result_layout.addWidget(self.result_text)
|
|
179
|
+
result_layout.addStretch(1)
|
|
180
|
+
|
|
181
|
+
#Label:
|
|
182
|
+
output2expected = QtWidgets.QLabel()
|
|
183
|
+
output2expected.setText("Output ≈ <sup>1</sup>⁄<sub>Diminish</sub> * Expected(<sup>x</sup>⁄<sub>Stretch</sub> - Shift)")
|
|
184
|
+
right_layout.addWidget(output2expected)
|
|
185
|
+
|
|
186
|
+
#Graph 2:
|
|
187
|
+
plot2 = pg.PlotWidget(title="Demodulated Signal vs X", background='w')
|
|
188
|
+
legend2 = plot2.addLegend()
|
|
189
|
+
legend2.anchor((1,0), (1,0))
|
|
190
|
+
right_layout.addWidget(plot2)
|
|
191
|
+
|
|
192
|
+
#Input Area 2:
|
|
193
|
+
input_layout2 = QtWidgets.QGridLayout()
|
|
194
|
+
for i, p in enumerate(params[22:]):
|
|
195
|
+
name, label = p[0], p[2]
|
|
196
|
+
row = i // cols
|
|
197
|
+
col = i % cols
|
|
198
|
+
|
|
199
|
+
h_layout = QtWidgets.QHBoxLayout()
|
|
200
|
+
h_layout.addWidget(QtWidgets.QLabel("<b>"+label+"</b>"))
|
|
201
|
+
h_layout.addWidget(self.line_edits[name])
|
|
202
|
+
input_layout2.addLayout(h_layout, row, col)
|
|
203
|
+
right_layout.addLayout(input_layout2)
|
|
204
|
+
|
|
205
|
+
#Plot Curve Initialization:
|
|
206
|
+
self.curve_orig = plot1.plot(pen=pg.mkPen(color='r', width=2), name="Original Signal")
|
|
207
|
+
self.curve_out = plot2.plot(pen=pg.mkPen(color='b', width=2), name="Output Signal")
|
|
208
|
+
self.curve_expected = plot2.plot(pen=pg.mkPen(color='r', width=2), name="Expected Signal")
|
|
209
|
+
self.curve_adjusted = plot2.plot(pen=pg.mkPen(color='g', width=2), name="Adjusted Signal")
|
|
210
|
+
|
|
211
|
+
#Show Curves Options:
|
|
212
|
+
curve_params = [ ("Output", self.curve_out), ("Expected", self.curve_expected), ("Adjusted", self.curve_adjusted) ]
|
|
213
|
+
for name, curve in curve_params:
|
|
214
|
+
cb = QtWidgets.QCheckBox(f"Show {name} Signal")
|
|
215
|
+
cb.setChecked(True)
|
|
216
|
+
cb.stateChanged.connect(lambda state, c=curve, box=cb: c.setVisible(box.isChecked()))
|
|
217
|
+
result_layout.addWidget(cb)
|
|
218
|
+
right_layout.insertLayout(2, result_layout)
|
|
219
|
+
|
|
220
|
+
self.update_plot()
|
|
221
|
+
|
|
222
|
+
def update_plot(self):
|
|
223
|
+
try:
|
|
224
|
+
# Read input v
|
|
225
|
+
v = {}
|
|
226
|
+
for name, (widget, extract) in self.inputs.items():
|
|
227
|
+
v[name] = extract(widget)
|
|
228
|
+
|
|
229
|
+
#Signal Setup
|
|
230
|
+
signal = 0
|
|
231
|
+
if v["signal_type"] == "Lorentzian":
|
|
232
|
+
signal = make_lorentzian(v["l_center"], v["l_width"], v["l_amp"])
|
|
233
|
+
else:
|
|
234
|
+
signal = make_gaussian(v["g_center"], v["g_width"], v["g_amp"])
|
|
235
|
+
|
|
236
|
+
#Noise Setup
|
|
237
|
+
jT = jR = sI = sR = oRMS = rTU = rTD = rSU = rSD = bD = bMIN = bMAX = 0
|
|
238
|
+
if v["Johnson Noise"]:
|
|
239
|
+
jT, jR = v["jT"], v["jR"]
|
|
240
|
+
if v["Shot Noise"]:
|
|
241
|
+
sI, sR = v["sI"], v["sR"]
|
|
242
|
+
if v["1/f Noise"]:
|
|
243
|
+
oRMS = v["oRMS"]
|
|
244
|
+
if v["Random Telegraph Noise"]:
|
|
245
|
+
rTU, rTD, rSU, rSD = v["rTU"], v["rTD"], v["rSU"], v["rSD"]
|
|
246
|
+
if v["Bit Noise"]:
|
|
247
|
+
bD, bMIN, bMAX = v["bD"], v["bMIN"], v["bMAX"]
|
|
248
|
+
|
|
249
|
+
# Run lock-in emulator
|
|
250
|
+
LI = Lock_in_emulator(
|
|
251
|
+
signal,
|
|
252
|
+
v["f_input"],
|
|
253
|
+
v["phase_input"],
|
|
254
|
+
v["xstart_input"],
|
|
255
|
+
v["xstop_input"],
|
|
256
|
+
v["xamp_input"],
|
|
257
|
+
v["time_input"],
|
|
258
|
+
v["dt_input"],
|
|
259
|
+
v["TC_input"],
|
|
260
|
+
v["order_input"],
|
|
261
|
+
v["plotpoints_input"],
|
|
262
|
+
v["buffersize_input"],
|
|
263
|
+
jT, jR, sI, sR, oRMS, rTU, rTD, rSU, rSD, bD, bMIN, bMAX
|
|
264
|
+
)
|
|
265
|
+
LI.run()
|
|
266
|
+
|
|
267
|
+
# Update plots
|
|
268
|
+
self.curve_orig.setData(LI.x_plot, LI.original_signal)
|
|
269
|
+
self.curve_out.setData(LI.x_plot, LI.output_signal)
|
|
270
|
+
self.curve_expected.setData(LI.x_plot, LI.expected_signal)
|
|
271
|
+
self.curve_adjusted.setData(LI.x_plot, LI.adjusted_signal)
|
|
272
|
+
|
|
273
|
+
# Update results
|
|
274
|
+
self.result_text.setText(f"<b>Diminish:</b> {LI.diminish:.6f}, "
|
|
275
|
+
f"<b>Shift:</b> {LI.shift:.6f}, "
|
|
276
|
+
f"<b>Stretch:</b> {LI.stretch:.6f}, "
|
|
277
|
+
f"<b>SNR</b>: {LI.snr:.6f}")
|
|
278
|
+
|
|
279
|
+
except Exception as e:
|
|
280
|
+
self.result_text.setText(f"Error: {e}")
|
|
281
|
+
|
|
282
|
+
def reset_fields(self):
|
|
283
|
+
for p in params:
|
|
284
|
+
name, default = p[0], p[1]
|
|
285
|
+
self.line_edits[name].clear()
|
|
286
|
+
self.line_edits[name].setText(str(default))
|
|
287
|
+
self.update_plot()
|
|
288
|
+
|
|
289
|
+
if __name__ == "__main__":
|
|
290
|
+
app = QtWidgets.QApplication(sys.argv)
|
|
291
|
+
w = MyWindow()
|
|
292
|
+
w.show()
|
|
293
|
+
sys.exit(app.exec_())
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
from barsukov.data.constants import deg2rad
|
|
2
|
+
from barsukov.time import time_stamp
|
|
3
|
+
|
|
4
|
+
from scipy.optimize import curve_fit
|
|
5
|
+
from scipy.optimize import differential_evolution
|
|
6
|
+
import numpy as np
|
|
7
|
+
import matplotlib.pyplot as plt
|
|
8
|
+
|
|
9
|
+
import glob
|
|
10
|
+
import sys
|
|
11
|
+
import os
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Change_phase:
|
|
15
|
+
### the phase you receive from auto, is a phase shift that you need to add to the phase of the original data.
|
|
16
|
+
### adding the two phases together gives to you the total effective lock-in phase of the calculated data.
|
|
17
|
+
### Note that lock-in phase corresponds to the reference, not to the signal itself.
|
|
18
|
+
### Lock-in phase is an artificial phase delay of the reference
|
|
19
|
+
### new reference is cos(Wt - phase)
|
|
20
|
+
### This script's phase, if added to the original phase of the lock-in, will give you a cumulative phase.
|
|
21
|
+
### The recalculated signal would correspond to lock-in signal if measured with this cumulative phase.
|
|
22
|
+
### This cumulative phase is the phase delay of your signal with respect to the original unaltered reference.
|
|
23
|
+
### The automatically recalculated data is correct only if considered together with the automatically calculated phase
|
|
24
|
+
### This means, you may get positive or negative signals in x-channel. So always consider the cummulative phase when evaluating the data.
|
|
25
|
+
|
|
26
|
+
def __init__(self, x=[], A=[], B=[], initial_phase=0):
|
|
27
|
+
self.x = np.array(x)
|
|
28
|
+
self.A = np.array(A)
|
|
29
|
+
self.B = np.array(B)
|
|
30
|
+
self.initial_phi = initial_phase
|
|
31
|
+
|
|
32
|
+
self.phi = initial_phase
|
|
33
|
+
self.newA = None
|
|
34
|
+
self.newB = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def read_from_file(self, full_file_path, x_column=0, A_column=1, B_column=2, initial_phase=0):
|
|
38
|
+
self.full_file_path = full_file_path
|
|
39
|
+
data = np.loadtxt(self.full_file_path, skiprows=0, unpack=True, usecols=(x_column, A_column, B_column))
|
|
40
|
+
self.x = data[0]
|
|
41
|
+
self.A = data[1]
|
|
42
|
+
self.B = data[2]
|
|
43
|
+
self.initial_phi = initial_phase
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def offset_phase(self, phi=None):
|
|
47
|
+
if phi is not None:
|
|
48
|
+
self.phi = float(phi)
|
|
49
|
+
self.newA = self.A * np.cos(self.phi*deg2rad) + self.B * np.sin(self.phi*deg2rad)
|
|
50
|
+
self.newB = - self.A * np.sin(self.phi*deg2rad) + self.B * np.cos(self.phi*deg2rad)
|
|
51
|
+
else:
|
|
52
|
+
def to_minimize(phi_val):
|
|
53
|
+
self.phi = phi_val[0] #Differential evolution passes arrays
|
|
54
|
+
self.newA = self.A * np.cos(self.phi*deg2rad) + self.B * np.sin(self.phi*deg2rad)
|
|
55
|
+
self.newB = - self.A * np.sin(self.phi*deg2rad) + self.B * np.cos(self.phi*deg2rad)
|
|
56
|
+
popt, pcov = curve_fit(lambda x,a,b: a+b*x, self.x, self.newB, p0=[0,0])
|
|
57
|
+
return 1-(pcov[0,1]/(pcov[0,0]*pcov[1,1]))**2
|
|
58
|
+
|
|
59
|
+
### MINIMIZES the sum of the data in the Y-channel (B)
|
|
60
|
+
result = differential_evolution(to_minimize, bounds=[(0,359.99)], strategy='best1bin')
|
|
61
|
+
if result.success:
|
|
62
|
+
self.offset_phase(phi=result.x[0])
|
|
63
|
+
print(f"Auto-adjusted phase: {result.x[0]} degrees")
|
|
64
|
+
else:
|
|
65
|
+
self.phi = initial_phase
|
|
66
|
+
print("Optimization failed!")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def plot_offset(self):
|
|
70
|
+
if not hasattr(self, "fig") or not hasattr(self, "axes"):
|
|
71
|
+
self.fig, self.axes = plt.subplots(nrows=4, ncols=1, figsize=(12,18))
|
|
72
|
+
|
|
73
|
+
self.lines = [
|
|
74
|
+
self.axes[0].plot(self.x, self.A ,'b-', label='X-')[0],
|
|
75
|
+
self.axes[0].plot(self.x, self.B, 'r-', label='Y-')[0],
|
|
76
|
+
self.axes[1].plot(self.x, self.newA, 'b-', label='X-')[0],
|
|
77
|
+
self.axes[2].plot(self.x, self.newB, 'r-', label='Y-')[0],
|
|
78
|
+
self.axes[3].plot(self.x, self.newA ,'b-', label='X-')[0],
|
|
79
|
+
self.axes[3].plot(self.x, self.newB, 'r-', label='Y-')[0],
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
self.axes[0].set_title('Original X- & Y-')
|
|
83
|
+
self.axes[1].set_title('Adjusted X-')
|
|
84
|
+
self.axes[2].set_title('Adjusted Y-')
|
|
85
|
+
self.axes[3].set_title('Adjusted X- & Y-')
|
|
86
|
+
|
|
87
|
+
for ax in self.axes:
|
|
88
|
+
ax.legend()
|
|
89
|
+
else:
|
|
90
|
+
for line in self.lines:
|
|
91
|
+
line.set_xdata(self.x)
|
|
92
|
+
|
|
93
|
+
self.lines[0].set_ydata(self.A)
|
|
94
|
+
self.lines[1].set_ydata(self.B)
|
|
95
|
+
self.lines[2].set_ydata(self.newA)
|
|
96
|
+
self.lines[3].set_ydata(self.newB)
|
|
97
|
+
self.lines[4].set_ydata(self.newA)
|
|
98
|
+
self.lines[5].set_ydata(self.newB)
|
|
99
|
+
|
|
100
|
+
for ax in self.axes:
|
|
101
|
+
ax.relim()
|
|
102
|
+
ax.autoscale_view()
|
|
103
|
+
|
|
104
|
+
if "IPython" in sys.modules:
|
|
105
|
+
from IPython.display import display
|
|
106
|
+
display(self.fig)
|
|
107
|
+
else:
|
|
108
|
+
plt.ion()
|
|
109
|
+
self.fig.canvas.draw()
|
|
110
|
+
self.fig.canvas.flush_events()
|
|
111
|
+
plt.show()
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def save_data(self, full_folder_path=None, file_name=None):
|
|
115
|
+
if hasattr(self, "full_file_path"):
|
|
116
|
+
full_folder_path, file_name = os.path.split(self.full_file_path)
|
|
117
|
+
file_name = f"Corrected_{round(self.phi)}_{file_name}"
|
|
118
|
+
else:
|
|
119
|
+
if full_folder_path is None:
|
|
120
|
+
full_folder_path = os.getcwd()
|
|
121
|
+
if file_name is None:
|
|
122
|
+
file_name = f"{time_stamp()}_Corrected_Phase_Lock_in_Data"
|
|
123
|
+
|
|
124
|
+
full_folder_path = os.path.join(full_folder_path, 'phase-corrected_data')
|
|
125
|
+
if not os.path.isdir(full_folder_path):
|
|
126
|
+
os.makedirs(full_folder_path)
|
|
127
|
+
|
|
128
|
+
full_file_path = os.path.join(full_folder_path, file_name)
|
|
129
|
+
with open(full_file_path, "w") as file:
|
|
130
|
+
for i in range(len(self.x)):
|
|
131
|
+
file.write(f"{self.x[i]} {self.newA[i]} {self.newB[i]} \n")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def offset_phase_script(self, full_folder_path=None, x_column=0, A_column=1, B_column=2, initial_phase=0, nocheck=False):
|
|
135
|
+
if full_folder_path is None:
|
|
136
|
+
full_folder_path = os.getcwd()
|
|
137
|
+
for file in glob.glob(os.path.join(full_folder_path, '*.txt')):
|
|
138
|
+
self.read_from_file(file, x_column, A_column, B_column, initial_phase)
|
|
139
|
+
|
|
140
|
+
self.offset_phase()
|
|
141
|
+
self.plot_offset()
|
|
142
|
+
|
|
143
|
+
while True:
|
|
144
|
+
if nocheck is False:
|
|
145
|
+
manual_input=input('Enter "auto" to auto-calculate phase, a number/float for manual phase, or press ENTER to skip: ')
|
|
146
|
+
|
|
147
|
+
if manual_input == "auto":
|
|
148
|
+
self.offset_phase()
|
|
149
|
+
self.plot_offset()
|
|
150
|
+
elif manual_input:
|
|
151
|
+
try:
|
|
152
|
+
self.offset_phase(manual_input)
|
|
153
|
+
self.plot_offset()
|
|
154
|
+
except ValueError:
|
|
155
|
+
print(f"Invalid input: {manual_input}. Please enter a valid phase value or 'auto'.")
|
|
156
|
+
if not manual_input:
|
|
157
|
+
print("Phase adjustment completed.")
|
|
158
|
+
break
|
|
159
|
+
self.save_data()
|
|
160
|
+
print("Adjusted phase data saved.")
|