Starting docstring on the same lines as the quotations
This commit is contained in:
parent
be6a6ca6fe
commit
35d47fe3ed
28 changed files with 92 additions and 214 deletions
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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``
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue