From dee77c295aaa0b003cf4f84f7dd9531ea6e0e773 Mon Sep 17 00:00:00 2001 From: Joel Collins Date: Wed, 18 Dec 2019 11:06:02 +0000 Subject: [PATCH] Migrated scan plugin --- openflexure_microscope/__init__.py | 3 +- openflexure_microscope/camera/pi.py | 3 - .../common/labthings/find.py | 19 +- .../common/labthings/labthing.py | 8 + .../common/labthings/plugins.py | 9 + openflexure_microscope/common/tasks/pool.py | 4 +- openflexure_microscope/microscope.py | 40 -- .../plugins/example/__init__.py | 4 - openflexure_microscope/plugins/example/api.py | 100 ----- .../plugins/example/plugin.py | 63 --- .../plugins/v2/autofocus.py | 4 + openflexure_microscope/plugins/v2/scan.py | 399 ++++++++++++++++++ openflexure_microscope/task.py | 76 ---- 13 files changed, 442 insertions(+), 290 deletions(-) delete mode 100644 openflexure_microscope/plugins/example/__init__.py delete mode 100644 openflexure_microscope/plugins/example/api.py delete mode 100644 openflexure_microscope/plugins/example/plugin.py create mode 100644 openflexure_microscope/plugins/v2/scan.py delete mode 100644 openflexure_microscope/task.py diff --git a/openflexure_microscope/__init__.py b/openflexure_microscope/__init__.py index ad56e205..85af80b2 100644 --- a/openflexure_microscope/__init__.py +++ b/openflexure_microscope/__init__.py @@ -1,8 +1,7 @@ -__all__ = ["Microscope", "config", "task", "utilities"] +__all__ = ["Microscope", "config", "utilities"] __version__ = "0.1.0" from .microscope import Microscope from . import config from . import utilities -from . import task from . import common diff --git a/openflexure_microscope/camera/pi.py b/openflexure_microscope/camera/pi.py index a3e1fc47..d73d0c02 100644 --- a/openflexure_microscope/camera/pi.py +++ b/openflexure_microscope/camera/pi.py @@ -317,9 +317,6 @@ class PiCameraStreamer(BaseCamera): """ Change the camera zoom, handling re-centering and scaling. """ - logging.warning( - "set_zoom is deprecated. Please use the 'zoom' property in picamera_settings." - ) with self.lock: self.status["zoom_value"] = float(zoom_value) if self.status["zoom_value"] < 1: diff --git a/openflexure_microscope/common/labthings/find.py b/openflexure_microscope/common/labthings/find.py index b5b6a7e0..7b09f43e 100644 --- a/openflexure_microscope/common/labthings/find.py +++ b/openflexure_microscope/common/labthings/find.py @@ -1,12 +1,17 @@ +import logging from flask import current_app from . import EXTENSION_NAME def _current_labthing(): - app = current_app + app = current_app._get_current_object() if not app: return None + logging.debug("Active app extensions:") + logging.debug(app.extensions) + logging.debug("Active labthing:") + logging.debug(app.extensions[EXTENSION_NAME]) return app.extensions[EXTENSION_NAME] @@ -30,3 +35,15 @@ def find_device(device_name, labthing_instance=None): return labthing_instance.devices[device_name] else: return None + +def find_plugin(plugin_name, labthing_instance=None): + if not labthing_instance: + labthing_instance = _current_labthing() + + logging.debug("Current labthing:") + logging.debug(_current_labthing()) + + if plugin_name in labthing_instance.plugins: + return labthing_instance.plugins[plugin_name] + else: + return None diff --git a/openflexure_microscope/common/labthings/labthing.py b/openflexure_microscope/common/labthings/labthing.py index d6197dfb..5c301fee 100644 --- a/openflexure_microscope/common/labthings/labthing.py +++ b/openflexure_microscope/common/labthings/labthing.py @@ -53,6 +53,14 @@ class LabThing(object): # TODO: Add plugin routes + for plugin_view_id, plugin_view in plugin_object.views.items(): + # Add route to the plugins blueprint + self.add_resource( + plugin_view["view"], + "/plugins" + plugin_view["rule"], + **plugin_view["kwargs"], + ) + ### Resource stuff def _complete_url(self, url_part, registration_prefix): diff --git a/openflexure_microscope/common/labthings/plugins.py b/openflexure_microscope/common/labthings/plugins.py index a7891379..2a3078ee 100644 --- a/openflexure_microscope/common/labthings/plugins.py +++ b/openflexure_microscope/common/labthings/plugins.py @@ -29,6 +29,8 @@ class BasePlugin: self.name = name + self.methods = {} + @property def views(self): return self._views @@ -102,6 +104,13 @@ class BasePlugin: def _name_uri_safe(self): return snake_to_spine(self._name_python_safe) + def add_method(self, method_name, method): + self.methods[method_name] = method + + if not hasattr(self, method_name): + setattr(self, method_name, method) + else: + logging.warning("Unable to bind method to plugin. Method name already exists.") def find_plugins(plugin_path, module_name="plugins"): print(f"Loading plugins from {plugin_path}") diff --git a/openflexure_microscope/common/tasks/pool.py b/openflexure_microscope/common/tasks/pool.py index 258e46c5..b8b526e9 100644 --- a/openflexure_microscope/common/tasks/pool.py +++ b/openflexure_microscope/common/tasks/pool.py @@ -4,6 +4,7 @@ from functools import wraps from .thread import TaskThread +from flask import copy_current_request_context class TaskMaster: def __init__(self, *args, **kwargs): @@ -26,7 +27,8 @@ class TaskMaster: return {t.id: t.state for t in self._tasks} def new(self, f, *args, **kwargs): - task = TaskThread(target=f, args=args, kwargs=kwargs) + # copy_current_request_context allows threads to access flask current_app + task = TaskThread(target=copy_current_request_context(f), args=args, kwargs=kwargs) self._tasks.append(task) return task diff --git a/openflexure_microscope/microscope.py b/openflexure_microscope/microscope.py index 981e78d6..a62c6fea 100644 --- a/openflexure_microscope/microscope.py +++ b/openflexure_microscope/microscope.py @@ -13,7 +13,6 @@ from openflexure_microscope.camera.mock import MockStreamer from openflexure_microscope.utilities import serialise_array_b64 from openflexure_microscope.plugins import PluginLoader -from openflexure_microscope.task import TaskOrchestrator from openflexure_microscope.common.lock import CompositeLock from openflexure_microscope.config import user_settings @@ -46,9 +45,6 @@ class Microscope: # Initialise with an empty composite lock self.lock = CompositeLock([]) - # Create a task orchestrator - self.task = TaskOrchestrator() - # Apply settings loaded from file self.apply_settings(user_settings.load()) @@ -151,26 +147,6 @@ class Microscope: else: return False - # Create unified state - @property - def state(self): - """Dictionary of the basic microscope state. - - Return: - dict: Dictionary containing position data, - and :py:attr:`openflexure_microscope.camera.base.BaseCamera.status` - """ - # DEPRECATED - logging.warning( - "Microscope.state is deprecated. Use Microscope.status instead. State will be removed in a future version." - ) - state = { - "camera": self.camera.status, - "stage": self.stage.status, - "plugin": self.plugins.state, - "version": pkg_resources.get_distribution("openflexure_microscope").version, - } - return state # Create unified status @property @@ -256,22 +232,6 @@ class Microscope: self.stage.save_settings() user_settings.save(current_config, backup=True) - @property - def config(self) -> dict: - logging.warning( - "Reading microscope through config property is deprecated.\ - Please use read_settings method instead." - ) - return self.read_settings() - - @config.setter - def config(self, config: dict) -> None: - logging.warning( - "Setting microscope through config property is deprecated.\ - Please use apply_settings method instead." - ) - self.apply_settings(config) - @property def metadata(self): """ diff --git a/openflexure_microscope/plugins/example/__init__.py b/openflexure_microscope/plugins/example/__init__.py deleted file mode 100644 index ce9672da..00000000 --- a/openflexure_microscope/plugins/example/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -__all__ = ["Plugin", "HelloWorldAPI", "IdentifyAPI"] - -from .plugin import Plugin -from .api import HelloWorldAPI, IdentifyAPI diff --git a/openflexure_microscope/plugins/example/api.py b/openflexure_microscope/plugins/example/api.py deleted file mode 100644 index ac818184..00000000 --- a/openflexure_microscope/plugins/example/api.py +++ /dev/null @@ -1,100 +0,0 @@ -from openflexure_microscope.api.utilities import JsonResponse -from openflexure_microscope.api.views import MicroscopeViewPlugin -from openflexure_microscope.exceptions import TaskDeniedException - -from flask import request, Response, escape, jsonify, abort - - -class IdentifyAPI(MicroscopeViewPlugin): - """ - A simple example API plugin, attached through the main microscope plugin. - """ - - def get(self): - """ - Method to call when an HTTP GET request is made. - """ - data = ( - self.plugin.identify() - ) # Call a method from our plugin, using the MicroscopeViewPlugin.plugin shortcut - return Response(escape(data)) - - -class HelloWorldAPI(MicroscopeViewPlugin): - """ - A method to create, set, and return a new microscope parameter. - """ - - def get(self): - """ - Method to call when an HTTP GET request is made. - """ - - # If the microscope does not already contain our plugin_string attribute - if not hasattr(self.microscope, "plugin_string"): - # Make a string, using the MicroscopeViewPlugin.plugin shortcut - self.microscope.plugin_string = self.plugin.hello_world() - - return Response(self.microscope.plugin_string) - - def post(self): - """ - Method to call when an HTTP POST request is made. - Assumes request will include a JSON payload. - """ - # Get payload JSON - payload = JsonResponse(request) - - # Extract a value from the JSON key 'plugin_string', and convert to a string. If no value is given, default to empty. - new_plugin_string = payload.param("plugin_string", default="", convert=str) - - if new_plugin_string: # If not None or empty - # Set microscope attribute to the specified string - self.microscope.plugin_string = new_plugin_string - - return Response(self.microscope.plugin_string) - - -class LongRunningAPI(MicroscopeViewPlugin): - """ - An example API plugin that uses a long-running plugin method. - """ - - def post(self): - """ - Method to call when an HTTP POST request is made. - """ - # Get payload JSON - payload = JsonResponse(request) - - # Extract a values from the JSON payload. - time_to_run = payload.param("time", default=10, convert=int) - - # Attach the long-running method as a microscope task - try: - task = self.microscope.task.start(self.plugin.long_running, time_to_run) - return jsonify(task.state), 201 - - except TaskDeniedException: - return abort(409) - - -class SomeExceptionAPI(MicroscopeViewPlugin): - """ - An example API plugin that uses a long-running but broken plugin method. - """ - - def post(self): - """ - Method to call when an HTTP POST request is made. - """ - # Get payload JSON - payload = JsonResponse(request) - - # Attach the long-running method as a microscope task - try: - task = self.microscope.task.start(self.plugin.some_exception) - return jsonify(task.state), 201 - - except TaskDeniedException: - return abort(409) diff --git a/openflexure_microscope/plugins/example/plugin.py b/openflexure_microscope/plugins/example/plugin.py deleted file mode 100644 index c61cb16e..00000000 --- a/openflexure_microscope/plugins/example/plugin.py +++ /dev/null @@ -1,63 +0,0 @@ -import random -import time -from openflexure_microscope.plugins import MicroscopePlugin - -from .api import IdentifyAPI, HelloWorldAPI, LongRunningAPI, SomeExceptionAPI - - -class Plugin(MicroscopePlugin): - """ - A set of default plugins - """ - - api_views = { - "/identify": IdentifyAPI, - "/hello": HelloWorldAPI, - "/long_running": LongRunningAPI, - "/some_exception": SomeExceptionAPI, - } - - def identify(self): - """ - Demonstrate access to Microscope.camera, and Microscope.stage - """ - - response = "My parent camera is {}, and my parent stage is {}.".format( - self.microscope.camera, self.microscope.stage - ) - print(response) - return response - - def hello_world(self): - """ - Demonstrate passive method - """ - - return "Hello world!" - - def some_exception(self): - """ - Demonstrate some broken plugin method that would be long running - """ - with self.microscope.lock: - time.sleep(3) - result = 15.0 / 0 - - return result - - def long_running(self, t_run): - """ - Demonstrate a long-running method that requires microscope hardware - """ - print("Starting a long-running task...") - n_array = [] - - print("Acquiring composite microscope lock...") - with self.microscope.camera.lock, self.microscope.stage.lock: - for _ in range(t_run): - n_array.append(random.random()) - time.sleep(1) - - print("Long-running task finished! Releasing locks.") - - return n_array diff --git a/openflexure_microscope/plugins/v2/autofocus.py b/openflexure_microscope/plugins/v2/autofocus.py index 875bb58d..3b4408ad 100644 --- a/openflexure_microscope/plugins/v2/autofocus.py +++ b/openflexure_microscope/plugins/v2/autofocus.py @@ -355,6 +355,10 @@ class FastAutofocusAPI(MethodView): autofocus_plugin_v2 = BasePlugin("autofocus") + +autofocus_plugin_v2.add_method("fast_autofocus", fast_autofocus) +autofocus_plugin_v2.add_method("autofocus", autofocus) + autofocus_plugin_v2.add_view("/measure_sharpness", MeasureSharpnessAPI) autofocus_plugin_v2.add_view("/autofocus", AutofocusAPI) autofocus_plugin_v2.add_view("/fast_autofocus", FastAutofocusAPI) diff --git a/openflexure_microscope/plugins/v2/scan.py b/openflexure_microscope/plugins/v2/scan.py new file mode 100644 index 00000000..7a6e2d2f --- /dev/null +++ b/openflexure_microscope/plugins/v2/scan.py @@ -0,0 +1,399 @@ +import numpy as np +import itertools +import logging +import uuid +from typing import Tuple +from functools import reduce + +from openflexure_microscope.camera.base import generate_basename +from openflexure_microscope.common.labthings.find import find_device, find_plugin +from openflexure_microscope.common.labthings.plugins import BasePlugin + +from openflexure_microscope.devel import ( + JsonResponse, + request, + jsonify, + taskify, + abort, + update_task_progress, + update_task_data, +) + +from flask.views import MethodView +import time + + +### Grid construction + +def construct_grid(initial, step_sizes, n_steps, style="raster"): + """ + Given an initial position, step sizes, and number of steps, + construct a 2-dimensional list of scan x-y positions. + """ + arr = [] + + for i in range(n_steps[0]): # x axis + arr.append([]) + for j in range(n_steps[1]): # y axis + # Create a coordinate array + coord = [initial[ax] + [i, j][ax] * step_sizes[ax] for ax in range(2)] + # Append coordinate array to position grid + arr[i].append(tuple(coord)) + + # Style modifiers + if style == "snake": + for i, line in enumerate(arr): + if i % 2 != 0: + line.reverse() + + return arr + + +def flatten_grid(grid): + """ + Convert a 3D list of scan positions into a flat list + of sequential positions. + """ + + grid = list(itertools.chain(*grid)) + return grid + + +### Progress + +_images_to_be_captured: int = 1 +_images_captured_so_far: int = 0 + +def progress(): + progress = (_images_captured_so_far / _images_to_be_captured) * 100 + logging.info(progress) + return progress + + +### Capturing + +def capture( + microscope, + basename, + scan_id, + temporary: bool = False, + use_video_port: bool = False, + resize: Tuple[int, int] = None, + bayer: bool = False, + metadata: dict = {}, + tags: list = [], +): + + # Construct a tile filename + filename = "{}_{}_{}_{}".format(basename, *microscope.stage.position) + folder = "SCAN_{}".format(basename) + + # Create output object + output = microscope.camera.new_image( + temporary=temporary, filename=filename, folder=folder + ) + + # Capture + microscope.camera.capture( + output.file, use_video_port=use_video_port, resize=resize, bayer=bayer + ) + + # Affix metadata + if "scan" not in tags: + tags.append("scan") + + # Inject system metadata + output.put_metadata(microscope.metadata, system=True) + + # Insert custom metadata + output.put_metadata(metadata) + + # Insert custom tags + output.put_tags(tags) + + +### Scanning + +def tile( + microscope, + basename: str = None, + temporary: bool = False, + step_size: int = [2000, 1500, 100], + grid: list = [3, 3, 5], + style="raster", + autofocus_dz: int = 50, + use_video_port: bool = False, + resize: Tuple[int, int] = None, + bayer: bool = False, + fast_autofocus=False, + metadata: dict = {}, + tags: list = [], +): + global _images_to_be_captured + global _images_captured_so_far + + # Keep task progress + # TODO: Make this line not nasty + _images_to_be_captured = reduce((lambda x, y: x * y), grid) + _images_captured_so_far = 0 + + # Generate a basename if none given + if not basename: + basename = generate_basename() + + # Generate a stack ID + scan_id = uuid.uuid4().hex + + # Store initial position + initial_position = microscope.stage.position + + # Add scan metadata + if "time" not in metadata: + metadata["time"] = generate_basename() + + metadata.update( + { + "scan_id": scan_id, + "basename": basename, + "scan_parameters": { + "step_size": step_size, + "grid": grid, + "style": style, + "autofocus_dz": autofocus_dz, + }, + } + ) + + # Check if autofocus is enabled + autofocus_plugin = find_plugin("autofocus") + if ( + autofocus_dz + and autofocus_plugin + and microscope.has_real_stage() + and microscope.has_real_camera() + ): + autofocus_enabled = True + else: + autofocus_enabled = False + + z_stack_dz = ( + grid[2] * step_size[2] if grid[2] > 1 else 0 + ) # shorthand for Z stack range + + # Construct an x-y grid (worry about z later) + x_y_grid = construct_grid( + initial_position, step_size[:2], grid[:2], style=style + ) + + # Keep the initial Z position the same as our current position + next_z = initial_position[2] + if fast_autofocus: # If fast autofocus is enabled, make + next_z += autofocus_dz / 2 # sure we start from the top of the range + initial_z = next_z # Save this value for use in raster scans + + # Now step through each point in the x-y coordinate array + for line in x_y_grid: + # If rastering, rather than snake (or eventually spiral) + # Return focus to initial position + if style == "raster": + next_z = initial_z + logging.debug("Returning to initial z position") + microscope.stage.move_abs( + [line[0][0], line[0][1], next_z] + ) # RWB: I think this line is redundant + + for x_y in line: + # Move to new grid position without changing z + logging.debug("Moving to step {}".format([x_y[0], x_y[1], next_z])) + microscope.stage.move_abs([x_y[0], x_y[1], next_z]) + # Refocus + if autofocus_enabled: + if fast_autofocus: + autofocus_plugin.fast_autofocus( + dz=autofocus_dz, + target_z=-z_stack_dz / 2.0, # Finish below the focus + initial_move_up=False, # We're already at the top of the scan + ) + # TODO: save the focus data for future reference? Use it for diagnostics? + else: + logging.debug("Running autofocus") + autofocus_plugin.autofocus( + range(-3 * autofocus_dz, 4 * autofocus_dz, autofocus_dz) + ) + logging.debug("Finished autofocus") + time.sleep(1) # TODO: Remove + + # If we're not doing a z-stack, just capture + if grid[2] <= 1: + capture( + microscope, + basename, + scan_id, + temporary=temporary, + use_video_port=use_video_port, + resize=resize, + bayer=bayer, + metadata=metadata, + tags=tags, + ) + # Update task progress + _images_captured_so_far += 1 + update_task_progress(progress()) + else: + logging.debug("Entering z-stack") + stack( + microscope=microscope, + basename=basename, + temporary=temporary, + scan_id=scan_id, + step_size=step_size[2], + steps=grid[2], + center=not fast_autofocus, # fast_autofocus does this for us! + return_to_start=not fast_autofocus, + use_video_port=use_video_port, + resize=resize, + bayer=bayer, + metadata=metadata, + tags=tags, + ) + # Make sure we use our current best estimate of focus (i.e. the current position) next point + next_z = microscope.stage.position[2] + if fast_autofocus: + next_z += ( + autofocus_dz / 2 + ) # Fast autofocus requires us to start at the top of the range + if grid[2] > 1: + next_z -= int( + grid[2] / 2.0 * step_size[2] + ) # Z stacking means we're higher up to start with + + logging.debug("Returning to {}".format(initial_position)) + microscope.stage.move_abs(initial_position) + +def stack( + microscope, + basename: str = None, + temporary: bool = False, + scan_id: str = None, + step_size: int = 100, + steps: int = 5, + center: bool = True, + return_to_start: bool = True, + use_video_port: bool = False, + resize: Tuple[int, int] = None, + bayer: bool = False, + metadata: dict = {}, + tags: list = [], +): + global _images_captured_so_far + + # Generate a basename if none given + if not basename: + basename = generate_basename() + + # Generate a stack ID + if not scan_id: + scan_id = uuid.uuid4().hex + + # Add scan metadata + if not "time" in metadata: + metadata["time"] = generate_basename() + + # Store initial position + initial_position = microscope.stage.position + + with microscope.lock: + # Move to center scan + if center: + logging.debug("Moving to starting position") + microscope.stage.move_rel([0, 0, int((-step_size * steps) / 2)]) + + for i in range(steps): + time.sleep(0.1) + logging.debug("Capturing...") + capture( + microscope, + basename, + scan_id, + temporary=temporary, + use_video_port=use_video_port, + resize=resize, + bayer=bayer, + metadata=metadata, + tags=tags, + ) + # Update task progress + _images_captured_so_far += 1 + update_task_progress(progress()) + + if i != steps - 1: + logging.debug("Moving z by {}".format(step_size)) + microscope.stage.move_rel([0, 0, step_size]) + if return_to_start: + logging.debug("Returning to {}".format(initial_position)) + microscope.stage.move_abs(initial_position) + + +### Web views + +class TileScanAPI(MethodView): + def post(self): + payload = JsonResponse(request) + microscope = find_device("openflexure_microscope") + + if not microscope: + abort(503, "No microscope connected. Unable to autofocus.") + + # Get params + filename = payload.param("filename") + temporary = payload.param("temporary", default=False, convert=bool) + + step_size = payload.param("step_size", default=[2000, 1500, 100], convert=list) + step_size = [int(i) for i in step_size] + + grid = payload.param("grid", default=[3, 3, 5], convert=list) + grid = [int(i) for i in grid] + + style = payload.param("style", default="raster", convert=str) + autofocus_dz = payload.param("autofocus_dz", default=50, convert=int) + fast_autofocus = payload.param("fast_autofocus", default=False, convert=bool) + + use_video_port = payload.param("use_video_port", default=True, convert=bool) + resize = payload.param("size", default=None) + if resize: + if ("width" in resize) and ("height" in resize): + resize = ( + int(resize["width"]), + int(resize["height"]), + ) # Convert dict to tuple + else: + abort(404) + + bayer = payload.param("bayer", default=False, convert=bool) + metadata = payload.param("metadata", default={}, convert=dict) + tags = payload.param("tags", default=[], convert=list) + + logging.info("Running tile scan...") + task = taskify(tile)( + microscope, + basename=filename, + temporary=temporary, + step_size=step_size, + grid=grid, + style=style, + autofocus_dz=autofocus_dz, + use_video_port=use_video_port, + resize=resize, + bayer=bayer, + fast_autofocus=fast_autofocus, + metadata=metadata, + tags=tags, + ) + + # return a handle on the scan task + return jsonify(task.state), 201 + + +scan_plugin_v2 = BasePlugin("scan") + +scan_plugin_v2.add_view("/tile", TileScanAPI) diff --git a/openflexure_microscope/task.py b/openflexure_microscope/task.py deleted file mode 100644 index c195e3fc..00000000 --- a/openflexure_microscope/task.py +++ /dev/null @@ -1,76 +0,0 @@ -import logging -from openflexure_microscope.common.tasks import ( - tasks, - states, - taskify, - cleanup_tasks, - remove_task, -) - -# DEPRECATED -class TaskOrchestrator: - """ - DEPRECATED: Class responsible for spawning threaded tasks, and storing their returns. - A microscope should contain exactly one instance of `TaskOrchestrator`. - - Attributes: - tasks (list): List of `Task` objects - - """ - - @property - def tasks(self): - logging.warning( - "TaskOrchestrator class is deprecated. Please use common.tasks.tasks().values() instead." - ) - return tasks().values() - - @property - def state(self): - """ - Returns a list of dictionary representations of all tasks in the session. - """ - logging.warning( - "TaskOrchestrator class is deprecated. Please use common.tasks.tasks() instead." - ) - return states() - - def task_from_id(self, task_id: str): - """ - Returns a particular task object with the specified `task_id`. - """ - return tasks()[task_id] - - def start(self, function, *args, **kwargs): - """ - Attach and start a new long-running task, if allowed by `start_condition()`. - Returns an instance of :py:class:`openflexure_microscope.task.Task`, which can be used to check the state - or eventual return of the task. - - Args: - function (function): The target function to run in the background - args, kwargs: Arguments that will be passed to `function` at runtime. - """ - logging.warning( - "TaskOrchestrator class is deprecated. Please use common.tasks.taskify() instead." - ) - return taskify(function)(*args, **kwargs) - - def clean(self): - """ - Remove all inactive tasks from the task array. - This will remove tasks that either finished or never started. - """ - logging.warning( - "TaskOrchestrator class is deprecated. Please use common.tasks.cleanup_tasks() instead." - ) - cleanup_tasks() - - def delete(self, task_id: str): - """ - Delete a given task by ID, only if task is not currently running. - """ - logging.warning( - "TaskOrchestrator class is deprecated. Please use common.tasks.remove_task() instead." - ) - remove_task(task_id)