barsukov 1.3.4__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 (32) hide show
  1. {barsukov-1.3.4/src/barsukov.egg-info → barsukov-1.3.7}/PKG-INFO +9 -3
  2. {barsukov-1.3.4 → barsukov-1.3.7}/README.md +1 -1
  3. {barsukov-1.3.4 → barsukov-1.3.7}/pyproject.toml +8 -0
  4. {barsukov-1.3.4 → barsukov-1.3.7}/src/barsukov/data/Lock_in_emulator.py +1 -9
  5. {barsukov-1.3.4 → barsukov-1.3.7}/src/barsukov/data/__init__.py +2 -1
  6. barsukov-1.3.7/src/barsukov/data/color_map_app.py +127 -0
  7. barsukov-1.3.7/src/barsukov/data/color_map_core.py +283 -0
  8. barsukov-1.3.7/src/barsukov/data/color_map_widget.py +614 -0
  9. {barsukov-1.3.4/src/barsukov/app → barsukov-1.3.7/src/barsukov/data}/lock_in_emulator_app.py +26 -22
  10. {barsukov-1.3.4 → barsukov-1.3.7/src/barsukov.egg-info}/PKG-INFO +9 -3
  11. {barsukov-1.3.4 → barsukov-1.3.7}/src/barsukov.egg-info/SOURCES.txt +4 -1
  12. barsukov-1.3.7/src/barsukov.egg-info/requires.txt +8 -0
  13. barsukov-1.3.4/src/barsukov.egg-info/requires.txt +0 -3
  14. {barsukov-1.3.4 → barsukov-1.3.7}/.github/workflows/versioning.yml +0 -0
  15. {barsukov-1.3.4 → barsukov-1.3.7}/.gitignore +0 -0
  16. {barsukov-1.3.4 → barsukov-1.3.7}/MANIFEST.in +0 -0
  17. {barsukov-1.3.4 → barsukov-1.3.7}/setup.cfg +0 -0
  18. {barsukov-1.3.4 → barsukov-1.3.7}/src/barsukov/__init__.py +0 -0
  19. {barsukov-1.3.4 → barsukov-1.3.7}/src/barsukov/data/Change_phase.py +0 -0
  20. {barsukov-1.3.4 → barsukov-1.3.7}/src/barsukov/data/constants.py +0 -0
  21. {barsukov-1.3.4 → barsukov-1.3.7}/src/barsukov/data/fft.py +0 -0
  22. {barsukov-1.3.4 → barsukov-1.3.7}/src/barsukov/data/noise.py +0 -0
  23. {barsukov-1.3.4 → barsukov-1.3.7}/src/barsukov/exp/__init__.py +0 -0
  24. {barsukov-1.3.4 → barsukov-1.3.7}/src/barsukov/exp/exp_utils.py +0 -0
  25. {barsukov-1.3.4 → barsukov-1.3.7}/src/barsukov/exp/mwHP.py +0 -0
  26. {barsukov-1.3.4 → barsukov-1.3.7}/src/barsukov/exp/smKE.py +0 -0
  27. {barsukov-1.3.4 → barsukov-1.3.7}/src/barsukov/logger.py +0 -0
  28. {barsukov-1.3.4 → barsukov-1.3.7}/src/barsukov/obj2file.py +0 -0
  29. {barsukov-1.3.4 → barsukov-1.3.7}/src/barsukov/script.py +0 -0
  30. {barsukov-1.3.4 → barsukov-1.3.7}/src/barsukov/time.py +0 -0
  31. {barsukov-1.3.4 → barsukov-1.3.7}/src/barsukov.egg-info/dependency_links.txt +0 -0
  32. {barsukov-1.3.4 → barsukov-1.3.7}/src/barsukov.egg-info/top_level.txt +0 -0
@@ -1,8 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: barsukov
3
- Version: 1.3.4
3
+ Version: 1.3.7
4
4
  Summary: Experiment Automation Package
5
- Author-email: Igor Barsukov <igorb@ucr.edu>, Steven Castaneda <scast206@ucr.edu>
5
+ Author-email: Igor Barsukov <igorb@ucr.edu>
6
+ Maintainer-email: Steven Castaneda <scast206@ucr.edu>, Marlon Lopez <mlope589@ucr.edu>
6
7
  Project-URL: Homepage, https://barsukov.ucr.edu
7
8
  Classifier: Programming Language :: Python :: 3
8
9
  Classifier: Operating System :: OS Independent
@@ -11,6 +12,11 @@ Description-Content-Type: text/markdown
11
12
  Requires-Dist: pytz>=2014.10
12
13
  Requires-Dist: numpy>=1.0.0
13
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
14
20
 
