Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion docs/api/pylabrobot.plate_reading.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ This package contains APIs for working with plate readers.
:nosignatures:
:recursive:

plate_reader.PlateReader
plate_reader.PlateReader
imager.Imager
standard.ImagingResult


Backends
Expand Down
28 changes: 14 additions & 14 deletions docs/user_guide/02_analytical/plate-reading/cytation5.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@
},
{
"cell_type": "code",
"execution_count": 10,
"execution_count": null,
"metadata": {},
"outputs": [
{
Expand All @@ -342,7 +342,7 @@
}
],
"source": [
"ims = await pr.capture(\n",
"res = await pr.capture(\n",
" well=(1, 2),\n",
" mode=ImagingMode.BRIGHTFIELD,\n",
" objective=Objective.O_4x_PL_FL_PHASE,\n",
Expand All @@ -351,7 +351,7 @@
" gain=16,\n",
" led_intensity=10,\n",
")\n",
"plt.imshow(ims[0], cmap=\"gray\", vmin=0, vmax=255)"
"plt.imshow(res.images[0], cmap=\"gray\", vmin=0, vmax=255)"
]
},
{
Expand All @@ -374,7 +374,7 @@
},
{
"cell_type": "code",
"execution_count": 41,
"execution_count": null,
"metadata": {},
"outputs": [
{
Expand All @@ -399,7 +399,7 @@
}
],
"source": [
"ims = await pr.capture(\n",
"res = await pr.capture(\n",
" well=(1, 2),\n",
" mode=ImagingMode.BRIGHTFIELD,\n",
" objective=Objective.O_4x_PL_FL_PHASE,\n",
Expand All @@ -408,7 +408,7 @@
" gain=16,\n",
" led_intensity=10\n",
")\n",
"plt.imshow(ims[0], cmap=\"gray\", vmin=0, vmax=255)"
"plt.imshow(res.images[0], cmap=\"gray\", vmin=0, vmax=255)"
]
},
{
Expand Down Expand Up @@ -440,7 +440,7 @@
"from pylabrobot.plate_reading.imager import Imager, max_pixel_at_fraction, fraction_overexposed\n",
"from pylabrobot.plate_reading.standard import AutoExposure\n",
"\n",
"ims = await pr.capture(\n",
"res = await pr.capture(\n",
" exposure_time=AutoExposure(\n",
" # evaluate_exposure=fraction_overexposed(fraction=0.005, margin=0.005/10),\n",
" evaluate_exposure=max_pixel_at_fraction(fraction=0.90, margin=0.05),\n",
Expand All @@ -467,14 +467,14 @@
},
{
"cell_type": "code",
"execution_count": 13,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from PIL import Image\n",
"import numpy as np\n",
"\n",
"array = np.array(ims[0], dtype=np.float32)\n",
"array = np.array(res.images[0], dtype=np.float32)\n",
"array_uint16 = (array * (65535 / 255)).astype(np.uint16)\n",
"Image.fromarray(array_uint16).save(\"test.tiff\")"
]
Expand All @@ -492,7 +492,7 @@
},
{
"cell_type": "code",
"execution_count": 31,
"execution_count": null,
"metadata": {},
"outputs": [
{
Expand All @@ -510,7 +510,7 @@
"num_rows = 4\n",
"num_cols = 4\n",
"\n",
"ims = await pr.capture(\n",
"res = await pr.capture(\n",
" well=(1, 2),\n",
" mode=ImagingMode.BRIGHTFIELD,\n",
" objective=Objective.O_4x_PL_FL_PHASE,\n",
Expand All @@ -520,12 +520,12 @@
" coverage=(num_rows, num_cols),\n",
" center_position=(-6, 0),\n",
")\n",
"len(ims)"
"len(res.images)"
]
},
{
"cell_type": "code",
"execution_count": 30,
"execution_count": null,
"metadata": {},
"outputs": [
{
Expand All @@ -544,7 +544,7 @@
"for row in range(num_rows):\n",
" for col in range(num_cols):\n",
" plt.subplot(num_rows, num_cols, row * num_cols + col + 1)\n",
" plt.imshow(ims[row * num_cols + col], cmap=\"gray\", vmin=0, vmax=255)\n",
" plt.imshow(res.images[row * num_cols + col], cmap=\"gray\", vmin=0, vmax=255)\n",
" plt.axis(\"off\")"
]
},
Expand Down
9 changes: 8 additions & 1 deletion pylabrobot/plate_reading/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,11 @@
from .image_reader import ImageReader
from .imager import Imager
from .plate_reader import PlateReader
from .standard import Exposure, FocalPosition, Gain, ImagingMode, Objective
from .standard import (
Exposure,
FocalPosition,
Gain,
ImagingMode,
ImagingResult,
Objective,
)
4 changes: 2 additions & 2 deletions pylabrobot/plate_reading/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
Exposure,
FocalPosition,
Gain,
Image,
ImagingMode,
ImagingResult,
Objective,
)
from pylabrobot.resources.plate import Plate
Expand Down Expand Up @@ -69,7 +69,7 @@ async def capture(
focal_height: FocalPosition,
gain: Gain,
plate: Plate,
) -> List[Image]:
) -> ImagingResult:
"""Capture an image of the plate in the specified mode."""


