barsukov 1.2.6__tar.gz → 1.3.7__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.

Files changed (41) hide show
  1. {barsukov-1.2.6 → barsukov-1.3.7}/.gitignore +2 -2
  2. barsukov-1.3.7/MANIFEST.in +3 -0
  3. barsukov-1.3.7/PKG-INFO +57 -0
  4. barsukov-1.3.7/README.md +37 -0
  5. barsukov-1.3.7/pyproject.toml +40 -0
  6. {barsukov-1.2.6 → barsukov-1.3.7}/src/barsukov/__init__.py +1 -4
  7. barsukov-1.3.7/src/barsukov/data/Change_phase.py +160 -0
  8. barsukov-1.3.7/src/barsukov/data/Lock_in_emulator.py +175 -0
  9. barsukov-1.3.7/src/barsukov/data/__init__.py +6 -0
  10. barsukov-1.3.7/src/barsukov/data/color_map_app.py +127 -0
  11. barsukov-1.3.7/src/barsukov/data/color_map_core.py +283 -0
  12. barsukov-1.3.7/src/barsukov/data/color_map_widget.py +614 -0
  13. barsukov-1.3.7/src/barsukov/data/constants.py +10 -0
  14. barsukov-1.3.7/src/barsukov/data/fft.py +132 -0
  15. barsukov-1.3.7/src/barsukov/data/lock_in_emulator_app.py +297 -0
  16. barsukov-1.3.7/src/barsukov/data/noise.py +276 -0
  17. barsukov-1.3.7/src/barsukov/exp/__init__.py +3 -0
  18. {barsukov-1.2.6 → barsukov-1.3.7}/src/barsukov/exp/mwHP.py +11 -2
  19. barsukov-1.3.7/src/barsukov/exp/smKE.py +148 -0
  20. {barsukov-1.2.6 → barsukov-1.3.7}/src/barsukov/logger.py +4 -3
  21. {barsukov-1.2.6 → barsukov-1.3.7}/src/barsukov/script.py +16 -4
  22. {barsukov-1.2.6 → barsukov-1.3.7}/src/barsukov/time.py +1 -1
  23. barsukov-1.3.7/src/barsukov.egg-info/PKG-INFO +57 -0
  24. {barsukov-1.2.6 → barsukov-1.3.7}/src/barsukov.egg-info/SOURCES.txt +11 -3
  25. barsukov-1.3.7/src/barsukov.egg-info/requires.txt +8 -0
  26. barsukov-1.2.6/MANIFEST.in +0 -3
  27. barsukov-1.2.6/PKG-INFO +0 -57
  28. barsukov-1.2.6/README.md +0 -33
  29. barsukov-1.2.6/setup.py +0 -30
  30. barsukov-1.2.6/src/Untitled.ipynb +0 -527
  31. barsukov-1.2.6/src/barsukov/data/__init__.py +0 -1
  32. barsukov-1.2.6/src/barsukov/data/fft.py +0 -87
  33. barsukov-1.2.6/src/barsukov/exp/__init__.py +0 -1
  34. barsukov-1.2.6/src/barsukov.egg-info/PKG-INFO +0 -57
  35. barsukov-1.2.6/src/barsukov.egg-info/requires.txt +0 -3
  36. {barsukov-1.2.6 → barsukov-1.3.7}/.github/workflows/versioning.yml +0 -0
  37. {barsukov-1.2.6 → barsukov-1.3.7}/setup.cfg +0 -0
  38. {barsukov-1.2.6 → barsukov-1.3.7}/src/barsukov/exp/exp_utils.py +0 -0
  39. {barsukov-1.2.6 → barsukov-1.3.7}/src/barsukov/obj2file.py +0 -0
  40. {barsukov-1.2.6 → barsukov-1.3.7}/src/barsukov.egg-info/dependency_links.txt +0 -0
  41. {barsukov-1.2.6 → barsukov-1.3.7}/src/barsukov.egg-info/top_level.txt +0 -0
