Starting docstring on the same lines as the quotations

This commit is contained in:
Julian Stirling 2025-07-10 01:04:26 +01:00
parent be6a6ca6fe
commit 35d47fe3ed
28 changed files with 92 additions and 214 deletions

View file

@ -68,8 +68,7 @@ def retrieve_log_from_file() -> PlainTextResponse:
class OFMHandler(logging.Handler):
"""
A child class of logging.Handler. This class handles storing the most recent
"""A child class of logging.Handler. This class handles storing the most recent
logs for access by the server.
"""
@ -79,8 +78,7 @@ class OFMHandler(logging.Handler):
self._max_logs = max_logs
def append_record(self, record):
"""
Use the built in formatter to format the record, then save
"""Use the built in formatter to format the record, then save
it to an array. Pop any in excess of the maximum number of logs
"""
self._log.append(self.format(record))
@ -88,9 +86,7 @@ class OFMHandler(logging.Handler):
self._log.pop(0)
def emit(self, record):
"""
Emit will save the logged record to the log
"""
"""Emit will save the logged record to the log"""
# Basic filter for now that simply stops uvicorn.access logs
# These are the logs each time an API endpoint is accessed
# This is only the log for the UI.
@ -108,9 +104,7 @@ class OFMHandler(logging.Handler):
@property
def log_history(self):
"""
Return the log history up to the maximum number of logs
"""
"""Return the log history up to the maximum number of logs"""
return "\n".join(self._log)

View file

@ -31,9 +31,7 @@ class ScanInfo(BaseModel):
class ScanDirectoryManager:
"""
A class for managing interactions with scan directories
"""
"""A class for managing interactions with scan directories"""
_base_scan_dir: str
@ -180,8 +178,7 @@ class ScanDirectoryManager:
return ScanDirectory(scan_name, self.base_dir).zip_files(final_version)
def check_free_disk_space(self, min_space: int = 500000000) -> None:
"""
Raise an exception if there is not enough free disk space to continue scanning
"""Raise an exception if there is not enough free disk space to continue scanning
:param min_space: the minimum space required in bytes.
Default = 500,000,000 (500MB)
@ -227,8 +224,7 @@ class ScanDirectory:
@property
def images_dir(self) -> Optional[str]:
"""
The path to the images directory. None is returned if no images directory
"""The path to the images directory. None is returned if no images directory
was created
"""
im_path = os.path.join(self.dir_path, IMG_DIR_NAME)

View file

@ -58,8 +58,7 @@ def enforce_xyz_tuple(value: XYZPos) -> XYZPos:
class ScanPlanner:
"""
A base class for a scan planner.
"""A base class for a scan planner.
This should never be used directly for a scan, it should be subclassed.
@ -116,9 +115,7 @@ class ScanPlanner:
@property
def imaged_locations(self) -> XYZPosList:
"""
Property to access a copy of the imaged_locations
"""
"""Property to access a copy of the imaged_locations"""
return copy(self._imaged_locations)
@property
@ -148,22 +145,17 @@ class ScanPlanner:
raise NotImplementedError("Did you call the ScanPlanner base class?")
def position_visited(self, position: XYPos) -> bool:
"""
Return True if input xy position has been visited before
"""
"""Return True if input xy position has been visited before"""
# Ensure tuple for correct matching!
return tuple(position) in self._path_history
def position_planned(self, position: XYPos) -> bool:
"""
Return True if input xy position is planned
"""
"""Return True if input xy position is planned"""
# Ensure tuple for correct matching!
return tuple(position) in self._remaining_locations
def get_next_location_and_z_estimate(self) -> tuple[XYPos, Optional[int]]:
"""
Return the next location to scan, and the estimated z-position
"""Return the next location to scan, and the estimated z-position
for this location.
Note z-position may be None! This indicates that the current z, position
@ -184,8 +176,7 @@ class ScanPlanner:
return next_location, z
def closest_focus_site(self, xy_pos: XYPos) -> Optional[XYZPos]:
"""
Return the xyz position of the closest site where focus was achieved
"""Return the xyz position of the closest site where focus was achieved
to the input xy_position, with the most recently taken image returned in
the case of a tie
@ -212,8 +203,7 @@ class ScanPlanner:
def mark_location_visited(
self, xyz_pos: XYZPos, imaged: bool, focused: bool
) -> None:
"""
Mark the location as visited
"""Mark the location as visited
Args:
xyz_pos: the x_y_z position
@ -290,8 +280,7 @@ class SmartSpiral(ScanPlanner):
def mark_location_visited(
self, xyz_pos: XYZPos, imaged: bool = True, focused: bool = True
) -> None:
"""
Mark the location as visited. Adjust extra positions accordingly
"""Mark the location as visited. Adjust extra positions accordingly
Args:
xyz_pos: the x_y_z position
@ -336,9 +325,7 @@ class SmartSpiral(ScanPlanner):
self._remaining_locations.append(new_pos)
def _re_sort_remaining_locations(self, current_pos: XYPos) -> None:
"""
Sort the remaining positions besed on the current location
"""
"""Sort the remaining positions besed on the current location"""
# Defined rather than use a lambda for readability
def sort_key(pos):
@ -351,8 +338,7 @@ class SmartSpiral(ScanPlanner):
self._remaining_locations.sort(key=sort_key)
def get_next_location_and_z_estimate(self) -> tuple[XYPos, Optional[int]]:
"""
Return the next location to scan, and the estimated z-position
"""Return the next location to scan, and the estimated z-position
for this location. This overrides the default behaviour of ScanPlanner
to take the lowest value of nearest neighbours as this works best for smart stack
@ -374,8 +360,7 @@ class SmartSpiral(ScanPlanner):
return next_location, z
def select_nearby_focus_site(self, xy_pos: XYPos) -> Optional[XYZPos]:
"""
Return the xyz position of the nearby site with the lowest z position.
"""Return the xyz position of the nearby site with the lowest z position.
Lowest position is best, as starting too high causes smart stacking to
autofocus and restart. Starting too low just requires extra movements in +z.
Nearby is defined as within NEIGHBOUR_CUTOFF times the distance to the closest neighbour.
@ -412,8 +397,7 @@ class SmartSpiral(ScanPlanner):
starting_pos: XYPos | np.ndarray,
ending_pos: XYPos | np.ndarray,
) -> float:
"""
Return the number of moves between two xy positions in the x or y direction
"""Return the number of moves between two xy positions in the x or y direction
whichever is largest
Args:
@ -434,8 +418,7 @@ class SmartSpiral(ScanPlanner):
def distance_between(
current_pos: XYPos | np.ndarray, next_pos: XYPos | np.ndarray
) -> float:
"""
Calculate the distance between the two xy positions
"""Calculate the distance between the two xy positions
This was previously called ``distance_to_site``
"""

