imswitchclient 0.1.2__tar.gz → 0.1.3__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.
Files changed (37) hide show
  1. imswitchclient-0.1.3/PKG-INFO +168 -0
  2. imswitchclient-0.1.3/README.md +137 -0
  3. {imswitchclient-0.1.2 → imswitchclient-0.1.3}/imswitchclient/ImSwitchClient.py +2 -0
  4. {imswitchclient-0.1.2 → imswitchclient-0.1.3}/imswitchclient/__init__.py +1 -1
  5. imswitchclient-0.1.3/imswitchclient/histoscanManager.py +132 -0
  6. {imswitchclient-0.1.2 → imswitchclient-0.1.3}/imswitchclient/positionersManager.py +40 -2
  7. imswitchclient-0.1.3/imswitchclient/recordingManager.py +106 -0
  8. imswitchclient-0.1.3/imswitchclient.egg-info/PKG-INFO +168 -0
  9. {imswitchclient-0.1.2 → imswitchclient-0.1.3}/imswitchclient.egg-info/SOURCES.txt +1 -11
  10. {imswitchclient-0.1.2 → imswitchclient-0.1.3}/setup.cfg +2 -2
  11. {imswitchclient-0.1.2 → imswitchclient-0.1.3}/setup.py +2 -2
  12. imswitchclient-0.1.2/AUTHORS.rst +0 -13
  13. imswitchclient-0.1.2/PKG-INFO +0 -102
  14. imswitchclient-0.1.2/README.md +0 -80
  15. imswitchclient-0.1.2/docs/Makefile +0 -20
  16. imswitchclient-0.1.2/docs/authors.rst +0 -1
  17. imswitchclient-0.1.2/docs/conf.py +0 -162
  18. imswitchclient-0.1.2/docs/contributing.rst +0 -1
  19. imswitchclient-0.1.2/docs/history.rst +0 -1
  20. imswitchclient-0.1.2/docs/index.rst +0 -20
  21. imswitchclient-0.1.2/docs/installation.rst +0 -51
  22. imswitchclient-0.1.2/docs/make.bat +0 -36
  23. imswitchclient-0.1.2/docs/readme.rst +0 -1
  24. imswitchclient-0.1.2/docs/usage.rst +0 -7
  25. imswitchclient-0.1.2/imswitchclient/recordingManager.py +0 -28
  26. imswitchclient-0.1.2/imswitchclient.egg-info/PKG-INFO +0 -102
  27. {imswitchclient-0.1.2 → imswitchclient-0.1.3}/CONTRIBUTING.rst +0 -0
  28. {imswitchclient-0.1.2 → imswitchclient-0.1.3}/HISTORY.rst +0 -0
  29. {imswitchclient-0.1.2 → imswitchclient-0.1.3}/LICENSE +0 -0
  30. {imswitchclient-0.1.2 → imswitchclient-0.1.3}/MANIFEST.in +0 -0
  31. {imswitchclient-0.1.2 → imswitchclient-0.1.3}/imswitchclient/client.py +0 -0
  32. {imswitchclient-0.1.2 → imswitchclient-0.1.3}/imswitchclient/lasersManager.py +0 -0
  33. {imswitchclient-0.1.2 → imswitchclient-0.1.3}/imswitchclient.egg-info/dependency_links.txt +0 -0
  34. {imswitchclient-0.1.2 → imswitchclient-0.1.3}/imswitchclient.egg-info/not-zip-safe +0 -0
  35. {imswitchclient-0.1.2 → imswitchclient-0.1.3}/imswitchclient.egg-info/top_level.txt +0 -0
  36. {imswitchclient-0.1.2 → imswitchclient-0.1.3}/tests/__init__.py +0 -0
  37. {imswitchclient-0.1.2 → imswitchclient-0.1.3}/tests/test_imswitchclient.py +0 -0
