Class docstrings

This commit is contained in:
Julian Stirling 2025-07-10 13:26:23 +01:00
parent 11a1fd7f85
commit a30b726b91
16 changed files with 118 additions and 10 deletions

View file

@ -130,7 +130,6 @@ ignore = [
"D400", # A stricter version of #415 that doesn't allow !
# The checkers below should be turned on as they complain about missing docstrings.
"D102",
"D101",
"D103",
"D104",
"D105",

View file

@ -13,6 +13,8 @@ import requests
class Zenodo:
"""A class that packages data for Zenodo."""
def __init__(self, api_token, use_sandbox=True):
self._api_token = api_token
self._use_sandbox = use_sandbox

View file

@ -16,7 +16,7 @@ IMAGE_REGEX = re.compile(r"-?[0-9]+_-?[0-9]+\.jpe?g$")
class NotEnoughFreeSpaceError(IOError):
pass
"""An exception raised if there is not enough free space on disk to scan."""
class ScanInfo(BaseModel):

View file

@ -16,6 +16,12 @@ AutofocusDep = lt.deps.direct_thing_client_dependency(AutofocusThing, "/autofocu
class RecentringThing(lt.Thing):
"""A Thing for recentring the stage by monitoring parasitic z motion of the stage.
As the stage moves over a sphere-cap there is parasitic motion in z during xy
movement. The highest z position is the centre of motion.
"""
@lt.thing_action
def recentre(
self,

View file

@ -175,6 +175,13 @@ def _get_capture_index_by_id(captures: list[CaptureInfo], buffer_id: int) -> int
class SharpnessDataArrays(BaseModel):
"""A BaseModel with the position and sharpness data from JPEGSharpnessMonitor.
Each JPEG Size (representing a sharpness metric) has an associated timestamp,
as does each stage position. The stage positions need to be interpolated so
they correspond with the image timestamps.
"""
jpeg_times: NDArray
jpeg_sizes: NDArray
stage_times: NDArray
@ -182,6 +189,21 @@ class SharpnessDataArrays(BaseModel):
class JPEGSharpnessMonitor:
"""A class with direct access to the CameraThing for monitoring the MJPEG stream.
The autofocus algorithm uses sharpness calculated from the file size of the
images in the MJPEG stream. This class monitors both the stage position and the
jpeg sharpness over time.
The ``run`` context manager is used to start monitoring the camera stream. Position
monitoring happens during ``focus_rel``. Raw data can be retrieved with
``data_dict`` and data with interpolate ``z`` positions can be retrieved with
move_data.
A new JPEGSharpnessMonitor instance is created each time an action with the
SharpnessMonitorDep as an argument is called.
"""
def __init__(self, stage: Stage, camera: Camera, portal: lt.deps.BlockingPortal):
self.camera = camera
self.stage = stage
@ -564,7 +586,7 @@ class AutofocusThing(lt.Thing):
) -> tuple[bool, list[CaptureInfo], Optional[int]]:
"""Capture a series of images checking that sharpest image central.
The images are seperated in z offset by stack_parameters.stack_dz, as they
The images are separated in z offset by stack_parameters.stack_dz, as they
are captured the last stack_parameters.min_images_to_test images are checked
to see if the sharpest image is central enough in the stack. If it is the stack
completes.
@ -577,7 +599,7 @@ class AutofocusThing(lt.Thing):
* the stack result (True for successful stack, False for failed stack),
* a list of CaptureInfo objects,
* the buffer_id of the shapest image (or None if the stack failed)
* the buffer_id of the sharpest image (or None if the stack failed).
"""
# Move down by the height of the z stack, plus an overshoot
# Better to start too low and take too many images than too high and need to refocus

View file

@ -17,12 +17,24 @@ from .camera import CameraDependency as CamDep
class ChannelDistributions(BaseModel):
"""A BaseModel for storing the channel distribution of a background image."""
means: list[float]
"""The mean of each channel in the colourspace."""
standard_deviations: list[float]
"""The standard deviation of each channel in the colourspace."""
colorspace: str = "LUV"
"""The colourspace used."""
class BackgroundDetectThing(lt.Thing):
"""Thing for setting a background image and detecting sample in the field of view.
This uses an LUV colour space checking only the mean and standard deviation of the
UV channels. Over time different, selectable, background detection methods will be
added.
"""
# Requires a getter and a setter to support being a BaseModel but being
# saved to file as a dict
_background_distributions: Optional[ChannelDistributions] = None

View file

@ -19,6 +19,8 @@ from labthings_fastapi.types.numpy import NDArray
class JPEGBlob(lt.blob.Blob):
"""A class representing a JPEG image as a LabThings FastAPI Blob."""
media_type: str = "image/jpeg"

View file

@ -45,6 +45,14 @@ XYCoordinateType = Tuple[float, float]
class HardwareInterfaceModel(BaseModel):
"""A pydantic base model for a stage and camera interface.
This class provides access to both the stage and the camera, but
it is confusing both because it is a BaseModel of callables rather
than a class with methods, and because it uses names from the underlying
Thing actions, but performs different actions.
"""
move: Callable[[NDArray], None]
get_position: Callable[[], NDArray]
grab_image: Callable[[], NDArray]
@ -123,6 +131,14 @@ HardwareInterfaceDep = Annotated[
class MoveHistory(NamedTuple):
"""A named tuple containing the position over time for a single move.
This a named tuple with elements:
* ``times``
* ``stage_positions``
"""
times: List[float]
stage_positions: List[CoordinateType]
@ -163,6 +179,13 @@ class LoggingMoveWrapper:
class CSMUncalibratedError(HTTPException):
"""An HTTP Exception raised if camera stage mapping data is needed but unavailable.
Camera Stage Mapping data is needed to convert from distances specified in fractions
of the feild of view to distances in motor steps. This is used when clicking on the
live preview to move, or when performing a scan.
"""
def __init__(self):
HTTPException.__init__(
self,
@ -186,9 +209,8 @@ class CameraStageMapper(lt.Thing):
direction: Tuple[float, float, float],
) -> DenumpifyingDict:
"""Move a microscope's stage in 1D, and figure out the relationship with the camera."""
move = LoggingMoveWrapper(
hw.move
) # log positions and times for stage calibration
# log positions and times for stage calibration
move = LoggingMoveWrapper(hw.move)
tracker = Tracker(hw.grab_image, hw.get_position, settle=hw.settle)
direction_array: np.ndarray = np.array(direction)

View file

@ -43,6 +43,12 @@ def nested_dict_get(data: dict, key: Sequence[str], create=False) -> Any:
class SettingsManager(lt.Thing):
"""Provides functionality to other Things about the current server state.
The SettingsManager is used to get information the microscope ID, the hostname
and the state of other Things.
"""
external_metadata = lt.ThingSetting(
initial_value={},
model=Mapping,

View file

@ -75,6 +75,13 @@ def _scan_running(method):
class SmartScanThing(lt.Thing):
"""A Thing for scanning samples and interacting with past scans.
SmartScanThing exposes all functionality for automatically scanning samples,
previewing live stitching, retrieving data from past scans, and for deleting
past scans.
"""
def __init__(self, scans_folder):
self._scan_dir_manager = scan_directories.ScanDirectoryManager(scans_folder)
self._preview_stitch_popen = None

View file

@ -10,6 +10,8 @@ from pydantic import BaseModel
class CommandOutput(BaseModel):
"""A pydantic model passing the STDOUT and STDERR from a subprocess over HTTP."""
output: str
error: str

View file

@ -9,6 +9,13 @@ answers to functions.
class MockAutoFocusThing:
"""A mock autofocus Thing that imports no code from AutofocusThing.
The class needs functionality added to it over time as more complex
mocking is needed. It imports no code from AutofocusThing so that coverage
is not artificially inflated.
"""
# Counter for checking functions were called
mock_call_count = {"looping_autofocus": 0}

View file

@ -10,7 +10,14 @@ answers to functions.
from openflexure_microscope_server.things.background_detect import ChannelDistributions
class MockBackgoundDetectThing:
class MockBackgroundDetectThing:
"""A mock background detect Thing that imports no code from BackgroundDetectThing.
The class needs functionality added to it over time as more complex
mocking is needed. It imports no code from BackgroundDetectThing so that coverage
is not artificially inflated.
"""
background_distributions = ChannelDistributions(
means=[128.0, 128.0, 128.0],
standard_deviations=[3.0, 3.0, 3.0],

View file

@ -9,4 +9,11 @@ answers to functions.
class MockCSMThing:
"""A mock CSM Thing that imports no code from CameraStageMapper.
The class needs functionality added to it over time as more complex
mocking is needed. It imports no code from CameraStageMapper so that coverage
is not artificially inflated.
"""
image_resolution = (123, 456)

View file

@ -9,4 +9,11 @@ answers to functions.
class MockStageThing:
"""A mock Thing for a stage that imports no code from BaseStage.
The class needs functionality added to it over time as more complex
mocking is needed. It imports no code from BaseStage so that coverage
is not artificially inflated.
"""
position = (111, 222, 333)

View file

@ -30,7 +30,7 @@ from openflexure_microscope_server.things.smart_scan import (
from .mock_things.mock_csm import MockCSMThing
from .mock_things.mock_autofocus import MockAutoFocusThing
from .mock_things.mock_stage import MockStageThing
from .mock_things.mock_background_detect import MockBackgoundDetectThing
from .mock_things.mock_background_detect import MockBackgroundDetectThing
# A global logger to pass in as an Invocation Logger
LOGGER = logging.getLogger("mock-invocation_logger")
@ -172,7 +172,7 @@ def _run_only_outer_scan(adjust_inital_state: Optional[Callable] = None):
cam_mock = 4 # not called
meta_mock = 5 # not called
csm_mock = MockCSMThing()
bkgrnd_det_mock = MockBackgoundDetectThing()
bkgrnd_det_mock = MockBackgroundDetectThing()
class MockedSmartScanThing(SmartScanThing):
"""Mocked version of SmartScanThing with a patched _run_scan method."""