View file

@ -14,8 +14,7 @@ from ..logging import configure_logging, retrieve_log, retrieve_log_from_file
def set_shutdown_function(shutdown_function: Callable[[], None]):
"""
Ensure a function is called before the shutdown
"""Ensure a function is called before the shutdown
This monkey patches the Uvicorn Server's handle_exit. This is needed because
the uvicorn ``lifecycle`` events and FastAPI ``shutdown`` events only fire once
@ -52,8 +51,7 @@ def customise_server(
def _get_scans_dir(config: dict) -> Optional[str]:
"""
Read the config and return the scans directory.
"""Read the config and return the scans directory.
Return is None if there is no /smart_scan/ thing loaded.
"""

View file

@ -107,9 +107,7 @@ class StackParams:
@property
def steps_undershoot(self) -> int:
"""
The distance to deliberately undershoot the estimated optimal starting point
"""
"""The distance to deliberately undershoot the estimated optimal starting point"""
# Starting too low by "steps_undershoot" makes smart stacking faster.
# Starting a stack too high requires it to move to the start,
# autofocus and then re-stack. Starting slightly too low only
@ -135,9 +133,7 @@ class StackParams:
@dataclass
class CaptureInfo:
"""
The information from a capture in a z_stack
"""
"""The information from a capture in a z_stack"""
buffer_id: int
position: dict[str, int]

View file

@ -43,8 +43,7 @@ class NoImageInMemoryError(RuntimeError):
class CameraMemoryBuffer:
"""
A class that holds images in memory. The images are by default PIL images.
"""A class that holds images in memory. The images are by default PIL images.
However subclasses of BaseCamera can use this class to store other object types
"""
@ -62,8 +61,7 @@ class CameraMemoryBuffer:
def add_image(
self, image: Any, metadata: Optional[dict] = None, buffer_max: int = 1
) -> int:
"""
Add an image to the Memory buffer
"""Add an image to the Memory buffer
This will add an image to the memory buffer. By default the buffer will
be cleared. To allow saving multiple images the buffer_max must be set
@ -85,8 +83,7 @@ class CameraMemoryBuffer:
def get_image(
self, buffer_id: Optional[int] = None, remove: bool = True
) -> tuple[Any, Optional[dict]]:
"""
Return the image with the given id.
"""Return the image with the given id.
If no id is given the most recent image is returned. However, the
buffer is also cleared, otherwise it would be possible to accidentally
@ -117,14 +114,11 @@ class CameraMemoryBuffer:
) from e
def clear(self):
"""
Clear all images from memory
"""
"""Clear all images from memory"""
self._storage.clear()
def _create_space(self, buffer_max: int) -> None:
"""
Create space to add an image.
"""Create space to add an image.
:param buffer_max: The maximum number of images that should be in the buffer
once another images is added.
@ -170,8 +164,7 @@ class BaseCamera(lt.Thing):
)
def kill_mjpeg_streams(self):
"""
Kill the streams now as the server is shutting down.
"""Kill the streams now as the server is shutting down.
This is called when uvicorn gets the a shutdown signal. As this is called from
the event loop it cannot interact with the our ThingProperties or run
@ -293,8 +286,7 @@ class BaseCamera(lt.Thing):
metadata_getter: lt.deps.GetThingStates,
buffer_max: int = 1,
) -> None:
"""
Capture an image to memory. This can be saved later with ``save_from_memory``
"""Capture an image to memory. This can be saved later with ``save_from_memory``
Note that only one image is held in memory so this will overwrite any image
in memory.
@ -319,8 +311,7 @@ class BaseCamera(lt.Thing):
save_resolution: Optional[Tuple[int, int]] = None,
buffer_id: Optional[int] = None,
) -> None:
"""
Save an image that has been captured to memory.
"""Save an image that has been captured to memory.
:param jpeg_path: The path to save the file to
:param logger: This should be injected automatically by Labthings FastAPI

