Skip to content

Commit d6a78a9

Browse files
authored
linear_tip_spot_generator is a class (#395)
1 parent 3766be1 commit d6a78a9

File tree

2 files changed

+98
-32
lines changed

2 files changed

+98
-32
lines changed

docs/user_guide/tip-spot-generators.ipynb

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
{
3838
"data": {
3939
"text/plain": [
40-
"TipSpot(name=tip_rack_0_tipspot_0_0, location=(007.200, 068.300, -83.500), size_x=9.0, size_y=9.0, size_z=0, category=tip_spot)"
40+
"TipSpot(name=tip_rack_0_tipspot_0_0, location=Coordinate(007.200, 068.300, -83.500), size_x=9.0, size_y=9.0, size_z=0, category=tip_spot)"
4141
]
4242
},
4343
"execution_count": 2,
@@ -88,7 +88,7 @@
8888
{
8989
"data": {
9090
"text/plain": [
91-
"TipSpot(name=tip_rack_0_tipspot_0_0, location=(007.200, 068.300, -83.500), size_x=9.0, size_y=9.0, size_z=0, category=tip_spot)"
91+
"TipSpot(name=tip_rack_0_tipspot_0_0, location=Coordinate(007.200, 068.300, -83.500), size_x=9.0, size_y=9.0, size_z=0, category=tip_spot)"
9292
]
9393
},
9494
"execution_count": 4,
@@ -129,6 +129,50 @@
129129
"[ts.name for ts in tip_spots]"
130130
]
131131
},
132+
{
133+
"cell_type": "markdown",
134+
"metadata": {},
135+
"source": [
136+
"Save the state of the generator at an arbitrary point by calling `save_state`. This method will be called automatically when the program crashes or is interrupted."
137+
]
138+
},
139+
{
140+
"cell_type": "code",
141+
"execution_count": 6,
142+
"metadata": {},
143+
"outputs": [],
144+
"source": [
145+
"linear_generator.save_state()"
146+
]
147+
},
148+
{
149+
"cell_type": "markdown",
150+
"metadata": {},
151+
"source": [
152+
"Override the index by calling `set_index`."
153+
]
154+
},
155+
{
156+
"cell_type": "code",
157+
"execution_count": 7,
158+
"metadata": {},
159+
"outputs": [
160+
{
161+
"data": {
162+
"text/plain": [
163+
"TipSpot(name=tip_rack_0_tipspot_1_4, location=Coordinate(016.200, 032.300, -83.500), size_x=9.0, size_y=9.0, size_z=0, category=tip_spot)"
164+
]
165+
},
166+
"execution_count": 7,
167+
"metadata": {},
168+
"output_type": "execute_result"
169+
}
170+
],
171+
"source": [
172+
"linear_generator.set_index(12)\n",
173+
"await linear_generator.__anext__()"
174+
]
175+
},
132176
{
133177
"cell_type": "markdown",
134178
"metadata": {},
@@ -140,7 +184,7 @@
140184
},
141185
{
142186
"cell_type": "code",
143-
"execution_count": 6,
187+
"execution_count": 8,
144188
"metadata": {},
145189
"outputs": [],
146190
"source": [
@@ -153,16 +197,16 @@
153197
},
154198
{
155199
"cell_type": "code",
156-
"execution_count": 7,
200+
"execution_count": 9,
157201
"metadata": {},
158202
"outputs": [
159203
{
160204
"data": {
161205
"text/plain": [
162-
"TipSpot(name=tip_rack_0_tipspot_0_2, location=(007.200, 050.300, -83.500), size_x=9.0, size_y=9.0, size_z=0, category=tip_spot)"
206+
"TipSpot(name=tip_rack_0_tipspot_0_3, location=Coordinate(007.200, 041.300, -83.500), size_x=9.0, size_y=9.0, size_z=0, category=tip_spot)"
163207
]
164208
},
165-
"execution_count": 7,
209+
"execution_count": 9,
166210
"metadata": {},
167211
"output_type": "execute_result"
168212
}
@@ -188,7 +232,7 @@
188232
"name": "python",
189233
"nbconvert_exporter": "python",
190234
"pygments_lexer": "ipython3",
191-
"version": "3.9.19"
235+
"version": "3.13.1"
192236
}
193237
},
194238
"nbformat": 4,

pylabrobot/resources/functional.py

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import atexit
12
import json
23
import logging
34
import os
@@ -14,31 +15,52 @@ def get_all_tip_spots(tip_racks: List[TipRack]) -> List[TipSpot]:
1415
return [spot for rack in tip_racks for spot in rack.get_all_items()]
1516

1617

17-
async def linear_tip_spot_generator(
18-
tip_spots: List[TipSpot],
19-
cache_file_path: Optional[str] = None,
20-
repeat: bool = False,
21-
) -> AsyncGenerator[TipSpot, None]:
22-
"""Tip spot generator with disk caching. Linearly iterate through all tip spots and
23-
raise StopIteration when all spots have been used."""
24-
tip_spot_idx = 0
25-
if cache_file_path is not None and os.path.exists(cache_file_path):
26-
with open(cache_file_path, "r", encoding="utf-8") as f:
27-
data = json.load(f)
28-
tip_spot_idx = data["tip_spot_idx"]
29-
logger.info("loaded tip idx from disk: %s", data)
30-
31-
while True:
32-
if cache_file_path is not None:
33-
with open(cache_file_path, "w", encoding="utf-8") as f:
34-
json.dump({"tip_spot_idx": tip_spot_idx}, f)
35-
yield tip_spots[tip_spot_idx]
36-
tip_spot_idx += 1
37-
if tip_spot_idx >= len(tip_spots):
38-
if repeat:
39-
tip_spot_idx = 0
40-
else:
41-
return
18+
class linear_tip_spot_generator:
19+
def __init__(
20+
self, tip_spots: List[TipSpot], cache_file_path: Optional[str] = None, repeat: bool = False
21+
):
22+
self.tip_spots = tip_spots
23+
self.cache_file_path = cache_file_path
24+
self.repeat = repeat
25+
self._tip_spot_idx = 0
26+
27+
if self.cache_file_path and os.path.exists(self.cache_file_path):
28+
try:
29+
with open(self.cache_file_path, "r", encoding="utf-8") as f:
30+
data = json.load(f)
31+
self._tip_spot_idx = data.get("tip_spot_idx", 0)
32+
logger.info("Loaded tip idx from disk: %s", data)
33+
except Exception as e:
34+
logger.error("Failed to load cache file: %s", e)
35+
36+
atexit.register(self.save_state)
37+
38+
def __aiter__(self):
39+
return self
40+
41+
async def __anext__(self) -> TipSpot:
42+
while True:
43+
self.save_state()
44+
if self._tip_spot_idx >= len(self.tip_spots):
45+
if self.repeat:
46+
self._tip_spot_idx = 0
47+
else:
48+
raise StopAsyncIteration
49+
50+
self._tip_spot_idx += 1
51+
return self.tip_spots[self._tip_spot_idx - 1]
52+
53+
def save_state(self):
54+
if self.cache_file_path:
55+
try:
56+
with open(self.cache_file_path, "w", encoding="utf-8") as f:
57+
json.dump({"tip_spot_idx": self._tip_spot_idx}, f)
58+
logger.info("Saved tip idx to disk: %s", self._tip_spot_idx)
59+
except Exception as e:
60+
logger.error("Failed to save cache file: %s", e)
61+
62+
def set_index(self, index: int):
63+
self._tip_spot_idx = index
4264

4365

4466
async def randomized_tip_spot_generator(

0 commit comments

Comments
 (0)