puda-drivers 0.0.10__tar.gz → 0.0.12__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.
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/PKG-INFO +11 -16
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/README.md +10 -15
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/pyproject.toml +1 -1
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/src/puda_drivers/labware/polyelectric_8_wellplate_30000ul.json +1 -1
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/src/puda_drivers/labware/trash_bin.json +1 -1
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/src/puda_drivers/machines/first.py +174 -15
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/tests/first.py +12 -11
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/uv.lock +1 -1
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/.gitignore +0 -0
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/LICENSE +0 -0
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/src/puda_drivers/__init__.py +0 -0
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/src/puda_drivers/core/__init__.py +0 -0
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/src/puda_drivers/core/logging.py +0 -0
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/src/puda_drivers/core/position.py +0 -0
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/src/puda_drivers/core/serialcontroller.py +0 -0
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/src/puda_drivers/cv/__init__.py +0 -0
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/src/puda_drivers/cv/camera.py +0 -0
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/src/puda_drivers/labware/__init__.py +0 -0
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/src/puda_drivers/labware/labware.py +0 -0
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/src/puda_drivers/labware/opentrons_96_tiprack_300ul.json +0 -0
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/src/puda_drivers/machines/__init__.py +0 -0
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/src/puda_drivers/move/__init__.py +0 -0
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/src/puda_drivers/move/deck.py +0 -0
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/src/puda_drivers/move/gcode.py +0 -0
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/src/puda_drivers/move/grbl/__init__.py +0 -0
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/src/puda_drivers/move/grbl/api.py +0 -0
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/src/puda_drivers/move/grbl/constants.py +0 -0
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/src/puda_drivers/py.typed +0 -0
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/src/puda_drivers/transfer/liquid/sartorius/__init__.py +0 -0
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/src/puda_drivers/transfer/liquid/sartorius/api.py +0 -0
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/src/puda_drivers/transfer/liquid/sartorius/constants.py +0 -0
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/src/puda_drivers/transfer/liquid/sartorius/sartorius.py +0 -0
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/tests/example.py +0 -0
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/tests/pipette.py +0 -0
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/tests/qubot.py +0 -0
- {puda_drivers-0.0.10 → puda_drivers-0.0.12}/tests/webcam.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: puda-drivers
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.12
|
|
4
4
|
Summary: Hardware drivers for the PUDA platform.
|
|
5
5
|
Project-URL: Homepage, https://github.com/zhao-bears/puda-drivers
|
|
6
6
|
Project-URL: Issues, https://github.com/zhao-bears/puda-drivers/issues
|
|
@@ -85,6 +85,7 @@ When file logging is enabled, logs are saved to timestamped files (unless a cust
|
|
|
85
85
|
The `First` machine integrates motion control, deck management, liquid handling, and camera capabilities:
|
|
86
86
|
|
|
87
87
|
```python
|
|
88
|
+
import time
|
|
88
89
|
import logging
|
|
89
90
|
from puda_drivers.machines import First
|
|
90
91
|
from puda_drivers.core.logging import setup_logging
|
|
@@ -102,14 +103,8 @@ machine = First(
|
|
|
102
103
|
camera_index=0,
|
|
103
104
|
)
|
|
104
105
|
|
|
105
|
-
#
|
|
106
|
-
machine.
|
|
107
|
-
|
|
108
|
-
# Home the gantry
|
|
109
|
-
machine.qubot.home()
|
|
110
|
-
|
|
111
|
-
# Initialize the pipette
|
|
112
|
-
machine.pipette.initialize()
|
|
106
|
+
# Start up the machine (connects all controllers, homes gantry, and initializes pipette)
|
|
107
|
+
machine.startup()
|
|
113
108
|
|
|
114
109
|
# Load labware onto the deck
|
|
115
110
|
machine.load_deck({
|
|
@@ -119,19 +114,19 @@ machine.load_deck({
|
|
|
119
114
|
})
|
|
120
115
|
|
|
121
116
|
# Start video recording
|
|
122
|
-
machine.
|
|
117
|
+
machine.start_video_recording()
|
|
123
118
|
|
|
124
119
|
# Perform liquid handling operations
|
|
125
120
|
machine.attach_tip(slot="A3", well="G8")
|
|
126
|
-
machine.aspirate_from(slot="C2", well="A1", amount=100)
|
|
127
|
-
machine.dispense_to(slot="C2", well="B4", amount=100)
|
|
128
|
-
machine.drop_tip(slot="C1", well="A1")
|
|
121
|
+
machine.aspirate_from(slot="C2", well="A1", amount=100, height_from_bottom=10.0)
|
|
122
|
+
machine.dispense_to(slot="C2", well="B4", amount=100, height_from_bottom=30.0)
|
|
123
|
+
machine.drop_tip(slot="C1", well="A1", height_from_bottom=10)
|
|
129
124
|
|
|
130
125
|
# Stop video recording
|
|
131
|
-
machine.
|
|
126
|
+
machine.stop_video_recording()
|
|
132
127
|
|
|
133
|
-
#
|
|
134
|
-
machine.
|
|
128
|
+
# Shutdown the machine (gracefully disconnects all controllers)
|
|
129
|
+
machine.shutdown()
|
|
135
130
|
```
|
|
136
131
|
|
|
137
132
|
**Discovering Available Methods**: To explore what methods are available on any class instance, you can use Python's built-in `help()` function:
|
|
@@ -64,6 +64,7 @@ When file logging is enabled, logs are saved to timestamped files (unless a cust
|
|
|
64
64
|
The `First` machine integrates motion control, deck management, liquid handling, and camera capabilities:
|
|
65
65
|
|
|
66
66
|
```python
|
|
67
|
+
import time
|
|
67
68
|
import logging
|
|
68
69
|
from puda_drivers.machines import First
|
|
69
70
|
from puda_drivers.core.logging import setup_logging
|
|
@@ -81,14 +82,8 @@ machine = First(
|
|
|
81
82
|
camera_index=0,
|
|
82
83
|
)
|
|
83
84
|
|
|
84
|
-
#
|
|
85
|
-
machine.
|
|
86
|
-
|
|
87
|
-
# Home the gantry
|
|
88
|
-
machine.qubot.home()
|
|
89
|
-
|
|
90
|
-
# Initialize the pipette
|
|
91
|
-
machine.pipette.initialize()
|
|
85
|
+
# Start up the machine (connects all controllers, homes gantry, and initializes pipette)
|
|
86
|
+
machine.startup()
|
|
92
87
|
|
|
93
88
|
# Load labware onto the deck
|
|
94
89
|
machine.load_deck({
|
|
@@ -98,19 +93,19 @@ machine.load_deck({
|
|
|
98
93
|
})
|
|
99
94
|
|
|
100
95
|
# Start video recording
|
|
101
|
-
machine.
|
|
96
|
+
machine.start_video_recording()
|
|
102
97
|
|
|
103
98
|
# Perform liquid handling operations
|
|
104
99
|
machine.attach_tip(slot="A3", well="G8")
|
|
105
|
-
machine.aspirate_from(slot="C2", well="A1", amount=100)
|
|
106
|
-
machine.dispense_to(slot="C2", well="B4", amount=100)
|
|
107
|
-
machine.drop_tip(slot="C1", well="A1")
|
|
100
|
+
machine.aspirate_from(slot="C2", well="A1", amount=100, height_from_bottom=10.0)
|
|
101
|
+
machine.dispense_to(slot="C2", well="B4", amount=100, height_from_bottom=30.0)
|
|
102
|
+
machine.drop_tip(slot="C1", well="A1", height_from_bottom=10)
|
|
108
103
|
|
|
109
104
|
# Stop video recording
|
|
110
|
-
machine.
|
|
105
|
+
machine.stop_video_recording()
|
|
111
106
|
|
|
112
|
-
#
|
|
113
|
-
machine.
|
|
107
|
+
# Shutdown the machine (gracefully disconnects all controllers)
|
|
108
|
+
machine.shutdown()
|
|
114
109
|
```
|
|
115
110
|
|
|
116
111
|
**Discovering Available Methods**: To explore what methods are available on any class instance, you can use Python's built-in `help()` function:
|
|
@@ -9,7 +9,9 @@ This class demonstrates the integration of:
|
|
|
9
9
|
|
|
10
10
|
import logging
|
|
11
11
|
import time
|
|
12
|
+
from pathlib import Path
|
|
12
13
|
from typing import Optional, Dict, Tuple, Type, Union
|
|
14
|
+
import numpy as np
|
|
13
15
|
from puda_drivers.move import GCodeController, Deck
|
|
14
16
|
from puda_drivers.core import Position
|
|
15
17
|
from puda_drivers.transfer.liquid.sartorius import SartoriusController
|
|
@@ -118,21 +120,44 @@ class First:
|
|
|
118
120
|
camera_index if camera_index is not None else self.DEFAULT_CAMERA_INDEX,
|
|
119
121
|
)
|
|
120
122
|
|
|
121
|
-
def
|
|
122
|
-
"""
|
|
123
|
-
|
|
123
|
+
def startup(self):
|
|
124
|
+
"""
|
|
125
|
+
Start up the machine by connecting all controllers and initializing subsystems.
|
|
126
|
+
|
|
127
|
+
This method:
|
|
128
|
+
- Connects to all controllers (gantry, pipette, camera)
|
|
129
|
+
- Homes the gantry to establish a known position
|
|
130
|
+
- Initializes the pipette to reset it to a known state
|
|
131
|
+
|
|
132
|
+
The machine is ready for operations after this method completes.
|
|
133
|
+
"""
|
|
134
|
+
self._logger.info("Starting up machine and connecting all controllers")
|
|
124
135
|
self.qubot.connect()
|
|
125
136
|
self.pipette.connect()
|
|
126
137
|
self.camera.connect()
|
|
127
138
|
self._logger.info("All controllers connected successfully")
|
|
128
139
|
|
|
129
|
-
|
|
130
|
-
"
|
|
131
|
-
self.
|
|
140
|
+
# Home the gantry to establish known position
|
|
141
|
+
self._logger.info("Homing gantry...")
|
|
142
|
+
self.qubot.home()
|
|
143
|
+
|
|
144
|
+
# Initialize the pipette (all pipette operations need to wait 5 seconds for completion)
|
|
145
|
+
self._logger.info("Initializing pipette...")
|
|
146
|
+
self.pipette.initialize()
|
|
147
|
+
time.sleep(5)
|
|
148
|
+
self._logger.info("Machine startup complete - ready for operations")
|
|
149
|
+
|
|
150
|
+
def shutdown(self):
|
|
151
|
+
"""
|
|
152
|
+
Gracefully shut down the machine by disconnecting all controllers.
|
|
153
|
+
|
|
154
|
+
This method ensures all connections are properly closed and resources are released.
|
|
155
|
+
"""
|
|
156
|
+
self._logger.info("Shutting down machine and disconnecting all controllers")
|
|
132
157
|
self.qubot.disconnect()
|
|
133
158
|
self.pipette.disconnect()
|
|
134
159
|
self.camera.disconnect()
|
|
135
|
-
self._logger.info("
|
|
160
|
+
self._logger.info("Machine shutdown complete")
|
|
136
161
|
|
|
137
162
|
def load_labware(self, slot: str, labware_name: str):
|
|
138
163
|
"""Load a labware object into a slot."""
|
|
@@ -185,14 +210,30 @@ class First:
|
|
|
185
210
|
self.qubot.home(axis="Z")
|
|
186
211
|
self._logger.debug("Z axis homed after tip attachment")
|
|
187
212
|
|
|
188
|
-
def drop_tip(self, slot: str, well: str):
|
|
189
|
-
"""
|
|
213
|
+
def drop_tip(self, slot: str, well: str, height_from_bottom: float = 0.0):
|
|
214
|
+
"""
|
|
215
|
+
Drop a tip into a slot.
|
|
216
|
+
|
|
217
|
+
Args:
|
|
218
|
+
slot: Slot name (e.g., 'A1', 'B2')
|
|
219
|
+
well: Well name within the slot (e.g., 'A1' for a well in a tiprack)
|
|
220
|
+
height_from_bottom: Height from the bottom of the well in mm. Defaults to 0.0.
|
|
221
|
+
Positive values move up from the bottom. Negative values
|
|
222
|
+
may cause a ValueError if the resulting position is outside
|
|
223
|
+
the Z axis limits.
|
|
224
|
+
|
|
225
|
+
Raises:
|
|
226
|
+
ValueError: If no tip is attached, or if the resulting position is outside
|
|
227
|
+
the Z axis limits.
|
|
228
|
+
"""
|
|
190
229
|
if not self.pipette.is_tip_attached():
|
|
191
230
|
self._logger.error("Cannot drop tip: no tip attached")
|
|
192
231
|
raise ValueError("Tip not attached")
|
|
193
232
|
|
|
194
233
|
self._logger.info("Dropping tip into slot '%s', well '%s'", slot, well)
|
|
195
234
|
pos = self.get_absolute_z_position(slot, well)
|
|
235
|
+
# add height from bottom
|
|
236
|
+
pos += Position(z=height_from_bottom)
|
|
196
237
|
# move up by the tip length
|
|
197
238
|
pos += Position(z=self.TIP_LENGTH)
|
|
198
239
|
self._logger.debug("Moving to position %s (adjusted for tip length) for tip drop", pos)
|
|
@@ -204,31 +245,67 @@ class First:
|
|
|
204
245
|
self.pipette.set_tip_attached(attached=False)
|
|
205
246
|
self._logger.info("Tip dropped successfully")
|
|
206
247
|
|
|
207
|
-
def aspirate_from(self, slot:str, well:str, amount:int):
|
|
208
|
-
"""
|
|
248
|
+
def aspirate_from(self, slot:str, well:str, amount:int, height_from_bottom: float = 0.0):
|
|
249
|
+
"""
|
|
250
|
+
Aspirate a volume of liquid from a slot.
|
|
251
|
+
|
|
252
|
+
Args:
|
|
253
|
+
slot: Slot name (e.g., 'A1', 'B2')
|
|
254
|
+
well: Well name within the slot (e.g., 'A1')
|
|
255
|
+
amount: Volume to aspirate in µL
|
|
256
|
+
height_from_bottom: Height from the bottom of the well in mm. Defaults to 0.0.
|
|
257
|
+
Positive values move up from the bottom. Negative values
|
|
258
|
+
may cause a ValueError if the resulting position is outside
|
|
259
|
+
the Z axis limits.
|
|
260
|
+
|
|
261
|
+
Raises:
|
|
262
|
+
ValueError: If no tip is attached, or if the resulting position is outside
|
|
263
|
+
the Z axis limits.
|
|
264
|
+
"""
|
|
209
265
|
if not self.pipette.is_tip_attached():
|
|
210
266
|
self._logger.error("Cannot aspirate: no tip attached")
|
|
211
267
|
raise ValueError("Tip not attached")
|
|
212
268
|
|
|
213
269
|
self._logger.info("Aspirating %d µL from slot '%s', well '%s'", amount, slot, well)
|
|
214
270
|
pos = self.get_absolute_z_position(slot, well)
|
|
271
|
+
# add height from bottom
|
|
272
|
+
pos += Position(z=height_from_bottom)
|
|
215
273
|
self._logger.debug("Moving Z axis to position %s", pos)
|
|
216
274
|
self.qubot.move_absolute(position=pos)
|
|
275
|
+
|
|
217
276
|
self._logger.debug("Aspirating %d µL", amount)
|
|
218
277
|
self.pipette.aspirate(amount=amount)
|
|
219
278
|
time.sleep(5)
|
|
220
279
|
self._logger.info("Aspiration completed: %d µL from slot '%s', well '%s'", amount, slot, well)
|
|
221
280
|
|
|
222
|
-
def dispense_to(self, slot:str, well:str, amount:int):
|
|
223
|
-
"""
|
|
281
|
+
def dispense_to(self, slot:str, well:str, amount:int, height_from_bottom: float = 0.0):
|
|
282
|
+
"""
|
|
283
|
+
Dispense a volume of liquid to a slot.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
slot: Slot name (e.g., 'A1', 'B2')
|
|
287
|
+
well: Well name within the slot (e.g., 'A1')
|
|
288
|
+
amount: Volume to dispense in µL
|
|
289
|
+
height_from_bottom: Height from the bottom of the well in mm. Defaults to 0.0.
|
|
290
|
+
Positive values move up from the bottom. Negative values
|
|
291
|
+
may cause a ValueError if the resulting position is outside
|
|
292
|
+
the Z axis limits.
|
|
293
|
+
|
|
294
|
+
Raises:
|
|
295
|
+
ValueError: If no tip is attached, or if the resulting position is outside
|
|
296
|
+
the Z axis limits.
|
|
297
|
+
"""
|
|
224
298
|
if not self.pipette.is_tip_attached():
|
|
225
299
|
self._logger.error("Cannot dispense: no tip attached")
|
|
226
300
|
raise ValueError("Tip not attached")
|
|
227
301
|
|
|
228
302
|
self._logger.info("Dispensing %d µL to slot '%s', well '%s'", amount, slot, well)
|
|
229
303
|
pos = self.get_absolute_z_position(slot, well)
|
|
304
|
+
# add height from bottom
|
|
305
|
+
pos += Position(z=height_from_bottom)
|
|
230
306
|
self._logger.debug("Moving Z axis to position %s", pos)
|
|
231
307
|
self.qubot.move_absolute(position=pos)
|
|
308
|
+
|
|
232
309
|
self._logger.debug("Dispensing %d µL", amount)
|
|
233
310
|
self.pipette.dispense(amount=amount)
|
|
234
311
|
time.sleep(5)
|
|
@@ -275,8 +352,7 @@ class First:
|
|
|
275
352
|
# the deck is rotated 90 degrees clockwise for this machine
|
|
276
353
|
pos += well_pos.swap_xy()
|
|
277
354
|
# get z
|
|
278
|
-
|
|
279
|
-
pos += z
|
|
355
|
+
pos += Position(z=self.deck[slot].get_height() - self.CEILING_HEIGHT)
|
|
280
356
|
self._logger.debug("Absolute Z position for slot '%s', well '%s': %s", slot, well, pos)
|
|
281
357
|
else:
|
|
282
358
|
self._logger.debug("Absolute Z position for slot '%s': %s", slot, pos)
|
|
@@ -299,3 +375,86 @@ class First:
|
|
|
299
375
|
else:
|
|
300
376
|
self._logger.debug("Absolute A position for slot '%s': %s", slot, pos)
|
|
301
377
|
return pos
|
|
378
|
+
|
|
379
|
+
def start_video_recording(
|
|
380
|
+
self,
|
|
381
|
+
filename: Optional[Union[str, Path]] = None,
|
|
382
|
+
fps: Optional[float] = None
|
|
383
|
+
) -> Path:
|
|
384
|
+
"""
|
|
385
|
+
Start recording a video.
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
filename: Optional filename for the video. If not provided, a timestamped
|
|
389
|
+
filename will be generated. If provided without extension, .mp4 will be added.
|
|
390
|
+
fps: Optional frames per second for the video. Defaults to 30.0 if not specified.
|
|
391
|
+
|
|
392
|
+
Returns:
|
|
393
|
+
Path to the video file where recording is being saved
|
|
394
|
+
|
|
395
|
+
Raises:
|
|
396
|
+
IOError: If camera is not connected or recording fails to start
|
|
397
|
+
ValueError: If already recording
|
|
398
|
+
"""
|
|
399
|
+
return self.camera.start_video_recording(filename=filename, fps=fps)
|
|
400
|
+
|
|
401
|
+
def stop_video_recording(self) -> Optional[Path]:
|
|
402
|
+
"""
|
|
403
|
+
Stop recording a video.
|
|
404
|
+
|
|
405
|
+
Returns:
|
|
406
|
+
Path to the saved video file, or None if no recording was in progress
|
|
407
|
+
|
|
408
|
+
Raises:
|
|
409
|
+
IOError: If video writer fails to release
|
|
410
|
+
"""
|
|
411
|
+
return self.camera.stop_video_recording()
|
|
412
|
+
|
|
413
|
+
def record_video(
|
|
414
|
+
self,
|
|
415
|
+
duration_seconds: float,
|
|
416
|
+
filename: Optional[Union[str, Path]] = None,
|
|
417
|
+
fps: Optional[float] = None
|
|
418
|
+
) -> Path:
|
|
419
|
+
"""
|
|
420
|
+
Record a video for a specified duration.
|
|
421
|
+
|
|
422
|
+
Args:
|
|
423
|
+
duration_seconds: Duration of the video in seconds
|
|
424
|
+
filename: Optional filename for the video. If not provided, a timestamped
|
|
425
|
+
filename will be generated. If provided without extension, .mp4 will be added.
|
|
426
|
+
fps: Optional frames per second for the video. Defaults to 30.0 if not specified.
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
Path to the saved video file
|
|
430
|
+
|
|
431
|
+
Raises:
|
|
432
|
+
IOError: If camera is not connected or recording fails
|
|
433
|
+
"""
|
|
434
|
+
return self.camera.record_video(
|
|
435
|
+
duration_seconds=duration_seconds,
|
|
436
|
+
filename=filename,
|
|
437
|
+
fps=fps
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
def capture_image(
|
|
441
|
+
self,
|
|
442
|
+
save: bool = False,
|
|
443
|
+
filename: Optional[Union[str, Path]] = None
|
|
444
|
+
) -> np.ndarray:
|
|
445
|
+
"""
|
|
446
|
+
Capture a single image from the camera.
|
|
447
|
+
|
|
448
|
+
Args:
|
|
449
|
+
save: If True, save the image to the captures folder
|
|
450
|
+
filename: Optional filename for the saved image. If not provided and save=True,
|
|
451
|
+
a timestamped filename will be generated. If provided without extension,
|
|
452
|
+
.jpg will be added.
|
|
453
|
+
|
|
454
|
+
Returns:
|
|
455
|
+
Captured image as a numpy array (BGR format)
|
|
456
|
+
|
|
457
|
+
Raises:
|
|
458
|
+
IOError: If camera is not connected or capture fails
|
|
459
|
+
"""
|
|
460
|
+
return self.camera.capture_image(save=save, filename=filename)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Test script for the First machine driver."""
|
|
2
2
|
|
|
3
3
|
import random
|
|
4
|
+
import time
|
|
4
5
|
import logging
|
|
5
6
|
from puda_drivers.machines import First
|
|
6
7
|
from puda_drivers.labware import get_available_labware
|
|
@@ -8,7 +9,7 @@ from puda_drivers.core import setup_logging
|
|
|
8
9
|
|
|
9
10
|
setup_logging(
|
|
10
11
|
enable_file_logging=False,
|
|
11
|
-
log_level=logging.
|
|
12
|
+
log_level=logging.INFO, # Use logging.DEBUG to see all (DEBUG, INFO, WARNING, ERROR, CRITICAL) logs
|
|
12
13
|
)
|
|
13
14
|
|
|
14
15
|
if __name__ == "__main__":
|
|
@@ -34,15 +35,15 @@ if __name__ == "__main__":
|
|
|
34
35
|
print(machine.deck)
|
|
35
36
|
print(machine.deck["C2"])
|
|
36
37
|
|
|
37
|
-
machine.
|
|
38
|
-
machine.qubot.home()
|
|
39
|
-
machine.pipette.initialize()
|
|
38
|
+
machine.startup() # Connects all controllers, homes gantry, and initializes pipette
|
|
40
39
|
|
|
41
|
-
machine.
|
|
40
|
+
# machine.record_video(duration_seconds=10, filename="test.mp4")
|
|
41
|
+
machine.start_video_recording()
|
|
42
42
|
machine.attach_tip(slot="A3", well="G8")
|
|
43
|
-
machine.aspirate_from(slot="C2", well="A1", amount=100)
|
|
44
|
-
machine.
|
|
45
|
-
machine.
|
|
43
|
+
machine.aspirate_from(slot="C2", well="A1", amount=100, height_from_bottom=10)
|
|
44
|
+
# machine.capture_image()
|
|
45
|
+
machine.dispense_to(slot="C2", well="B4", amount=100, height_from_bottom=50)
|
|
46
|
+
machine.drop_tip(slot="C1", well="A1", height_from_bottom=10)
|
|
46
47
|
|
|
47
48
|
# tiprack_wells = machine.deck["A3"].wells
|
|
48
49
|
# # get pick up pipette one by one and drop it in the trash bin
|
|
@@ -58,7 +59,7 @@ if __name__ == "__main__":
|
|
|
58
59
|
# machine.drop_tip("C1", "A1")
|
|
59
60
|
|
|
60
61
|
|
|
61
|
-
machine.
|
|
62
|
+
machine.stop_video_recording()
|
|
62
63
|
|
|
63
|
-
#
|
|
64
|
-
machine.
|
|
64
|
+
# Shutdown machine
|
|
65
|
+
machine.shutdown()
|
|
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
|
{puda_drivers-0.0.10 → puda_drivers-0.0.12}/src/puda_drivers/labware/opentrons_96_tiprack_300ul.json
RENAMED
|
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
|
{puda_drivers-0.0.10 → puda_drivers-0.0.12}/src/puda_drivers/transfer/liquid/sartorius/__init__.py
RENAMED
|
File without changes
|
{puda_drivers-0.0.10 → puda_drivers-0.0.12}/src/puda_drivers/transfer/liquid/sartorius/api.py
RENAMED
|
File without changes
|
{puda_drivers-0.0.10 → puda_drivers-0.0.12}/src/puda_drivers/transfer/liquid/sartorius/constants.py
RENAMED
|
File without changes
|
{puda_drivers-0.0.10 → puda_drivers-0.0.12}/src/puda_drivers/transfer/liquid/sartorius/sartorius.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|