15
21
  # Barsukov
16
22
 
@@ -27,7 +33,7 @@ Use the package manager [pip](https://pip.pypa.io/en/stable/) to install barsuko
27
33
  ```bash
28
34
  pip install barsukov
29
35
  ```
30
-
36
+ Dependencies: pytz, dill, numpy, scipy, matplotlib, pyvisa, IPython, PyQt5, pyqtgraph
31
37
  ## Usage
32
38
 
33
39
  ```python
@@ -13,7 +13,7 @@ Use the package manager [pip](https://pip.pypa.io/en/stable/) to install barsuko
13
13
  ```bash
14
14
  pip install barsukov
15
15
  ```
16
-
16
+ Dependencies: pytz, dill, numpy, scipy, matplotlib, pyvisa, IPython, PyQt5, pyqtgraph
17
17
  ## Usage
18
18
 
19
19
  ```python
@@ -9,10 +9,18 @@ dependencies = [
9
9
  "pytz>=2014.10",
10
10
  "numpy>=1.0.0",
11
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",
12
17
  ]
13
18
  authors = [
14
19
  { name = "Igor Barsukov", email = "igorb@ucr.edu" },
20
+ ]
21
+ maintainers = [
15
22
  { name = "Steven Castaneda", email = "scast206@ucr.edu" },
23
+ { name = "Marlon Lopez", email = "mlope589@ucr.edu"},
16
24
  ]
17
25
  requires-python = ">=3.6"
18
26
  description = "Experiment Automation Package"
@@ -44,16 +44,9 @@ class Lock_in_emulator:
44
44
  self.x_plot = self.x_arr(self.t_plot)
45
45
 
46
46
  self.original_signal = self.signal_arr(self.x_plot)
47
- self.expected_signal_num = 0.5 * self.x_amp * np.gradient(self.original_signal, self.x_plot)
47
+ self.expected_signal = 0.5 * self.x_amp * np.gradient(self.original_signal, self.x_plot)
48
48
  self.output_signal = self.signal_output_arr(self.t_plot)
49
49
 
50
- #Symbolic feature:
51
- x, p, w, a = sp.symbols('x p w a')
52
- f = a / ((x - p)**2 + w**2)
53
- dfdx = sp.diff(f, x)
54
- f_prime = sp.lambdify((x, p, w, a), dfdx, 'numpy')
55
- self.expected_signal = 0.5 * self.x_amp * f_prime(self.x_plot, 2, 1, 1e-6)
56
-
57
50
  self.fit()
58
51
  #self.plot()
59
52
 
@@ -165,7 +158,6 @@ class Lock_in_emulator:
165
158
  self.axes[0].plot(self.x_plot, self.original_signal, 'r-', label='Original Signal')[0],
166
159
  self.axes[1].plot(self.x_plot, self.output_signal, 'b-', label='Demodulated Signal (Lock-In)')[0],
167
160
  self.axes[1].plot(self.x_plot, self.expected_signal, 'r-', label='Demodulated Signal (Expected)')[0],
168
- self.axes[1].plot(self.x_plot, self.expected_signal_num, 'm-', label='Demodulated Signal (Expected Numerical)')[0],
169
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],
170
162
  ]
171
163
 
@@ -2,4 +2,5 @@ from . import constants
2
2
  from . import noise
3
3
  from .fft import *
4
4
  from .Change_phase import Change_phase
