Skip to content

Commit f449717

Browse files
authored
Add ImagingResult to imager capture (#613)
1 parent ec71935 commit f449717

File tree

7 files changed

+56
-34
lines changed

7 files changed

+56
-34
lines changed

docs/api/pylabrobot.plate_reading.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ This package contains APIs for working with plate readers.
1010
:nosignatures:
1111
:recursive:
1212

13-
plate_reader.PlateReader
13+
plate_reader.PlateReader
14+
imager.Imager
15+
standard.ImagingResult
1416

1517

1618
Backends

docs/user_guide/02_analytical/plate-reading/cytation5.ipynb

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@
317317
},
318318
{
319319
"cell_type": "code",
320-
"execution_count": 10,
320+
"execution_count": null,
321321
"metadata": {},
322322
"outputs": [
323323
{
@@ -342,7 +342,7 @@
342342
}
343343
],
344344
"source": [
345-
"ims = await pr.capture(\n",
345+
"res = await pr.capture(\n",
346346
" well=(1, 2),\n",
347347
" mode=ImagingMode.BRIGHTFIELD,\n",
348348
" objective=Objective.O_4x_PL_FL_PHASE,\n",
@@ -351,7 +351,7 @@
351351
" gain=16,\n",
352352
" led_intensity=10,\n",
353353
")\n",
354-
"plt.imshow(ims[0], cmap=\"gray\", vmin=0, vmax=255)"
354+
"plt.imshow(res.images[0], cmap=\"gray\", vmin=0, vmax=255)"
355355
]
356356
},
357357
{
@@ -374,7 +374,7 @@
374374
},
375375
{
376376
"cell_type": "code",
377-
"execution_count": 41,
377+
"execution_count": null,
378378
"metadata": {},
379379
"outputs": [
380380
{
@@ -399,7 +399,7 @@
399399
}
400400
],
401401
"source": [
402-
"ims = await pr.capture(\n",
402+
"res = await pr.capture(\n",
403403
" well=(1, 2),\n",
404404
" mode=ImagingMode.BRIGHTFIELD,\n",
405405
" objective=Objective.O_4x_PL_FL_PHASE,\n",
@@ -408,7 +408,7 @@
408408
" gain=16,\n",
409409
" led_intensity=10\n",
410410
")\n",
411-
"plt.imshow(ims[0], cmap=\"gray\", vmin=0, vmax=255)"
411+
"plt.imshow(res.images[0], cmap=\"gray\", vmin=0, vmax=255)"
412412
]
413413
},
414414
{
@@ -440,7 +440,7 @@
440440
"from pylabrobot.plate_reading.imager import Imager, max_pixel_at_fraction, fraction_overexposed\n",
441441
"from pylabrobot.plate_reading.standard import AutoExposure\n",
442442
"\n",
443-
"ims = await pr.capture(\n",
443+
"res = await pr.capture(\n",
444444
" exposure_time=AutoExposure(\n",
445445
" # evaluate_exposure=fraction_overexposed(fraction=0.005, margin=0.005/10),\n",
446446
" evaluate_exposure=max_pixel_at_fraction(fraction=0.90, margin=0.05),\n",
@@ -467,14 +467,14 @@
467467
},
468468
{
469469
"cell_type": "code",
470-
"execution_count": 13,
470+
"execution_count": null,
471471
"metadata": {},
472472
"outputs": [],
473473
"source": [
474474
"from PIL import Image\n",
475475
"import numpy as np\n",
476476
"\n",
477-
"array = np.array(ims[0], dtype=np.float32)\n",
477+
"array = np.array(res.images[0], dtype=np.float32)\n",
478478
"array_uint16 = (array * (65535 / 255)).astype(np.uint16)\n",
479479
"Image.fromarray(array_uint16).save(\"test.tiff\")"
480480
]
@@ -492,7 +492,7 @@
492492
},
493493
{
494494
"cell_type": "code",
495-
"execution_count": 31,
495+
"execution_count": null,
496496
"metadata": {},
497497
"outputs": [
498498
{
@@ -510,7 +510,7 @@
510510
"num_rows = 4\n",
511511
"num_cols = 4\n",
512512
"\n",
513-
"ims = await pr.capture(\n",
513+
"res = await pr.capture(\n",
514514
" well=(1, 2),\n",
515515
" mode=ImagingMode.BRIGHTFIELD,\n",
516516
" objective=Objective.O_4x_PL_FL_PHASE,\n",
@@ -520,12 +520,12 @@
520520
" coverage=(num_rows, num_cols),\n",
521521
" center_position=(-6, 0),\n",
522522
")\n",
523-
"len(ims)"
523+
"len(res.images)"
524524
]
525525
},
526526
{
527527
"cell_type": "code",
528-
"execution_count": 30,
528+
"execution_count": null,
529529
"metadata": {},
530530
"outputs": [
531531
{
@@ -544,7 +544,7 @@
544544
"for row in range(num_rows):\n",
545545
" for col in range(num_cols):\n",
546546
" plt.subplot(num_rows, num_cols, row * num_cols + col + 1)\n",
547-
" plt.imshow(ims[row * num_cols + col], cmap=\"gray\", vmin=0, vmax=255)\n",
547+
" plt.imshow(res.images[row * num_cols + col], cmap=\"gray\", vmin=0, vmax=255)\n",
548548
" plt.axis(\"off\")"
549549
]
550550
},

pylabrobot/plate_reading/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,11 @@
33
from .image_reader import ImageReader
44
from .imager import Imager
55
from .plate_reader import PlateReader
6-
from .standard import Exposure, FocalPosition, Gain, ImagingMode, Objective
6+
from .standard import (
7+
Exposure,
8+
FocalPosition,
9+
Gain,
10+
ImagingMode,
11+
ImagingResult,
12+
Objective,
13+
)

pylabrobot/plate_reading/backend.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
Exposure,
99
FocalPosition,
1010
Gain,
11-
Image,
1211
ImagingMode,
12+
ImagingResult,
1313
Objective,
1414
)
1515
from pylabrobot.resources.plate import Plate
@@ -69,7 +69,7 @@ async def capture(
6969
focal_height: FocalPosition,
7070
gain: Gain,
7171
plate: Plate,
72-
) -> List[Image]:
72+
) -> ImagingResult:
7373
"""Capture an image of the plate in the specified mode."""
7474

