Record fom as other sharpness metric
This commit is contained in:
parent
eff66eb667
commit
881970ce34
2 changed files with 50 additions and 5 deletions
|
|
@ -38,6 +38,7 @@ class SharpnessMethod(enum.Enum):
|
||||||
"""The possible SharpnessMethods for autofocus."""
|
"""The possible SharpnessMethods for autofocus."""
|
||||||
|
|
||||||
JPEG = enum.auto()
|
JPEG = enum.auto()
|
||||||
|
FOCUS_FOM = enum.auto()
|
||||||
|
|
||||||
|
|
||||||
class AutofocusParams(BaseModel):
|
class AutofocusParams(BaseModel):
|
||||||
|
|
@ -235,6 +236,7 @@ class SharpnessDataArrays(BaseModel):
|
||||||
|
|
||||||
jpeg_times: NDArray
|
jpeg_times: NDArray
|
||||||
jpeg_sizes: NDArray
|
jpeg_sizes: NDArray
|
||||||
|
focus_foms: NDArray
|
||||||
stage_times: NDArray
|
stage_times: NDArray
|
||||||
stage_positions: list[dict[str, int]]
|
stage_positions: list[dict[str, int]]
|
||||||
|
|
||||||
|
|
@ -253,7 +255,12 @@ class JPEGSharpnessMonitor:
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, stage: BaseStage, camera: BaseCamera) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
stage: BaseStage,
|
||||||
|
camera: BaseCamera,
|
||||||
|
method: SharpnessMethod = SharpnessMethod.JPEG,
|
||||||
|
) -> None:
|
||||||
"""Initialise a new JPEGSharpnessMonitor. The args are injected automatically.
|
"""Initialise a new JPEGSharpnessMonitor. The args are injected automatically.
|
||||||
|
|
||||||
:param stage: A direct_thing_client dependency for the the microscope stage.
|
:param stage: A direct_thing_client dependency for the the microscope stage.
|
||||||
|
|
@ -262,11 +269,13 @@ class JPEGSharpnessMonitor:
|
||||||
"""
|
"""
|
||||||
self.camera = camera
|
self.camera = camera
|
||||||
self.stage = stage
|
self.stage = stage
|
||||||
|
self.method = method
|
||||||
LOGGER.debug(f"Created sharpness monitor with {stage}, {camera}")
|
LOGGER.debug(f"Created sharpness monitor with {stage}, {camera}")
|
||||||
self._stage_positions: list[Mapping[str, int]] = []
|
self._stage_positions: list[Mapping[str, int]] = []
|
||||||
self._stage_times: list[float] = []
|
self._stage_times: list[float] = []
|
||||||
self._jpeg_times: list[float] = []
|
self._jpeg_times: list[float] = []
|
||||||
self._jpeg_sizes: list[int] = []
|
self._jpeg_sizes: list[int] = []
|
||||||
|
self._focus_foms: list[float] = []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def stage_positions(self) -> Sequence[Mapping[str, int]]:
|
def stage_positions(self) -> Sequence[Mapping[str, int]]:
|
||||||
|
|
@ -288,14 +297,28 @@ class JPEGSharpnessMonitor:
|
||||||
"""The recorded JPEG frame sizes used as a sharpness metric."""
|
"""The recorded JPEG frame sizes used as a sharpness metric."""
|
||||||
return self._jpeg_sizes
|
return self._jpeg_sizes
|
||||||
|
|
||||||
|
@property
|
||||||
|
def focus_foms(self) -> Sequence[float]:
|
||||||
|
"""The recorded FocusFoM values."""
|
||||||
|
return self._focus_foms
|
||||||
|
|
||||||
running = False
|
running = False
|
||||||
|
|
||||||
async def monitor_sharpness(self) -> None:
|
async def monitor_sharpness(self) -> None:
|
||||||
"""Start monitoring the frame sizes."""
|
"""Start monitoring sharpness metrics."""
|
||||||
self.running = True
|
self.running = True
|
||||||
async for frame in self.camera.lores_mjpeg_stream.frame_async_generator():
|
async for frame in self.camera.lores_mjpeg_stream.frame_async_generator():
|
||||||
self._jpeg_times.append(time.time())
|
self._jpeg_times.append(time.time())
|
||||||
|
|
||||||
|
# JPEG sharpness metric
|
||||||
self._jpeg_sizes.append(len(frame))
|
self._jpeg_sizes.append(len(frame))
|
||||||
|
|
||||||
|
# FocusFoM metric
|
||||||
|
fom = getattr(self.camera, "_focus_fom", None)
|
||||||
|
if fom is not None:
|
||||||
|
self._focus_foms.append(float(fom))
|
||||||
|
else:
|
||||||
|
self._focus_foms.append(np.nan)
|
||||||
if not self.running:
|
if not self.running:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
@ -352,7 +375,13 @@ class JPEGSharpnessMonitor:
|
||||||
if istop is None:
|
if istop is None:
|
||||||
istop = istart + 2
|
istop = istart + 2
|
||||||
jpeg_times: np.ndarray = np.array(self.jpeg_times)
|
jpeg_times: np.ndarray = np.array(self.jpeg_times)
|
||||||
jpeg_sizes: np.ndarray = np.array(self.jpeg_sizes)
|
# Two sharpness metrics are measured - this chooses which to use to focus
|
||||||
|
if self.method == SharpnessMethod.JPEG:
|
||||||
|
sharpnesses = np.array(self.jpeg_sizes)
|
||||||
|
elif self.method == SharpnessMethod.FOCUS_FOM:
|
||||||
|
sharpnesses = np.array(self.focus_foms)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"Unknown sharpness method: {self.method}")
|
||||||
stage_times: np.ndarray = np.array(self.stage_times)[istart:istop]
|
stage_times: np.ndarray = np.array(self.stage_times)[istart:istop]
|
||||||
stage_heights: np.ndarray = np.array(
|
stage_heights: np.ndarray = np.array(
|
||||||
[p["z"] for p in self.stage_positions[istart:istop]]
|
[p["z"] for p in self.stage_positions[istart:istop]]
|
||||||
|
|
@ -373,7 +402,7 @@ class JPEGSharpnessMonitor:
|
||||||
LOGGER.debug("changing stop to %s", (stop))
|
LOGGER.debug("changing stop to %s", (stop))
|
||||||
jpeg_times = jpeg_times[start:stop]
|
jpeg_times = jpeg_times[start:stop]
|
||||||
jpeg_heights: np.ndarray = np.interp(jpeg_times, stage_times, stage_heights)
|
jpeg_heights: np.ndarray = np.interp(jpeg_times, stage_times, stage_heights)
|
||||||
return jpeg_times, jpeg_heights, jpeg_sizes[start:stop]
|
return jpeg_times, jpeg_heights, sharpnesses[start:stop]
|
||||||
|
|
||||||
def sharpest_z_on_move(self, data_index: int) -> int:
|
def sharpest_z_on_move(self, data_index: int) -> int:
|
||||||
"""Return the z position of the sharpest image on a given move."""
|
"""Return the z position of the sharpest image on a given move."""
|
||||||
|
|
@ -388,7 +417,13 @@ class JPEGSharpnessMonitor:
|
||||||
def data_to_array(self) -> SharpnessDataArrays:
|
def data_to_array(self) -> SharpnessDataArrays:
|
||||||
"""Return the gathered data as SharpnessDataArrays."""
|
"""Return the gathered data as SharpnessDataArrays."""
|
||||||
data = {}
|
data = {}
|
||||||
for k in ["jpeg_times", "jpeg_sizes", "stage_times", "stage_positions"]:
|
for k in [
|
||||||
|
"jpeg_times",
|
||||||
|
"jpeg_sizes",
|
||||||
|
"stage_times",
|
||||||
|
"focus_foms",
|
||||||
|
"stage_positions",
|
||||||
|
]:
|
||||||
data[k] = getattr(self, k)
|
data[k] = getattr(self, k)
|
||||||
return SharpnessDataArrays(**data)
|
return SharpnessDataArrays(**data)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ from types import TracebackType
|
||||||
from typing import Annotated, Any, Iterator, Literal, Mapping, Optional, Self
|
from typing import Annotated, Any, Iterator, Literal, Mapping, Optional, Self
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
from libcamera import Request
|
||||||
from picamera2 import Picamera2
|
from picamera2 import Picamera2
|
||||||
from picamera2.encoders import MJPEGEncoder
|
from picamera2.encoders import MJPEGEncoder
|
||||||
from picamera2.outputs import Output
|
from picamera2.outputs import Output
|
||||||
|
|
@ -366,6 +367,9 @@ class StreamingPiCamera2(BaseCamera):
|
||||||
if self._picamera is None:
|
if self._picamera is None:
|
||||||
# Type narrow (error if failure)
|
# Type narrow (error if failure)
|
||||||
raise RuntimeError("Failed to start Picamera")
|
raise RuntimeError("Failed to start Picamera")
|
||||||
|
|
||||||
|
self._picamera.pre_callback = self._on_frame_complete
|
||||||
|
|
||||||
if check_sensor_model:
|
if check_sensor_model:
|
||||||
hw_sensor_model = self._picamera.camera_properties["Model"]
|
hw_sensor_model = self._picamera.camera_properties["Model"]
|
||||||
if hw_sensor_model != self._sensor_info.sensor_model:
|
if hw_sensor_model != self._sensor_info.sensor_model:
|
||||||
|
|
@ -374,6 +378,12 @@ class StreamingPiCamera2(BaseCamera):
|
||||||
f"but found {hw_sensor_model}."
|
f"but found {hw_sensor_model}."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _on_frame_complete(self, request: Request) -> None:
|
||||||
|
md = request.get_metadata()
|
||||||
|
fom = md.get("FocusFoM")
|
||||||
|
if fom is not None:
|
||||||
|
self._focus_fom = fom
|
||||||
|
|
||||||
def __enter__(self) -> Self:
|
def __enter__(self) -> Self:
|
||||||
"""Start streaming when the Thing context manager is opened.
|
"""Start streaming when the Thing context manager is opened.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue