diff --git a/check_version_sync.py b/check_version_sync.py index c47365d7..fddb9f6b 100755 --- a/check_version_sync.py +++ b/check_version_sync.py @@ -1,7 +1,6 @@ #! /usr/bin/env python3 -""" -A script to check that the npm version string matches the python +"""A script to check that the npm version string matches the python version string exactly. """ diff --git a/hardware-specific-tests/picamera2/test_acquisition.py b/hardware-specific-tests/picamera2/test_acquisition.py index b8226cad..bb7d10ea 100644 --- a/hardware-specific-tests/picamera2/test_acquisition.py +++ b/hardware-specific-tests/picamera2/test_acquisition.py @@ -32,8 +32,7 @@ def test_calibration(client): def test_jpeg_and_array(client): - """ - Check that grabbing a jpeg from the stream results in the same size + """Check that grabbing a jpeg from the stream results in the same size image as a array capture or a jpeg capture. """ # Grab a jpeg from the stream diff --git a/hardware-specific-tests/picamera2/test_exposure_time_drift.py b/hardware-specific-tests/picamera2/test_exposure_time_drift.py index 4e712e5e..ffd81914 100644 --- a/hardware-specific-tests/picamera2/test_exposure_time_drift.py +++ b/hardware-specific-tests/picamera2/test_exposure_time_drift.py @@ -1,5 +1,4 @@ -""" -Check exposure times do not drift. +"""Check exposure times do not drift. This can get very tedious. Recommend running pytest with -s option to monitor progress. @@ -19,8 +18,7 @@ logging.basicConfig(level=logging.DEBUG) def _test_exposure_time_drift(desired_time): - """ - Capture 10 full resolution images and check that the exposure time remains constant + """Capture 10 full resolution images and check that the exposure time remains constant This confirms that automatic exposure time adjustment is fully turned off """ @@ -64,8 +62,6 @@ def _test_exposure_time_drift(desired_time): def test_exposure_time_drift(): - """ - Performs the exposure time test for a range of exposure time values. - """ + """Performs the exposure time test for a range of exposure time values.""" for desired_time in [100, 1000, 10000]: _test_exposure_time_drift(desired_time) diff --git a/hardware-specific-tests/picamera2/test_sensor_mode.py b/hardware-specific-tests/picamera2/test_sensor_mode.py index fbc575d6..0fa24099 100644 --- a/hardware-specific-tests/picamera2/test_sensor_mode.py +++ b/hardware-specific-tests/picamera2/test_sensor_mode.py @@ -12,9 +12,7 @@ logging.basicConfig(level=logging.DEBUG) def test_sensor_mode(): - """ - Test capturing raw arrays in two different sensor modes - """ + """Test capturing raw arrays in two different sensor modes""" cam = StreamingPiCamera2() server = ThingServer() server.add_thing(cam, "/camera/") diff --git a/hardware-specific-tests/picamera2/test_tuning.py b/hardware-specific-tests/picamera2/test_tuning.py index a6fd9b76..883aa017 100644 --- a/hardware-specific-tests/picamera2/test_tuning.py +++ b/hardware-specific-tests/picamera2/test_tuning.py @@ -1,6 +1,4 @@ -""" -Tests that check that a tuning file can be reloaded -""" +"""Tests that check that a tuning file can be reloaded""" import os @@ -16,17 +14,13 @@ MODEL = Picamera2.global_camera_info()[0]["Model"] def load_default_tuning(): - """ - Return the default tuning file for the connected camera. - """ + """Return the default tuning file for the connected camera.""" fname = f"{MODEL}.json" return Picamera2.load_tuning_file(fname) def generate_bad_tuning(): - """ - Return a tuning file with an invalid version number to force an error when loaded. - """ + """Return a tuning file with an invalid version number to force an error when loaded.""" default_tuning = load_default_tuning() bad_tuning = default_tuning.copy() bad_tuning["version"] = 999 @@ -34,8 +28,7 @@ def generate_bad_tuning(): def print_tuning(read_file: bool = False): - """ - Print the path of the default tuning file from the the environment variable. + """Print the path of the default tuning file from the the environment variable. :param read_file: Boolean, set true to also print the file contents. @@ -53,8 +46,7 @@ def print_tuning(read_file: bool = False): def _test_bad_tuning_after_good_tuning(configure: bool = False): - """ - Load the default tuning file into the camera, re-load with a broken tuning file, + """Load the default tuning file into the camera, re-load with a broken tuning file, check it errors. Finally check the default tuning file will load again afterwards. :param configure: Boolean, set true to configure the camera on initial loading diff --git a/integration-tests/testfile.py b/integration-tests/testfile.py index 40c7543c..2fd8fb58 100755 --- a/integration-tests/testfile.py +++ b/integration-tests/testfile.py @@ -124,8 +124,7 @@ def set_up_working_dir() -> None: def start_server() -> subprocess.Popen: - """ - Start the server in a subprocess. + """Start the server in a subprocess. The server is started in a subprocess and all outputs are buffered. diff --git a/pyproject.toml b/pyproject.toml index feff2e8f..c611e8e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -128,8 +128,7 @@ ignore = [ "D203", # incompatible with D204 "D213", # incompatible with D212 # Below are checkers to be turned on gradually - "D212", - "D205", + "D205", # Force single line summary! "D415", "D400", "D200", diff --git a/scripts/zenodo/zenodo.py b/scripts/zenodo/zenodo.py index 55904848..a0e3c9d3 100644 --- a/scripts/zenodo/zenodo.py +++ b/scripts/zenodo/zenodo.py @@ -1,5 +1,4 @@ -""" -Copyright (c) 2020 Bath Open Instrumentation Group +"""Copyright (c) 2020 Bath Open Instrumentation Group Adapted from: https://gitlab.com/schlauch/zenodo-api-test/ Copyright (c) 2018 German Aerospace Center (DLR). All rights reserved. SPDX-License-Identifier: MIT-DLR diff --git a/src/openflexure_microscope_server/logging.py b/src/openflexure_microscope_server/logging.py index e3bc8cdd..352cf687 100644 --- a/src/openflexure_microscope_server/logging.py +++ b/src/openflexure_microscope_server/logging.py @@ -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) diff --git a/src/openflexure_microscope_server/scan_directories.py b/src/openflexure_microscope_server/scan_directories.py index 6bb8a8e4..13b242a4 100644 --- a/src/openflexure_microscope_server/scan_directories.py +++ b/src/openflexure_microscope_server/scan_directories.py @@ -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) diff --git a/src/openflexure_microscope_server/scan_planners.py b/src/openflexure_microscope_server/scan_planners.py index 556fbca2..dc2dec71 100644 --- a/src/openflexure_microscope_server/scan_planners.py +++ b/src/openflexure_microscope_server/scan_planners.py @@ -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`` """ diff --git a/src/openflexure_microscope_server/server/__init__.py b/src/openflexure_microscope_server/server/__init__.py index 4b7ec87d..601b9f62 100644 --- a/src/openflexure_microscope_server/server/__init__.py +++ b/src/openflexure_microscope_server/server/__init__.py @@ -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. """ diff --git a/src/openflexure_microscope_server/things/autofocus.py b/src/openflexure_microscope_server/things/autofocus.py index e7b9c339..216e47e5 100644 --- a/src/openflexure_microscope_server/things/autofocus.py +++ b/src/openflexure_microscope_server/things/autofocus.py @@ -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] diff --git a/src/openflexure_microscope_server/things/camera/__init__.py b/src/openflexure_microscope_server/things/camera/__init__.py index 6710ee4d..1a4c105f 100644 --- a/src/openflexure_microscope_server/things/camera/__init__.py +++ b/src/openflexure_microscope_server/things/camera/__init__.py @@ -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 diff --git a/src/openflexure_microscope_server/things/camera/picamera.py b/src/openflexure_microscope_server/things/camera/picamera.py index 25950476..d045d562 100644 --- a/src/openflexure_microscope_server/things/camera/picamera.py +++ b/src/openflexure_microscope_server/things/camera/picamera.py @@ -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. diff --git a/src/openflexure_microscope_server/things/camera/picamera_recalibrate_utils.py b/src/openflexure_microscope_server/things/camera/picamera_recalibrate_utils.py index 12b4c90f..2edbecb9 100644 --- a/src/openflexure_microscope_server/things/camera/picamera_recalibrate_utils.py +++ b/src/openflexure_microscope_server/things/camera/picamera_recalibrate_utils.py @@ -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 diff --git a/src/openflexure_microscope_server/things/camera_stage_mapping.py b/src/openflexure_microscope_server/things/camera_stage_mapping.py index abafa487..59efe2e3 100644 --- a/src/openflexure_microscope_server/things/camera_stage_mapping.py +++ b/src/openflexure_microscope_server/things/camera_stage_mapping.py @@ -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 diff --git a/src/openflexure_microscope_server/things/settings_manager.py b/src/openflexure_microscope_server/things/settings_manager.py index 8ac540b0..2882e7e1 100644 --- a/src/openflexure_microscope_server/things/settings_manager.py +++ b/src/openflexure_microscope_server/things/settings_manager.py @@ -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 diff --git a/src/openflexure_microscope_server/things/smart_scan.py b/src/openflexure_microscope_server/things/smart_scan.py index 2c45d20d..13383bdb 100644 --- a/src/openflexure_microscope_server/things/smart_scan.py +++ b/src/openflexure_microscope_server/things/smart_scan.py @@ -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 diff --git a/src/openflexure_microscope_server/things/system_control.py b/src/openflexure_microscope_server/things/system_control.py index c294b2f7..88287a52 100644 --- a/src/openflexure_microscope_server/things/system_control.py +++ b/src/openflexure_microscope_server/things/system_control.py @@ -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, diff --git a/src/openflexure_microscope_server/utilities.py b/src/openflexure_microscope_server/utilities.py index c40403a0..ceee622a 100644 --- a/src/openflexure_microscope_server/utilities.py +++ b/src/openflexure_microscope_server/utilities.py @@ -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) diff --git a/tests/test_camera_buffer.py b/tests/test_camera_buffer.py index f8e7f30a..65f16eef 100644 --- a/tests/test_camera_buffer.py +++ b/tests/test_camera_buffer.py @@ -1,6 +1,4 @@ -""" -Tests for the built in buffer for the camera. -""" +"""Tests for the built in buffer for the camera.""" from random import randint diff --git a/tests/test_scan_directories.py b/tests/test_scan_directories.py index 32a85edb..0a79476d 100644 --- a/tests/test_scan_directories.py +++ b/tests/test_scan_directories.py @@ -156,9 +156,7 @@ def test_basic_directory_operations(): def test_scan_sequence_and_listing(): - """ - Check created scans are added in order and listed correctly - """ + """Check created scans are added in order and listed correctly""" _clear_scan_dir() scan_dir_manager = ScanDirectoryManager(BASE_SCAN_DIR) # Make 4 scans @@ -185,8 +183,7 @@ def test_scan_sequence_and_listing(): def test_scan_name_non_sequential(): - """ - Check created scans is the correct name if the directories + """Check created scans is the correct name if the directories are not sequential """ _clear_scan_dir() @@ -213,9 +210,7 @@ def test_scan_name_non_sequential(): def test_all_scan_names_taken(): - """ - If the next sequential scan name needs more than 4 digits check error is thrown. - """ + """If the next sequential scan name needs more than 4 digits check error is thrown.""" _clear_scan_dir() os.makedirs(os.path.join(BASE_SCAN_DIR, "fake_scan_9999")) scan_dir_manager = ScanDirectoryManager(BASE_SCAN_DIR) @@ -224,9 +219,7 @@ def test_all_scan_names_taken(): def test_no_scan_names_given(): - """ - Check correct default scan name is used if empty string is given - """ + """Check correct default scan name is used if empty string is given""" _clear_scan_dir() scan_dir_manager = ScanDirectoryManager(BASE_SCAN_DIR) scan_dir = scan_dir_manager.new_scan_dir("") diff --git a/tests/test_scan_planners.py b/tests/test_scan_planners.py index 9abe67c8..fe057bed 100644 --- a/tests/test_scan_planners.py +++ b/tests/test_scan_planners.py @@ -222,8 +222,7 @@ def test_mark_wrong_location(): def test_closest_focus_wth_large_numbers(): - """ - The number of steps gets very large in reality runs some tests to check + """The number of steps gets very large in reality runs some tests to check that everything works well with huge numbers of steps """ intial_position = (0, 0) diff --git a/tests/test_smart_scan.py b/tests/test_smart_scan.py index 83636042..b35ec008 100644 --- a/tests/test_smart_scan.py +++ b/tests/test_smart_scan.py @@ -1,5 +1,4 @@ -""" -Test the SmartScanThing *without* connecting it to a LabThings Server. +"""Test the SmartScanThing *without* connecting it to a LabThings Server. By testing without connecting to the LabThings server it is possible to directly poll any properties and to start any methods. @@ -151,8 +150,7 @@ def test_delete_all_scans(smart_scan_thing, caplog): def _run_only_outer_scan(adjust_inital_state: Optional[Callable] = None): - """ - Create a subclass of SmartScanThing to mock _run_scan and run sample_scan + """Create a subclass of SmartScanThing to mock _run_scan and run sample_scan This should do all the set up for a scan, move into the mocked _run_scan method where this can be tested. Once this is done diff --git a/tests/test_stack.py b/tests/test_stack.py index d40fb977..f1fb5e9a 100644 --- a/tests/test_stack.py +++ b/tests/test_stack.py @@ -1,5 +1,4 @@ -""" -Tests for the smart/fast stacking. +"""Tests for the smart/fast stacking. Currently these tests don't test the Thing itself, just surrounding functionality """ @@ -146,8 +145,7 @@ def test_even_images_to_save(save_ims, extra_ims): def test_computed_stack_params(): - """ - Test StackParams computed properties are as expected + """Test StackParams computed properties are as expected Not using hypothesis or we will just copy in the same formulas. """ @@ -188,8 +186,7 @@ def test_computed_stack_params(): def random_capture(set_id: Optional[int] = None): - """ - Create a capture with random values + """Create a capture with random values :param set_id: Optional, use to set a fixed id rather than a random one """ @@ -206,18 +203,14 @@ def random_capture(set_id: Optional[int] = None): def test_capture_filename_matches_regex(): - """ - For 100 random captures check the image always matches the regex - """ + """For 100 random captures check the image always matches the regex""" for _ in range(100): assert IMAGE_REGEX.search(random_capture().filename) @given(st.integers(min_value=0, max_value=5000)) def test_retrieval_of_captures(start): - """ - For 20 random captures, check each can be retrieved correctly by id - """ + """For 20 random captures, check each can be retrieved correctly by id""" captures = [random_capture(start + i) for i in range(20)] for i, capture in enumerate(captures): diff --git a/tests/utilities/__init__.py b/tests/utilities/__init__.py index 29a7e314..3012d375 100644 --- a/tests/utilities/__init__.py +++ b/tests/utilities/__init__.py @@ -1,5 +1,4 @@ -""" -Utilities for testing and debugging. +"""Utilities for testing and debugging. At the top level are some basic testing functions. More specific testing utilities are provided by modules inside the package. diff --git a/tests/utilities/scan_test_helpers.py b/tests/utilities/scan_test_helpers.py index d1a6a0fe..54086255 100644 --- a/tests/utilities/scan_test_helpers.py +++ b/tests/utilities/scan_test_helpers.py @@ -14,21 +14,18 @@ THIS_DIR = os.path.dirname(os.path.realpath(__file__)) class FakeSample: - """ - A fake sample to test scan algorithms. The sample is able to return + """A fake sample to test scan algorithms. The sample is able to return whether a given position is sample, no image associated with the sample """ def __init__(self, xy_points: list[tuple[int, int]]): - """ - Create the sample from a spline interpolation around + """Create the sample from a spline interpolation around the given points. """ self._sample_perimeter = interp_closed_path(xy_points, 500) def is_sample(self, pos: tuple[int, int], im_size: tuple[int, int]) -> bool: - """ - Return whether an image at a given location with a given image size + """Return whether an image at a given location with a given image size is on the sample This doesn't check the entire image field as this is designed to be used @@ -47,18 +44,14 @@ class FakeSample: @property def patch(self) -> PathPatch: - """ - The sample as a matplotlib patch for plotting - """ + """The sample as a matplotlib patch for plotting""" patch = PathPatch(self._sample_perimeter) patch.set(color=(1.0, 0.8, 1.0, 1.0)) return patch def visualise_scan(sample: FakeSample, planner: scan_planners.ScanPlanner) -> Figure: - """ - For a given sample and scanner object return a matplotlib figure of the scan - """ + """For a given sample and scanner object return a matplotlib figure of the scan""" fig, ax = plt.subplots(figsize=(8, 8)) ax.add_artist(sample.patch) xh, yh = zip(*planner._path_history) @@ -84,8 +77,7 @@ def visualise_scan(sample: FakeSample, planner: scan_planners.ScanPlanner) -> Fi def interp_closed_path(xy_points: list[tuple[int, int]], n_points: int) -> MatPath: - """ - Given a lists of xy_points interpolate an n_point closed curve. This can be used + """Given a lists of xy_points interpolate an n_point closed curve. This can be used to create an arbitrary sample shape plan a scan. Modified from: @@ -113,8 +105,7 @@ def interp_closed_path(xy_points: list[tuple[int, int]], n_points: int) -> MatPa def example_smart_spiral( sample_name: str = "lobed", ) -> tuple[FakeSample, scan_planners.ScanPlanner]: - """ - Run an example scan and return the sample scanned and the planner object + """Run an example scan and return the sample scanned and the planner object after scan is complete """ xy_sample_points = load_sample_points(sample_name) @@ -135,8 +126,7 @@ def example_smart_spiral( def profile_and_save_plot_for_example_smart_spiral(): - """ - Run the example scan and save a plot and the profile data + """Run the example scan and save a plot and the profile data Also print the cumulative stats @@ -162,8 +152,7 @@ def profile_and_save_plot_for_example_smart_spiral(): def update_example_smart_spiral_pickle(sample_name: str): - """ - Pickle the ScanPlanner for the example_smart_spiral(), + """Pickle the ScanPlanner for the example_smart_spiral(), this is done so the history can be compared by testing to check the algorithm is unchanged. @@ -182,8 +171,7 @@ def update_example_smart_spiral_pickle(sample_name: str): def get_expected_result_for_example_smart_spiral( sample_name: str, ) -> scan_planners.ScanPlanner: - """ - Return the expected ScanPlanner object for the example_smart_spiral(), + """Return the expected ScanPlanner object for the example_smart_spiral(), this is pickled, so that it can be committed. """ pkl_fname = os.path.join(THIS_DIR, f"example_smart_spiral_{sample_name}.pkl")