7575

pylabrobot/plate_reading/biotek_backend.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
Gain,
4444
Image,
4545
ImagingMode,
46+
ImagingResult,
4647
Objective,
4748
)
4849

@@ -882,7 +883,7 @@ async def auto_focus(self, timeout: float = 30):
882883

883884
# objective function: variance of laplacian
884885
async def evaluate_focus(focus_value):
885-
images = await self.capture( # TODO: _acquire_image
886+
result = await self.capture( # TODO: _acquire_image
886887
plate=plate,
887888
row=row,
888889
column=column,
@@ -892,7 +893,7 @@ async def evaluate_focus(focus_value):
892893
exposure_time=exposure,
893894
gain=gain,
894895
)
895-
image = images[0] # self.capture returns List now
896+
image = result.images[0]
896897

897898
if not CV2_AVAILABLE:
898899
raise RuntimeError(
@@ -1158,7 +1159,7 @@ async def capture(
11581159
overlap: Optional[float] = None,
11591160
color_processing_algorithm: int = SPINNAKER_COLOR_PROCESSING_ALGORITHM_HQ_LINEAR,
11601161
pixel_format: int = PixelFormat_Mono8,
1161-
) -> List[Image]:
1162+
) -> ImagingResult:
11621163
"""Capture image using the microscope
11631164
11641165
speed: 211 ms ± 331 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)
@@ -1234,4 +1235,8 @@ def image_size(magnification: float) -> Tuple[float, float]:
12341235
)
12351236
)
12361237

1237-
return images
1238+
exposure_ms = float(self.cam.ExposureTime.GetValue()) / 1000
1239+
assert self._focal_height is not None, "Focal height not set. Run set_focus() first."
1240+
focal_height_val = float(self._focal_height)
1241+
1242+
return ImagingResult(images=images, exposure_time=exposure_ms, focal_height=focal_height_val)

pylabrobot/plate_reading/imager.py

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import math
2-
from typing import Awaitable, Callable, List, Literal, Optional, Tuple, Union, cast
2+
from typing import Awaitable, Callable, Literal, Optional, Tuple, Union, cast
33

44
from pylabrobot.machines import Machine
55
from pylabrobot.plate_reading.backend import ImagerBackend
@@ -10,6 +10,7 @@
1010
Gain,
1111
Image,
1212
ImagingMode,
13+
ImagingResult,
1314
NoPlateError,
1415
Objective,
1516
)
@@ -68,7 +69,7 @@ async def _capture_auto_exposure(
6869
focal_height: float,
6970
gain: float,
7071
**backend_kwargs,
71-
) -> List[Image]:
72+
) -> ImagingResult:
7273
"""
7374
Capture an image with auto exposure.
7475
@@ -99,7 +100,7 @@ def _rms_split(low: float, high: float) -> float:
99100
rounds += 1
100101

101102
p = _rms_split(low, high)
102-
ims = await self.capture(
103+
res = await self.capture(
103104
well=well,
104105
mode=mode,
105106
objective=objective,
@@ -108,18 +109,18 @@ def _rms_split(low: float, high: float) -> float:
108109
gain=gain,
109110
**backend_kwargs,
110111
)
111-
assert len(ims) == 1, "Expected exactly one image to be returned"
112-
im = ims[0]
113-
result = await auto_exposure.evaluate_exposure(im)
112+
assert len(res.images) == 1, "Expected exactly one image to be returned"
113+
im = res.images[0]
114+
evaluation = await auto_exposure.evaluate_exposure(im)
114115

115-
if result == "good":
116-
return ims
117-
if result == "lower":
116+
if evaluation == "good":
117+
return res
118+
if evaluation == "lower":
118119
high = p
119-
elif result == "higher":
120+
elif evaluation == "higher":
120121
low = p
121122
else:
122-
raise ValueError(f"Unexpected evaluation result: {result}")
123+
raise ValueError(f"Unexpected evaluation result: {evaluation}")
123124

124125
raise RuntimeError("Failed to find a good exposure time.")
125126

@@ -132,7 +133,7 @@ async def capture(
132133
focal_height: FocalPosition = "machine-auto",
133134
gain: Gain = "machine-auto",
134135
**backend_kwargs,
135-
) -> List[Image]:
136+
) -> ImagingResult:
136137
if isinstance(well, tuple):
137138
row, column = well
138139
else:

pylabrobot/plate_reading/standard.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,3 +101,10 @@ class AutoExposure:
101101
Exposure = Union[float, Literal["machine-auto"]]
102102
FocalPosition = Union[float, Literal["machine-auto"]]
103103
Gain = Union[float, Literal["machine-auto"]]
104+
105+
106+
@dataclass
107+
class ImagingResult:
108+
images: List[Image]
109+
exposure_time: float
110+
focal_height: float

0 commit comments

Comments
 (0)