@@ -27,7 +27,6 @@ share/python-wheels/
27
27
  *.egg-info/
28
28
  .installed.cfg
29
29
  *.egg
30
- MANIFEST.in
31
30
 
32
31
  # PyInstaller
33
32
  # Usually these files are written by a python script from a template
@@ -79,6 +78,7 @@ docs/_build/
79
78
  target/
80
79
 
81
80
  # Jupyter Notebook
81
+ *.ipynb
82
82
  .ipynb_checkpoints
83
83
 
84
84
  # IPython
@@ -241,4 +241,4 @@ fabric.properties
241
241
  .idea/httpRequests
242
242
 
243
243
  # Android studio 3.1+ serialized cache file
244
- .idea/caches/build_file_checksums.ser
244
+ .idea/caches/build_file_checksums.ser
@@ -0,0 +1,3 @@
1
+ include README.md
2
+
3
+ recursive-include src *.py
@@ -0,0 +1,57 @@
1
+ Metadata-Version: 2.4
2
+ Name: barsukov
3
+ Version: 1.3.7
4
+ Summary: Experiment Automation Package
5
+ Author-email: Igor Barsukov <igorb@ucr.edu>
6
+ Maintainer-email: Steven Castaneda <scast206@ucr.edu>, Marlon Lopez <mlope589@ucr.edu>
7
+ Project-URL: Homepage, https://barsukov.ucr.edu
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.6
11
+ Description-Content-Type: text/markdown
12
+ Requires-Dist: pytz>=2014.10
13
+ Requires-Dist: numpy>=1.0.0
14
+ Requires-Dist: scipy>=0.9.0
15
+ Requires-Dist: matplotlib>=3.8
16
+ Requires-Dist: sympy>=1.2
17
+ Requires-Dist: PyQt5>=5.15
18
+ Requires-Dist: pyqtgraph>=0.13.7
19
+ Requires-Dist: dill>=0.4.0
20
+
21
+ # Barsukov
22
+
23
+ Barsukov is a Python library for experiment automation.
24
+
25
+ ## For Developers
26
+
27
+ 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).
28
+
29
+ ## Installation
30
+
31
+ Use the package manager [pip](https://pip.pypa.io/en/stable/) to install barsukov.
32
+
33
+ ```bash
34
+ pip install barsukov
35
+ ```
36
+ Dependencies: pytz, dill, numpy, scipy, matplotlib, pyvisa, IPython, PyQt5, pyqtgraph
37
+ ## Usage
38
+
39
+ ```python
40
+ #
41
+ #
42
+ #
43
+ #
44
+ #
45
+ #
46
+ #
47
+ ```
48
+
49
+ ## Contributing
50
+
51
+ -
52
+ -
53
+ -
54
+
55
+ ## License
56
+
57
+ [MIT](https://choosealicense.com/licenses/mit/)
@@ -0,0 +1,37 @@
1
+ # Barsukov
2
+
3
+ Barsukov is a Python library for experiment automation.
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
+
9
+ ## Installation
10
+
11
+ Use the package manager [pip](https://pip.pypa.io/en/stable/) to install barsukov.
12
+
13
+ ```bash
14
+ pip install barsukov
15
+ ```
16
+ Dependencies: pytz, dill, numpy, scipy, matplotlib, pyvisa, IPython, PyQt5, pyqtgraph
17
+ ## Usage
18
+
19
+ ```python
20
+ #
21
+ #
22
+ #
23
+ #
24
+ #
25
+ #
26
+ #
27
+ ```
28
+
29
+ ## Contributing
30
+
31
+ -
32
+ -
33
+ -
34
+
35
+ ## License
36
+
37
+ [MIT](https://choosealicense.com/licenses/mit/)
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["setuptools>=64", "setuptools-scm>=8"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "barsukov"
7
+ dynamic = ["version"]
8
+ dependencies = [
9
+ "pytz>=2014.10",
10
+ "numpy>=1.0.0",
11
+ "scipy>=0.9.0",
12
+ "matplotlib>=3.8",
13
+ "sympy >=1.2",
14
+ "PyQt5 >=5.15",
15
+ "pyqtgraph >=0.13.7",
16
+ "dill >=0.4.0",
17
+ ]
18
+ authors = [
19
+ { name = "Igor Barsukov", email = "igorb@ucr.edu" },
20
+ ]
21
+ maintainers = [
22
+ { name = "Steven Castaneda", email = "scast206@ucr.edu" },
23
+ { name = "Marlon Lopez", email = "mlope589@ucr.edu"},
24
+ ]
25
+ requires-python = ">=3.6"
26
+ description = "Experiment Automation Package"
27
+ readme = "README.md"
28
+ classifiers = [
29
+ "Programming Language :: Python :: 3",
30
+ "Operating System :: OS Independent",
31
+ ]
32
+
33
+ [project.urls]
34
+ Homepage = "https://barsukov.ucr.edu"
35
+ #Repository = <githublink>
36
+
37
+ [tool.setuptools.packages.find]
38
+ where = ["src"]
39
+
40
+ [tool.setuptools_scm]
@@ -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,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.")
@@ -0,0 +1,175 @@
1
+ import numpy as np
2
+ from barsukov.data import noise
3
+
4
+ import sympy as sp
5
+
6
+ class Lock_in_emulator:
7
+ def __init__(self, signal, f, phase, x_start, x_stop, x_amp, time, dt, TC, order, plot_points, buffer_size,
8
+ johnson_T=0, johnson_R=0,
9
+ shot_I=0, shot_R=0,
10
+ onef_rms=0,
11
+ rtn_tau_up=0, rtn_tau_down=0, rtn_state_up=0, rtn_state_down=0,
12
+ bit_depth=0, bit_measure_min=0, bit_measure_max=0):
13
+
14
+ #Signal Properties
15
+ self.signal_arr = signal
16
+ self.f = f
17
+ self.phase = np.pi * phase / 180
18
+ self.x_start = x_start
19
+ self.x_stop = x_stop
20
+ self.x_amp = x_amp
21
+ self.time = abs(time)
22
+
23
+ #Noise Properties
24
+ self.jT, self.jR = johnson_T, johnson_R
25
+ self.sI, self.sR = shot_I, shot_R
26
+ self.oRMS = onef_rms
27
+ self.rTU, self.rTD, self.rSU, self.rSD = rtn_tau_up, rtn_tau_down, rtn_state_up, rtn_state_down
28
+ self.bD, self.bMIN, self.bMAX = bit_depth, bit_measure_min, bit_measure_max
29
+
30
+ #Filter Properties
31
+ self.dt = abs(dt)
32
+ self.TC = TC
33
+ self.n = abs(order)
34
+
35
+
36
+ #Plotting Properties
37
+ self.plot_points = plot_points
38
+ self.buffer_size = buffer_size
39
+ self.buffer_period = self.buffer_size*self.dt
40
+ self.buffer_offset = np.linspace(-self.buffer_period, 0, self.buffer_size)
41
+
42
+ def run(self):
43
+ self.t_plot = np.linspace(self.buffer_period, self.time, self.plot_points)
44
+ self.x_plot = self.x_arr(self.t_plot)
45
+
46
+ self.original_signal = self.signal_arr(self.x_plot)
47
+ self.expected_signal = 0.5 * self.x_amp * np.gradient(self.original_signal, self.x_plot)
48
+ self.output_signal = self.signal_output_arr(self.t_plot)
49
+
50
+ self.fit()
51
+ #self.plot()
52
+
53
+ ### BEGIN: FIELD
54
+ def x_arr(self, t_arr):
55
+ return self.x_start + t_arr * (self.x_stop - self.x_start) / self.time
56
+
57
+ def x_with_mod_arr(self, t_arr):
58
+ return self.x_arr(t_arr) + self.x_amp * np.cos(2 * np.pi * self.f * t_arr)
59
+
60
+
61
+ ### BEGIN: NOISE
62
+ def noise(self, t_arr):
63
+ if self.jT and self.jR: self.johnson = noise.johnson(t_arr, self.jT, self.jR)
64
+ else: self.johnson = np.zeros(len(t_arr))
65
+
66
+ if self.sI and self.sR: self.shot = noise.shot(t_arr, self.sI, self.sR)
67
+ else: self.shot = np.zeros(len(t_arr))
68
+
69
+ if self.oRMS: self.onef = noise.color(t_arr, self.oRMS)
70
+ else: self.onef = np.zeros(len(t_arr))
71
+
72
+ if (self.rTU and self.rTD) or (self.rSU and self.rSD): self.rtn = noise.rtn(t_arr, self.rTU, self.rTD, self.rSU, self.rSD)
73
+ else: self.rtn = np.zeros(len(t_arr))
74
+ return self.johnson + self.shot + self.onef + self.rtn
75
+
76
+
77
+ def lp_filter_arr(self, t_arr):
78
+ t_arr = -(t_arr - t_arr[-1])
79
+ factorial = np.math.factorial(self.n - 1)
80
+ lp_filter_arr = (t_arr ** (self.n - 1)) * np.exp(-t_arr / self.TC) / (self.TC**self.n * factorial)
81
+ return lp_filter_arr / abs(np.sum(lp_filter_arr) * self.dt)
82
+
83
+ def signal_output(self, t):
84
+ t_arr = t + self.buffer_offset
85
+
86
+ x_arr = self.x_with_mod_arr(t_arr)
87
+ s_arr = self.signal_arr(x_arr)
88
+ noise_arr = self.noise(t_arr)
89
+ filter_arr = self.lp_filter_arr(t_arr)
90
+ ref_X = np.cos(2 * np.pi * self.f * t_arr - self.phase)
91
+
92
+ integrand = (s_arr+noise_arr) * ref_X * filter_arr * self.dt
93
+ return np.sum(integrand)
94
+
95
+ def signal_output_arr(self, t_arr):
96
+ output = np.array([self.signal_output(t) for t in t_arr])
97
+
98
+ if self.bD or (self.bMIN and self.bMAX):
99
+ return noise.bit(output, self.bD, self.bMIN, self.bMAX)
100
+ else:
101
+ return output
102
+
103
+
104
+ ### BEGIN: FIT:
105
+ def fit(self):
106
+ from scipy.interpolate import interp1d
107
+ from scipy.optimize import curve_fit
108
+
109
+ interp = interp1d(self.x_plot, self.expected_signal, kind='cubic', fill_value=0.0, bounds_error=False)
110
+
111
+ def model(x, diminish, stretch, shift):
112
+ x_trans = (x / stretch) - shift
113
+ return (1.0 / diminish) * interp(x_trans)
114
+
115
+ expected_max_idx, expected_min_idx = np.argmax(self.expected_signal), np.argmin(self.expected_signal)
116
+ output_max_idx, output_min_idx = np.argmax(self.output_signal), np.argmin(self.output_signal)
117
+
118
+ expected_peak_xdif = np.abs(self.x_plot[expected_max_idx] - self.x_plot[expected_min_idx])
119
+ output_peak_xdif = np.abs(self.x_plot[output_max_idx] - self.x_plot[output_min_idx])
120
+
121
+ initial_stretch = np.abs(output_peak_xdif / expected_peak_xdif)
122
+ initial_diminish = np.ptp(self.expected_signal) / np.ptp(self.output_signal)
123
+
124
+ initial_transform = model(self.x_plot, initial_diminish, initial_stretch, 0)
125
+ transform_max_idx = np.argmax(initial_transform)
126
+
127
+ initial_shift = (self.x_plot[output_max_idx] - self.x_plot[transform_max_idx]) / initial_stretch
128
+
129
+ initial_guess = [np.max([1,initial_diminish]), np.max([1,initial_stretch]), np.max([-np.ptp(self.x_plot),initial_shift])]
130
+
131
+ bounds = [(1, 1, -np.ptp(self.x_plot)), (10, 0.5*np.ptp(self.x_plot), np.ptp(self.x_plot) )]
132
+ popt, _ = curve_fit(model, self.x_plot, self.output_signal, sigma=1e-4, p0=initial_guess, bounds=bounds, method='trf')
133
+
134
+ #print(f"initial diminish: {initial_diminish}\ninitial stretch: {initial_stretch}\ninitial shift: {initial_shift}")
135
+ self.diminish = popt[0]
136
+ self.stretch = popt[1]
137
+ self.shift = popt[2]
138
+ self.adjusted_signal = model(self.x_plot, self.diminish, self.stretch, self.shift)
139
+
140
+ #SNR Calculation
141
+ p2p = np.max(self.adjusted_signal) - np.min(self.adjusted_signal)
142
+ noise_sample = self.output_signal[int(len(self.adjusted_signal) * 0.9):] #last 10% of the output signal
143
+ v_rms = np.sqrt(np.mean(noise_sample**2))
144
+
145
+ self.snr = p2p / v_rms
146
+
147
+ def plot(self):
148
+ import matplotlib.pyplot as plt
149
+
150
+ print(f'diminish: {self.diminish}')
151
+ print(f'stretch: {self.stretch}')
152
+ print(f'shift: {self.shift}')
153
+ print(f'Signal to Noise Ratio: {self.snr}')
154
+
155
+ self.fig, self.axes = plt.subplots(nrows=2, ncols=1, figsize=(12,18))
156
+
157
+ self.lines = [
158
+ self.axes[0].plot(self.x_plot, self.original_signal, 'r-', label='Original Signal')[0],
159
+ self.axes[1].plot(self.x_plot, self.output_signal, 'b-', label='Demodulated Signal (Lock-In)')[0],
160
+ self.axes[1].plot(self.x_plot, self.expected_signal, 'r-', label='Demodulated Signal (Expected)')[0],
161
+ self.axes[1].plot(self.x_plot, self.adjusted_signal, 'g-', label=f'Demodulated Signal (Adjusted)\n Diminish: {self.diminish}\n Stretch:{self.stretch}\n Shift: {self.shift}')[0],
162
+ ]
163
+
164
+ self.axes[0].set_title('Original Signal vs x')
165
+ self.axes[1].set_title('Demodulated Signal vs x')
166
+
167
+ plt.legend()
168
+ plt.show()
169
+
170
+ def spectrum_average(self, num):
171
+ result = np.zeros_like(self.t_plot)
172
+ for i in range(0, num):
173
+ result += self.signal_output_arr(self.t_plot)
174
+
175
+ return result / num
@@ -0,0 +1,6 @@
1
+ from . import constants
2
+ from . import noise
3
+ from .fft import *
4
+ from .Change_phase import Change_phase
5
+ from .Lock_in_emulator import Lock_in_emulator
6
+ from . import lock_in_emulator_app
@@ -0,0 +1,127 @@
1
+ import sys
2
+ import os
3
+ import argparse
4
+ import numpy as np
5
+
6
+ try:
7
+ from PyQt5 import QtCore, QtWidgets
8
+ except ImportError:
9
+ from PySide6 import QtCore, QtWidgets
10
+
11
+ from color_map_core import (
12
+ DataLoader,
13
+ DEFAULT_XY_FILE,
14
+ DEFAULT_Z_FILE,
15
+ DEFAULT_CMAP
16
+ )
17
+
18
+ from color_map_widget import InteractiveHeatmapWidget
19
+
20
+
21
+ def build_arg_parser():
22
+ """Build command line argument parser"""
23
+ p = argparse.ArgumentParser(
24
+ description="Interactive heatmap viewer (CSV/TSV/space-delimited supported)."
25
+ )
26
+ p.add_argument("--xy", dest="xy_file", default=DEFAULT_XY_FILE,
27
+ help="Path to XY file (X,Y vectors; header optional).")
28
+ p.add_argument("--z", dest="z_file", default=DEFAULT_Z_FILE,
29
+ help="Path to Z matrix file (rows form image lines).")
30
+ p.add_argument("--x-col", type=int, default=1,
31
+ help="Zero-based column index to use for X (default: 1).")
32
+ p.add_argument("--y-col", type=int, default=0,
33
+ help="Zero-based column index to use for Y (default: 0).")
34
+ p.add_argument("--cmap", default=DEFAULT_CMAP,
35
+ help="Colormap to use (e.g., viridis, plasma, inferno, magma, cividis, gray, jet).")
36
+ return p
37
+
38
+
39
+ def main():
40
+ """Main CLI entry point"""
41
+ # Parse command line arguments
42
+ parser = build_arg_parser()
43
+ args = parser.parse_args()
44
+
45
+ # Load data with data loader
46
+ loader = DataLoader()
47
+
48
+ # Use command line arguments
49
+ xy_file = args.xy_file
50
+ z_file = args.z_file
51
+ x_col = args.x_col
52
+ y_col = args.y_col
53
+ cmap = args.cmap
54
+
55
+ try:
56
+ # Check if file exists and detect columns
57
+ if os.path.exists(xy_file):
58
+ num_cols, col_names = loader.detect_columns(xy_file)
59
+ if num_cols > 0:
60
+ # Load with specified columns
61
+ y, x = loader.load_xy_data(xy_file, x_col=x_col, y_col=y_col)
62
+ # Use column names for labels if available
63
+ if col_names and len(col_names) > max(x_col, y_col):
64
+ x_label = f'X ({col_names[x_col]})'
65
+ y_label = f'Y ({col_names[y_col]})'
66
+ else:
67
+ x_label = 'X'
68
+ y_label = 'Y'
69
+ else:
70
+ raise ValueError("No columns detected")
71
+ else:
72
+ raise FileNotFoundError(f"{xy_file} not found")
73
+
74
+ Z = loader.load_matrix_data(z_file)
75
+ except Exception as e:
76
+ print(f"Error loading files: {e}")
77
+ print("Creating demo data...")
78
+ # Create demo data if files not found
79
+ x = np.linspace(0, 10, 100)
80
+ y = np.linspace(0, 10, 100)
81
+ xx, yy = np.meshgrid(x, y)
82
+ Z = np.sin(xx) * np.cos(yy)
83
+ x_label = 'X'
84
+ y_label = 'Y'
85
+
86
+ # Calculate ranges
87
+ xmin, xmax = float(np.nanmin(x)), float(np.nanmax(x))
88
+ ymin, ymax = float(np.nanmin(y)), float(np.nanmax(y))
89
+
90
+ # Create the app
91
+ app = QtWidgets.QApplication.instance()
92
+ created_app = False
93
+ if app is None:
94
+ app = QtWidgets.QApplication(sys.argv)
95
+ created_app = True
96
+ # Enable high DPI support
97
+ try:
98
+ app.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)
99
+ app.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True)
100
+ except:
101
+ pass # Older Qt version
102
+
103
+ # Initial vmin/vmax from data
104
+ vmin = float(np.nanmin(Z))
105
+ vmax = float(np.nanmax(Z))
106
+
107
+ # Create widget
108
+ win = InteractiveHeatmapWidget(
109
+ data=Z,
110
+ x_range=(xmin, xmax),
111
+ y_range=(ymin, ymax),
112
+ x_label=x_label,
113
+ y_label=y_label,
114
+ cmap=cmap,
115
+ vmin=vmin,
116
+ vmax=vmax
117
+ )
118
+
119
+ win.show()
120
+
121
+ # For CLI, run the event loop
122
+ if created_app:
123
+ sys.exit(app.exec_())
124
+
125
+
126
+ if __name__ == "__main__":
127
+ main()