View file

@ -62,8 +62,7 @@ class PicameraStreamOutput(Output):
class SensorMode(BaseModel):
"""
A Pydantic model holding all the information about a specific
"""A Pydantic model holding all the information about a specific
sensor mode as reported by the PiCamera.
"""
@ -77,8 +76,7 @@ class SensorMode(BaseModel):
class SensorModeSelector(BaseModel):
"""
A Pydantic model holding the two values needed to select a PiCamera
"""A Pydantic model holding the two values needed to select a PiCamera
Sensor mode. The output size and the bit depth.
This is a Pydantic modell so that it can be saved to the disk.
@ -89,8 +87,7 @@ class SensorModeSelector(BaseModel):
class LensShading(BaseModel):
"""
A Pydantic model holding the lens shading tables.
"""A Pydantic model holding the lens shading tables.
PiCamera needs three numpy arrays for lens shading correction. Each array is
(12, 16) in size. The arrays are luminance, red-difference chroma (Cr), and
@ -363,8 +360,7 @@ class StreamingPiCamera2(BaseCamera):
def start_streaming(
self, main_resolution: tuple[int, int] = (820, 616), buffer_count: int = 6
) -> None:
"""
Start the MJPEG stream. This is where persistent controls are sent to camera.
"""Start the MJPEG stream. This is where persistent controls are sent to camera.
Sets the camera resolutions based on input parameters, and sets the low-res
resolution to (320, 240). Note: (320, 240) is a standard from the Pi Camera
@ -434,9 +430,7 @@ class StreamingPiCamera2(BaseCamera):
@lt.thing_action
def stop_streaming(self, stop_web_stream: bool = True) -> None:
"""
Stop the MJPEG stream
"""
"""Stop the MJPEG stream"""
with self._streaming_picamera() as picam:
try:
picam.stop_recording() # This should also stop the extra lores encoder
@ -679,8 +673,7 @@ class StreamingPiCamera2(BaseCamera):
@lt.thing_action
def reset_ccm(self):
"""
Overwrite the colour correction matrix in camera tuning with default values.
"""Overwrite the colour correction matrix in camera tuning with default values.
These values are from the Raspberry Pi Camera Algorithm and Tuning Guide, page
45.

View file

@ -1,5 +1,4 @@
"""
Functions to set up a Raspberry Pi Camera v2 for scientific use
"""Functions to set up a Raspberry Pi Camera v2 for scientific use
This module provides slower, simpler functions to set the
gain, exposure, and white balance of a Raspberry Pi camera, using

View file

@ -1,5 +1,4 @@
"""
OpenFlexure Microscope API extension for stage calibration
"""OpenFlexure Microscope API extension for stage calibration
This file contains the HTTP API for camera/stage calibration. It
includes calibration functions that measure the relationship between

View file

@ -1,5 +1,4 @@
"""
OpenFlexure Settings Management
"""OpenFlexure Settings Management
This module provides some settings management across the other Things, and
for code that currently lives in clients but needs to persist settings on

View file

@ -229,8 +229,7 @@ class SmartScanThing(lt.Thing):
@_scan_running
def _calc_displacement_from_test_image(self, overlap: int) -> tuple[int, int]:
"""
Take a test image and use camera stage mapping to calculate x and y displacement
"""Take a test image and use camera stage mapping to calculate x and y displacement
:param overlap: The desired overlap as a fraction of the image. i.e. 0.5 means
that each image should overlap its nearest neighbour by 50%.
@ -312,8 +311,7 @@ class SmartScanThing(lt.Thing):
@_scan_running
def _save_scan_inputs_json(self):
"""
Save scan inputs as a JSON file in the scan folder, to allow
"""Save scan inputs as a JSON file in the scan folder, to allow
the user to review the settings used in the scan
"""
# Should this be a method of the scan_data dataclass?
@ -337,8 +335,7 @@ class SmartScanThing(lt.Thing):
@_scan_running
def _update_scan_data_json(self, scan_result: str):
"""
Update scan data as a JSON file in the scan folder, with
"""Update scan data as a JSON file in the scan folder, with
data only known at the end of the scan.
Takes scan_result, a string that is either "success",
@ -372,9 +369,7 @@ class SmartScanThing(lt.Thing):
@_scan_running
def _manage_stitching_threads(self):
"""
Manage the stitching threads, starting them if needed and not already running.
"""
"""Manage the stitching threads, starting them if needed and not already running."""
# Assume 4 images means at least one offset in x and y, making the stitching
# well constrained.
if self._scan_images_taken > 3:
@ -383,8 +378,7 @@ class SmartScanThing(lt.Thing):
@_scan_running
def _run_scan(self):
"""
Prepare and run the main scan, handling the threads performing
"""Prepare and run the main scan, handling the threads performing
stitching. Uses the result (or exception) from the scanning to
determine whether the scan should be stitched and the microscope
should return to the starting x,y,z position
@ -703,9 +697,7 @@ class SmartScanThing(lt.Thing):
@lt.thing_action
def purge_empty_scans(self, logger: lt.deps.InvocationLogger) -> None:
"""
Delete all scan folders containing no images at the top level
"""
"""Delete all scan folders containing no images at the top level"""
# JSON is ignored as it's created before any images are captured
for scan_info in self._scan_dir_manager.all_scans_info():
if scan_info.number_of_images == 0:
@ -813,9 +805,7 @@ class SmartScanThing(lt.Thing):
@_scan_running
def _preview_stitch_wait(self):
"""
Wait for an ongoing preview stitch to return
"""
"""Wait for an ongoing preview stitch to return"""
if self._preview_stitch_running():
with self._preview_stitch_popen_lock:
self._preview_stitch_popen.wait()
@ -826,8 +816,7 @@ class SmartScanThing(lt.Thing):
cancel: lt.deps.CancelHook,
cmd: list[str],
) -> CompletedProcess:
"""
Run a subprocess and log any output
"""Run a subprocess and log any output
Raises:
ChildProcessError if exit code is not zero