5
- from .Lock_in_emulator import Lock_in_emulator
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()
@@ -0,0 +1,283 @@
1
+ """
2
+ Core computation and data handling for color map analysis
3
+ """
4
+ import re
5
+ import numpy as np
6
+
7
+ # ---------------------------
8
+ # CONSTANTS
9
+ # ---------------------------
10
+ DEFAULT_XY_FILE = "table1.txt"
11
+ DEFAULT_Z_FILE = "table2.txt"
12
+ DEFAULT_KC = -0.01
13
+ DEFAULT_KB = 0.002
14
+ DEFAULT_CMAP = 'viridis'
15
+
16
+
17
+ # ---------------------------
18
+ # DATA I/O
19
+ # ---------------------------
20
+ class DataLoader:
21
+ """Handles file I/O operations for XY and matrix data"""
22
+
23
+ @staticmethod
24
+ def detect_columns(path):
25
+ """Detect the number and names of columns in a data file"""
26
+ try:
27
+ with open(path, 'r', encoding='utf-8', errors='ignore') as f:
28
+ # Read header line
29
+ header = f.readline().strip()
30
+
31
+ # Read first data line to determine actual column count
32
+ first_data_line = None
33
+ for line in f:
34
+ line = line.strip()
35
+ if line and not line.startswith('#'):
36
+ first_data_line = line
37
+ break
38
+
39
+ # Determine delimiter from first data line (more reliable)
40
+ if first_data_line:
41
+ if '\t' in first_data_line:
42
+ delim = '\t'
43
+ splitter = re.compile(r'\t+')
44
+ elif ',' in first_data_line:
45
+ delim = ','
46
+ splitter = re.compile(r',')
47
+ else:
48
+ delim = None
49
+ splitter = re.compile(r'\s+')
50
+
51
+ # Count columns in data line
52
+ data_parts = [p for p in splitter.split(first_data_line) if p.strip()]
53
+ num_cols = len(data_parts)
54
+
55
+ # Parse header if present
56
+ if header:
57
+ header_parts = [p.strip() for p in splitter.split(header) if p.strip()]
58
+ # Check if header contains numbers (likely not a header)
59
+ try:
60
+ float(header_parts[0])
61
+ # First line is data, not header
62
+ return num_cols, [f"Column {i}" for i in range(num_cols)]
63
+ except:
64
+ # First line is header, but might have fewer columns
65
+ # Use actual data column count
66
+ col_names = header_parts[:num_cols]
67
+ # Pad with generic names if header has fewer columns
68
+ while len(col_names) < num_cols:
69
+ col_names.append(f"Column {len(col_names)}")
70
+ return num_cols, col_names
71
+ else:
72
+ return num_cols, [f"Column {i}" for i in range(num_cols)]
73
+
74
+ # Fallback: try to parse header
75
+ if header:
76
+ parts = re.split(r'[,\t\s]+', header)
77
+ parts = [p.strip() for p in parts if p.strip()]
78
+ try:
79
+ float(parts[0])
80
+ return len(parts), [f"Column {i}" for i in range(len(parts))]
81
+ except:
82
+ return len(parts), parts
83
+
84
+ return 0, []
85
+ except:
86
+ return 0, []
87
+
88
+ @staticmethod
89
+ def load_xy_data(path, x_col=1, y_col=0):
90
+ """Load X/Y data with column selection
91
+
92
+ Uses np.genfromtxt to handle missing values gracefully (converts to NaN).
93
+ This matches legacy behavior where missing first column values become NaN in y,
94
+ but the rest of the row still contributes to x.
95
+ """
96
+ delim = None # Initialize delim before try block
97
+ skip_header = 0
98
+
99
+ # First detect delimiter and header from first data line (more reliable)
100
+ try:
101
+ with open(path, 'r', encoding='utf-8', errors='ignore') as f:
102
+ first_line = f.readline().strip()
103
+
104
+ # Check if first line is header by trying to parse first element as float
105
+ try:
106
+ # Try to parse first element as number
107
+ float(first_line.split()[0].strip())
108
+ skip_header = 0
109
+ # First line is data, use it for delimiter detection
110
+ test_line = first_line
111
+ except (ValueError, IndexError):
112
+ # First line is likely header, read next line for delimiter detection
113
+ skip_header = 1
114
+ test_line = None
115
+ for line in f:
116
+ line = line.strip()
117
+ if line and not line.startswith('#'):
118
+ test_line = line
119
+ break
120
+
121
+ # Detect delimiter from actual data line (more reliable)
122
+ if test_line:
123
+ if '\t' in test_line:
124
+ delim = '\t'
125
+ elif ',' in test_line:
126
+ delim = ','
127
+ else:
128
+ delim = None # whitespace
129
+ except:
130
+ pass
131
+
132
+ # Try np.loadtxt first (faster for well-formed data)
133
+ try:
134
+ data = np.loadtxt(path, skiprows=skip_header, dtype=np.float64, ndmin=2, delimiter=delim)
135
+ if data.size == 0:
136
+ raise ValueError("No data found in file")
137
+
138
+ # Handle single column case
139
+ if data.ndim == 1:
140
+ data = data.reshape(-1, 1)
141
+
142
+ # Validate column indices
143
+ num_cols = data.shape[1]
144
+ if x_col >= num_cols:
145
+ x_col = min(x_col, num_cols - 1)
146
+ if y_col >= num_cols:
147
+ y_col = min(y_col, num_cols - 1)
148
+
149
+ return data[:, y_col], data[:, x_col] # y, x
150
+ except (ValueError, IndexError, UnicodeDecodeError):
151
+ # np.loadtxt failed (likely due to missing values), fall back to np.genfromtxt
152
+ # np.genfromtxt handles missing values by converting them to NaN
153
+ pass
154
+
155
+ # Use np.genfromtxt (handles missing values gracefully)
156
+ # If delim wasn't set, try to detect it from first data line
157
+ if delim is None:
158
+ try:
159
+ with open(path, 'r', encoding='utf-8', errors='ignore') as f:
160
+ # Skip header
161
+ first_line = f.readline().strip()
162
+ try:
163
+ float(first_line.split()[0].strip())
164
+ test_line = first_line
165
+ skip_header = 0
166
+ except (ValueError, IndexError):
167
+ skip_header = 1
168
+ test_line = None
169
+ for line in f:
170
+ line = line.strip()
171
+ if line and not line.startswith('#'):
172
+ test_line = line
173
+ break
174
+
175
+ if test_line:
176
+ if '\t' in test_line:
177
+ delim = '\t'
178
+ elif ',' in test_line:
179
+ delim = ','
180
+ else:
181
+ delim = None
182
+ except:
183
+ delim = None
184
+
185
+ # Use np.genfromtxt which handles missing values (converts to NaN)
186
+ # This matches legacy behavior: missing first column -> NaN in y, but row still contributes to x
187
+ try:
188
+ y, x = np.genfromtxt(
189
+ path,
190
+ delimiter=delim,
191
+ dtype=np.float64,
192
+ skip_header=skip_header,
193
+ usecols=(y_col, x_col),
194
+ unpack=True,
195
+ autostrip=True,
196
+ invalid_raise=False, # Don't raise on invalid values, convert to NaN
197
+ )
198
+ return y, x
199
+ except Exception:
200
+ # If all else fails, try default columns
201
+ y, x = np.genfromtxt(
202
+ path,
203
+ delimiter=delim,
204
+ dtype=np.float64,
205
+ skip_header=skip_header,
206
+ usecols=(0, 1),
207
+ unpack=True,
208
+ autostrip=True,
209
+ invalid_raise=False,
210
+ )
211
+ return y, x
212
+
213
+ @staticmethod
214
+ def load_matrix_data(path):
215
+ """Optimized matrix loading with better memory usage"""
216
+ # First pass: determine dimensions
217
+ with open(path, "r", encoding="utf-8", errors="ignore") as f:
218
+ lines = [line.strip() for line in f if line.strip() and not line.strip().startswith("#")]
219
+
220
+ if not lines:
221
+ raise ValueError("No numeric rows found in file")
222
+
223
+ first = lines[0]
224
+ if ',' in first:
225
+ sep = ','
226
+ splitter = re.compile(r"\s*,\s*")
227
+ elif '\t' in first:
228
+ sep = '\t'
229
+ splitter = re.compile(r"\t+")
230
+ else:
231
+ sep = ' ' # whitespace
232
+ splitter = re.compile(r"\s+")
233
+
234
+ rows = []
235
+ for line in lines:
236
+ try:
237
+ row = np.fromstring(line, sep=sep, dtype=np.float64)
238
+ if row.size:
239
+ rows.append(row)
240
+ continue
241
+ except Exception:
242
+ pass
243
+ # Fallback parser (handles stray spaces/commas)
244
+ parts = [p for p in splitter.split(line) if p]
245
+ vals = []
246
+ for p in parts:
247
+ try:
248
+ vals.append(float(p))
249
+ except ValueError:
250
+ pass
251
+ if vals:
252
+ rows.append(np.asarray(vals, dtype=np.float64))
253
+
254
+ if not rows:
255
+ raise ValueError("No numeric rows found in file")
256
+
257
+ widths = np.array([r.size for r in rows], dtype=int)
258
+ modal_w = np.bincount(widths).argmax()
259
+ good_rows = [r for r in rows if r.size == modal_w]
260
+ A = np.vstack(good_rows).astype(np.float64)
261
+ if A.shape[0] > A.shape[1]:
262
+ A = A.T
263
+ return A
264
+
265
+
266
+ # ---------------------------
267
+ # COMPUTATION UTILITIES
268
+ # ---------------------------
269
+ def calculate_effective_range(vmin, vmax, brightness, contrast):
270
+ """Calculate effective value range after brightness/contrast adjustment"""
271
+ vmin_p = (vmin - brightness) / max(1e-12, contrast)
272
+ vmax_p = (vmax - brightness) / max(1e-12, contrast)
273
+ return vmin_p, vmax_p
274
+
275
+
276
+ def calculate_marker_position(brightness, contrast, span, kc=DEFAULT_KC):
277
+ """Calculate control pad marker position from brightness/contrast values"""
278
+ import math
279
+ u = np.clip(0.5 + brightness / max(1e-9, span), 0.0, 1.0)
280
+ v = 0.5
281
+ if contrast > 0:
282
+ v = np.clip(0.5 - math.log(contrast) / (2 * max(1e-9, abs(kc))), 0.0, 1.0)
283
+ return u, v