pyrpoc 0.1.0__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.
pyrpoc-0.1.0/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright 2025, Ishaan Singh
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
pyrpoc-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,40 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyrpoc
3
+ Version: 0.1.0
4
+ Summary: Real-time Precision Opto-Control Toolkit
5
+ Author-email: Ishaan Singh <sing1125@purdue.com>
6
+ License: Copyright 2025, Ishaan Singh
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
11
+
12
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
13
+ Requires-Python: >=3.8
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Requires-Dist: numpy
17
+ Requires-Dist: scipy
18
+ Requires-Dist: opencv-python
19
+ Requires-Dist: pillow
20
+ Requires-Dist: matplotlib
21
+ Requires-Dist: pyqt5
22
+ Requires-Dist: superqt
23
+ Requires-Dist: cellpose
24
+ Requires-Dist: nidaqmx
25
+ Requires-Dist: pyvisa
26
+ Dynamic: license-file
27
+
28
+ # pyrpoc
29
+
30
+ Author: Ishaan Singh, Zhang Group (https://sites.google.com/view/zhangresearchgroup)
31
+
32
+ ## Basic Installation
33
+
34
+ Navigate to the directory that contains ```setup.py```. Run
35
+
36
+ ```
37
+ pip install -e .
38
+ ```
39
+
40
+ Please reach out to sing1125@purdue.edu with any suggestions or feedback on the GUI.
pyrpoc-0.1.0/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # pyrpoc
2
+
3
+ Author: Ishaan Singh, Zhang Group (https://sites.google.com/view/zhangresearchgroup)
4
+
5
+ ## Basic Installation
6
+
7
+ Navigate to the directory that contains ```setup.py```. Run
8
+
9
+ ```
10
+ pip install -e .
11
+ ```
12
+
13
+ Please reach out to sing1125@purdue.edu with any suggestions or feedback on the GUI.
@@ -0,0 +1,32 @@
1
+ [project]
2
+ name = "pyrpoc"
3
+ version = "0.1.0"
4
+ description = "Real-time Precision Opto-Control Toolkit"
5
+ readme = "README.md"
6
+ requires-python = ">=3.8"
7
+ license = { file = "LICENSE" }
8
+
9
+ authors = [
10
+ { name = "Ishaan Singh", email = "sing1125@purdue.com" }
11
+ ]
12
+
13
+ dependencies = [
14
+ "numpy",
15
+ "scipy",
16
+ "opencv-python",
17
+ "pillow",
18
+ "matplotlib",
19
+ "pyqt5",
20
+ "superqt",
21
+ "cellpose",
22
+ "nidaqmx",
23
+ "pyvisa",
24
+ # Add any others your app depends on
25
+ ]
26
+
27
+ [project.scripts]
28
+ pyrpoc = "pyrpoc.main:main"
29
+
30
+ [build-system]
31
+ requires = ["setuptools>=61.0"]
32
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,125 @@
1
+ import tkinter as tk
2
+ from tkinter import ttk, messagebox
3
+ import time, threading
4
+ from matplotlib.figure import Figure
5
+ from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
6
+ from pyrpoc.helpers.galvo_funcs import Galvo
7
+ from pyrpoc.helpers.run_image_2d import run_scan
8
+ from pyrpoc.helpers.utils import generate_data
9
+
10
+ # TODO: make the calibration plot dark mode because pretty
11
+ def calibrate_stage(gui):
12
+ cal_win = tk.Toplevel(gui.root)
13
+ cal_win.title("Stage Calibration")
14
+ cal_win.geometry("900x600")
15
+
16
+ config_frame = ttk.Frame(cal_win, padding=10)
17
+ config_frame.pack(side=tk.TOP, fill=tk.X)
18
+
19
+ ttk.Label(config_frame, text='Start Position (µm)').grid(row=0, column=0, sticky='w', padx=5, pady=3)
20
+ start_entry = ttk.Entry(config_frame, width=12, font=('Calibri', 14))
21
+ start_entry.insert(0, str(gui.hyper_config['start_um']))
22
+ start_entry.grid(row=0, column=1, padx=5, pady=3)
23
+
24
+ ttk.Label(config_frame, text='Stop Position (µm)').grid(row=1, column=0, sticky='w', padx=5, pady=3)
25
+ stop_entry = ttk.Entry(config_frame, width=12, font=('Calibri', 14))
26
+ stop_entry.insert(0, str(gui.hyper_config['stop_um']))
27
+ stop_entry.grid(row=1, column=1, padx=5, pady=3)
28
+
29
+ ttk.Label(config_frame, text='Number of Steps').grid(row=2, column=0, sticky='w', padx=5, pady=3)
30
+ cal_steps_entry = ttk.Entry(config_frame, width=10, font=('Calibri', 14))
31
+ cal_steps_entry.insert(0, '10')
32
+ cal_steps_entry.grid(row=2, column=1, padx=5, pady=3)
33
+
34
+ start_button = ttk.Button(config_frame, text='Start Calibration')
35
+ start_button.grid(row=3, column=0, columnspan=2, padx=5, pady=10)
36
+
37
+ fig = Figure(figsize=(5, 3), dpi=100)
38
+ ax = fig.add_subplot(111)
39
+ ax.set_title('Calibration Data')
40
+ ax.set_xlabel('Stage Position (µm)')
41
+ ax.set_ylabel('Average Intensity')
42
+
43
+ canvas = FigureCanvasTkAgg(fig, master=cal_win)
44
+ canvas_widget = canvas.get_tk_widget()
45
+ canvas_widget.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
46
+
47
+ cal_running = [False]
48
+
49
+ def run_calibration():
50
+ cal_running[0] = True
51
+ try:
52
+ start_val = float(start_entry.get().strip())
53
+ stop_val = float(stop_entry.get().strip())
54
+ n_steps = int(cal_steps_entry.get().strip())
55
+ if n_steps < 1 or start_val >= stop_val:
56
+ raise ValueError
57
+ except ValueError:
58
+ messagebox.showerror("Error", "Invalid calibration settings.")
59
+ cal_running[0] = False
60
+ return
61
+
62
+ try:
63
+ gui.zaber_stage.connect()
64
+ except Exception as e:
65
+ messagebox.showerror("Zaber Error", str(e))
66
+ cal_running[0] = False
67
+ return
68
+
69
+ positions_to_scan = ([start_val]
70
+ if n_steps == 1 else
71
+ [start_val + i * (stop_val - start_val) / (n_steps - 1)
72
+ for i in range(n_steps)]
73
+ )
74
+
75
+ positions, intensities = [], []
76
+
77
+ for pos in positions_to_scan:
78
+ if not cal_running[0]:
79
+ break
80
+ try:
81
+ gui.zaber_stage.move_absolute_um(pos)
82
+ except Exception as e:
83
+ messagebox.showerror("Zaber Error", str(e))
84
+ cal_running[0] = False
85
+ break
86
+
87
+ # TODO: calibration can still acquire simulation - probably good to display a warning about that though
88
+ if gui.simulation_mode.get():
89
+ data_list = generate_data(len(gui.config['ai_chans']), config=gui.config)
90
+ data = data_list[0]
91
+ else:
92
+ galvo = Galvo(gui.config)
93
+ data_list = raster_scan(
94
+ [f"{gui.config['device']}/{ch}" for ch in gui.config['ai_chans']],
95
+ galvo
96
+ )
97
+ data = data_list[0]
98
+
99
+ avg_val = data.mean()
100
+ positions.append(pos)
101
+ intensities.append(avg_val)
102
+
103
+ ax.clear()
104
+ ax.set_title('Calibration Data')
105
+ ax.set_xlabel('Stage Position (µm)')
106
+ ax.set_ylabel('Average Intensity')
107
+ ax.plot(positions, intensities, '-o', color='blue')
108
+ canvas.draw()
109
+ canvas.flush_events()
110
+
111
+ time.sleep(0.2)
112
+
113
+ cal_running[0] = False
114
+
115
+ def start_cal():
116
+ if not cal_running[0]:
117
+ threading.Thread(target=run_calibration, daemon=True).start()
118
+
119
+ start_button.configure(command=start_cal)
120
+
121
+ stop_button = ttk.Button(
122
+ config_frame, text='Stop Calibration',
123
+ command=lambda: cal_running.__setitem__(0, False)
124
+ )
125
+ stop_button.grid(row=4, column=0, columnspan=2, padx=5, pady=5)
@@ -0,0 +1,136 @@
1
+ import nidaqmx
2
+ from nidaqmx.constants import AcquisitionType
3
+ import numpy as np
4
+ from PIL import Image
5
+ from tkinter import messagebox
6
+
7
+ class Galvo:
8
+ def __init__(self, config, rpoc_mask=None, rpoc_do_chan=None, rpoc_mode=None, dwell_multiplier=2.0, **kwargs):
9
+ defaults = {
10
+ "numsteps_x": 400,
11
+ "numsteps_y": 400,
12
+ "extrasteps_left": 50,
13
+ "extrasteps_right": 50,
14
+ "offset_x": 0.0,
15
+ "offset_y": 0.0,
16
+ "dwell": 10e-6,
17
+ "amp_x": 0.5,
18
+ "amp_y": 0.5,
19
+ "rate": 10000,
20
+ "device": 'Dev1',
21
+ "ao_chans": ['ao1', 'ao0']
22
+ }
23
+ if config:
24
+ defaults.update(config)
25
+ defaults.update(kwargs)
26
+ for key, val in defaults.items():
27
+ setattr(self, key, val)
28
+
29
+ self.rpoc_mask = rpoc_mask
30
+ self.rpoc_do_chan = rpoc_do_chan
31
+ self.rpoc_mode = rpoc_mode # "standard" or "variable" so far
32
+ self.dwell_multiplier = dwell_multiplier
33
+
34
+ self.pixel_samples = max(1, int(self.dwell * self.rate))
35
+ self.total_x = self.numsteps_x + self.extrasteps_left + self.extrasteps_right
36
+ self.total_y = self.numsteps_y
37
+ self.total_samples = self.total_x * self.total_y * self.pixel_samples
38
+
39
+ if self.rpoc_mode == "variable":
40
+ self.waveform = None # or maybe generate it here...
41
+ else:
42
+ self.waveform = self.gen_raster()
43
+
44
+ def gen_raster(self):
45
+ total_rowsamples = self.pixel_samples * self.total_x
46
+ self.total_samples = total_rowsamples * self.total_y
47
+
48
+ single_row_ramp = np.linspace(
49
+ self.offset_x - self.amp_x,
50
+ self.offset_x + self.amp_x,
51
+ total_rowsamples,
52
+ endpoint=False
53
+ )
54
+ x_waveform = np.tile(single_row_ramp, self.total_y)
55
+
56
+ y_steps = np.linspace(
57
+ self.offset_y + self.amp_y,
58
+ self.offset_y - self.amp_y,
59
+ self.total_y
60
+ )
61
+ y_waveform = np.repeat(y_steps, total_rowsamples)
62
+
63
+ composite = np.vstack([x_waveform, y_waveform])
64
+
65
+ if self.rpoc_mask is not None and self.rpoc_do_chan is not None:
66
+ rpoc_wave = build_rpoc_wave(
67
+ self.rpoc_mask,
68
+ self.pixel_samples,
69
+ self.total_x,
70
+ self.total_y,
71
+ high_voltage=5.0
72
+ )
73
+ if rpoc_wave.size != y_waveform.size:
74
+ raise ValueError("RPOC wave length does not match total scan length!")
75
+ composite = np.vstack([composite, rpoc_wave])
76
+
77
+ if x_waveform.size < self.total_samples:
78
+ x_waveform = np.pad(
79
+ x_waveform,
80
+ (0, self.total_samples - x_waveform.size),
81
+ constant_values=x_waveform[-1]
82
+ )
83
+ else:
84
+ x_waveform = x_waveform[:self.total_samples]
85
+ composite[0] = x_waveform
86
+
87
+ return composite
88
+
89
+
90
+ def gen_variable_waveform(self, mask, dwell_multiplier):
91
+ dwell = self.dwell
92
+ rate = self.rate
93
+ num_y = self.numsteps_y
94
+ num_x = self.numsteps_x + self.extrasteps_left + self.extrasteps_right
95
+
96
+ mask_shape = np.shape(mask)
97
+ if mask_shape[1] != num_y or mask_shape[0] != (num_x - self.extrasteps_left - self.extrasteps_right):
98
+ raise ValueError(f'Error in galvo_funcs.gen_variable_waveform(). Mask is not the right size. Load a mask of dimensions {num_x - self.extrasteps_left - self.extrasteps_right} by {num_y}.')
99
+
100
+
101
+ dwell_on = dwell * dwell_multiplier
102
+ dwell_off = dwell
103
+
104
+
105
+ x_min = self.offset_x - self.amp_x
106
+ x_max = self.offset_x + self.amp_x
107
+ x_positions = np.linspace(x_min, x_max, num_x, endpoint=False)
108
+ y_positions = np.linspace(self.offset_y + self.amp_y,
109
+ self.offset_y - self.amp_y,
110
+ num_y)
111
+
112
+ x_wave_list = []
113
+ y_wave_list = []
114
+ pixel_map = np.zeros((num_y, num_x), dtype=int)
115
+
116
+ for row_idx in range(num_y):
117
+ row_y = y_positions[row_idx]
118
+ for col_idx in range(num_x):
119
+ if col_idx < self.extrasteps_left or col_idx >= (num_x - self.extrasteps_right): # for clarity im leaving this out of the else - we need to remember to account for extrasteps here
120
+ this_dwell = dwell_off
121
+ elif mask[row_idx, col_idx - self.extrasteps_left]:
122
+ this_dwell = dwell_on
123
+ else:
124
+ this_dwell = dwell_off
125
+
126
+ pixel_samps = max(1, int(this_dwell * rate))
127
+
128
+ x_val = x_positions[col_idx]
129
+ x_wave_list.append(np.full(pixel_samps, x_val))
130
+
131
+ y_wave_list.append(np.full(pixel_samps, row_y))
132
+
133
+ pixel_map[row_idx, col_idx] = pixel_samps
134
+ x_wave = np.concatenate(x_wave_list)
135
+ y_wave = np.concatenate(y_wave_list)
136
+ return x_wave, y_wave, pixel_map
@@ -0,0 +1,176 @@
1
+ from ctypes import WinDLL, create_string_buffer
2
+ import os
3
+ import time
4
+ import numpy as np
5
+ from pyrpoc.mains import acquisition
6
+ import cv2
7
+
8
+ DLL_PATH = os.path.join(os.path.dirname(__file__), "PriorScientificSDK.dll")
9
+ SDKPrior = None
10
+ sessionID = None
11
+ _prior_connected = False # Track connection state
12
+
13
+
14
+ def connect_prior(port=4):
15
+ global _prior_connected
16
+ if _prior_connected:
17
+ return
18
+
19
+ ret, _ = send_command(f"controller.connect {port}")
20
+ if ret == 0:
21
+ _prior_connected = True
22
+ print(f"Connected to Prior stage on COM{port}")
23
+ else:
24
+ raise RuntimeError(f"Failed to connect to Prior stage on COM{port}")
25
+
26
+
27
+ def initialize_sdk():
28
+ global SDKPrior, sessionID
29
+
30
+ if SDKPrior is None:
31
+ if os.path.exists(DLL_PATH):
32
+ SDKPrior = WinDLL(DLL_PATH)
33
+ else:
34
+ raise RuntimeError("DLL could not be loaded.")
35
+
36
+ ret = SDKPrior.PriorScientificSDK_Initialise()
37
+ if ret != 0:
38
+ raise RuntimeError(f"Failed to initialize Prior SDK. Error code: {ret}")
39
+
40
+ print("Prior SDK Initialized.")
41
+
42
+ if sessionID is None:
43
+ sessionID = SDKPrior.PriorScientificSDK_OpenNewSession()
44
+ if sessionID < 0:
45
+ raise RuntimeError(f"Failed to open Prior SDK session. SessionID: {sessionID}")
46
+
47
+ print(f"SDK Session Opened. Session ID: {sessionID}")
48
+
49
+
50
+ def send_command(command):
51
+ initialize_sdk()
52
+
53
+ rx = create_string_buffer(1000)
54
+ ret = SDKPrior.PriorScientificSDK_cmd(
55
+ sessionID, create_string_buffer(command.encode()), rx
56
+ )
57
+ response = rx.value.decode().strip()
58
+
59
+ if ret != 0:
60
+ print(f"Error executing command: {command} (Return Code: {ret})")
61
+
62
+ return ret, response
63
+
64
+
65
+ def wait_for_z_motion():
66
+ while True:
67
+ _, response = send_command("controller.z.busy.get")
68
+
69
+ if response:
70
+ try:
71
+ status = int(response)
72
+ if status == 0:
73
+ break
74
+ except ValueError:
75
+ print(f"Invalid response from controller: '{response}'")
76
+ else:
77
+ print("No response from controller, is it connected?")
78
+
79
+ time.sleep(0.1)
80
+
81
+ def auto_focus(gui, port: int, channel_name: str, step_size=10):
82
+ connect_prior(port)
83
+
84
+ gui.simulation_mode.set(False)
85
+ gui.acquiring = True
86
+
87
+ try:
88
+ channel_index = gui.config["channel_names"].index(channel_name)
89
+ except ValueError:
90
+ raise RuntimeError(f"Invalid channel name: '{channel_name}'")
91
+
92
+ ret, current_z = send_command("controller.z.position.get")
93
+ if ret != 0:
94
+ raise RuntimeError("Failed to retrieve current Z position.")
95
+ try:
96
+ current_z = int(current_z)
97
+ except ValueError:
98
+ raise RuntimeError(f"Invalid Z position response: '{current_z}'")
99
+
100
+ z_positions = [current_z + i * step_size for i in range(-10, 11)] # total of 21 points
101
+ best_focus = -1
102
+ best_z = current_z
103
+
104
+ gui.progress_label.config(text=f'(0/{len(z_positions)})')
105
+ gui.root.update_idletasks()
106
+
107
+ for i, z in enumerate(z_positions):
108
+ if not gui.acquiring:
109
+ print("[Autofocus] Interrupted by Stop.")
110
+ break
111
+
112
+ try:
113
+ send_command(f"controller.z.goto-position {z}")
114
+ wait_for_z_motion()
115
+ except Exception as e:
116
+ gui.acquiring = False
117
+ raise RuntimeError(f"Stage move to Z={z} failed: {e}")
118
+
119
+ acquisition.acquire(gui, auxilary=True)
120
+ gui.root.update_idletasks()
121
+ gui.root.update()
122
+
123
+ image = gui.data[channel_index]
124
+ x, y = np.shape(image)
125
+ metric = cv2.Laplacian(image[int(3*x/8):int(5*x/8), int(3*y/8):int(5*y/8)], cv2.CV_64F).var() # only use the middle eighth for focus
126
+ print(f"Z={z} → Focus Metric={metric:.2f}")
127
+
128
+ if metric > best_focus:
129
+ best_focus = metric
130
+ best_z = z
131
+
132
+ gui.progress_label.config(text=f'({i + 1}/{len(z_positions)})')
133
+ gui.root.update_idletasks()
134
+
135
+ if gui.acquiring:
136
+ send_command(f"controller.z.goto-position {best_z}")
137
+ wait_for_z_motion()
138
+
139
+ acquisition.acquire(gui, auxilary=True)
140
+ gui.root.update_idletasks()
141
+ gui.root.update()
142
+ print(f"[Autofocus] Best Z = {best_z}, Metric = {best_focus:.2f}")
143
+
144
+ gui.acquiring = False
145
+ return best_z, best_focus
146
+
147
+
148
+ def move_z(port: int, z_height: int):
149
+ connect_prior(port)
150
+
151
+ if not (0 <= z_height <= 50000):
152
+ raise ValueError("Z height must be between 0 and 50,000 µm.")
153
+
154
+ ret, _ = send_command(f"controller.z.goto-position {z_height}")
155
+ if ret != 0:
156
+ raise RuntimeError(f"Could not move Prior stage to {z_height} µm.")
157
+ wait_for_z_motion()
158
+
159
+
160
+ def move_xy(port: int, x: int, y: int):
161
+ connect_prior(port)
162
+
163
+ if not (0 <= x <= 50000) or not (0 <= y <= 50000):
164
+ raise ValueError("X and Y positions must be between 0 and 50,000 µm.")
165
+
166
+ ret, _ = send_command(f"controller.stage.goto-position {x} {y}")
167
+ if ret != 0:
168
+ raise RuntimeError(f"Could not move Prior stage to {x}, {y}.")
169
+
170
+
171
+ if __name__ == "__main__":
172
+ print("connecting")
173
+ connect_prior(4)
174
+
175
+ move_xy(4, 1000, 1000)
176
+ move_z(4, 2500)
@@ -0,0 +1,129 @@
1
+ # Created by Diego Alonso Alvarez on the 12th July 2020
2
+ #
3
+ # Copyright (c) 2020 Imperial College London
4
+ #
5
+ #Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ #of this software and associated documentation files (the "Software"), to deal
7
+ #in the Software without restriction, including without limitation the rights
8
+ #to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ #copies of the Software, and to permit persons to whom the Software is
10
+ #furnished to do so, subject to the following conditions:
11
+ #
12
+ #The above copyright notice and this permission notice shall be included in all
13
+ #copies or substantial portions of the Software.
14
+ #
15
+ #THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ #IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ #FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ #AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ #LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ #OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ #SOFTWARE.
22
+
23
+ from ctypes import WinDLL, create_string_buffer
24
+ import os
25
+ import sys
26
+
27
+ path = r"C:\Users\Lab Admin\Documents\PythonStuff\pysrs\pysrs\instruments\prior_stage\PriorScientificSDK.dll"
28
+
29
+ if os.path.exists(path):
30
+ SDKPrior = WinDLL(path)
31
+ else:
32
+ raise RuntimeError("DLL could not be loaded.")
33
+
34
+ rx = create_string_buffer(1000)
35
+ realhw = True
36
+
37
+
38
+ def cmd(msg):
39
+ print(msg)
40
+ ret = SDKPrior.PriorScientificSDK_cmd(
41
+ sessionID, create_string_buffer(msg.encode()), rx
42
+ )
43
+ if ret:
44
+ print(f"Api error {ret}")
45
+ else:
46
+ print(f"46 OK {rx.value.decode()}")
47
+
48
+ input("48 Press ENTER to continue...")
49
+ return ret, rx.value.decode()
50
+
51
+
52
+ ret = SDKPrior.PriorScientificSDK_Initialise()
53
+ if ret:
54
+ print(f"54 Error initialising {ret}")
55
+ sys.exit()
56
+ else:
57
+ print(f"57 Ok initialising {ret}")
58
+
59
+
60
+ ret = SDKPrior.PriorScientificSDK_Version(rx)
61
+ print(f"61 dll version api ret={ret}, version={rx.value.decode()}")
62
+
63
+
64
+ sessionID = SDKPrior.PriorScientificSDK_OpenNewSession()
65
+ if sessionID < 0:
66
+ print(f"66 Error getting sessionID {ret}")
67
+ else:
68
+ print(f"68 SessionID = {sessionID}")
69
+
70
+
71
+ ret = SDKPrior.PriorScientificSDK_cmd(
72
+ sessionID, create_string_buffer(b"72 dll.apitest 33 goodresponse"), rx
73
+ )
74
+ print(f"74 api response {ret}, rx = {rx.value.decode()}")
75
+ input("75 Press ENTER to continue...")
76
+
77
+
78
+ ret = SDKPrior.PriorScientificSDK_cmd(
79
+ sessionID, create_string_buffer(b"dll.apitest -300 stillgoodresponse"), rx
80
+ )
81
+ print(f"81 api response {ret}, rx = {rx.value.decode()}")
82
+ input("82 Press ENTER to continue...")
83
+
84
+
85
+ if realhw:
86
+ print("Connecting...")
87
+ # substitute 3 with your com port Id
88
+ cmd("controller.connect 4")
89
+
90
+ # test an illegal command
91
+ cmd("controller.stage.position.getx")
92
+
93
+ # get current XY position in default units of microns
94
+ cmd("controller.stage.position.get")
95
+
96
+ # re-define this current position as 1234,5678
97
+ cmd("controller.stage.position.set 1234 5678")
98
+
99
+ # check it worked
100
+ cmd("controller.stage.position.get")
101
+
102
+ # set it back to 0,0
103
+ cmd("controller.stage.position.set 0 0")
104
+ cmd("controller.stage.position.get")
105
+
106
+ # start a move to a new position, normally you would poll
107
+ # 'controller.stage.busy.get' until response = 0
108
+ cmd("controller.stage.goto-position 1234 5678")
109
+
110
+ # example velocity move of 10u/s in both x and y
111
+ cmd("controller.stage.move-at-velocity 10 10")
112
+
113
+ # see busy status
114
+ cmd("controller.stage.busy.get")
115
+
116
+ # stop velocity move
117
+ cmd("controller.stage.move-at-velocity 0 0")
118
+
119
+ # see busy status */
120
+ cmd("controller.stage.busy.get")
121
+
122
+ # see new position
123
+ cmd("controller.stage.position.get")
124
+
125
+ # disconnect cleanly from controller
126
+ cmd("controller.disconnect")
127
+
128
+ else:
129
+ input("Press ENTER to continue...")