View file

@ -1,5 +1,4 @@
"""
OpenFlexure Microscope system control Thing
"""OpenFlexure Microscope system control Thing
This module defines a Thing that can shut down or restart the host computer.
"""
@ -16,15 +15,11 @@ class CommandOutput(BaseModel):
class SystemControlThing(lt.Thing):
"""
Attempt to shutdown the device
"""
"""Attempt to shutdown the device"""
@lt.thing_action
def shutdown(self) -> CommandOutput:
"""
Attempt to shutdown the device
"""
"""Attempt to shutdown the device"""
p = subprocess.Popen(
["sudo", "shutdown", "-h", "now"],
stderr=subprocess.PIPE,

View file

@ -17,9 +17,7 @@ class ErrorCapturingThread(Thread):
"""
def __init__(self, group=None, target=None, args=None, kwargs=None, daemon=None):
"""
Initialise with the same arguments as Thread
"""
"""Initialise with the same arguments as Thread"""
# As all inputs are keywords we need to set the default values for args and kwargs:
if args is None:
args = ()
@ -45,8 +43,7 @@ class ErrorCapturingThread(Thread):
)
def join(self, timeout=None):
"""
Join when the thread is complete. If the thread ended due to an unhandled exception,
"""Join when the thread is complete. If the thread ended due to an unhandled exception,
the exception will be raised when this method is called.
"""
super().join(timeout)