"""
Implementation of RoboInterface extending the PF400 to the specific uppsala pf400 in the imaging room.
"""
from .movement_profiles import slow_profile
from .pf_implementation import PFImplementation
import logging
import time
from pathlib import Path
from ...control.graph_manager import JointState
from datetime import datetime
# nikon stage is very narrow so we need to open is just barely
NIKON_GRIP_OPEN = 80
z_offsets = {
"SquidA_nest": 5,
"SquidB_nest": 5,
"Nikon_nest": 3,
}
REGRIP_SLOT = "Hotel_nest11"
# robot drives there to have the barcode scanned
bc_reader_position = "Liconic_transit"
[docs]
def approach_position(device: str) -> str:
approach_pos = f"{device}_approach"
logging.debug(f"{datetime.now()}: Approach position to device {device} is {approach_pos}.")
return approach_pos
[docs]
def device_from_identifier(identifier: str) -> str:
device_name = identifier.split('_')[0]
logging.debug(f"{datetime.now()}: {identifier} belongs to device {device_name}.")
return device_name
[docs]
class ImagerPF(PFImplementation):
origin_offset = 0 # how much higher we are gripped the currently held plate compared to the default grip
GRIPPER_OPEN = 95
graph_dir = Path("/home/pharmbio/stefans_automation/imager-automation/lab_adaption/")
[docs]
@classmethod
def get_name(cls) -> str:
return "ImagerPF"
[docs]
def move_to_coordinates(self, coords: JointState, **kwargs) -> None:
# go slower if the next or last position is a nest or the current position is unknown
profile = self.move_profile
if self.speed > 10:
keywords = {"nest"}
if not self.current_position or any(
keyword in self.next_positions[0] or keyword in self.current_position
for keyword in keywords
):
profile = slow_profile # be careful around nests
with self.use_move_profile(profile):
super().move_to_coordinates(coords, **kwargs)
[docs]
def site_to_position_identifier(self, device: str, slot: int) -> str:
# devices with just one handover position are tagged like this
pos_identifier = f"{device}_nest"
# devices with multiple handover positions have their number attached (starting at 0)
if not self.graph_manager.position_known(pos_identifier):
pos_identifier += str(slot)
if not self.graph_manager.position_known(pos_identifier):
logging.error(f"{datetime.now()}: neither {device}_nest nor {pos_identifier} are known positions")
return pos_identifier
[docs]
def move_to_safe_pos(self, identifier: str, offset: dict[str, float] | None = None):
"""
Moves to the approach position belonging to the specified position
:param offset:
:param identifier:
:return:
"""
device_name = device_from_identifier(identifier)
safe_pos = approach_position(device_name)
self.move_to_position(safe_pos, offset=offset)
[docs]
def pick_at_position(self, identifier: str, offset: dict[str, float] | None=None):
self.origin_offset = z_offsets.get(identifier, 0)
old_width = self.GRIPPER_OPEN
if "Nikon" in identifier:
self.GRIPPER_OPEN = NIKON_GRIP_OPEN
super().pick_at_position(identifier, offset = offset)
self.GRIPPER_OPEN = old_width
self.move_to_safe_pos(identifier, offset=offset)
if not self.is_in_simulation_mode:
# throw an error if the gripper closed fully, i.e. no plate was picked up
if not self.currently_gripping_plate:
raise RuntimeError(f"Expected a plate at {identifier} while there was none")
[docs]
def regrip(self, target_offset: int, offset: dict[str, float]):
# do a regrip in Hotel nest 11.
# place it with the current offset
place_offset = offset.copy()
place_offset["z"] = self.origin_offset + offset.get("z", 0)
super().place_at_position(REGRIP_SLOT, offset=place_offset)
self.move_to_safe_pos(REGRIP_SLOT, offset=place_offset)
# pick it with the correct offset for the target position
pick_offset = offset.copy()
pick_offset["z"] = target_offset + offset.get("z", 0)
super().pick_at_position(REGRIP_SLOT, offset=pick_offset)
self.origin_offset = target_offset
self.move_to_safe_pos(REGRIP_SLOT, offset=pick_offset)
[docs]
def place_at_position(self, identifier: str, offset: dict[str, float] | None = None):
if offset is None:
offset = {}
# how much higher the held plate is gripped compared to the gripping height in the target position
target_offset = z_offsets.get(identifier, 0)
grip_diff = self.origin_offset - target_offset
if grip_diff > 0:
# there is no space to place a plate gripped higher except for slots 11 and 19, so do a regrip
if identifier in [f"Hotel_nest{i}" for i in list(range(1,11)) + list(range(12, 19))]:
logging.info(f"{datetime.now()}: Regripping: We can not move from Squid to tight(all but 11 and 19) hotel slots.")
self.regrip(target_offset, offset)
else:
offset["z"] = grip_diff + offset.get("z", 0)
elif grip_diff < 0:
self.regrip(target_offset, offset)
# manage the gripping with for accessing the nikon
old_width = self.GRIPPER_OPEN
if "Nikon" in identifier:
self.GRIPPER_OPEN = NIKON_GRIP_OPEN
super().place_at_position(identifier, offset=offset)
self.GRIPPER_OPEN = old_width
self.move_to_safe_pos(identifier, offset=offset)
[docs]
def prepare_for_output(self, internal_pos: int, device: str, position: int) -> bool:
safe_pos = approach_position(device)
self.move_to_position(safe_pos)
return True
# InteractiveTransferInterface
@property
def available_intermediate_actions(self) -> list[str]:
return ["read_barcode"]
[docs]
def get_barcode_scanned(self):
if self.graph_manager.position_known(bc_reader_position):
self.move_to_position(bc_reader_position)
time.sleep(.5)
else:
logging.error(f"{datetime.now()}: barcode reader position {bc_reader_position} is not a known position")
[docs]
def get_labware(self, intermediate_actions: list[str], device: str, position: int):
position_identifier = self.site_to_position_identifier(device, position)
self.pick_at_position(position_identifier)
if "read_barcode" in intermediate_actions:
self.get_barcode_scanned()