Expand Down
13 changes: 9 additions & 4 deletions pylabrobot/plate_reading/biotek_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
Gain,
Image,
ImagingMode,
ImagingResult,
Objective,
)

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

# objective function: variance of laplacian
async def evaluate_focus(focus_value):
images = await self.capture( # TODO: _acquire_image
result = await self.capture( # TODO: _acquire_image
plate=plate,
row=row,
column=column,
Expand All @@ -892,7 +893,7 @@ async def evaluate_focus(focus_value):
exposure_time=exposure,
gain=gain,
)
image = images[0] # self.capture returns List now
image = result.images[0]

if not CV2_AVAILABLE:
raise RuntimeError(
Expand Down Expand Up @@ -1158,7 +1159,7 @@ async def capture(
overlap: Optional[float] = None,
color_processing_algorithm: int = SPINNAKER_COLOR_PROCESSING_ALGORITHM_HQ_LINEAR,
pixel_format: int = PixelFormat_Mono8,
) -> List[Image]:
) -> ImagingResult:
"""Capture image using the microscope

speed: 211 ms ± 331 μs per loop (mean ± std. dev. of 7 runs, 10 loops each)
Expand Down Expand Up @@ -1234,4 +1235,8 @@ def image_size(magnification: float) -> Tuple[float, float]:
)
)

return images
exposure_ms = float(self.cam.ExposureTime.GetValue()) / 1000
assert self._focal_height is not None, "Focal height not set. Run set_focus() first."
focal_height_val = float(self._focal_height)

return ImagingResult(images=images, exposure_time=exposure_ms, focal_height=focal_height_val)
25 changes: 13 additions & 12 deletions pylabrobot/plate_reading/imager.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import math
from typing import Awaitable, Callable, List, Literal, Optional, Tuple, Union, cast
from typing import Awaitable, Callable, Literal, Optional, Tuple, Union, cast

from pylabrobot.machines import Machine
from pylabrobot.plate_reading.backend import ImagerBackend
Expand All @@ -10,6 +10,7 @@
Gain,
Image,
ImagingMode,
ImagingResult,
NoPlateError,
Objective,
)
Expand Down Expand Up @@ -68,7 +69,7 @@ async def _capture_auto_exposure(
focal_height: float,
gain: float,
**backend_kwargs,
) -> List[Image]:
) -> ImagingResult:
"""
Capture an image with auto exposure.

Expand Down Expand Up @@ -99,7 +100,7 @@ def _rms_split(low: float, high: float) -> float:
rounds += 1

p = _rms_split(low, high)
ims = await self.capture(
res = await self.capture(
well=well,
mode=mode,
objective=objective,
Expand All @@ -108,18 +109,18 @@ def _rms_split(low: float, high: float) -> float:
gain=gain,
**backend_kwargs,
)
assert len(ims) == 1, "Expected exactly one image to be returned"
im = ims[0]
result = await auto_exposure.evaluate_exposure(im)
assert len(res.images) == 1, "Expected exactly one image to be returned"
im = res.images[0]
evaluation = await auto_exposure.evaluate_exposure(im)

if result == "good":
return ims
if result == "lower":
if evaluation == "good":
return res
if evaluation == "lower":
high = p
elif result == "higher":
elif evaluation == "higher":
low = p
else:
raise ValueError(f"Unexpected evaluation result: {result}")
raise ValueError(f"Unexpected evaluation result: {evaluation}")

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

Expand All @@ -132,7 +133,7 @@ async def capture(
focal_height: FocalPosition = "machine-auto",
gain: Gain = "machine-auto",
**backend_kwargs,
) -> List[Image]:
) -> ImagingResult:
if isinstance(well, tuple):
row, column = well
else:
Expand Down
7 changes: 7 additions & 0 deletions pylabrobot/plate_reading/standard.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,10 @@ class AutoExposure:
Exposure = Union[float, Literal["machine-auto"]]
FocalPosition = Union[float, Literal["machine-auto"]]
Gain = Union[float, Literal["machine-auto"]]


@dataclass
class ImagingResult:
images: List[Image]
exposure_time: float
focal_height: float