@@ -0,0 +1,168 @@
1
+ Metadata-Version: 2.2
2
+ Name: imswitchclient
3
+ Version: 0.1.3
4
+ Summary: This is a package that connects ImSwitch's REST API to the rest of the world (e.g. jupyter lab)
5
+ Home-page: https://github.com/openuc2/imswitchclient
6
+ Author: Benedict Diederich
7
+ Author-email: benedictdied@gmail.com
8
+ License: MIT license
9
+ Keywords: imswitchclient
10
+ Classifier: Development Status :: 2 - Pre-Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Natural Language :: English
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.6
16
+ Classifier: Programming Language :: Python :: 3.7
17
+ Classifier: Programming Language :: Python :: 3.8
18
+ Requires-Python: >=3.6
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Dynamic: author
22
+ Dynamic: author-email
23
+ Dynamic: classifier
24
+ Dynamic: description
25
+ Dynamic: description-content-type
26
+ Dynamic: home-page
27
+ Dynamic: keywords
28
+ Dynamic: license
29
+ Dynamic: requires-python
30
+ Dynamic: summary
31
+
32
+ # ImSwitchClient Documentation
33
+
34
+ ## Introduction
35
+
36
+ `ImSwitchClient` is a Python wrapper designed for interacting with the ImSwitch REST API, enabling remote control over ImSwitch functionalities, such as stage positioning, laser control, and image acquisition. This client simplifies API interactions and allows seamless integration into Python scripts and Jupyter Notebooks.
37
+
38
+ [![PyPI Version](https://img.shields.io/pypi/v/imswitchclient.svg)](https://pypi.python.org/pypi/imswitchclient)
39
+
40
+ ## Features
41
+
42
+ - **Remote Control**: Interface with ImSwitch through REST API endpoints.
43
+ - **Comprehensive API Access**: Easily control positioners, lasers, detectors, and imaging settings.
44
+ - **Interactive API Exploration**: Utilize the FastAPI Swagger UI at `http://localhost:8000/docs`.
45
+ - **Modular Design**: Includes managers for lasers, positioners, image acquisition, and more.
46
+ - **Open Source**: Inspired by OpenFlexure Client, freely available under the MIT license.
47
+
48
+ ## Installation
49
+
50
+ You can install `ImSwitchClient` via pip:
51
+
52
+ ```bash
53
+ pip install imswitchclient
54
+ ```
55
+
56
+ ## Getting Started
57
+
58
+ ### Initializing the Client
59
+
60
+ ```python
61
+ import imswitchclient.ImSwitchClient as imc
62
+
63
+ # Initialize the client
64
+ client = imc.ImSwitchClient(host="0.0.0.0", isHttps=True, port=8001)
65
+ ```
66
+
67
+ ### Example: Moving a Stage and Acquiring an Image
68
+
69
+ ```python
70
+ import numpy as np
71
+ import matplotlib.pyplot as plt
72
+ import time
73
+
74
+ # Retrieve positioner names
75
+ positioner_names = client.positionersManager.getAllDeviceNames()
76
+ positioner_name = positioner_names[0]
77
+
78
+ # Get current position
79
+ current_positions = client.positionersManager.getPositionerPositions()[positioner_name]
80
+ initial_position = (current_positions["X"], current_positions["Y"])
81
+
82
+ # Turn on illumination
83
+ laser_name = client.lasersManager.getLaserNames()[0]
84
+ client.lasersManager.setLaserActive(laser_name, True)
85
+ client.lasersManager.setLaserValue(laser_name, 512)
86
+
87
+ # Move the stage and capture an image
88
+ def capture_image_at_position(x, y):
89
+ client.positionersManager.movePositioner(positioner_name, "X", x, is_absolute=True, is_blocking=True)
90
+ client.positionersManager.movePositioner(positioner_name, "Y", y, is_absolute=True, is_blocking=True)
91
+ last_frame = client.recordingManager.snapNumpyToFastAPI()
92
+ plt.imshow(last_frame)
93
+ plt.show()
94
+
95
+ # Example scanning
96
+ for ix in range(5):
97
+ for iy in range(5):
98
+ new_x = initial_position[0] + ix * 50
99
+ new_y = initial_position[1] + iy * 50
100
+ capture_image_at_position(new_x, new_y)
101
+
102
+ # Return stage to initial position
103
+ client.positionersManager.movePositioner(positioner_name, "X", initial_position[0], is_absolute=True, is_blocking=True)
104
+ client.positionersManager.movePositioner(positioner_name, "Y", initial_position[1], is_absolute=True, is_blocking=True)
105
+ ```
106
+
107
+ ### Laser Control Example
108
+
109
+ ```python
110
+ laser_name = client.lasersManager.getLaserNames()[0]
111
+ client.lasersManager.setLaserActive(laser_name, True)
112
+ client.lasersManager.setLaserValue(laser_name, 800)
113
+
114
+ # Verify laser status
115
+ print(client.lasersManager.getLaserNames())
116
+ client.lasersManager.setLaserActive(laser_name, False)
117
+ ```
118
+
119
+ ### Recording an Image
120
+
121
+ ```python
122
+ # Take a snapshot
123
+ image = client.recordingManager.snapNumpyToFastAPI()
124
+ plt.imshow(image)
125
+ plt.show()
126
+ ```
127
+
128
+ ### Setting Live View
129
+
130
+ ```python
131
+ client.viewManager.setLiveViewActive(True)
132
+ client.viewManager.setLiveViewCrosshairVisible(True)
133
+ client.viewManager.setLiveViewGridVisible(False)
134
+ ```
135
+
136
+ ## API Overview
137
+
138
+ The ImSwitch API provides access to various components:
139
+
140
+ ### Positioners Manager
141
+ - `getAllDeviceNames()` - Get all available positioners.
142
+ - `getPositionerPositions()` - Get current position.
143
+ - `movePositioner(name, axis, value, is_absolute, is_blocking)` - Move the stage.
144
+ - `homeAxis(name, axis, is_blocking)` - Home the positioner.
145
+
146
+ ### Lasers Manager
147
+ - `getLaserNames()` - Get available lasers.
148
+ - `setLaserActive(name, status)` - Turn laser on/off.
149
+ - `setLaserValue(name, value)` - Set laser intensity.
150
+
151
+ ### Recording Manager
152
+ - `snapNumpyToFastAPI()` - Capture an image.
153
+ - `startRecording()` - Begin recording.
154
+ - `stopRecording()` - Stop recording.
155
+
156
+ ### View Manager
157
+ - `setLiveViewActive(status)` - Enable live view.
158
+ - `setLiveViewCrosshairVisible(status)` - Show/hide crosshair.
159
+ - `setLiveViewGridVisible(status)` - Show/hide grid.
160
+
161
+ ## Contributing
162
+
163
+ Contributions are welcome! Visit the GitHub repository for details: [https://github.com/openUC2/imswitchclient](https://github.com/openUC2/imswitchclient)
164
+
165
+ ## License
166
+
167
+ This project is licensed under the MIT License.
168
+
@@ -0,0 +1,137 @@
1
+ # ImSwitchClient Documentation
2
+
3
+ ## Introduction
4
+
5
+ `ImSwitchClient` is a Python wrapper designed for interacting with the ImSwitch REST API, enabling remote control over ImSwitch functionalities, such as stage positioning, laser control, and image acquisition. This client simplifies API interactions and allows seamless integration into Python scripts and Jupyter Notebooks.
6
+
7
+ [![PyPI Version](https://img.shields.io/pypi/v/imswitchclient.svg)](https://pypi.python.org/pypi/imswitchclient)
8
+
9
+ ## Features
10
+
11
+ - **Remote Control**: Interface with ImSwitch through REST API endpoints.
12
+ - **Comprehensive API Access**: Easily control positioners, lasers, detectors, and imaging settings.
13
+ - **Interactive API Exploration**: Utilize the FastAPI Swagger UI at `http://localhost:8000/docs`.
14
+ - **Modular Design**: Includes managers for lasers, positioners, image acquisition, and more.
15
+ - **Open Source**: Inspired by OpenFlexure Client, freely available under the MIT license.
16
+
17
+ ## Installation
18
+
19
+ You can install `ImSwitchClient` via pip:
20
+
21
+ ```bash
22
+ pip install imswitchclient
23
+ ```
24
+
25
+ ## Getting Started
26
+
27
+ ### Initializing the Client
28
+
29
+ ```python
30
+ import imswitchclient.ImSwitchClient as imc
31
+
32
+ # Initialize the client
33
+ client = imc.ImSwitchClient(host="0.0.0.0", isHttps=True, port=8001)
34
+ ```
35
+
36
+ ### Example: Moving a Stage and Acquiring an Image
37
+
38
+ ```python
39
+ import numpy as np
40
+ import matplotlib.pyplot as plt
41
+ import time
42
+
43
+ # Retrieve positioner names
44
+ positioner_names = client.positionersManager.getAllDeviceNames()
45
+ positioner_name = positioner_names[0]
46
+
47
+ # Get current position
48
+ current_positions = client.positionersManager.getPositionerPositions()[positioner_name]
49
+ initial_position = (current_positions["X"], current_positions["Y"])
50
+
51
+ # Turn on illumination
52
+ laser_name = client.lasersManager.getLaserNames()[0]
53
+ client.lasersManager.setLaserActive(laser_name, True)
54
+ client.lasersManager.setLaserValue(laser_name, 512)
55
+
56
+ # Move the stage and capture an image
57
+ def capture_image_at_position(x, y):
58
+ client.positionersManager.movePositioner(positioner_name, "X", x, is_absolute=True, is_blocking=True)
59
+ client.positionersManager.movePositioner(positioner_name, "Y", y, is_absolute=True, is_blocking=True)
60
+ last_frame = client.recordingManager.snapNumpyToFastAPI()
61
+ plt.imshow(last_frame)
62
+ plt.show()
63
+
64
+ # Example scanning
65
+ for ix in range(5):
66
+ for iy in range(5):
67
+ new_x = initial_position[0] + ix * 50
68
+ new_y = initial_position[1] + iy * 50
69
+ capture_image_at_position(new_x, new_y)
70
+
71
+ # Return stage to initial position
72
+ client.positionersManager.movePositioner(positioner_name, "X", initial_position[0], is_absolute=True, is_blocking=True)
73
+ client.positionersManager.movePositioner(positioner_name, "Y", initial_position[1], is_absolute=True, is_blocking=True)
74
+ ```
75
+
76
+ ### Laser Control Example
77
+
78
+ ```python
79
+ laser_name = client.lasersManager.getLaserNames()[0]
80
+ client.lasersManager.setLaserActive(laser_name, True)
81
+ client.lasersManager.setLaserValue(laser_name, 800)
82
+
83
+ # Verify laser status
84
+ print(client.lasersManager.getLaserNames())
85
+ client.lasersManager.setLaserActive(laser_name, False)
86
+ ```
87
+
88
+ ### Recording an Image
89
+
90
+ ```python
91
+ # Take a snapshot
92
+ image = client.recordingManager.snapNumpyToFastAPI()
93
+ plt.imshow(image)
94
+ plt.show()
95
+ ```
96
+
97
+ ### Setting Live View
98
+
99
+ ```python
100
+ client.viewManager.setLiveViewActive(True)
101
+ client.viewManager.setLiveViewCrosshairVisible(True)
102
+ client.viewManager.setLiveViewGridVisible(False)
103
+ ```
104
+
105
+ ## API Overview
106
+
107
+ The ImSwitch API provides access to various components:
108
+
109
+ ### Positioners Manager
110
+ - `getAllDeviceNames()` - Get all available positioners.
111
+ - `getPositionerPositions()` - Get current position.
112
+ - `movePositioner(name, axis, value, is_absolute, is_blocking)` - Move the stage.
113
+ - `homeAxis(name, axis, is_blocking)` - Home the positioner.
114
+
115
+ ### Lasers Manager
116
+ - `getLaserNames()` - Get available lasers.
117
+ - `setLaserActive(name, status)` - Turn laser on/off.
118
+ - `setLaserValue(name, value)` - Set laser intensity.
119
+
120
+ ### Recording Manager
121
+ - `snapNumpyToFastAPI()` - Capture an image.
122
+ - `startRecording()` - Begin recording.
123
+ - `stopRecording()` - Stop recording.
124
+
125
+ ### View Manager
126
+ - `setLiveViewActive(status)` - Enable live view.
127
+ - `setLiveViewCrosshairVisible(status)` - Show/hide crosshair.
128
+ - `setLiveViewGridVisible(status)` - Show/hide grid.
129
+
130
+ ## Contributing
131
+
132
+ Contributions are welcome! Visit the GitHub repository for details: [https://github.com/openUC2/imswitchclient](https://github.com/openUC2/imswitchclient)
133
+
134
+ ## License
135
+
136
+ This project is licensed under the MIT License.
137
+
@@ -10,6 +10,7 @@ import os
10
10
  from .positionersManager import positionersManager
11
11
  from .recordingManager import recordingManager
12
12
  from .lasersManager import lasersManager
13
+ from .histoscanManager import histoscanManager
13
14
 
14
15
 
15
16
  class ImSwitchClient(object):
@@ -25,6 +26,7 @@ class ImSwitchClient(object):
25
26
  self.positionersManager = positionersManager(self)
26
27
  self.recordingManager = recordingManager(self)
27
28
  self.lasersManager = lasersManager(self)
29
+ self.histoscanManager = histoscanManager(self)
28
30
 
29
31
  @property
30
32
  def base_uri(self):
@@ -3,4 +3,4 @@ from imswitchclient import *
3
3
 
4
4
  __author__ = """Benedict Diederich"""
5
5
  __email__ = 'benedictdied@gmail.com'
6
- __version__ = "v0.1.2"#'0.1.2'
6
+ __version__ = "v0.1.3"#'0.1.2'
@@ -0,0 +1,132 @@
1
+ from PIL import Image
2
+ import numpy as np
3
+ import io
4
+ import json
5
+
6
+ class histoscanManager(object):
7
+
8
+ '''
9
+ http://localhost:8001/HistoScanController/startHistoScanTileBasedByParameters?numberTilesX=2&numberTilesY=2&stepSizeX=100&stepSizeY=100&nTimes=1&tPeriod=1&initPosX=0&initPosY=0
10
+ http://localhost:8001/HistoScanController/startStageMapping
11
+ http://localhost:8001/HistoScanController/stopHistoScan
12
+ '''
13
+
14
+ def __init__(self, parent):
15
+ self.parent = parent
16
+
17
+ def stopHistoScan(self):
18
+ url = f"{self.parent.base_uri}/HistoScanController/stopHistoScan"
19
+ headers = {'accept': 'application/json'}
20
+ response = self.parent.get_json(url, headers=headers)
21
+ return response
22
+
23
+ def startStageScanningPositionlistbased(self, positionList:list, nTimes:int=1, tPeriod:int=1, illuSource:str=None):
24
+ url = f"{self.parent.base_uri}/HistoScanController/startStageScanningPositionlistbased"
25
+
26
+ payload = {
27
+ "positionList": str(positionList),
28
+ "nTimes": nTimes,
29
+ "tPeriod": tPeriod,
30
+ "illuSource": illuSource
31
+ }
32
+ if nTimes < 1:
33
+ raise ValueError("nTimes must be greater than 0")
34
+ response = self.parent.get_json(url, payload=payload)
35
+ return response
36
+
37
+
38
+ def startStageScanningPositionlistbased(self, positionList:str, nTimes:int=1, tPeriod:int=1, illuSource:str=None):
39
+ '''
40
+ https://localhost:8001/HistoScanController/startStageScanningPositionlistbased?positionList=dadf&nTimes=1&tPeriod=0
41
+
42
+ positionList: list of tuples with X/Y positions (e.g. "[(10, 10, 100), (100, 100, 100)]")
43
+ nTimes: number of times to repeat the scan
44
+ tPeriod: time between scans
45
+ '''
46
+
47
+ url = f"{self.parent.base_uri}/HistoScanController/startStageScanningPositionlistbased"
48
+ payload = {
49
+ "positionList": positionList,
50
+ "nTimes": nTimes,
51
+ "tPeriod": tPeriod,
52
+ "illuSource": illuSource
53
+ }
54
+ if nTimes < 1:
55
+ raise ValueError("nTimes must be greater than 0")
56
+ response = self.parent.get_json(url, payload=payload)
57
+ return response
58
+
59
+
60
+ def startHistoScanTileBasedByParameters(self, numberTilesX, numberTilesY, stepSizeX, stepSizeY, initPosX, initPosY, nTimes=1, tPeriod=1):
61
+ url = f"{self.parent.base_uri}/HistoScanController/startHistoScanTileBasedByParameters"
62
+ payload = {
63
+ "numberTilesX": numberTilesX,
64
+ "numberTilesY": numberTilesY,
65
+ "stepSizeX": stepSizeX,
66
+ "stepSizeY": stepSizeY,
67
+ "nTimes": nTimes,
68
+ "tPeriod": tPeriod,
69
+ "initPosX": initPosX,
70
+ "initPosY": initPosY
71
+ }
72
+ if nTimes < 1:
73
+ raise ValueError("nTimes must be greater than 0")
74
+ response = self.parent.get_json(url, payload=payload)
75
+ return response
76
+
77
+ def startStageMapping(self):
78
+ url = f"{self.parent.base_uri}/HistoScanController/startStageMapping"
79
+ payload = {
80
+ }
81
+ response = self.parent.get_json(url, payload=payload)
82
+ return response
83
+
84
+ def getStatusScanRunning(self):
85
+ url = f"{self.parent.base_uri}/HistoScanController/getStatusScanRunning"
86
+ payload = {
87
+ }
88
+ response = self.parent.get_json(url, payload=payload)
89
+ return response
90
+
91
+ def getLastStitchedImage(self):
92
+ '''
93
+ https://localhost:8001/HistoScanController/
94
+ Retrieve numpy image based on
95
+ return Response(im_bytes, headers=headers, media_type='image/png')
96
+ '''
97
+ url = f"{self.parent.base_uri}/HistoScanController/getLastStitchedImage"
98
+ headers = {'accept': 'application/json'}
99
+ response = self.parent.get_json(url, headers=headers)
100
+ content_type = response.headers.get('content-type')
101
+ if content_type == 'image/png':
102
+ image_data = response.content
103
+ image = Image.open(io.BytesIO(image_data))
104
+ return np.array(image)
105
+ else:
106
+ raise ValueError("Unexpected response content-type. Expected 'image/png'.")
107
+
108
+
109
+ def startStageScanning(self, minPosX:float=None, maxPosX:float=None, minPosY:float=None, maxPosY:float=None,
110
+ overlap:float=None, nTimes:int=1, tPeriod:int=0, positionList:list=None,
111
+ isStitchAshlar:bool=False, isStitchAshlarFlipX:bool=False, isStitchAshlarFlipY:bool=False,
112
+ resizeFactor=0.25):
113
+ '''
114
+ generic stage scan, rather low-level
115
+ '''
116
+ url = f"{self.parent.base_uri}/HistoScanController/startStageScanning"
117
+ payload = {
118
+ "minPosX": minPosX,
119
+ "maxPosX": maxPosX,
120
+ "minPosY": minPosY,
121
+ "maxPosY": maxPosY,
122
+ "overlap": overlap,
123
+ "nTimes": nTimes,
124
+ "tPeriod": tPeriod,
125
+ "positionList": json.dumps(positionList),
126
+ "isStitchAshlar": isStitchAshlar,
127
+ "isStitchAshlarFlipX": isStitchAshlarFlipX,
128
+ "isStitchAshlarFlipY": isStitchAshlarFlipY,
129
+ "resizeFactor": resizeFactor
130
+ }
131
+ response = self.parent.get_json(url, payload=payload)
132
+ return response
@@ -3,6 +3,7 @@ class positionersManager(object):
3
3
 
4
4
  def __init__(self, parent):
5
5
  self.parent = parent
6
+ self.allStageNames = self.getAllDeviceNames()
6
7
 
7
8
  def getAllDeviceNames(self):
8
9
  url = f"{self.parent.base_uri}/PositionerController/getPositionerNames"
@@ -10,10 +11,26 @@ class positionersManager(object):
10
11
 
11
12
  response = self.parent.get_json(url, headers=headers)
12
13
  return response
13
-
14
+
15
+ def stop(self, positioner_name, axis):
16
+ if positioner_name is None:
17
+ positioner_name = self.allStageNames[0]
18
+
19
+ url = f"{self.parent.base_uri}/PositionerController/stop"
20
+ payload = {
21
+ 'positionerName': positioner_name,
22
+ 'axis': axis
23
+ }
24
+ response = self.parent.get_json(url, payload=payload)
25
+ return response
26
+
14
27
  def movePositionerForever(self, speed=(0, 0, 0, 0), is_stop=False):
28
+ if positioner_name is None:
29
+ positioner_name = self.allStageNames[0]
30
+
15
31
  url = f"{self.parent.base_uri}/PositionerController/movePositionerForever"
16
32
  payload = {
33
+ 'positionerName': positioner_name,
17
34
  'speed': speed,
18
35
  'isStop': is_stop
19
36
  }
@@ -21,6 +38,9 @@ class positionersManager(object):
21
38
  return response
22
39
 
23
40
  def setPositionerSpeed(self, positioner_name, axis, speed):
41
+ if positioner_name is None:
42
+ positioner_name = self.allStageNames[0]
43
+
24
44
  url = f"{self.parent.base_uri}/PositionerController/setPositionerSpeed"
25
45
  payload = {
26
46
  'positionerName': positioner_name,
@@ -31,6 +51,9 @@ class positionersManager(object):
31
51
  return response
32
52
 
33
53
  def movePositioner(self, positioner_name, axis, dist, is_absolute=True, is_blocking=True, speed=10000):
54
+ if positioner_name is None:
55
+ positioner_name = self.allStageNames[0]
56
+
34
57
  url = f"{self.parent.base_uri}/PositionerController/movePositioner"
35
58
  payload = {
36
59
  'positionerName': positioner_name,
@@ -45,9 +68,24 @@ class positionersManager(object):
45
68
  response = self.parent.get_json(url, payload=payload)
46
69
  return response
47
70
 
48
- def getPositionerPositions(self):
71
+ def getPositionerPositions(self, id=None):
49
72
  url = f"{self.parent.base_uri}/PositionerController/getPositionerPositions"
50
73
  payload = {
51
74
  }
52
75
  response = self.parent.get_json(url, payload=payload)
76
+ if type(id) == int:
77
+ return response[id]
78
+ return response
79
+
80
+ def homeAxis(self, positioner_name, axis, is_blocking=False):
81
+ url = f"{self.parent.base_uri}/PositionerController/homeAxis"
82
+ if positioner_name is None:
83
+ positioner_name = self.allStageNames[0]
84
+
85
+ payload = {
86
+ 'positionerName': positioner_name,
87
+ 'axis': axis,
88
+ 'isBlocking': is_blocking
89
+ }
90
+ response = self.parent.get_json(url, payload=payload)
53
91
  return response
@@ -0,0 +1,106 @@
1
+
2
+ import requests
3
+ import json
4
+ import numpy as np
5
+ import PIL
6
+ import io
7
+ import cv2
8
+ import threading
9
+ import time
10
+
11
+ class MJPEGStreamReceiver:
12
+ def __init__(self, stream_url):
13
+ self.stream_url = stream_url
14
+ self.frame = None
15
+ self.streaming = False
16
+ self.thread = None
17
+ self.lock = threading.Lock()
18
+
19
+ def startStream(self):
20
+ if not self.streaming:
21
+ self.streaming = True
22
+ self.thread = threading.Thread(target=self._pull_frames, daemon=True)
23
+ self.thread.start()
24
+
25
+ def _pull_frames(self):
26
+ # Continuously pull frames from the MJPEG stream
27
+ with requests.get(self.stream_url, stream=True, verify=False) as response:
28
+ if response.status_code == 200:
29
+ bytes_stream = bytes()
30
+ for chunk in response.iter_content(chunk_size=1024):
31
+ bytes_stream += chunk
32
+ a = bytes_stream.find(b'\xff\xd8') # Start of JPEG
33
+ b = bytes_stream.find(b'\xff\xd9') # End of JPEG
34
+ if a != -1 and b != -1:
35
+ jpg_data = bytes_stream[a:b+2]
36
+ bytes_stream = bytes_stream[b+2:]
37
+
38
+ # Decode the image and store it
39
+ image = cv2.imdecode(np.frombuffer(jpg_data, np.uint8), cv2.IMREAD_COLOR)
40
+ if image is not None:
41
+ with self.lock:
42
+ self.frame = image
43
+
44
+ def stopStream(self):
45
+ if self.streaming:
46
+ self.streaming = False
47
+ self.thread.join()
48
+
49
+ def getFrame(self) -> np.ndarray:
50
+ # Retrieve the most recent frame
51
+ with self.lock:
52
+ if self.frame is not None:
53
+ return self.frame.copy() # Return a copy to avoid race conditions
54
+ else:
55
+ return None
56
+
57
+ if __name__ == "__main__":
58
+ # Example usage with FastAPI
59
+ stream_url = "https://localhost:8001/RecordingController/video_feeder"
60
+ mjpeg_receiver = MJPEGStreamReceiver(stream_url)
61
+ mjpeg_receiver.startStream()
62
+ time.sleep(1)
63
+ frame = mjpeg_receiver.getFrame()
64
+ mjpeg_receiver.stopStream()
65
+
66
+
67
+
68
+ class recordingManager(object):
69
+
70
+ def __init__(self, parent):
71
+ self.parent = parent
72
+ self.videoStream = MJPEGStreamReceiver(f"{self.parent.base_uri}/RecordingController/video_feeder")
73
+
74
+ def snapNumpyToFastAPI(self, resizeFactor=1):
75
+ url = f"{self.parent.base_uri}/RecordingController/snapNumpyToFastAPI"
76
+ headers = {'accept': 'application/json'}
77
+ payload ={'resizeFactor':resizeFactor}
78
+ # we do not use parent's get function here
79
+ response = requests.get(url, params=payload, headers=headers, verify=False)
80
+ response.raise_for_status()
81
+ content_type = response.headers.get('content-type')
82
+ if content_type == 'image/png':
83
+ image_data = response.content
84
+ image = PIL.Image.open(io.BytesIO(image_data))
85
+ return np.array(image)
86
+ else:
87
+ raise ValueError("Unexpected response content-type. Expected 'image/png'.")
88
+
89
+ def startVideoStream(self):
90
+ '''
91
+ Start pulling frames from the MJPEG stream
92
+ '''
93
+ self.videoStream.startStream()
94
+
95
+ def stopVideoStream(self):
96
+ '''
97
+ Stop pulling frames from the MJPEG stream
98
+ '''
99
+ self.videoStream.stopStream()
100
+
101
+ def getVideoFrame(self):
102
+ '''
103
+ Get the most recent frame from the MJPEG stream
104
+ '''
105
+ return self.videoStream.getFrame()
106
+