Documentation of python magic class methods
This commit is contained in:
parent
864ca91e5c
commit
e33fecaef0
13 changed files with 119 additions and 32 deletions
|
|
@ -129,8 +129,6 @@ ignore = [
|
|||
"D213", # incompatible with D212
|
||||
"D400", # A stricter version of #415 that doesn't allow !
|
||||
# The checkers below should be turned on as they complain about missing docstrings.
|
||||
"D105",
|
||||
"D107",
|
||||
]
|
||||
|
||||
[tool.ruff.lint.pydocstyle]
|
||||
|
|
|
|||
|
|
@ -16,6 +16,12 @@ class Zenodo:
|
|||
"""A class that packages data for Zenodo."""
|
||||
|
||||
def __init__(self, api_token, use_sandbox=True):
|
||||
"""Initialise the zenodo packager with the API token.
|
||||
|
||||
:param api_token: Your Zenodo API key.
|
||||
:param use_sandbox: True to pushing to ``sandbox.zenodo.org`` rather than
|
||||
``zenodo.org`` for testing.
|
||||
"""
|
||||
self._api_token = api_token
|
||||
self._use_sandbox = use_sandbox
|
||||
if use_sandbox:
|
||||
|
|
|
|||
|
|
@ -92,6 +92,13 @@ class OFMHandler(logging.Handler):
|
|||
"""A logging.Handler that stores the most recent logs for access by the server."""
|
||||
|
||||
def __init__(self, level=logging.INFO, max_logs=250):
|
||||
"""Inialise the handler with a set logging level and message buffer size.
|
||||
|
||||
:param level: The level of logs captured. As standard logs of INFO and above
|
||||
are captured.
|
||||
:param max_logs: The maximum number of logs to hold in memory. This sets
|
||||
how many can be returned over HTTP.
|
||||
"""
|
||||
super().__init__(level=level)
|
||||
self._log = []
|
||||
self._max_logs = max_logs
|
||||
|
|
|
|||
|
|
@ -36,6 +36,10 @@ class ScanDirectoryManager:
|
|||
_base_scan_dir: str
|
||||
|
||||
def __init__(self, base_scan_dir: str):
|
||||
"""Initialise the scan directory manager.
|
||||
|
||||
:param base_scan_dir: Path of the directory that holds all scans.
|
||||
"""
|
||||
self._base_scan_dir = base_scan_dir
|
||||
if not os.path.exists(self._base_scan_dir):
|
||||
os.makedirs(self._base_scan_dir)
|
||||
|
|
@ -194,17 +198,17 @@ class ScanDirectoryManager:
|
|||
|
||||
|
||||
class ScanDirectory:
|
||||
"""A class for handling interactions with scan directories.
|
||||
|
||||
Initalisation parameters:
|
||||
name: the name of the scan (the scan directory basename)
|
||||
base_scan_dir: Path of the directory that holds all scans
|
||||
"""
|
||||
"""A class for handling interactions with scan directories."""
|
||||
|
||||
_name: str
|
||||
_base_scan_dir: str
|
||||
|
||||
def __init__(self, name: str, base_scan_dir: str):
|
||||
"""Initialise the scan directory.
|
||||
|
||||
:param name: the name of the scan (the scan directory basename).
|
||||
:param base_scan_dir: Path of the directory that holds all scans.
|
||||
"""
|
||||
self._name = name
|
||||
self._base_scan_dir = base_scan_dir
|
||||
if not os.path.isdir(self.dir_path):
|
||||
|
|
|
|||
|
|
@ -26,21 +26,7 @@ from .stage import StageDependency as Stage
|
|||
|
||||
|
||||
class StackParams:
|
||||
"""A class for holding for scan parameters.
|
||||
|
||||
All arguments are keyword only
|
||||
|
||||
:param stack_dz: The number of motor steps between images
|
||||
:param images_to_save: The number of images to save to disk
|
||||
:param min_images_to_test: The minimum number of images in the stack before, the
|
||||
stack is evaluated for focus. As more images are captured evaluation of the
|
||||
focus is always evaluated with the same number of images. i.e. if
|
||||
``min_images_to_test=9``, then 9 images are captured, if the stack is not well
|
||||
focused, a 10th image is captured and images 2 to 10 are evaluated for focus
|
||||
:param autofocus_dz: The number of steps in a full autofocus (when required)
|
||||
:param images_dir: The directory to save images to disk
|
||||
:param save_resolution: The resolution to save the captures to disk with
|
||||
"""
|
||||
"""A class for holding for stack parameters, and returning computed ones."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
|
@ -52,6 +38,20 @@ class StackParams:
|
|||
images_dir: str,
|
||||
save_resolution: tuple[int, int],
|
||||
) -> None:
|
||||
"""Initalise the parameters. All arguments are keyword only.
|
||||
|
||||
:param stack_dz: The number of motor steps between images
|
||||
:param images_to_save: The number of images to save to disk
|
||||
:param min_images_to_test: The minimum number of images in the stack before,
|
||||
the stack is evaluated for focus. As more images are captured evaluation
|
||||
of the focus is always evaluated with the same number of images. i.e. if
|
||||
``min_images_to_test=9``, then 9 images are captured, if the stack is not
|
||||
well focused, a 10th image is captured and images 2 to 10 are evaluated
|
||||
for focus
|
||||
:param autofocus_dz: The number of steps in a full autofocus (when required)
|
||||
:param images_dir: The directory to save images to disk
|
||||
:param save_resolution: The resolution to save the captures to disk with
|
||||
"""
|
||||
if min_images_to_test < images_to_save:
|
||||
raise ValueError("Can't save more images than the minimum number tested")
|
||||
if min_images_to_test % 2 == 0 or min_images_to_test <= 0:
|
||||
|
|
@ -205,6 +205,13 @@ class JPEGSharpnessMonitor:
|
|||
"""
|
||||
|
||||
def __init__(self, stage: Stage, camera: Camera, portal: lt.deps.BlockingPortal):
|
||||
"""Initalise a new JPEGSharpnessMonitor. The args are injected automatically.
|
||||
|
||||
:param stage: A direct_thing_client dependency for the the microscope stage.
|
||||
:param camera: A raw_thing_client depeendency for the camera. This is a raw
|
||||
dependency as the underlying class needs to be
|
||||
:param portal: The asyncio blocking portal for asynchronous task scheduling.
|
||||
"""
|
||||
self.camera = camera
|
||||
self.stage = stage
|
||||
self.portal = portal
|
||||
|
|
|
|||
|
|
@ -41,18 +41,19 @@ class CaptureError(RuntimeError):
|
|||
|
||||
|
||||
class NoImageInMemoryError(RuntimeError):
|
||||
"""An error called if no image in in memory when an method is called to use that image."""
|
||||
"""An error called if no image is in memory when accessed."""
|
||||
|
||||
|
||||
class CameraMemoryBuffer:
|
||||
"""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
|
||||
However subclasses of BaseCamera can use this class to store other object types.
|
||||
"""
|
||||
|
||||
_storage: dict[int, tuple[Any, Optional[dict]]]
|
||||
|
||||
def __init__(self):
|
||||
"""Create the buffer instance."""
|
||||
# This dictionary is the main store for data. Dictionaries are ordered since
|
||||
# Python 3.6, so the order in the dictionary is the capture order
|
||||
self._storage = {}
|
||||
|
|
@ -142,16 +143,22 @@ class CameraMemoryBuffer:
|
|||
|
||||
|
||||
class BaseCamera(lt.Thing):
|
||||
"""The base class for all cameras. All cameras must directly inherit from this class."""
|
||||
"""The base class for all cameras. All cameras must directly inherit from this class.
|
||||
|
||||
The connection to the camera hardware should be added to the ``__enter__`` method not
|
||||
``__init__`` method of the subclass.
|
||||
"""
|
||||
|
||||
mjpeg_stream = lt.outputs.MJPEGStreamDescriptor()
|
||||
lores_mjpeg_stream = lt.outputs.MJPEGStreamDescriptor()
|
||||
_memory_buffer = CameraMemoryBuffer()
|
||||
|
||||
def __enter__(self) -> None:
|
||||
"""Open hardware connection when the Thing context manager is opened."""
|
||||
raise NotImplementedError("CameraThings must define their own __enter__ method")
|
||||
|
||||
def __exit__(self, _exc_type, _exc_value, _traceback) -> None:
|
||||
"""Close hardware connection when the Thing context manager is closed."""
|
||||
raise NotImplementedError("CameraThings must define their own __exit__ method")
|
||||
|
||||
@lt.thing_action
|
||||
|
|
|
|||
|
|
@ -23,14 +23,19 @@ from . import BaseCamera, JPEGBlob
|
|||
|
||||
|
||||
class OpenCVCamera(BaseCamera):
|
||||
"""A Thing representing an OpenCV camera."""
|
||||
"""A Thing that provides and interface to an OpenCV Camera."""
|
||||
|
||||
def __init__(self, camera_index: int = 0):
|
||||
"""Iniatilise the thing storing the index of the camera to use.
|
||||
|
||||
:param camera_index: The index of the camera to use for the microscope.
|
||||
"""
|
||||
self.camera_index = camera_index
|
||||
self._capture_thread: Optional[Thread] = None
|
||||
self._capture_enabled = False
|
||||
|
||||
def __enter__(self):
|
||||
"""Start the capture thread when the Thing context manager is opened."""
|
||||
self.cap = cv2.VideoCapture(self.camera_index)
|
||||
self._capture_enabled = True
|
||||
self._capture_thread = Thread(target=self._capture_frames)
|
||||
|
|
@ -38,6 +43,10 @@ class OpenCVCamera(BaseCamera):
|
|||
return self
|
||||
|
||||
def __exit__(self, _exc_type, _exc_value, _traceback):
|
||||
"""Release the camera when the Thing context manager is closed.
|
||||
|
||||
Before releasing the camera the capture thread is closed.
|
||||
"""
|
||||
if self.stream_active:
|
||||
self._capture_enabled = False
|
||||
self._capture_thread.join()
|
||||
|
|
|
|||
|
|
@ -104,9 +104,20 @@ class LensShading(BaseModel):
|
|||
|
||||
|
||||
class StreamingPiCamera2(BaseCamera):
|
||||
"""A Thing that represents an OpenCV camera."""
|
||||
"""A Thing that provides and interface to the Raspberry Pi Camera.
|
||||
|
||||
Currently the Thing only supports the PiCamera v2 board. This needs
|
||||
generalisation.
|
||||
"""
|
||||
|
||||
def __init__(self, camera_num: int = 0):
|
||||
"""Initialise the camera with the given camera number.
|
||||
|
||||
This makes no connection to the camera (except to get the default tuning file).
|
||||
|
||||
:param camera_num: The number of the camera. This should generally be left as 0
|
||||
as most Raspberry Pi boards only support 1 camera.
|
||||
"""
|
||||
self._setting_save_in_progress = False
|
||||
self.camera_num = camera_num
|
||||
self.camera_configs: dict[str, dict] = {}
|
||||
|
|
@ -324,6 +335,11 @@ class StreamingPiCamera2(BaseCamera):
|
|||
self._picamera_lock = RLock()
|
||||
|
||||
def __enter__(self):
|
||||
"""Start streaming when the Thing context manager is opened.
|
||||
|
||||
This opens the picamera connection, initialises the camera, sets the
|
||||
sensor_modes property, and then starts the streams.
|
||||
"""
|
||||
self._initialise_picamera()
|
||||
# populate sensor modes by reading the property
|
||||
self.sensor_modes
|
||||
|
|
@ -360,7 +376,7 @@ class StreamingPiCamera2(BaseCamera):
|
|||
self.start_streaming()
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
# Shut down the camera
|
||||
"""Close the picamera connection when the Thing context manager is closed."""
|
||||
self.stop_streaming()
|
||||
with self._streaming_picamera() as cam:
|
||||
cam.close()
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ RATIO = 0.2
|
|||
|
||||
|
||||
class SimulatedCamera(BaseCamera):
|
||||
"""A Thing representing an OpenCV camera."""
|
||||
"""A Thing that simulates a camera for testing."""
|
||||
|
||||
_stage: Optional[BaseStage] = None
|
||||
_server: Optional[lt.ThingServer] = None
|
||||
|
|
@ -42,6 +42,16 @@ class SimulatedCamera(BaseCamera):
|
|||
canvas_shape: tuple[int, int, int] = (3000, 4000, 3),
|
||||
frame_interval: float = 0.1,
|
||||
):
|
||||
"""Initalise the simulated with settings for how images are generated.
|
||||
|
||||
:param shape: The shape (size) of the generated image.
|
||||
:param glyph_shape: The size randomly positioned glyphs.
|
||||
:param canvas_shape: The shape (size) of the canvas generated on initialisation
|
||||
that images are cropped from. If this is too large the it uses resources,
|
||||
but its size limits the range of motion of the simulation.
|
||||
:param frame_interval: Nominally the time between frames on the MJPEG stream,
|
||||
however the rate may be slower due to calculation time for focus.
|
||||
"""
|
||||
self.shape = shape
|
||||
self.glyph_shape = glyph_shape
|
||||
self.canvas_shape = canvas_shape
|
||||
|
|
@ -143,12 +153,14 @@ class SimulatedCamera(BaseCamera):
|
|||
return self.generate_image((pos["y"], pos["x"], pos["z"]))
|
||||
|
||||
def __enter__(self):
|
||||
"""Start the capture thread when the Thing context manager is opened."""
|
||||
self._capture_enabled = True
|
||||
self._capture_thread = Thread(target=self._capture_frames)
|
||||
self._capture_thread.start()
|
||||
return self
|
||||
|
||||
def __exit__(self, _exc_type, _exc_value, _traceback):
|
||||
"""Close the capture thread when the Thing context manager is closed."""
|
||||
if self.stream_active:
|
||||
self._capture_enabled = False
|
||||
self._capture_thread.join()
|
||||
|
|
|
|||
|
|
@ -155,6 +155,10 @@ class LoggingMoveWrapper:
|
|||
"""
|
||||
|
||||
def __init__(self, move_function: Callable):
|
||||
"""Set the movement function to be wrapped.
|
||||
|
||||
:param move_function: the movement function to be wrapped
|
||||
"""
|
||||
self._move_function: Callable = move_function
|
||||
self._current_position: Optional[CoordinateType] = None
|
||||
self.clear_history()
|
||||
|
|
@ -187,6 +191,7 @@ class CSMUncalibratedError(HTTPException):
|
|||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Customise the default error code and message of HTTPException."""
|
||||
HTTPException.__init__(
|
||||
self,
|
||||
503,
|
||||
|
|
|
|||
|
|
@ -82,7 +82,13 @@ class SmartScanThing(lt.Thing):
|
|||
past scans.
|
||||
"""
|
||||
|
||||
def __init__(self, scans_folder):
|
||||
def __init__(self, scans_folder: str):
|
||||
"""Initialise a SmartScanThing saving to and loading from the input directory.
|
||||
|
||||
:param scans_folder: This is the path to the directory where all scans will be
|
||||
saved. Any scans already in this directory will be accessible through the
|
||||
HTTP interface.
|
||||
"""
|
||||
self._scan_dir_manager = scan_directories.ScanDirectoryManager(scans_folder)
|
||||
self._preview_stitch_popen = None
|
||||
self._preview_stitch_popen_lock = threading.Lock()
|
||||
|
|
|
|||
|
|
@ -18,14 +18,22 @@ class DummyStage(BaseStage):
|
|||
"""
|
||||
|
||||
def __init__(self, step_time: float = 0.001, **kwargs):
|
||||
"""Initialise the Dummy stage, setting the step_time to adjust the speed.
|
||||
|
||||
:param step_time: The time in seconds per "motor" step. The default of 0.001
|
||||
works well for the live simulation. For unit testing it is very slow
|
||||
so the speed can be increased. Increasing it too far is problematic if
|
||||
also doing computationally heavy tasks like simulated image blurring.
|
||||
"""
|
||||
super().__init__(**kwargs)
|
||||
self.step_time = step_time
|
||||
|
||||
def __enter__(self):
|
||||
"""Register the stage position when the Thing context manager is opened."""
|
||||
self.instantaneous_position = self.position
|
||||
|
||||
def __exit__(self, _exc_type, _exc_value, _traceback):
|
||||
pass
|
||||
"""Nothing to do when the Thing context manager is closed."""
|
||||
|
||||
@lt.thing_action
|
||||
def move_relative(
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ class SangaboardThing(BaseStage):
|
|||
self.sangaboard_kwargs["port"] = port
|
||||
|
||||
def __enter__(self):
|
||||
"""Connect to the sangaboard when the Thing context manager is opened."""
|
||||
self._sangaboard = sangaboard.Sangaboard(**self.sangaboard_kwargs)
|
||||
self._sangaboard_lock = threading.RLock()
|
||||
with self.sangaboard() as sb:
|
||||
|
|
@ -51,6 +52,7 @@ class SangaboardThing(BaseStage):
|
|||
self.update_position()
|
||||
|
||||
def __exit__(self, _exc_type, _exc_value, _traceback):
|
||||
"""Close the sangaboard connection when the Thing context manager is closed."""
|
||||
with self.sangaboard() as sb:
|
||||
sb.close()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue