Add ANN20X rules to ruff
This commit is contained in:
parent
d673196671
commit
4c46330959
20 changed files with 98 additions and 86 deletions
|
|
@ -12,7 +12,7 @@ from gitlab.exceptions import GitlabGetError
|
||||||
PROJECT_ID = 9238334
|
PROJECT_ID = 9238334
|
||||||
|
|
||||||
|
|
||||||
def main(tag: str, branch: str = "v3"):
|
def main(tag: str, branch: str = "v3") -> None:
|
||||||
"""Create a list of the MRs into a given branch after a tag.
|
"""Create a list of the MRs into a given branch after a tag.
|
||||||
|
|
||||||
:param tag: The tag the mrs should be since.
|
:param tag: The tag the mrs should be since.
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ def extract_zip(zip_data: bytes) -> None:
|
||||||
print(f"Extracted zip to: {THIS_DIR}")
|
print(f"Extracted zip to: {THIS_DIR}")
|
||||||
|
|
||||||
|
|
||||||
def main(branch: str = "v3"):
|
def main(branch: str = "v3") -> None:
|
||||||
"""Fetch build artifacts from GitLab for a given branch and unzip.
|
"""Fetch build artifacts from GitLab for a given branch and unzip.
|
||||||
|
|
||||||
Clear any existing static directory before extract the new files.
|
Clear any existing static directory before extract the new files.
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,7 @@ select = [
|
||||||
"N", # PEP8 naming
|
"N", # PEP8 naming
|
||||||
"D", # Docstring checks
|
"D", # Docstring checks
|
||||||
"ERA001", # Commented out code!
|
"ERA001", # Commented out code!
|
||||||
"ANN001", "ANN002", "ANN003",
|
"ANN",
|
||||||
]
|
]
|
||||||
|
|
||||||
ignore = [
|
ignore = [
|
||||||
|
|
@ -134,7 +134,9 @@ ignore = [
|
||||||
"D203", # incompatible with D204
|
"D203", # incompatible with D204
|
||||||
"D213", # incompatible with D212
|
"D213", # incompatible with D212
|
||||||
"D400", # A stricter version of #415 that doesn't allow !
|
"D400", # A stricter version of #415 that doesn't allow !
|
||||||
# The checkers below should be turned on as they complain about missing docstrings.
|
"ANN204", # Requrite type hints for
|
||||||
|
"ANN401", # Disalows Any, Any is needed at times, Once MyPy is running Any will be
|
||||||
|
# handled appropriately
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.ruff.lint.per-file-ignores]
|
[tool.ruff.lint.per-file-ignores]
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ from fastapi import HTTPException
|
||||||
OFM_LOG_FILE = None
|
OFM_LOG_FILE = None
|
||||||
|
|
||||||
|
|
||||||
def configure_logging(log_folder: str):
|
def configure_logging(log_folder: str) -> None:
|
||||||
"""Configure logging for the server while it is running.
|
"""Configure logging for the server while it is running.
|
||||||
|
|
||||||
This modifies the root logger to have a rotating file handler and
|
This modifies the root logger to have a rotating file handler and
|
||||||
|
|
@ -103,7 +103,7 @@ def retrieve_log_from_file() -> PlainTextResponse:
|
||||||
class OFMLogFileFormatter(logging.Formatter):
|
class OFMLogFileFormatter(logging.Formatter):
|
||||||
"""The formatter used for the OpenFlexure Microscope Server log file."""
|
"""The formatter used for the OpenFlexure Microscope Server log file."""
|
||||||
|
|
||||||
def format(self, record: logging.LogRecord):
|
def format(self, record: logging.LogRecord) -> str:
|
||||||
"""Adjust the logging formatting for uvicorn logs.
|
"""Adjust the logging formatting for uvicorn logs.
|
||||||
|
|
||||||
uvicorn has two loggers. Each API access is ``uvicorn.access`` which we filter
|
uvicorn has two loggers. Each API access is ``uvicorn.access`` which we filter
|
||||||
|
|
@ -132,7 +132,7 @@ class OFMHandler(logging.Handler):
|
||||||
self._log = []
|
self._log = []
|
||||||
self._max_logs = max_logs
|
self._max_logs = max_logs
|
||||||
|
|
||||||
def append_record(self, record: logging.LogRecord):
|
def append_record(self, record: logging.LogRecord) -> None:
|
||||||
"""Format message and append it to a list of records.
|
"""Format message and append it to a list of records.
|
||||||
|
|
||||||
The built in formatter is used to format the record.
|
The built in formatter is used to format the record.
|
||||||
|
|
@ -143,7 +143,7 @@ class OFMHandler(logging.Handler):
|
||||||
while len(self._log) > self._max_logs:
|
while len(self._log) > self._max_logs:
|
||||||
self._log.pop(0)
|
self._log.pop(0)
|
||||||
|
|
||||||
def emit(self, record: logging.LogRecord):
|
def emit(self, record: logging.LogRecord) -> None:
|
||||||
"""Emit will save the logged record to the log."""
|
"""Emit will save the logged record to the log."""
|
||||||
try:
|
try:
|
||||||
if record.levelno >= self.level:
|
if record.levelno >= self.level:
|
||||||
|
|
@ -156,7 +156,7 @@ class OFMHandler(logging.Handler):
|
||||||
self.handleError(record)
|
self.handleError(record)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def log_history(self):
|
def log_history(self) -> str:
|
||||||
"""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)
|
return "\n".join(self._log)
|
||||||
|
|
||||||
|
|
@ -164,7 +164,7 @@ class OFMHandler(logging.Handler):
|
||||||
class UvicornAccessFilter(logging.Filter):
|
class UvicornAccessFilter(logging.Filter):
|
||||||
"""A logging filter to filter out "uvicorn.access" messages."""
|
"""A logging filter to filter out "uvicorn.access" messages."""
|
||||||
|
|
||||||
def filter(self, record: logging.LogRecord):
|
def filter(self, record: logging.LogRecord) -> bool:
|
||||||
"""Return False if record is from "uvicorn.access"."""
|
"""Return False if record is from "uvicorn.access"."""
|
||||||
return not record.name.startswith("uvicorn.access")
|
return not record.name.startswith("uvicorn.access")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ class ScanData(BaseModel):
|
||||||
This should be set with ``set_final_data()`` to ensure duration is set.
|
This should be set with ``set_final_data()`` to ensure duration is set.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def set_final_data(self, result: str):
|
def set_final_data(self, result: str) -> None:
|
||||||
"""Set the final data for the scan, scan duration is automatically calculated.
|
"""Set the final data for the scan, scan duration is automatically calculated.
|
||||||
|
|
||||||
:param result: A string describing the result.
|
:param result: A string describing the result.
|
||||||
|
|
@ -423,13 +423,13 @@ class ScanDirectory:
|
||||||
"""The time the directory was created on disk."""
|
"""The time the directory was created on disk."""
|
||||||
return os.path.getctime(self.dir_path)
|
return os.path.getctime(self.dir_path)
|
||||||
|
|
||||||
def get_scan_files(self):
|
def get_scan_files(self) -> list[str]:
|
||||||
"""Return a list of the files in the images dir."""
|
"""Return a list of the files in the images dir."""
|
||||||
if self.images_dir is None:
|
if self.images_dir is None:
|
||||||
return []
|
return []
|
||||||
return os.listdir(self.images_dir)
|
return os.listdir(self.images_dir)
|
||||||
|
|
||||||
def _extract_scan_images(self, file_list: list[str]):
|
def _extract_scan_images(self, file_list: list[str]) -> list[str]:
|
||||||
"""Extract files which match the naming convention for scan images.
|
"""Extract files which match the naming convention for scan images.
|
||||||
|
|
||||||
:param file_list: The list of files to search. Normally this would be
|
:param file_list: The list of files to search. Normally this would be
|
||||||
|
|
@ -439,7 +439,7 @@ class ScanDirectory:
|
||||||
"""
|
"""
|
||||||
return [i for i in file_list if IMAGE_REGEX.search(i)]
|
return [i for i in file_list if IMAGE_REGEX.search(i)]
|
||||||
|
|
||||||
def _extract_final_stitches(self, file_list: list[str]):
|
def _extract_final_stitches(self, file_list: list[str]) -> list[str]:
|
||||||
"""Extract files which match the naming convention for final stitches.
|
"""Extract files which match the naming convention for final stitches.
|
||||||
|
|
||||||
:param file_list: The list of files to search.
|
:param file_list: The list of files to search.
|
||||||
|
|
@ -448,7 +448,7 @@ class ScanDirectory:
|
||||||
"""
|
"""
|
||||||
return [i for i in file_list if STITCH_REGEX.search(i)]
|
return [i for i in file_list if STITCH_REGEX.search(i)]
|
||||||
|
|
||||||
def _extract_dzi_files(self, file_list: list[str]):
|
def _extract_dzi_files(self, file_list: list[str]) -> list[str]:
|
||||||
"""Extract files which match the naming convention for dzi_files.
|
"""Extract files which match the naming convention for dzi_files.
|
||||||
|
|
||||||
:param file_list: The list of files to search.
|
:param file_list: The list of files to search.
|
||||||
|
|
@ -509,7 +509,7 @@ class ScanDirectory:
|
||||||
files.append(os.path.relpath(full_path, self.dir_path))
|
files.append(os.path.relpath(full_path, self.dir_path))
|
||||||
return files
|
return files
|
||||||
|
|
||||||
def save_scan_data(self, scan_data: ScanData):
|
def save_scan_data(self, scan_data: ScanData) -> None:
|
||||||
"""Save the scan data for this scan to disk."""
|
"""Save the scan data for this scan to disk."""
|
||||||
if self.scan_data_path is None:
|
if self.scan_data_path is None:
|
||||||
raise FileNotFoundError(
|
raise FileNotFoundError(
|
||||||
|
|
@ -551,7 +551,7 @@ class ScanDirectory:
|
||||||
return zip_fname
|
return zip_fname
|
||||||
|
|
||||||
|
|
||||||
def get_files_in_zip(zip_path: str):
|
def get_files_in_zip(zip_path: str) -> list[str]:
|
||||||
"""List the relative paths of all files and folders in the zip folder specified."""
|
"""List the relative paths of all files and folders in the zip folder specified."""
|
||||||
scan_zip = zipfile.ZipFile(zip_path)
|
scan_zip = zipfile.ZipFile(zip_path)
|
||||||
return [os.path.normpath(i) for i in scan_zip.namelist()]
|
return [os.path.normpath(i) for i in scan_zip.namelist()]
|
||||||
|
|
|
||||||
|
|
@ -332,7 +332,7 @@ class SmartSpiral(ScanPlanner):
|
||||||
"""Sort the remaining positions based on the current location."""
|
"""Sort the remaining positions based on the current location."""
|
||||||
|
|
||||||
# Defined rather than use a lambda for readability
|
# Defined rather than use a lambda for readability
|
||||||
def sort_key(pos: XYPos):
|
def sort_key(pos: XYPos) -> tuple[float, float, float]:
|
||||||
return (
|
return (
|
||||||
self.moves_between(current_pos, pos),
|
self.moves_between(current_pos, pos),
|
||||||
self.moves_between(self._initial_position, pos),
|
self.moves_between(self._initial_position, pos),
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ from .legacy_api import add_v2_endpoints
|
||||||
from ..logging import configure_logging, retrieve_log, retrieve_log_from_file
|
from ..logging import configure_logging, retrieve_log, retrieve_log_from_file
|
||||||
|
|
||||||
|
|
||||||
def set_shutdown_function(shutdown_function: Callable[[], None]):
|
def set_shutdown_function(shutdown_function: Callable[[], None]) -> 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
|
This monkey patches the Uvicorn Server's handle_exit. This is needed because
|
||||||
|
|
@ -32,7 +32,7 @@ def set_shutdown_function(shutdown_function: Callable[[], None]):
|
||||||
original_handler = Server.handle_exit
|
original_handler = Server.handle_exit
|
||||||
|
|
||||||
@wraps(Server.handle_exit)
|
@wraps(Server.handle_exit)
|
||||||
def handle_exit(*args: Any, **kwargs: Any):
|
def handle_exit(*args: Any, **kwargs: Any) -> None:
|
||||||
shutdown_function()
|
shutdown_function()
|
||||||
original_handler(*args, **kwargs)
|
original_handler(*args, **kwargs)
|
||||||
|
|
||||||
|
|
@ -41,7 +41,7 @@ def set_shutdown_function(shutdown_function: Callable[[], None]):
|
||||||
|
|
||||||
def customise_server(
|
def customise_server(
|
||||||
server: lt.ThingServer, log_folder: str, scans_folder: Optional[str]
|
server: lt.ThingServer, log_folder: str, scans_folder: Optional[str]
|
||||||
):
|
) -> None:
|
||||||
"""Customise the server with additional endpoints, etc."""
|
"""Customise the server with additional endpoints, etc."""
|
||||||
configure_logging(log_folder)
|
configure_logging(log_folder)
|
||||||
add_v2_endpoints(server)
|
add_v2_endpoints(server)
|
||||||
|
|
@ -66,7 +66,7 @@ def _get_scans_dir(config: dict) -> Optional[str]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def serve_from_cli(argv: Optional[list[str]] = None):
|
def serve_from_cli(argv: Optional[list[str]] = None) -> None:
|
||||||
"""Start the server from the command line."""
|
"""Start the server from the command line."""
|
||||||
args = lt.cli.parse_args(argv)
|
args = lt.cli.parse_args(argv)
|
||||||
|
|
||||||
|
|
@ -85,7 +85,7 @@ def serve_from_cli(argv: Optional[list[str]] = None):
|
||||||
server = lt.cli.server_from_config(config)
|
server = lt.cli.server_from_config(config)
|
||||||
customise_server(server, log_folder, scans_folder)
|
customise_server(server, log_folder, scans_folder)
|
||||||
|
|
||||||
def shutdown_call():
|
def shutdown_call() -> None:
|
||||||
try:
|
try:
|
||||||
# Kill any mjpeg streams so that StreamingResponses close.
|
# Kill any mjpeg streams so that StreamingResponses close.
|
||||||
server.things["/camera/"].kill_mjpeg_streams()
|
server.things["/camera/"].kill_mjpeg_streams()
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ def add_static_files(app: FastAPI, scans_folder: Optional[str]) -> None:
|
||||||
check_static_dir()
|
check_static_dir()
|
||||||
|
|
||||||
@app.get("/", response_class=RedirectResponse)
|
@app.get("/", response_class=RedirectResponse)
|
||||||
async def redirect_fastapi():
|
async def redirect_fastapi() -> str:
|
||||||
return "/index.html"
|
return "/index.html"
|
||||||
|
|
||||||
# Mounting the webapp at / file by file to allow other endpoints to be created
|
# Mounting the webapp at / file by file to allow other endpoints to be created
|
||||||
|
|
@ -77,7 +77,7 @@ def add_static_files(app: FastAPI, scans_folder: Optional[str]) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def check_static_dir():
|
def check_static_dir() -> None:
|
||||||
"""Check that the static dir exists and contains expected files and dirs."""
|
"""Check that the static dir exists and contains expected files and dirs."""
|
||||||
if not os.path.isdir(STATIC_PATH):
|
if not os.path.isdir(STATIC_PATH):
|
||||||
raise FileNotFoundError(
|
raise FileNotFoundError(
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ class StitcherValidationError(RuntimeError):
|
||||||
"""The stitcher received values that it deems unsafe to create a command from."""
|
"""The stitcher received values that it deems unsafe to create a command from."""
|
||||||
|
|
||||||
|
|
||||||
def validate_command(cmd: list[str]):
|
def validate_command(cmd: list[str]) -> None:
|
||||||
"""Validate that the command only characters that are allowed in a path.
|
"""Validate that the command only characters that are allowed in a path.
|
||||||
|
|
||||||
The values in the commands should be numbers, commandline flags, paths, and
|
The values in the commands should be numbers, commandline flags, paths, and
|
||||||
|
|
@ -340,7 +340,7 @@ class FinalStitcher(BaseStitcher):
|
||||||
# Print everything in the buffer when program finishes
|
# Print everything in the buffer when program finishes
|
||||||
self.log_buffer(process.stdout)
|
self.log_buffer(process.stdout)
|
||||||
|
|
||||||
def log_buffer(self, buffer: TextIOWrapper):
|
def log_buffer(self, buffer: TextIOWrapper) -> None:
|
||||||
"""Log everything in the buffer at INFO level."""
|
"""Log everything in the buffer at INFO level."""
|
||||||
while line := buffer.readline():
|
while line := buffer.readline():
|
||||||
self.logger.info(line)
|
self.logger.info(line)
|
||||||
|
|
|
||||||
|
|
@ -127,7 +127,7 @@ class StackParams:
|
||||||
"""
|
"""
|
||||||
return self.min_images_to_test + 15
|
return self.min_images_to_test + 15
|
||||||
|
|
||||||
def slice_to_save(self, sharpest_index: int):
|
def slice_to_save(self, sharpest_index: int) -> slice:
|
||||||
"""Return the slice of images to save given the index of the sharpest image."""
|
"""Return the slice of images to save given the index of the sharpest image."""
|
||||||
images_each_side = (self.images_to_save - 1) // 2
|
images_each_side = (self.images_to_save - 1) // 2
|
||||||
return slice(
|
return slice(
|
||||||
|
|
@ -234,7 +234,7 @@ class JPEGSharpnessMonitor:
|
||||||
|
|
||||||
running = False
|
running = False
|
||||||
|
|
||||||
async def monitor_sharpness(self):
|
async def monitor_sharpness(self) -> None:
|
||||||
"""Start monitoring the frame sizes."""
|
"""Start monitoring the frame sizes."""
|
||||||
self.running = True
|
self.running = True
|
||||||
async for frame in self.camera.lores_mjpeg_stream.frame_async_generator():
|
async for frame in self.camera.lores_mjpeg_stream.frame_async_generator():
|
||||||
|
|
@ -244,7 +244,7 @@ class JPEGSharpnessMonitor:
|
||||||
break
|
break
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def run(self):
|
def run(self) -> None:
|
||||||
"""Context manager, during which we will monitor sharpness from the camera."""
|
"""Context manager, during which we will monitor sharpness from the camera."""
|
||||||
self.portal.start_task_soon(self.monitor_sharpness)
|
self.portal.start_task_soon(self.monitor_sharpness)
|
||||||
try:
|
try:
|
||||||
|
|
@ -403,7 +403,7 @@ class AutofocusThing(lt.Thing):
|
||||||
sharpness_monitor: SharpnessMonitorDep,
|
sharpness_monitor: SharpnessMonitorDep,
|
||||||
dz: int = 2000,
|
dz: int = 2000,
|
||||||
start: Literal["centre", "base"] = "centre",
|
start: Literal["centre", "base"] = "centre",
|
||||||
):
|
) -> tuple[list[float], list[float]]:
|
||||||
"""Repeatedly autofocus the stage until it looks focused.
|
"""Repeatedly autofocus the stage until it looks focused.
|
||||||
|
|
||||||
This action will run the ``fast_autofocus`` action until it settles on a point
|
This action will run the ``fast_autofocus`` action until it settles on a point
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,7 @@ class CameraMemoryBuffer:
|
||||||
"No image with matching id in memory to retrieve."
|
"No image with matching id in memory to retrieve."
|
||||||
) from e
|
) from e
|
||||||
|
|
||||||
def clear(self):
|
def clear(self) -> None:
|
||||||
"""Clear all images from memory."""
|
"""Clear all images from memory."""
|
||||||
self._storage.clear()
|
self._storage.clear()
|
||||||
|
|
||||||
|
|
@ -208,7 +208,7 @@ class BaseCamera(lt.Thing):
|
||||||
"CameraThings must define their own start_streaming method"
|
"CameraThings must define their own start_streaming method"
|
||||||
)
|
)
|
||||||
|
|
||||||
def kill_mjpeg_streams(self):
|
def kill_mjpeg_streams(self) -> None:
|
||||||
"""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
|
This is called when uvicorn gets the a shutdown signal. As this is called from
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ class OpenCVCamera(BaseCamera):
|
||||||
return self._capture_thread.is_alive()
|
return self._capture_thread.is_alive()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def _capture_frames(self):
|
def _capture_frames(self) -> None:
|
||||||
portal = lt.get_blocking_portal(self)
|
portal = lt.get_blocking_portal(self)
|
||||||
while self._capture_enabled:
|
while self._capture_enabled:
|
||||||
ret, frame = self.cap.read()
|
ret, frame = self.cap.read()
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,7 @@ class PicameraStreamOutput(Output):
|
||||||
_timestamp: Optional[int] = None,
|
_timestamp: Optional[int] = None,
|
||||||
_packet: Any = None,
|
_packet: Any = None,
|
||||||
_audio: bool = False,
|
_audio: bool = False,
|
||||||
):
|
) -> None:
|
||||||
"""Add a frame to the stream's ringbuffer."""
|
"""Add a frame to the stream's ringbuffer."""
|
||||||
self.stream.add_frame(frame, self.portal)
|
self.stream.add_frame(frame, self.portal)
|
||||||
|
|
||||||
|
|
@ -173,7 +173,7 @@ class StreamingPiCamera2(BaseCamera):
|
||||||
)
|
)
|
||||||
"""Whether the MJPEG stream is active."""
|
"""Whether the MJPEG stream is active."""
|
||||||
|
|
||||||
def save_settings(self):
|
def save_settings(self) -> None:
|
||||||
"""Override save_settings to ensure that camera properties don't recurse.
|
"""Override save_settings to ensure that camera properties don't recurse.
|
||||||
|
|
||||||
This method is run by any Thing when a ThingSetting is saved. However, the
|
This method is run by any Thing when a ThingSetting is saved. However, the
|
||||||
|
|
@ -204,7 +204,7 @@ class StreamingPiCamera2(BaseCamera):
|
||||||
return self._analogue_gain
|
return self._analogue_gain
|
||||||
|
|
||||||
@analogue_gain.setter
|
@analogue_gain.setter
|
||||||
def analogue_gain(self, value: float):
|
def analogue_gain(self, value: float) -> None:
|
||||||
self._analogue_gain = value
|
self._analogue_gain = value
|
||||||
if self.streaming:
|
if self.streaming:
|
||||||
with self._streaming_picamera() as cam:
|
with self._streaming_picamera() as cam:
|
||||||
|
|
@ -224,7 +224,7 @@ class StreamingPiCamera2(BaseCamera):
|
||||||
return self._colour_gains
|
return self._colour_gains
|
||||||
|
|
||||||
@colour_gains.setter
|
@colour_gains.setter
|
||||||
def colour_gains(self, value: tuple[float, float]):
|
def colour_gains(self, value: tuple[float, float]) -> None:
|
||||||
self._colour_gains = value
|
self._colour_gains = value
|
||||||
if self.streaming:
|
if self.streaming:
|
||||||
with self._streaming_picamera() as cam:
|
with self._streaming_picamera() as cam:
|
||||||
|
|
@ -248,7 +248,7 @@ class StreamingPiCamera2(BaseCamera):
|
||||||
return self._exposure_time
|
return self._exposure_time
|
||||||
|
|
||||||
@exposure_time.setter
|
@exposure_time.setter
|
||||||
def exposure_time(self, value: int):
|
def exposure_time(self, value: int) -> None:
|
||||||
self._exposure_time = value
|
self._exposure_time = value
|
||||||
if self.streaming:
|
if self.streaming:
|
||||||
with self._streaming_picamera() as cam:
|
with self._streaming_picamera() as cam:
|
||||||
|
|
@ -292,7 +292,7 @@ class StreamingPiCamera2(BaseCamera):
|
||||||
return SensorModeSelector(**self._sensor_mode)
|
return SensorModeSelector(**self._sensor_mode)
|
||||||
|
|
||||||
@sensor_mode.setter
|
@sensor_mode.setter
|
||||||
def sensor_mode(self, new_mode: Optional[SensorModeSelector | dict]):
|
def sensor_mode(self, new_mode: Optional[SensorModeSelector | dict]) -> None:
|
||||||
"""Change the sensor mode used."""
|
"""Change the sensor mode used."""
|
||||||
if new_mode is None:
|
if new_mode is None:
|
||||||
self._sensor_mode = None
|
self._sensor_mode = None
|
||||||
|
|
@ -352,7 +352,7 @@ class StreamingPiCamera2(BaseCamera):
|
||||||
) from e
|
) from e
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _initialise_picamera(self):
|
def _initialise_picamera(self) -> None:
|
||||||
"""Acquire the picamera device and store it as ``self._picamera``.
|
"""Acquire the picamera device and store it as ``self._picamera``.
|
||||||
|
|
||||||
This duplicates logic in ``Picamera2.__init__`` to provide a tuning file that
|
This duplicates logic in ``Picamera2.__init__`` to provide a tuning file that
|
||||||
|
|
@ -646,7 +646,7 @@ class StreamingPiCamera2(BaseCamera):
|
||||||
self,
|
self,
|
||||||
target_white_level: int = 700,
|
target_white_level: int = 700,
|
||||||
percentile: float = 99.9,
|
percentile: float = 99.9,
|
||||||
):
|
) -> None:
|
||||||
"""Adjust exposure until a the target white level is reached.
|
"""Adjust exposure until a the target white level is reached.
|
||||||
|
|
||||||
Starting from the minimum exposure, gradually increase exposure until
|
Starting from the minimum exposure, gradually increase exposure until
|
||||||
|
|
@ -672,7 +672,7 @@ class StreamingPiCamera2(BaseCamera):
|
||||||
self,
|
self,
|
||||||
method: Literal["percentile", "centre"] = "centre",
|
method: Literal["percentile", "centre"] = "centre",
|
||||||
luminance_power: float = 1.0,
|
luminance_power: float = 1.0,
|
||||||
):
|
) -> None:
|
||||||
"""Correct the white balance of the image.
|
"""Correct the white balance of the image.
|
||||||
|
|
||||||
This calibration requires a neutral image, such that the 99th centile
|
This calibration requires a neutral image, such that the 99th centile
|
||||||
|
|
@ -747,7 +747,7 @@ class StreamingPiCamera2(BaseCamera):
|
||||||
self._initialise_picamera()
|
self._initialise_picamera()
|
||||||
|
|
||||||
@lt.thing_action
|
@lt.thing_action
|
||||||
def reset_ccm(self):
|
def reset_ccm(self) -> None:
|
||||||
"""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
|
These values are from the Raspberry Pi Camera Algorithm and Tuning Guide, page
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ See repository root for licensing information.
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import logging
|
import logging
|
||||||
from typing import Literal, Optional
|
from typing import Literal, Optional, Mapping
|
||||||
from types import TracebackType
|
from types import TracebackType
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
import time
|
import time
|
||||||
|
|
@ -76,7 +76,7 @@ class SimulatedCamera(BaseCamera):
|
||||||
self.generate_blobs()
|
self.generate_blobs()
|
||||||
self.generate_canvas()
|
self.generate_canvas()
|
||||||
|
|
||||||
def generate_sprites(self):
|
def generate_sprites(self) -> None:
|
||||||
"""Generate sprites to populate the image."""
|
"""Generate sprites to populate the image."""
|
||||||
sprite_sizes = [5, 7, 10, 21, 36, 40]
|
sprite_sizes = [5, 7, 10, 21, 36, 40]
|
||||||
self.sprites = []
|
self.sprites = []
|
||||||
|
|
@ -109,7 +109,7 @@ class SimulatedCamera(BaseCamera):
|
||||||
# Convert to uint8 and append to the list
|
# Convert to uint8 and append to the list
|
||||||
self.sprites.append(sprite.astype(np.uint8))
|
self.sprites.append(sprite.astype(np.uint8))
|
||||||
|
|
||||||
def generate_blobs(self, n_blobs: int = 1000):
|
def generate_blobs(self, n_blobs: int = 1000) -> None:
|
||||||
"""Generate coordinates of blobs and their sizes.
|
"""Generate coordinates of blobs and their sizes.
|
||||||
|
|
||||||
A 1000x3 array is returned. Each row represents (x,y) coordinate
|
A 1000x3 array is returned. Each row represents (x,y) coordinate
|
||||||
|
|
@ -125,7 +125,7 @@ class SimulatedCamera(BaseCamera):
|
||||||
self.blobs[:, 1] = RNG.uniform(w / 2, self.canvas_shape[1] - w / 2, n_blobs)
|
self.blobs[:, 1] = RNG.uniform(w / 2, self.canvas_shape[1] - w / 2, n_blobs)
|
||||||
self.blobs[:, 2] = RNG.choice(len(self.sprites), n_blobs)
|
self.blobs[:, 2] = RNG.choice(len(self.sprites), n_blobs)
|
||||||
|
|
||||||
def generate_canvas(self):
|
def generate_canvas(self) -> None:
|
||||||
"""Generate a canvas.
|
"""Generate a canvas.
|
||||||
|
|
||||||
Canvas is int16 so that random noise can be added to simulation image before
|
Canvas is int16 so that random noise can be added to simulation image before
|
||||||
|
|
@ -145,7 +145,7 @@ class SimulatedCamera(BaseCamera):
|
||||||
self.canvas[self.canvas < 0] = 0
|
self.canvas[self.canvas < 0] = 0
|
||||||
self.canvas[self.canvas > 255] = 255
|
self.canvas[self.canvas > 255] = 255
|
||||||
|
|
||||||
def generate_image(self, pos: tuple[int, int, int]):
|
def generate_image(self, pos: tuple[int, int, int]) -> np.ndarray:
|
||||||
"""Generate an image with blobs based on supplied coordinates."""
|
"""Generate an image with blobs based on supplied coordinates."""
|
||||||
canvas_width, canvas_height, _ = self.canvas_shape
|
canvas_width, canvas_height, _ = self.canvas_shape
|
||||||
image_width, image_height, _ = self.shape
|
image_width, image_height, _ = self.shape
|
||||||
|
|
@ -180,16 +180,16 @@ class SimulatedCamera(BaseCamera):
|
||||||
|
|
||||||
def attach_to_server(
|
def attach_to_server(
|
||||||
self, server: lt.ThingServer, path: str, setting_storage_path: str
|
self, server: lt.ThingServer, path: str, setting_storage_path: str
|
||||||
):
|
) -> None:
|
||||||
"""Wrap the attach_to_server method so the server instance can be stored.
|
"""Wrap the attach_to_server method so the server instance can be stored.
|
||||||
|
|
||||||
Direct access to the server instance is needed to get the stage position while
|
Direct access to the server instance is needed to get the stage position while
|
||||||
maintaining the same public API as a real camera that doesn't need this access.
|
maintaining the same public API as a real camera that doesn't need this access.
|
||||||
"""
|
"""
|
||||||
self._server = server
|
self._server = server
|
||||||
return super().attach_to_server(server, path, setting_storage_path)
|
super().attach_to_server(server, path, setting_storage_path)
|
||||||
|
|
||||||
def get_stage_position(self):
|
def get_stage_position(self) -> Mapping[str, int]:
|
||||||
"""Return the stage position.
|
"""Return the stage position.
|
||||||
|
|
||||||
The simulation camera has access to the stage position so it can generate a
|
The simulation camera has access to the stage position so it can generate a
|
||||||
|
|
@ -199,7 +199,7 @@ class SimulatedCamera(BaseCamera):
|
||||||
self._stage = self._server.things["/stage/"]
|
self._stage = self._server.things["/stage/"]
|
||||||
return self._stage.instantaneous_position
|
return self._stage.instantaneous_position
|
||||||
|
|
||||||
def generate_frame(self):
|
def generate_frame(self) -> np.ndarray:
|
||||||
"""Generate a frame with blobs based on the stage coordinates."""
|
"""Generate a frame with blobs based on the stage coordinates."""
|
||||||
try:
|
try:
|
||||||
pos = self.get_stage_position()
|
pos = self.get_stage_position()
|
||||||
|
|
@ -256,7 +256,7 @@ class SimulatedCamera(BaseCamera):
|
||||||
|
|
||||||
noise_level = lt.ThingProperty(float, 2.0)
|
noise_level = lt.ThingProperty(float, 2.0)
|
||||||
|
|
||||||
def _capture_frames(self):
|
def _capture_frames(self) -> None:
|
||||||
portal = lt.get_blocking_portal(self)
|
portal = lt.get_blocking_portal(self)
|
||||||
while self._capture_enabled:
|
while self._capture_enabled:
|
||||||
time.sleep(self.frame_interval)
|
time.sleep(self.frame_interval)
|
||||||
|
|
@ -312,14 +312,14 @@ class SimulatedCamera(BaseCamera):
|
||||||
return Image.fromarray(self.generate_frame())
|
return Image.fromarray(self.generate_frame())
|
||||||
|
|
||||||
@lt.thing_action
|
@lt.thing_action
|
||||||
def remove_sample(self):
|
def remove_sample(self) -> None:
|
||||||
"""Show the simulated background with no sample."""
|
"""Show the simulated background with no sample."""
|
||||||
if not self._show_sample:
|
if not self._show_sample:
|
||||||
raise RuntimeError("Sample is already removed.")
|
raise RuntimeError("Sample is already removed.")
|
||||||
self._show_sample = False
|
self._show_sample = False
|
||||||
|
|
||||||
@lt.thing_action
|
@lt.thing_action
|
||||||
def load_sample(self):
|
def load_sample(self) -> None:
|
||||||
"""Show the simulated sample."""
|
"""Show the simulated sample."""
|
||||||
if self._show_sample:
|
if self._show_sample:
|
||||||
raise RuntimeError("Sample is already in place.")
|
raise RuntimeError("Sample is already in place.")
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ from typing import (
|
||||||
NamedTuple,
|
NamedTuple,
|
||||||
Optional,
|
Optional,
|
||||||
Tuple,
|
Tuple,
|
||||||
|
Mapping,
|
||||||
)
|
)
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
|
|
||||||
|
|
@ -85,7 +86,7 @@ class RecordedMove:
|
||||||
positions: List[CoordinateType] = [p for t, p in self._history if p is not None]
|
positions: List[CoordinateType] = [p for t, p in self._history if p is not None]
|
||||||
return MoveHistory(times, positions)
|
return MoveHistory(times, positions)
|
||||||
|
|
||||||
def clear_history(self):
|
def clear_history(self) -> None:
|
||||||
"""Reset our history to be an empty list."""
|
"""Reset our history to be an empty list."""
|
||||||
self._history = []
|
self._history = []
|
||||||
|
|
||||||
|
|
@ -241,7 +242,7 @@ class CameraStageMapper(lt.Thing):
|
||||||
return None
|
return None
|
||||||
return self.last_calibration["image_resolution"]
|
return self.last_calibration["image_resolution"]
|
||||||
|
|
||||||
def assert_calibrated(self):
|
def assert_calibrated(self) -> None:
|
||||||
"""Raise an exception if the image_to_stage_displacement matrix is not set."""
|
"""Raise an exception if the image_to_stage_displacement matrix is not set."""
|
||||||
if self.image_to_stage_displacement_matrix is None:
|
if self.image_to_stage_displacement_matrix is None:
|
||||||
# Disable check of no message in raised exception as the message is explicitly
|
# Disable check of no message in raised exception as the message is explicitly
|
||||||
|
|
@ -254,7 +255,7 @@ class CameraStageMapper(lt.Thing):
|
||||||
stage: Stage,
|
stage: Stage,
|
||||||
x: float,
|
x: float,
|
||||||
y: float,
|
y: float,
|
||||||
):
|
) -> None:
|
||||||
"""Move by a given number of pixels on the camera.
|
"""Move by a given number of pixels on the camera.
|
||||||
|
|
||||||
NB x and y here refer to what is usually understood to be the horizontal and
|
NB x and y here refer to what is usually understood to be the horizontal and
|
||||||
|
|
@ -273,7 +274,7 @@ class CameraStageMapper(lt.Thing):
|
||||||
stage.move_relative(x=relative_move[0], y=relative_move[1])
|
stage.move_relative(x=relative_move[0], y=relative_move[1])
|
||||||
|
|
||||||
@lt.thing_property
|
@lt.thing_property
|
||||||
def thing_state(self) -> dict[str, Any]:
|
def thing_state(self) -> Mapping[str, Any]:
|
||||||
"""Summary metadata describing the current state of the Thing."""
|
"""Summary metadata describing the current state of the Thing."""
|
||||||
return {
|
return {
|
||||||
k: getattr(self, k)
|
k: getattr(self, k)
|
||||||
|
|
|
||||||
|
|
@ -38,8 +38,17 @@ CSMDep = lt.deps.direct_thing_client_dependency(
|
||||||
)
|
)
|
||||||
AutofocusDep = lt.deps.direct_thing_client_dependency(AutofocusThing, "/autofocus/")
|
AutofocusDep = lt.deps.direct_thing_client_dependency(AutofocusThing, "/autofocus/")
|
||||||
|
|
||||||
JPEGBlob = lt.blob.blob_type("image/jpeg")
|
|
||||||
ZipBlob = lt.blob.blob_type("application/zip")
|
class JPEGBlob(lt.blob.Blob):
|
||||||
|
"""A class representing a JPEG image as a LabThings FastAPI Blob."""
|
||||||
|
|
||||||
|
media_type: str = "image/jpeg"
|
||||||
|
|
||||||
|
|
||||||
|
class ZipBlob(lt.blob.Blob):
|
||||||
|
"""A class representing a Zip file as a LabThings FastAPI Blob."""
|
||||||
|
|
||||||
|
media_type: str = "application/zip"
|
||||||
|
|
||||||
|
|
||||||
class ScanNotRunningError(RuntimeError):
|
class ScanNotRunningError(RuntimeError):
|
||||||
|
|
@ -116,7 +125,7 @@ class SmartScanThing(lt.Thing):
|
||||||
cam: CameraClient,
|
cam: CameraClient,
|
||||||
csm: CSMDep,
|
csm: CSMDep,
|
||||||
scan_name: str = "",
|
scan_name: str = "",
|
||||||
):
|
) -> None:
|
||||||
"""Move the stage to cover an area, taking images that can be tiled together.
|
"""Move the stage to cover an area, taking images that can be tiled together.
|
||||||
|
|
||||||
The stage will move in a pattern that grows outwards from the starting point,
|
The stage will move in a pattern that grows outwards from the starting point,
|
||||||
|
|
@ -171,7 +180,7 @@ class SmartScanThing(lt.Thing):
|
||||||
self._preview_stitcher = None
|
self._preview_stitcher = None
|
||||||
|
|
||||||
@_scan_running
|
@_scan_running
|
||||||
def _check_background_and_csm_set(self):
|
def _check_background_and_csm_set(self) -> None:
|
||||||
"""Before starting a scan, check that background and camera-stage-mapping are set.
|
"""Before starting a scan, check that background and camera-stage-mapping are set.
|
||||||
|
|
||||||
Raise error if:
|
Raise error if:
|
||||||
|
|
@ -311,7 +320,7 @@ class SmartScanThing(lt.Thing):
|
||||||
)
|
)
|
||||||
|
|
||||||
@_scan_running
|
@_scan_running
|
||||||
def _save_final_scan_data(self, scan_result: str):
|
def _save_final_scan_data(self, scan_result: str) -> None:
|
||||||
"""Update scan data JSON file with data only known at the end of the scan.
|
"""Update scan data JSON file with data only known at the end of the scan.
|
||||||
|
|
||||||
Takes scan_result, a string that is either "success", "cancelled by user",
|
Takes scan_result, a string that is either "success", "cancelled by user",
|
||||||
|
|
@ -321,7 +330,7 @@ class SmartScanThing(lt.Thing):
|
||||||
self._ongoing_scan.save_scan_data(self._scan_data)
|
self._ongoing_scan.save_scan_data(self._scan_data)
|
||||||
|
|
||||||
@_scan_running
|
@_scan_running
|
||||||
def _manage_stitching_threads(self):
|
def _manage_stitching_threads(self) -> None:
|
||||||
"""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
|
# Assume 4 images means at least one offset in x and y, making the stitching
|
||||||
# well constrained.
|
# well constrained.
|
||||||
|
|
@ -330,7 +339,7 @@ class SmartScanThing(lt.Thing):
|
||||||
self._preview_stitcher.start()
|
self._preview_stitcher.start()
|
||||||
|
|
||||||
@_scan_running
|
@_scan_running
|
||||||
def _run_scan(self):
|
def _run_scan(self) -> None:
|
||||||
"""Prepare and run the main scan, and perform final actions on completion.
|
"""Prepare and run the main scan, and perform final actions on completion.
|
||||||
|
|
||||||
The result (or exception) from the main scan loop determines whether the
|
The result (or exception) from the main scan loop determines whether the
|
||||||
|
|
@ -382,7 +391,7 @@ class SmartScanThing(lt.Thing):
|
||||||
self.purge_empty_scans(logger=self._scan_logger)
|
self.purge_empty_scans(logger=self._scan_logger)
|
||||||
|
|
||||||
@_scan_running
|
@_scan_running
|
||||||
def _main_scan_loop(self):
|
def _main_scan_loop(self) -> None:
|
||||||
"""Run the main loop of the scan.
|
"""Run the main loop of the scan.
|
||||||
|
|
||||||
This loop runs during a scan, until no more scan x,y positions
|
This loop runs during a scan, until no more scan x,y positions
|
||||||
|
|
@ -449,7 +458,7 @@ class SmartScanThing(lt.Thing):
|
||||||
self._ongoing_scan.zip_files()
|
self._ongoing_scan.zip_files()
|
||||||
|
|
||||||
@_scan_running
|
@_scan_running
|
||||||
def _return_to_starting_position(self):
|
def _return_to_starting_position(self) -> None:
|
||||||
"""Return to the initial scan position, if set."""
|
"""Return to the initial scan position, if set."""
|
||||||
self._scan_logger.info("Returning to starting position.")
|
self._scan_logger.info("Returning to starting position.")
|
||||||
if self._scan_data is not None:
|
if self._scan_data is not None:
|
||||||
|
|
@ -458,7 +467,7 @@ class SmartScanThing(lt.Thing):
|
||||||
)
|
)
|
||||||
|
|
||||||
@_scan_running
|
@_scan_running
|
||||||
def _perform_final_stitch(self):
|
def _perform_final_stitch(self) -> None:
|
||||||
"""Update the scan zip and perform final stitch of the data."""
|
"""Update the scan zip and perform final stitch of the data."""
|
||||||
if self._scan_data.image_count <= 3:
|
if self._scan_data.image_count <= 3:
|
||||||
self._scan_logger.info("Not performing a stitch as 3 or fewer images taken")
|
self._scan_logger.info("Not performing a stitch as 3 or fewer images taken")
|
||||||
|
|
@ -729,7 +738,7 @@ class SmartScanThing(lt.Thing):
|
||||||
def download_zip(
|
def download_zip(
|
||||||
self,
|
self,
|
||||||
scan_name: str,
|
scan_name: str,
|
||||||
):
|
) -> ZipBlob:
|
||||||
"""Return zip after including any files left until the end.
|
"""Return zip after including any files left until the end.
|
||||||
|
|
||||||
The zipfile is returned as a Blob.
|
The zipfile is returned as a Blob.
|
||||||
|
|
@ -740,7 +749,7 @@ class SmartScanThing(lt.Thing):
|
||||||
@lt.thing_action
|
@lt.thing_action
|
||||||
def stitch_all_scans(
|
def stitch_all_scans(
|
||||||
self, logger: lt.deps.InvocationLogger, cancel: lt.deps.CancelHook
|
self, logger: lt.deps.InvocationLogger, cancel: lt.deps.CancelHook
|
||||||
):
|
) -> None:
|
||||||
"""Check the list of scans, and stitch any that don't have a DZI associated with it.
|
"""Check the list of scans, and stitch any that don't have a DZI associated with it.
|
||||||
|
|
||||||
:raises RuntimeError: if the microscope is currently running a scan
|
:raises RuntimeError: if the microscope is currently running a scan
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ As the object will be used as a context manager create the hardware connection i
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from collections.abc import Sequence, Mapping
|
from collections.abc import Sequence, Mapping
|
||||||
from typing import Literal
|
from typing import Literal, Never, Any
|
||||||
|
|
||||||
import labthings_fastapi as lt
|
import labthings_fastapi as lt
|
||||||
|
|
||||||
|
|
@ -119,12 +119,12 @@ class BaseStage(lt.Thing):
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def thing_state(self):
|
def thing_state(self) -> Mapping[str, Any]:
|
||||||
"""Summary metadata describing the current state of the stage."""
|
"""Summary metadata describing the current state of the stage."""
|
||||||
return {"position": self.position}
|
return {"position": self.position}
|
||||||
|
|
||||||
@lt.thing_action
|
@lt.thing_action
|
||||||
def invert_axis_direction(self, axis: Literal["x", "y", "z"]):
|
def invert_axis_direction(self, axis: Literal["x", "y", "z"]) -> None:
|
||||||
"""Invert the direction setting of the given axis.
|
"""Invert the direction setting of the given axis.
|
||||||
|
|
||||||
:param axis: The axis name (x, y or z) to invert.
|
:param axis: The axis name (x, y or z) to invert.
|
||||||
|
|
@ -143,7 +143,7 @@ class BaseStage(lt.Thing):
|
||||||
cancel: lt.deps.CancelHook,
|
cancel: lt.deps.CancelHook,
|
||||||
block_cancellation: bool = False,
|
block_cancellation: bool = False,
|
||||||
**kwargs: int,
|
**kwargs: int,
|
||||||
):
|
) -> None:
|
||||||
"""Make a relative move. Keyword arguments should be axis names."""
|
"""Make a relative move. Keyword arguments should be axis names."""
|
||||||
self._hardware_move_relative(
|
self._hardware_move_relative(
|
||||||
cancel=cancel,
|
cancel=cancel,
|
||||||
|
|
@ -156,7 +156,7 @@ class BaseStage(lt.Thing):
|
||||||
cancel: lt.deps.CancelHook,
|
cancel: lt.deps.CancelHook,
|
||||||
block_cancellation: bool = False,
|
block_cancellation: bool = False,
|
||||||
**kwargs: int,
|
**kwargs: int,
|
||||||
):
|
) -> Never:
|
||||||
"""Make a relative move in the coordinate system used by the physical hardware.
|
"""Make a relative move in the coordinate system used by the physical hardware.
|
||||||
|
|
||||||
Make sure to use and update ``self._hardware_position`` not ``self.position``.
|
Make sure to use and update ``self._hardware_position`` not ``self.position``.
|
||||||
|
|
@ -171,7 +171,7 @@ class BaseStage(lt.Thing):
|
||||||
cancel: lt.deps.CancelHook,
|
cancel: lt.deps.CancelHook,
|
||||||
block_cancellation: bool = False,
|
block_cancellation: bool = False,
|
||||||
**kwargs: int,
|
**kwargs: int,
|
||||||
):
|
) -> None:
|
||||||
"""Make an absolute move. Keyword arguments should be axis names."""
|
"""Make an absolute move. Keyword arguments should be axis names."""
|
||||||
self._hardware_move_absolute(
|
self._hardware_move_absolute(
|
||||||
cancel=cancel,
|
cancel=cancel,
|
||||||
|
|
@ -184,7 +184,7 @@ class BaseStage(lt.Thing):
|
||||||
cancel: lt.deps.CancelHook,
|
cancel: lt.deps.CancelHook,
|
||||||
block_cancellation: bool = False,
|
block_cancellation: bool = False,
|
||||||
**kwargs: int,
|
**kwargs: int,
|
||||||
):
|
) -> Never:
|
||||||
"""Make a absolute move in the coordinate system used by the physical hardware.
|
"""Make a absolute move in the coordinate system used by the physical hardware.
|
||||||
|
|
||||||
Make sure to use and update ``self._hardware_position`` not ``self.position``.
|
Make sure to use and update ``self._hardware_position`` not ``self.position``.
|
||||||
|
|
@ -194,7 +194,7 @@ class BaseStage(lt.Thing):
|
||||||
)
|
)
|
||||||
|
|
||||||
@lt.thing_action
|
@lt.thing_action
|
||||||
def set_zero_position(self):
|
def set_zero_position(self) -> Never:
|
||||||
"""Make the current position zero in all axes.
|
"""Make the current position zero in all axes.
|
||||||
|
|
||||||
This action does not move the stage, but resets the position to zero.
|
This action does not move the stage, but resets the position to zero.
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ class DummyStage(BaseStage):
|
||||||
cancel: lt.deps.CancelHook,
|
cancel: lt.deps.CancelHook,
|
||||||
block_cancellation: bool = False,
|
block_cancellation: bool = False,
|
||||||
**kwargs: int,
|
**kwargs: int,
|
||||||
):
|
) -> None:
|
||||||
"""Make a relative move. Keyword arguments should be axis names."""
|
"""Make a relative move. Keyword arguments should be axis names."""
|
||||||
displacement = [kwargs.get(k, 0) for k in self.axis_names]
|
displacement = [kwargs.get(k, 0) for k in self.axis_names]
|
||||||
self.moving = True
|
self.moving = True
|
||||||
|
|
@ -93,7 +93,7 @@ class DummyStage(BaseStage):
|
||||||
cancel: lt.deps.CancelHook,
|
cancel: lt.deps.CancelHook,
|
||||||
block_cancellation: bool = False,
|
block_cancellation: bool = False,
|
||||||
**kwargs: int,
|
**kwargs: int,
|
||||||
):
|
) -> None:
|
||||||
"""Make an absolute move. Keyword arguments should be axis names."""
|
"""Make an absolute move. Keyword arguments should be axis names."""
|
||||||
displacement = {
|
displacement = {
|
||||||
axis: int(pos) - self._hardware_position[axis]
|
axis: int(pos) - self._hardware_position[axis]
|
||||||
|
|
@ -105,7 +105,7 @@ class DummyStage(BaseStage):
|
||||||
)
|
)
|
||||||
|
|
||||||
@lt.thing_action
|
@lt.thing_action
|
||||||
def set_zero_position(self):
|
def set_zero_position(self) -> None:
|
||||||
"""Make the current position zero in all axes.
|
"""Make the current position zero in all axes.
|
||||||
|
|
||||||
This action does not move the stage, but resets the position to zero.
|
This action does not move the stage, but resets the position to zero.
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ class OpenFlexureSystem(lt.Thing):
|
||||||
return UUID(self._microscope_id)
|
return UUID(self._microscope_id)
|
||||||
|
|
||||||
@microscope_id.setter
|
@microscope_id.setter
|
||||||
def microscope_id(self, uuid: UUID):
|
def microscope_id(self, uuid: UUID) -> None:
|
||||||
# TODO make read only but still settable from disk
|
# TODO make read only but still settable from disk
|
||||||
self._microscope_id = uuid
|
self._microscope_id = uuid
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -105,7 +105,7 @@ class ErrorCapturingThread(Thread):
|
||||||
daemon=daemon,
|
daemon=daemon,
|
||||||
)
|
)
|
||||||
|
|
||||||
def join(self, timeout: Optional[float] = None):
|
def join(self, timeout: Optional[float] = None) -> None:
|
||||||
"""Join when the thread is complete.
|
"""Join when the thread is complete.
|
||||||
|
|
||||||
If the thread ended due to an unhandled exception, the exception will be raised
|
If the thread ended due to an unhandled exception, the exception will be raised
|
||||||
|
|
@ -121,7 +121,7 @@ class ErrorCapturingThread(Thread):
|
||||||
|
|
||||||
def _wrap_and_catch_errors(
|
def _wrap_and_catch_errors(
|
||||||
target: Callable[..., Any], error_buffer: list, *args: Any, **kwargs: Any
|
target: Callable[..., Any], error_buffer: list, *args: Any, **kwargs: Any
|
||||||
):
|
) -> None:
|
||||||
"""Run target function in a try-except block.
|
"""Run target function in a try-except block.
|
||||||
|
|
||||||
This function is designed only to be used by ErrorCapturingThread.
|
This function is designed only to be used by ErrorCapturingThread.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue