Source code for AFL.automation.prepare.OT2Prepare
import warnings
from AFL.automation.prepare.OT2HTTPDriver import OT2HTTPDriver
from AFL.automation.prepare.PrepareDriver import PrepareDriver
[docs]
class OT2Prepare(OT2HTTPDriver, PrepareDriver):
defaults = {
"prep_targets": [],
"prepare_volume": "900 ul",
"catch_volume": "900 ul",
"deck": {},
"stocks": [],
"stock_mix_order": [],
"fixed_compositions": {},
"stock_locations": {}, # Maps stock names to deck positions: {'stockH2O': '3A2'}
"stock_transfer_params": {}, # Per-stock transfer parameters: {'stockH2O': {'mix_after': True}}
"catch_protocol": {}, # PipetteAction-formatted dict for catch transfer parameters
}
[docs]
def __init__(self, overrides=None):
OT2HTTPDriver.__init__(self, overrides=overrides)
PrepareDriver.__init__(self, driver_name="OT2Prepare", overrides=overrides)
self.last_target_location = None
self.useful_links["View Deck"] = "/visualize_deck"
[docs]
def status(self):
return PrepareDriver.status(self) + OT2HTTPDriver.status(self)
def _status_lines(self):
status = []
status.append(f"Stocks: {len(self.stocks)} configured")
status.append(f"Stock locations: {self.config['stock_locations']}")
status.append(f"{len(self.config['prep_targets'])} preparation targets available")
return status
[docs]
def resolve_destination(self, dest):
if dest is not None:
return dest
if not self.config.get("prep_targets"):
raise ValueError("No preparation targets configured. Cannot select a destination target.")
prep_targets = self.config["prep_targets"]
destination = prep_targets.pop(0)
self.config["prep_targets"] = prep_targets
return destination
[docs]
def execute_preparation(self, target, balanced_target, destination):
if not hasattr(balanced_target, "protocol") or not balanced_target.protocol:
raise ValueError("No protocol generated for the target solution")
protocol = self.reorder_protocol(balanced_target.protocol)
for step in protocol:
source = step.source
volume_ul = step.volume
stock_name = self.config.get("deck", {}).get(source)
if stock_name is None:
raise ValueError(f"No stock name found for deck location: {source}")
transfer_params = self.get_transfer_params(stock_name)
try:
self.transfer(
source=source,
dest=destination,
volume=volume_ul,
**transfer_params,
)
except Exception as e:
warnings.warn(f"Transfer failed from {source} to {destination}: {str(e)}", stacklevel=2)
return False
self.last_target_location = destination
return True
[docs]
def build_prepare_result(self, feasible_result, balanced_target):
result_dict = balanced_target.to_dict()
if hasattr(balanced_target, "volume") and balanced_target.volume is not None:
result_dict["total_volume"] = f"{balanced_target.volume.to('ul').magnitude} ul"
return result_dict
[docs]
def process_stocks(self):
PrepareDriver.process_stocks(self)
self._update_deck_config()
def _update_deck_config(self):
deck_config = {}
stock_locations = self.config.get("stock_locations", {})
for stock_name, deck_location in stock_locations.items():
deck_config[deck_location] = stock_name
self.config["deck"] = deck_config
[docs]
def get_transfer_params(self, stock_name):
stock_params = self.config.get("stock_transfer_params", {}).get(stock_name, {})
default_params = self.config.get("stock_transfer_params", {}).get("default", {})
params = default_params.copy()
params.update(stock_params)
return params
[docs]
def reorder_protocol(self, protocol):
stock_mix_order = self.config.get("stock_mix_order", [])
if not stock_mix_order:
return protocol
steps_by_source = {}
for step in protocol:
if step.source not in steps_by_source:
steps_by_source[step.source] = []
steps_by_source[step.source].append(step)
reordered = []
for stock_name in stock_mix_order:
if stock_name in steps_by_source:
reordered.extend(steps_by_source[stock_name])
del steps_by_source[stock_name]
for steps in steps_by_source.values():
reordered.extend(steps)
return reordered
[docs]
def transfer_to_catch(self, source=None, dest=None, **kwargs):
catch_params = self.config.get("catch_protocol", {}).copy()
if source is None:
if self.last_target_location is None:
raise ValueError(
"No source specified and no last target location available. "
"Call prepare() first or specify source."
)
source = self.last_target_location
kwargs["source"] = source
if dest is not None:
kwargs["dest"] = dest
catch_params.update(kwargs)
if "dest" not in catch_params:
raise ValueError("Destination 'dest' must be specified in catch_protocol config or as an argument.")
try:
self.transfer(**catch_params)
except Exception as e:
dest_val = catch_params.get("dest", "unknown")
warnings.warn(
f"Transfer to catch failed from {source} to {dest_val} using {catch_params}: {str(e)}",
stacklevel=2,
)
raise
[docs]
def reset(self):
self.reset_targets()
self.reset_stocks()
_DEFAULT_PORT = 5002
if __name__ == "__main__":
from AFL.automation.shared.launcher import *