imswitchclient 0.1.0__tar.gz → 0.1.2__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.
- imswitchclient-0.1.2/PKG-INFO +102 -0
- imswitchclient-0.1.2/README.md +80 -0
- imswitchclient-0.1.2/imswitchclient/ImSwitchClient.py +53 -0
- {imswitchclient-0.1.0 → imswitchclient-0.1.2}/imswitchclient/__init__.py +2 -2
- {imswitchclient-0.1.0 → imswitchclient-0.1.2}/imswitchclient/client.py +2 -0
- imswitchclient-0.1.2/imswitchclient/lasersManager.py +30 -0
- imswitchclient-0.1.2/imswitchclient/positionersManager.py +53 -0
- imswitchclient-0.1.2/imswitchclient/recordingManager.py +28 -0
- imswitchclient-0.1.2/imswitchclient.egg-info/PKG-INFO +102 -0
- {imswitchclient-0.1.0 → imswitchclient-0.1.2}/imswitchclient.egg-info/SOURCES.txt +4 -1
- {imswitchclient-0.1.0 → imswitchclient-0.1.2}/setup.cfg +2 -2
- {imswitchclient-0.1.0 → imswitchclient-0.1.2}/setup.py +6 -5
- imswitchclient-0.1.0/PKG-INFO +0 -72
- imswitchclient-0.1.0/README.rst +0 -41
- imswitchclient-0.1.0/imswitchclient/ImSwitchClient.py +0 -105
- imswitchclient-0.1.0/imswitchclient.egg-info/PKG-INFO +0 -72
- {imswitchclient-0.1.0 → imswitchclient-0.1.2}/AUTHORS.rst +0 -0
- {imswitchclient-0.1.0 → imswitchclient-0.1.2}/CONTRIBUTING.rst +0 -0
- {imswitchclient-0.1.0 → imswitchclient-0.1.2}/HISTORY.rst +0 -0
- {imswitchclient-0.1.0 → imswitchclient-0.1.2}/LICENSE +0 -0
- {imswitchclient-0.1.0 → imswitchclient-0.1.2}/MANIFEST.in +0 -0
- {imswitchclient-0.1.0 → imswitchclient-0.1.2}/docs/Makefile +0 -0
- {imswitchclient-0.1.0 → imswitchclient-0.1.2}/docs/authors.rst +0 -0
- {imswitchclient-0.1.0 → imswitchclient-0.1.2}/docs/conf.py +0 -0
- {imswitchclient-0.1.0 → imswitchclient-0.1.2}/docs/contributing.rst +0 -0
- {imswitchclient-0.1.0 → imswitchclient-0.1.2}/docs/history.rst +0 -0
- {imswitchclient-0.1.0 → imswitchclient-0.1.2}/docs/index.rst +0 -0
- {imswitchclient-0.1.0 → imswitchclient-0.1.2}/docs/installation.rst +0 -0
- {imswitchclient-0.1.0 → imswitchclient-0.1.2}/docs/make.bat +0 -0
- {imswitchclient-0.1.0 → imswitchclient-0.1.2}/docs/readme.rst +0 -0
- {imswitchclient-0.1.0 → imswitchclient-0.1.2}/docs/usage.rst +0 -0
- {imswitchclient-0.1.0 → imswitchclient-0.1.2}/imswitchclient.egg-info/dependency_links.txt +0 -0
- {imswitchclient-0.1.0 → imswitchclient-0.1.2}/imswitchclient.egg-info/not-zip-safe +0 -0
- {imswitchclient-0.1.0 → imswitchclient-0.1.2}/imswitchclient.egg-info/top_level.txt +0 -0
- {imswitchclient-0.1.0 → imswitchclient-0.1.2}/tests/__init__.py +0 -0
- {imswitchclient-0.1.0 → imswitchclient-0.1.2}/tests/test_imswitchclient.py +0 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: imswitchclient
|
|
3
|
+
Version: 0.1.2
|
|
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/beniroquai/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
|
+
License-File: AUTHORS.rst
|
|
22
|
+
|
|
23
|
+
# ImSwitchClient Documentation
|
|
24
|
+
|
|
25
|
+
`ImSwitchClient` is a Python package designed to connect to the ImSwitch REST API, enabling remote control of ImSwitchUC2 functionalities directly from Jupyter Notebooks. This client facilitates easy integration with the ImSwitch ecosystem, offering programmable access to various features like laser control, stage manipulation, and image acquisition.
|
|
26
|
+
|
|
27
|
+
[](https://pypi.python.org/pypi/imswitchclient)
|
|
28
|
+
|
|
29
|
+
## Features
|
|
30
|
+
|
|
31
|
+
- **Remote Control**: Interact with ImSwitchUC2 from Jupyter Notebooks via fastapi endpoints.
|
|
32
|
+
- **Comprehensive Documentation**: Access detailed documentation and explore API endpoints at [https://imswitchclient.readthedocs.io](https://imswitchclient.readthedocs.io).
|
|
33
|
+
- **API Exploration**: Utilize FastAPI's interface at http://localhost:8000/docs for an interactive API experience.
|
|
34
|
+
- **Broad Functionality**: Current implementations include laser control, stage manipulation, and image acquisition, with the possibility for future expansion based on user requests.
|
|
35
|
+
- **Global API Testing**: Test the client using the globally hosted API at [https://youseetoo.github.io/imswitch/api.html](https://youseetoo.github.io/imswitch/api.html).
|
|
36
|
+
- **Open Source**: Inspired by the OpenFlexure Client, `ImSwitchClient` is freely available for modification and distribution under the MIT license.
|
|
37
|
+
- **Implemented functions** (so far, please file an issue for feature requests):
|
|
38
|
+
- Laser
|
|
39
|
+
- Stage
|
|
40
|
+
- Image Acquisition
|
|
41
|
+
- You can test the client with the globally hosted api here: https://youseetoo.github.io/imswitch/api.html
|
|
42
|
+
- It is inspired by the OpenFlexure Client: https://gitlab.com/openflexure/openflexure-microscope-pyclient/-/blob/master/openflexure_microscope_client/microscope_client.py
|
|
43
|
+
- The source files can be found here: https://github.com/openUC2/imswitchclient/
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
To install `ImSwitchClient`, use the following pip command:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install imswitchclient
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quick Start Example
|
|
54
|
+
|
|
55
|
+
This example demonstrates basic usage of `ImSwitchClient` for moving a positioner and acquiring an image.
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
import imswitchclient.ImSwitchClient as imc
|
|
59
|
+
import numpy as np
|
|
60
|
+
import matplotlib.pyplot as plt
|
|
61
|
+
import time
|
|
62
|
+
|
|
63
|
+
# Initialize the client
|
|
64
|
+
client = imc.ImSwitchClient()
|
|
65
|
+
|
|
66
|
+
# Retrieve the first positioner's name and current position
|
|
67
|
+
positioner_names = client.positionersManager.getAllDeviceNames()
|
|
68
|
+
positioner_name = positioner_names[0]
|
|
69
|
+
currentPositions = client.positionersManager.getPositionerPositions()[positioner_name]
|
|
70
|
+
initialPosition = (currentPositions["X"], currentPositions["Y"])
|
|
71
|
+
|
|
72
|
+
# turn on illumination
|
|
73
|
+
mLaserName = client.lasersManager.getLaserNames()[0]
|
|
74
|
+
client.lasersManager.setLaserActive(mLaserName, True)
|
|
75
|
+
client.lasersManager.setLaserValue(mLaserName, 512)
|
|
76
|
+
|
|
77
|
+
for ix in range(10):
|
|
78
|
+
for iy in range(10):
|
|
79
|
+
# Define and move to a new position
|
|
80
|
+
newPosition = (initialPosition[0] + ix*50, initialPosition[1] + iy*50)
|
|
81
|
+
client.positionersManager.movePositioner(positioner_name, "X", newPosition[0], is_absolute=True, is_blocking=True)
|
|
82
|
+
client.positionersManager.movePositioner(positioner_name, "Y", newPosition[1], is_absolute=True, is_blocking=True)
|
|
83
|
+
|
|
84
|
+
# Acquire and display an image
|
|
85
|
+
#time.sleep(0.5) # Allow time for the move
|
|
86
|
+
lastFrame = client.recordingManager.snapNumpyToFastAPI()
|
|
87
|
+
plt.imshow(lastFrame)
|
|
88
|
+
plt.show()
|
|
89
|
+
|
|
90
|
+
# Return the positioner to its initial position
|
|
91
|
+
client.positionersManager.movePositioner(positioner_name, "X", initialPosition[0], is_absolute=True, is_blocking=True)
|
|
92
|
+
client.positionersManager.movePositioner(positioner_name, "Y", initialPosition[1], is_absolute=True, is_blocking=True)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Contributing
|
|
96
|
+
|
|
97
|
+
Contributions to `ImSwitchClient` are welcome! Please refer to the project's GitHub repository for contribution guidelines: [https://github.com/openUC2/imswitchclient/](https://github.com/openUC2/imswitchclient/).
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
`ImSwitchClient` is licensed under the MIT License. For more details, see the LICENSE file in the project repository.
|
|
102
|
+
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# ImSwitchClient Documentation
|
|
2
|
+
|
|
3
|
+
`ImSwitchClient` is a Python package designed to connect to the ImSwitch REST API, enabling remote control of ImSwitchUC2 functionalities directly from Jupyter Notebooks. This client facilitates easy integration with the ImSwitch ecosystem, offering programmable access to various features like laser control, stage manipulation, and image acquisition.
|
|
4
|
+
|
|
5
|
+
[](https://pypi.python.org/pypi/imswitchclient)
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Remote Control**: Interact with ImSwitchUC2 from Jupyter Notebooks via fastapi endpoints.
|
|
10
|
+
- **Comprehensive Documentation**: Access detailed documentation and explore API endpoints at [https://imswitchclient.readthedocs.io](https://imswitchclient.readthedocs.io).
|
|
11
|
+
- **API Exploration**: Utilize FastAPI's interface at http://localhost:8000/docs for an interactive API experience.
|
|
12
|
+
- **Broad Functionality**: Current implementations include laser control, stage manipulation, and image acquisition, with the possibility for future expansion based on user requests.
|
|
13
|
+
- **Global API Testing**: Test the client using the globally hosted API at [https://youseetoo.github.io/imswitch/api.html](https://youseetoo.github.io/imswitch/api.html).
|
|
14
|
+
- **Open Source**: Inspired by the OpenFlexure Client, `ImSwitchClient` is freely available for modification and distribution under the MIT license.
|
|
15
|
+
- **Implemented functions** (so far, please file an issue for feature requests):
|
|
16
|
+
- Laser
|
|
17
|
+
- Stage
|
|
18
|
+
- Image Acquisition
|
|
19
|
+
- You can test the client with the globally hosted api here: https://youseetoo.github.io/imswitch/api.html
|
|
20
|
+
- It is inspired by the OpenFlexure Client: https://gitlab.com/openflexure/openflexure-microscope-pyclient/-/blob/master/openflexure_microscope_client/microscope_client.py
|
|
21
|
+
- The source files can be found here: https://github.com/openUC2/imswitchclient/
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
To install `ImSwitchClient`, use the following pip command:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pip install imswitchclient
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Quick Start Example
|
|
32
|
+
|
|
33
|
+
This example demonstrates basic usage of `ImSwitchClient` for moving a positioner and acquiring an image.
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
import imswitchclient.ImSwitchClient as imc
|
|
37
|
+
import numpy as np
|
|
38
|
+
import matplotlib.pyplot as plt
|
|
39
|
+
import time
|
|
40
|
+
|
|
41
|
+
# Initialize the client
|
|
42
|
+
client = imc.ImSwitchClient()
|
|
43
|
+
|
|
44
|
+
# Retrieve the first positioner's name and current position
|
|
45
|
+
positioner_names = client.positionersManager.getAllDeviceNames()
|
|
46
|
+
positioner_name = positioner_names[0]
|
|
47
|
+
currentPositions = client.positionersManager.getPositionerPositions()[positioner_name]
|
|
48
|
+
initialPosition = (currentPositions["X"], currentPositions["Y"])
|
|
49
|
+
|
|
50
|
+
# turn on illumination
|
|
51
|
+
mLaserName = client.lasersManager.getLaserNames()[0]
|
|
52
|
+
client.lasersManager.setLaserActive(mLaserName, True)
|
|
53
|
+
client.lasersManager.setLaserValue(mLaserName, 512)
|
|
54
|
+
|
|
55
|
+
for ix in range(10):
|
|
56
|
+
for iy in range(10):
|
|
57
|
+
# Define and move to a new position
|
|
58
|
+
newPosition = (initialPosition[0] + ix*50, initialPosition[1] + iy*50)
|
|
59
|
+
client.positionersManager.movePositioner(positioner_name, "X", newPosition[0], is_absolute=True, is_blocking=True)
|
|
60
|
+
client.positionersManager.movePositioner(positioner_name, "Y", newPosition[1], is_absolute=True, is_blocking=True)
|
|
61
|
+
|
|
62
|
+
# Acquire and display an image
|
|
63
|
+
#time.sleep(0.5) # Allow time for the move
|
|
64
|
+
lastFrame = client.recordingManager.snapNumpyToFastAPI()
|
|
65
|
+
plt.imshow(lastFrame)
|
|
66
|
+
plt.show()
|
|
67
|
+
|
|
68
|
+
# Return the positioner to its initial position
|
|
69
|
+
client.positionersManager.movePositioner(positioner_name, "X", initialPosition[0], is_absolute=True, is_blocking=True)
|
|
70
|
+
client.positionersManager.movePositioner(positioner_name, "Y", initialPosition[1], is_absolute=True, is_blocking=True)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Contributing
|
|
74
|
+
|
|
75
|
+
Contributions to `ImSwitchClient` are welcome! Please refer to the project's GitHub repository for contribution guidelines: [https://github.com/openUC2/imswitchclient/](https://github.com/openUC2/imswitchclient/).
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
`ImSwitchClient` is licensed under the MIT License. For more details, see the LICENSE file in the project repository.
|
|
80
|
+
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import json
|
|
3
|
+
import time
|
|
4
|
+
import io
|
|
5
|
+
import PIL.Image
|
|
6
|
+
import numpy as np
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
|
|
10
|
+
from .positionersManager import positionersManager
|
|
11
|
+
from .recordingManager import recordingManager
|
|
12
|
+
from .lasersManager import lasersManager
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ImSwitchClient(object):
|
|
16
|
+
def __init__(self, host="0.0.0.0", isHttps=True, port=8001):
|
|
17
|
+
self.host = host
|
|
18
|
+
self.port = port
|
|
19
|
+
self.isHttps = isHttps
|
|
20
|
+
self.get_json(self.base_uri+"/openapi.json")
|
|
21
|
+
|
|
22
|
+
logging.info(f"Connecting to microscope {self.host}:{self.port}")
|
|
23
|
+
|
|
24
|
+
# register managers
|
|
25
|
+
self.positionersManager = positionersManager(self)
|
|
26
|
+
self.recordingManager = recordingManager(self)
|
|
27
|
+
self.lasersManager = lasersManager(self)
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def base_uri(self):
|
|
31
|
+
if self.isHttps:
|
|
32
|
+
return f"https://{self.host}:{self.port}"
|
|
33
|
+
else:
|
|
34
|
+
return f"http://{self.host}:{self.port}"
|
|
35
|
+
|
|
36
|
+
def get_json(self, path, payload={}, headers={}):
|
|
37
|
+
"""Perform an HTTP GET request and return the JSON response"""
|
|
38
|
+
if not path.startswith("http"):
|
|
39
|
+
path = self.base_uri + path
|
|
40
|
+
r = requests.get(path, params=payload, headers=headers, verify=False)
|
|
41
|
+
r.raise_for_status()
|
|
42
|
+
return r.json()
|
|
43
|
+
|
|
44
|
+
def post_json(self, path, payload={}, headers={}, wait_on_task="auto"):
|
|
45
|
+
"""Make an HTTP POST request and return the JSON response"""
|
|
46
|
+
if not path.startswith("http"):
|
|
47
|
+
path = self.base_uri + path
|
|
48
|
+
r = requests.post(path, json=payload, headers=headers, verify=False)
|
|
49
|
+
r.raise_for_status()
|
|
50
|
+
r = r.json()
|
|
51
|
+
return r
|
|
52
|
+
|
|
53
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
|
|
2
|
+
class lasersManager(object):
|
|
3
|
+
|
|
4
|
+
def __init__(self, parent):
|
|
5
|
+
self.parent = parent
|
|
6
|
+
|
|
7
|
+
def getLaserNames(self):
|
|
8
|
+
url = f"{self.parent.base_uri}/LaserController/getLaserNames"
|
|
9
|
+
headers = {'accept': 'application/json'}
|
|
10
|
+
|
|
11
|
+
response = self.parent.get_json(url, headers=headers)
|
|
12
|
+
return response
|
|
13
|
+
|
|
14
|
+
def setLaserValue(self, laser_name, value=0):
|
|
15
|
+
url = f"{self.parent.base_uri}/LaserController/setLaserValue"
|
|
16
|
+
payload = {
|
|
17
|
+
'laserName': laser_name,
|
|
18
|
+
'value': value
|
|
19
|
+
}
|
|
20
|
+
response = self.parent.get_json(url, payload=payload)
|
|
21
|
+
return response
|
|
22
|
+
|
|
23
|
+
def setLaserActive(self, laser_name, active=True):
|
|
24
|
+
url = f"{self.parent.base_uri}/LaserController/setLaserActive"
|
|
25
|
+
payload = {
|
|
26
|
+
"laserName": laser_name,
|
|
27
|
+
"active": active
|
|
28
|
+
}
|
|
29
|
+
response = self.parent.get_json(url, payload=payload)
|
|
30
|
+
return response
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
|
|
2
|
+
class positionersManager(object):
|
|
3
|
+
|
|
4
|
+
def __init__(self, parent):
|
|
5
|
+
self.parent = parent
|
|
6
|
+
|
|
7
|
+
def getAllDeviceNames(self):
|
|
8
|
+
url = f"{self.parent.base_uri}/PositionerController/getPositionerNames"
|
|
9
|
+
headers = {'accept': 'application/json'}
|
|
10
|
+
|
|
11
|
+
response = self.parent.get_json(url, headers=headers)
|
|
12
|
+
return response
|
|
13
|
+
|
|
14
|
+
def movePositionerForever(self, speed=(0, 0, 0, 0), is_stop=False):
|
|
15
|
+
url = f"{self.parent.base_uri}/PositionerController/movePositionerForever"
|
|
16
|
+
payload = {
|
|
17
|
+
'speed': speed,
|
|
18
|
+
'isStop': is_stop
|
|
19
|
+
}
|
|
20
|
+
response = self.parent.get_json(url, payload=payload)
|
|
21
|
+
return response
|
|
22
|
+
|
|
23
|
+
def setPositionerSpeed(self, positioner_name, axis, speed):
|
|
24
|
+
url = f"{self.parent.base_uri}/PositionerController/setPositionerSpeed"
|
|
25
|
+
payload = {
|
|
26
|
+
'positionerName': positioner_name,
|
|
27
|
+
'axis': axis,
|
|
28
|
+
'speed': speed
|
|
29
|
+
}
|
|
30
|
+
response = self.parent.get_json(url, payload=payload)
|
|
31
|
+
return response
|
|
32
|
+
|
|
33
|
+
def movePositioner(self, positioner_name, axis, dist, is_absolute=True, is_blocking=True, speed=10000):
|
|
34
|
+
url = f"{self.parent.base_uri}/PositionerController/movePositioner"
|
|
35
|
+
payload = {
|
|
36
|
+
'positionerName': positioner_name,
|
|
37
|
+
'axis': axis,
|
|
38
|
+
'dist': dist,
|
|
39
|
+
'isAbsolute': is_absolute,
|
|
40
|
+
'isBlocking': is_blocking,
|
|
41
|
+
'speed': speed
|
|
42
|
+
}
|
|
43
|
+
#headers = {'accept': 'application/json'}
|
|
44
|
+
|
|
45
|
+
response = self.parent.get_json(url, payload=payload)
|
|
46
|
+
return response
|
|
47
|
+
|
|
48
|
+
def getPositionerPositions(self):
|
|
49
|
+
url = f"{self.parent.base_uri}/PositionerController/getPositionerPositions"
|
|
50
|
+
payload = {
|
|
51
|
+
}
|
|
52
|
+
response = self.parent.get_json(url, payload=payload)
|
|
53
|
+
return response
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
import requests
|
|
3
|
+
import json
|
|
4
|
+
import numpy as np
|
|
5
|
+
import PIL
|
|
6
|
+
import io
|
|
7
|
+
|
|
8
|
+
class recordingManager(object):
|
|
9
|
+
|
|
10
|
+
def __init__(self, parent):
|
|
11
|
+
self.parent = parent
|
|
12
|
+
|
|
13
|
+
def snapNumpyToFastAPI(self, resizeFactor=1):
|
|
14
|
+
url = f"{self.parent.base_uri}/RecordingController/snapNumpyToFastAPI"
|
|
15
|
+
headers = {'accept': 'application/json'}
|
|
16
|
+
payload ={'resizeFactor':resizeFactor}
|
|
17
|
+
# we do not use parent's get function here
|
|
18
|
+
response = requests.get(url, params=payload, headers=headers, verify=False)
|
|
19
|
+
response.raise_for_status()
|
|
20
|
+
content_type = response.headers.get('content-type')
|
|
21
|
+
if content_type == 'image/png':
|
|
22
|
+
image_data = response.content
|
|
23
|
+
image = PIL.Image.open(io.BytesIO(image_data))
|
|
24
|
+
return np.array(image)
|
|
25
|
+
else:
|
|
26
|
+
raise ValueError("Unexpected response content-type. Expected 'image/png'.")
|
|
27
|
+
|
|
28
|
+
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: imswitchclient
|
|
3
|
+
Version: 0.1.2
|
|
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/beniroquai/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
|
+
License-File: AUTHORS.rst
|
|
22
|
+
|
|
23
|
+
# ImSwitchClient Documentation
|
|
24
|
+
|
|
25
|
+
`ImSwitchClient` is a Python package designed to connect to the ImSwitch REST API, enabling remote control of ImSwitchUC2 functionalities directly from Jupyter Notebooks. This client facilitates easy integration with the ImSwitch ecosystem, offering programmable access to various features like laser control, stage manipulation, and image acquisition.
|
|
26
|
+
|
|
27
|
+
[](https://pypi.python.org/pypi/imswitchclient)
|
|
28
|
+
|
|
29
|
+
## Features
|
|
30
|
+
|
|
31
|
+
- **Remote Control**: Interact with ImSwitchUC2 from Jupyter Notebooks via fastapi endpoints.
|
|
32
|
+
- **Comprehensive Documentation**: Access detailed documentation and explore API endpoints at [https://imswitchclient.readthedocs.io](https://imswitchclient.readthedocs.io).
|
|
33
|
+
- **API Exploration**: Utilize FastAPI's interface at http://localhost:8000/docs for an interactive API experience.
|
|
34
|
+
- **Broad Functionality**: Current implementations include laser control, stage manipulation, and image acquisition, with the possibility for future expansion based on user requests.
|
|
35
|
+
- **Global API Testing**: Test the client using the globally hosted API at [https://youseetoo.github.io/imswitch/api.html](https://youseetoo.github.io/imswitch/api.html).
|
|
36
|
+
- **Open Source**: Inspired by the OpenFlexure Client, `ImSwitchClient` is freely available for modification and distribution under the MIT license.
|
|
37
|
+
- **Implemented functions** (so far, please file an issue for feature requests):
|
|
38
|
+
- Laser
|
|
39
|
+
- Stage
|
|
40
|
+
- Image Acquisition
|
|
41
|
+
- You can test the client with the globally hosted api here: https://youseetoo.github.io/imswitch/api.html
|
|
42
|
+
- It is inspired by the OpenFlexure Client: https://gitlab.com/openflexure/openflexure-microscope-pyclient/-/blob/master/openflexure_microscope_client/microscope_client.py
|
|
43
|
+
- The source files can be found here: https://github.com/openUC2/imswitchclient/
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
To install `ImSwitchClient`, use the following pip command:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install imswitchclient
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quick Start Example
|
|
54
|
+
|
|
55
|
+
This example demonstrates basic usage of `ImSwitchClient` for moving a positioner and acquiring an image.
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
import imswitchclient.ImSwitchClient as imc
|
|
59
|
+
import numpy as np
|
|
60
|
+
import matplotlib.pyplot as plt
|
|
61
|
+
import time
|
|
62
|
+
|
|
63
|
+
# Initialize the client
|
|
64
|
+
client = imc.ImSwitchClient()
|
|
65
|
+
|
|
66
|
+
# Retrieve the first positioner's name and current position
|
|
67
|
+
positioner_names = client.positionersManager.getAllDeviceNames()
|
|
68
|
+
positioner_name = positioner_names[0]
|
|
69
|
+
currentPositions = client.positionersManager.getPositionerPositions()[positioner_name]
|
|
70
|
+
initialPosition = (currentPositions["X"], currentPositions["Y"])
|
|
71
|
+
|
|
72
|
+
# turn on illumination
|
|
73
|
+
mLaserName = client.lasersManager.getLaserNames()[0]
|
|
74
|
+
client.lasersManager.setLaserActive(mLaserName, True)
|
|
75
|
+
client.lasersManager.setLaserValue(mLaserName, 512)
|
|
76
|
+
|
|
77
|
+
for ix in range(10):
|
|
78
|
+
for iy in range(10):
|
|
79
|
+
# Define and move to a new position
|
|
80
|
+
newPosition = (initialPosition[0] + ix*50, initialPosition[1] + iy*50)
|
|
81
|
+
client.positionersManager.movePositioner(positioner_name, "X", newPosition[0], is_absolute=True, is_blocking=True)
|
|
82
|
+
client.positionersManager.movePositioner(positioner_name, "Y", newPosition[1], is_absolute=True, is_blocking=True)
|
|
83
|
+
|
|
84
|
+
# Acquire and display an image
|
|
85
|
+
#time.sleep(0.5) # Allow time for the move
|
|
86
|
+
lastFrame = client.recordingManager.snapNumpyToFastAPI()
|
|
87
|
+
plt.imshow(lastFrame)
|
|
88
|
+
plt.show()
|
|
89
|
+
|
|
90
|
+
# Return the positioner to its initial position
|
|
91
|
+
client.positionersManager.movePositioner(positioner_name, "X", initialPosition[0], is_absolute=True, is_blocking=True)
|
|
92
|
+
client.positionersManager.movePositioner(positioner_name, "Y", initialPosition[1], is_absolute=True, is_blocking=True)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Contributing
|
|
96
|
+
|
|
97
|
+
Contributions to `ImSwitchClient` are welcome! Please refer to the project's GitHub repository for contribution guidelines: [https://github.com/openUC2/imswitchclient/](https://github.com/openUC2/imswitchclient/).
|
|
98
|
+
|
|
99
|
+
## License
|
|
100
|
+
|
|
101
|
+
`ImSwitchClient` is licensed under the MIT License. For more details, see the LICENSE file in the project repository.
|
|
102
|
+
|
|
@@ -3,7 +3,7 @@ CONTRIBUTING.rst
|
|
|
3
3
|
HISTORY.rst
|
|
4
4
|
LICENSE
|
|
5
5
|
MANIFEST.in
|
|
6
|
-
README.
|
|
6
|
+
README.md
|
|
7
7
|
setup.cfg
|
|
8
8
|
setup.py
|
|
9
9
|
docs/Makefile
|
|
@@ -19,6 +19,9 @@ docs/usage.rst
|
|
|
19
19
|
imswitchclient/ImSwitchClient.py
|
|
20
20
|
imswitchclient/__init__.py
|
|
21
21
|
imswitchclient/client.py
|
|
22
|
+
imswitchclient/lasersManager.py
|
|
23
|
+
imswitchclient/positionersManager.py
|
|
24
|
+
imswitchclient/recordingManager.py
|
|
22
25
|
imswitchclient.egg-info/PKG-INFO
|
|
23
26
|
imswitchclient.egg-info/SOURCES.txt
|
|
24
27
|
imswitchclient.egg-info/dependency_links.txt
|
|
@@ -8,8 +8,8 @@ search = version='{current_version}'
|
|
|
8
8
|
replace = version='{new_version}'
|
|
9
9
|
|
|
10
10
|
[bumpversion:file:imswitchclient/__init__.py]
|
|
11
|
-
search = __version__ = "v0.
|
|
12
|
-
replace = __version__ = "v0.
|
|
11
|
+
search = __version__ = "v0.1.2"#'{current_version}'
|
|
12
|
+
replace = __version__ = "v0.1.2"#'{new_version}'
|
|
13
13
|
|
|
14
14
|
[bdist_wheel]
|
|
15
15
|
universal = 1
|
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
from setuptools import setup, find_packages
|
|
6
6
|
|
|
7
|
-
with open('README.
|
|
7
|
+
with open('README.md') as readme_file:
|
|
8
8
|
readme = readme_file.read()
|
|
9
9
|
|
|
10
|
-
with open(
|
|
11
|
-
history =
|
|
10
|
+
with open("HISTORY.rst", "r") as fh:
|
|
11
|
+
history = fh.read()
|
|
12
12
|
|
|
13
13
|
requirements = [ ]
|
|
14
14
|
|
|
@@ -31,7 +31,8 @@ setup(
|
|
|
31
31
|
description="This is a package that connects ImSwitch's REST API to the rest of the world (e.g. jupyter lab)",
|
|
32
32
|
install_requires=requirements,
|
|
33
33
|
license="MIT license",
|
|
34
|
-
long_description=
|
|
34
|
+
long_description=open('README.md').read(),
|
|
35
|
+
long_description_content_type='text/markdown',
|
|
35
36
|
include_package_data=True,
|
|
36
37
|
keywords='imswitchclient',
|
|
37
38
|
name='imswitchclient',
|
|
@@ -39,6 +40,6 @@ setup(
|
|
|
39
40
|
test_suite='tests',
|
|
40
41
|
tests_require=test_requirements,
|
|
41
42
|
url='https://github.com/beniroquai/imswitchclient',
|
|
42
|
-
version='0.1.
|
|
43
|
+
version='0.1.2',
|
|
43
44
|
zip_safe=False,
|
|
44
45
|
)
|
imswitchclient-0.1.0/PKG-INFO
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: imswitchclient
|
|
3
|
-
Version: 0.1.0
|
|
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/beniroquai/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
|
-
License-File: LICENSE
|
|
20
|
-
License-File: AUTHORS.rst
|
|
21
|
-
|
|
22
|
-
==============
|
|
23
|
-
ImSwitchClient
|
|
24
|
-
==============
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
.. image:: https://img.shields.io/pypi/v/imswitchclient.svg
|
|
28
|
-
:target: https://pypi.python.org/pypi/imswitchclient
|
|
29
|
-
|
|
30
|
-
.. image:: https://img.shields.io/travis/beniroquai/imswitchclient.svg
|
|
31
|
-
:target: https://travis-ci.com/beniroquai/imswitchclient
|
|
32
|
-
|
|
33
|
-
.. image:: https://readthedocs.org/projects/imswitchclient/badge/?version=latest
|
|
34
|
-
:target: https://imswitchclient.readthedocs.io/en/latest/?version=latest
|
|
35
|
-
:alt: Documentation Status
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
.. image:: https://pyup.io/repos/github/beniroquai/imswitchclient/shield.svg
|
|
39
|
-
:target: https://pyup.io/repos/github/beniroquai/imswitchclient/
|
|
40
|
-
:alt: Updates
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
This is a package that connects ImSwitch's REST API to the rest of the world (e.g. jupyter lab)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
* Free software: MIT license
|
|
48
|
-
* Documentation: https://imswitchclient.readthedocs.io.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
Features
|
|
52
|
-
--------
|
|
53
|
-
|
|
54
|
-
* TODO
|
|
55
|
-
|
|
56
|
-
Credits
|
|
57
|
-
-------
|
|
58
|
-
|
|
59
|
-
This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template.
|
|
60
|
-
|
|
61
|
-
.. _Cookiecutter: https://github.com/audreyr/cookiecutter
|
|
62
|
-
.. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
=======
|
|
66
|
-
History
|
|
67
|
-
=======
|
|
68
|
-
|
|
69
|
-
0.1.0 (2023-07-03)
|
|
70
|
-
------------------
|
|
71
|
-
|
|
72
|
-
* First release on PyPI.
|
imswitchclient-0.1.0/README.rst
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
==============
|
|
2
|
-
ImSwitchClient
|
|
3
|
-
==============
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
.. image:: https://img.shields.io/pypi/v/imswitchclient.svg
|
|
7
|
-
:target: https://pypi.python.org/pypi/imswitchclient
|
|
8
|
-
|
|
9
|
-
.. image:: https://img.shields.io/travis/beniroquai/imswitchclient.svg
|
|
10
|
-
:target: https://travis-ci.com/beniroquai/imswitchclient
|
|
11
|
-
|
|
12
|
-
.. image:: https://readthedocs.org/projects/imswitchclient/badge/?version=latest
|
|
13
|
-
:target: https://imswitchclient.readthedocs.io/en/latest/?version=latest
|
|
14
|
-
:alt: Documentation Status
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
.. image:: https://pyup.io/repos/github/beniroquai/imswitchclient/shield.svg
|
|
18
|
-
:target: https://pyup.io/repos/github/beniroquai/imswitchclient/
|
|
19
|
-
:alt: Updates
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
This is a package that connects ImSwitch's REST API to the rest of the world (e.g. jupyter lab)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
* Free software: MIT license
|
|
27
|
-
* Documentation: https://imswitchclient.readthedocs.io.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
Features
|
|
31
|
-
--------
|
|
32
|
-
|
|
33
|
-
* TODO
|
|
34
|
-
|
|
35
|
-
Credits
|
|
36
|
-
-------
|
|
37
|
-
|
|
38
|
-
This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template.
|
|
39
|
-
|
|
40
|
-
.. _Cookiecutter: https://github.com/audreyr/cookiecutter
|
|
41
|
-
.. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import requests
|
|
2
|
-
import json
|
|
3
|
-
import time
|
|
4
|
-
import io
|
|
5
|
-
import PIL.Image
|
|
6
|
-
import numpy as np
|
|
7
|
-
import logging
|
|
8
|
-
import zeroconf
|
|
9
|
-
import os
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class ImSwitchClient(object):
|
|
13
|
-
def __init__(self, host="localhost", port=8000):
|
|
14
|
-
self.host = host
|
|
15
|
-
self.port = port
|
|
16
|
-
self.get_json(self.base_uri+"/openapi.json")
|
|
17
|
-
logging.info(f"Connecting to microscope {self.host}:{self.port}")
|
|
18
|
-
|
|
19
|
-
@property
|
|
20
|
-
def base_uri(self):
|
|
21
|
-
return f"http://{self.host}:{self.port}"
|
|
22
|
-
|
|
23
|
-
def get_json(self, path, payload={}, headers={}):
|
|
24
|
-
"""Perform an HTTP GET request and return the JSON response"""
|
|
25
|
-
if not path.startswith("http"):
|
|
26
|
-
path = self.base_uri + path
|
|
27
|
-
r = requests.get(path, params=payload, headers=headers)
|
|
28
|
-
r.raise_for_status()
|
|
29
|
-
return r.json()
|
|
30
|
-
|
|
31
|
-
def post_json(self, path, payload={}, headers={}, wait_on_task="auto"):
|
|
32
|
-
"""Make an HTTP POST request and return the JSON response"""
|
|
33
|
-
if not path.startswith("http"):
|
|
34
|
-
path = self.base_uri + path
|
|
35
|
-
r = requests.post(path, json=payload, headers=headers)
|
|
36
|
-
r.raise_for_status()
|
|
37
|
-
r = r.json()
|
|
38
|
-
return r
|
|
39
|
-
|
|
40
|
-
def move_positioner(self, positioner_name, axis, dist, is_absolute=True, is_blocking=True):
|
|
41
|
-
url = f"{self.base_uri}/PositionerController/movePositioner"
|
|
42
|
-
payload = {
|
|
43
|
-
'positionerName': positioner_name,
|
|
44
|
-
'axis': axis,
|
|
45
|
-
'dist': dist,
|
|
46
|
-
'isAbsolute': is_absolute,
|
|
47
|
-
'isBlocking': is_blocking
|
|
48
|
-
}
|
|
49
|
-
#headers = {'accept': 'application/json'}
|
|
50
|
-
|
|
51
|
-
response = self.get_json(url, payload=payload)
|
|
52
|
-
return response
|
|
53
|
-
|
|
54
|
-
def snap_numpy_to_fastapi(self):
|
|
55
|
-
url = f"{self.base_uri}/RecordingController/snapNumpyToFastAPI"
|
|
56
|
-
headers = {'accept': 'application/json'}
|
|
57
|
-
|
|
58
|
-
response = requests.get(url, headers=headers)
|
|
59
|
-
response.raise_for_status()
|
|
60
|
-
content_type = response.headers.get('content-type')
|
|
61
|
-
if content_type == 'image/png':
|
|
62
|
-
image_data = response.content
|
|
63
|
-
image = PIL.Image.open(io.BytesIO(image_data))
|
|
64
|
-
return np.array(image)
|
|
65
|
-
else:
|
|
66
|
-
raise ValueError("Unexpected response content-type. Expected 'image/png'.")
|
|
67
|
-
|
|
68
|
-
def get_positioner_names(self):
|
|
69
|
-
url = f"{self.base_uri}/PositionerController/getPositionerNames"
|
|
70
|
-
headers = {'accept': 'application/json'}
|
|
71
|
-
|
|
72
|
-
response = self.get_json(url, headers=headers)
|
|
73
|
-
return response
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def task_href(t):
|
|
78
|
-
"""Extract the endpoint address from a task dictionary"""
|
|
79
|
-
return t["links"]["self"]["href"]
|
|
80
|
-
|
|
81
|
-
def is_a_task(t):
|
|
82
|
-
"""Return true if a parsed JSON return value represents a task"""
|
|
83
|
-
self_href = task_href(t)
|
|
84
|
-
try:
|
|
85
|
-
return ("/api/v2/tasks/" in self_href or "/api/v2/actions/" in self_href)
|
|
86
|
-
except:
|
|
87
|
-
return False
|
|
88
|
-
|
|
89
|
-
def poll_task(task):
|
|
90
|
-
"""Poll a task until it finishes, and return the return value"""
|
|
91
|
-
assert is_a_task(task), ("poll_task must be called with a "
|
|
92
|
-
"parsed JSON representation of a task")
|
|
93
|
-
log_n = 0
|
|
94
|
-
while task["status"] in ACTION_RUNNING_KEYWORDS:
|
|
95
|
-
r = requests.get(task_href(task))
|
|
96
|
-
r.raise_for_status()
|
|
97
|
-
task = r.json()
|
|
98
|
-
while len(task["log"]) > log_n:
|
|
99
|
-
d = task["log"][log_n]
|
|
100
|
-
logging.log(d["levelno"], d["message"])
|
|
101
|
-
log_n += 1
|
|
102
|
-
for output_key in ACTION_OUTPUT_KEYS:
|
|
103
|
-
if output_key in task:
|
|
104
|
-
return task[output_key]
|
|
105
|
-
return None
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: imswitchclient
|
|
3
|
-
Version: 0.1.0
|
|
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/beniroquai/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
|
-
License-File: LICENSE
|
|
20
|
-
License-File: AUTHORS.rst
|
|
21
|
-
|
|
22
|
-
==============
|
|
23
|
-
ImSwitchClient
|
|
24
|
-
==============
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
.. image:: https://img.shields.io/pypi/v/imswitchclient.svg
|
|
28
|
-
:target: https://pypi.python.org/pypi/imswitchclient
|
|
29
|
-
|
|
30
|
-
.. image:: https://img.shields.io/travis/beniroquai/imswitchclient.svg
|
|
31
|
-
:target: https://travis-ci.com/beniroquai/imswitchclient
|
|
32
|
-
|
|
33
|
-
.. image:: https://readthedocs.org/projects/imswitchclient/badge/?version=latest
|
|
34
|
-
:target: https://imswitchclient.readthedocs.io/en/latest/?version=latest
|
|
35
|
-
:alt: Documentation Status
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
.. image:: https://pyup.io/repos/github/beniroquai/imswitchclient/shield.svg
|
|
39
|
-
:target: https://pyup.io/repos/github/beniroquai/imswitchclient/
|
|
40
|
-
:alt: Updates
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
This is a package that connects ImSwitch's REST API to the rest of the world (e.g. jupyter lab)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
* Free software: MIT license
|
|
48
|
-
* Documentation: https://imswitchclient.readthedocs.io.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
Features
|
|
52
|
-
--------
|
|
53
|
-
|
|
54
|
-
* TODO
|
|
55
|
-
|
|
56
|
-
Credits
|
|
57
|
-
-------
|
|
58
|
-
|
|
59
|
-
This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template.
|
|
60
|
-
|
|
61
|
-
.. _Cookiecutter: https://github.com/audreyr/cookiecutter
|
|
62
|
-
.. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
=======
|
|
66
|
-
History
|
|
67
|
-
=======
|
|
68
|
-
|
|
69
|
-
0.1.0 (2023-07-03)
|
|
70
|
-
------------------
|
|
71
|
-
|
|
72
|
-
* First release on PyPI.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|