Source code for microscope_automation.orchestrator.write_zen_tiles_experiment
"""
Export position list for ZEN blue tiles experiment (.czsh)
Created on Jul 26, 2019
authors: Brian Kim, Calysta Yan, winfriedw
"""
import xml.etree.ElementTree as ET
import itertools
import string
import os
from microscope_automation.util import automation_messages_form_layout as message
[docs]class PositionWriter(object):
    """Converts positions to stage coordinates, saves positions to .czsh file
    Requirements:
     1) Ensure the dummy_tile_positions.czsh path is specified
     in preferences under key "PathDummy"
    """
    def __init__(self, zsd, plate, production_path, testing_mode=False):
        """
        Initialization
        Input:
         zsd: String or integer Id of ZSD used for acquisition
         plate: String of integer Id of plate
         production_path: Path of the prouduction folder
        Output:
         none
        """
        # Check Inputs
        if production_path is None:
            raise ValueError("Production path not specified")
        if type(plate) is not str:
            if type(plate) is int:
                plate = str(plate)
            else:
                raise ValueError("specified plate number is not a number or a string")
        if type(zsd) is not str:
            if type(zsd) is int:
                zsd = str(zsd)
            else:
                raise ValueError("specified ZSD id is not a number or a string")
        if isinstance(production_path, list):
            production_path = production_path[0]
        self.plate = plate
        self.path = os.path.join(production_path, plate, zsd)
        if not os.path.exists(self.path):
            raise OSError(
                "Please check zsd, plate, and production path"
                " info given to PositionWriter"
            )
        self.zsd = zsd
        self.gen = self.iter_all_strings()
[docs]    def convert_to_stage_coords(
        self, offset_x=0, offset_y=0, positions_list=[], header=False
    ):
        """Converts the distance of points from center of image to x-y coordinates in
        stage with 10 to 100x objective offsets.
        Input:
         offset_x: x_offset to account for in stage coordinate conversion
         offset_y: y_offset to accoutn for in stage coordinate conversion
         positions_list: center of well positions to convert to stage coordinates
         header: boolean of whether positions_list includes a header. Skips the
         first row if True. Default: False
        Output:
         converted_list: stage coordinates of positions converted
         from center of well positions
        """
        # Check to ensure there are positions to convert
        if len(positions_list) <= 1:
            raise AssertionError(
                "positions list from automation software needs to contain coordinates"
            )
        converted_list = []
        obj_offset = [offset_x, offset_y]
        start = 1 if header else 0
        for i in range(start, len(positions_list)):
            # positions_list[i][0] is name of position from automation software
            this_position = dict()
            this_position["name"] = positions_list[i][0]
            this_position["actual_x"] = (
                positions_list[i][1] + obj_offset[0]
            )  # coordinate X + offset for X
            this_position["actual_y"] = (
                positions_list[i][2] + obj_offset[1]
            )  # coordinate Y + offset Y
            this_position["actual_z"] = positions_list[i][
                3
            ]  # coordinate Z (no offset here)
            converted_list.append(this_position)
        return converted_list 
[docs]    def write(self, converted=[], dummy="", name_czsh="positions_output.czsh"):
        """
        Writes coordinates to a dummy.czsh file, and saves it
        Input:
         converted: positions to write to the czsh file
         dummy: empty (no coordinates) .czsh file to use for writing
         name_czsh: name to save written .czsh file as
        Output:
         none
        """
        # Where to write the positions
        to_write = os.path.join(self.path, name_czsh)
        to_append = self.get_next_pos_name()
        # append a letter to file name of .czsh
        split = name_czsh.split(".")
        split[len(split) - 2] += "_" + to_append
        to_write = os.path.join(self.path, ".".join(split))  # noqa
        # Theres no data here, or the file was mistakenly rewritten
        if len(converted) == 0:
            raise AssertionError(
                "Need positions to write, Did you call convert_to_stage_coords()?"
            )
        # Get empty file
        try:
            tree = ET.parse(os.path.abspath(dummy))
        except FileNotFoundError:
            dummy = message.read_string(
                "Dummy positions file " + dummy + " could not be found.",
                label="Enter a valid file path:",
                default="",
            )
            tree = ET.parse(os.path.abspath(dummy))
        root = tree.getroot()
        for single_tiles in root.iter("SingleTileRegions"):
            for n in converted:
                # Assign Values for writing
                tile = ET.SubElement(single_tiles, "SingleTileRegion")
                tile.set("Name", n["name"])
                # Need str for tree.write()
                ET.SubElement(tile, "X").text = str(n["actual_x"])
                ET.SubElement(tile, "Y").text = str(n["actual_y"])
                ET.SubElement(tile, "Z").text = str(n["actual_z"])
                ET.SubElement(tile, "IsUsedForAcquisition").text = "true"
        # Write Values
        tree.write(to_write) 
[docs]    def iter_all_strings(self):
        for size in itertools.count(1):
            for s in itertools.product(string.ascii_lowercase, repeat=size):
                yield "".join(s) 
[docs]    def label_gen(self):
        for s in self.gen:
            return s 
[docs]    def get_next_pos_name(self, test_mode=False, test_file_names=[]):
        """Find the next available letter label, but dont backfill files.
        Input:
         test_mode: False by default. Runs in test mode if True.
         test_file_names: list of test file names to use if running in test mode
        Output:
         label: unused alphabetical label for new positions list
        """
        if test_mode:
            prefixed = test_file_names
        else:
            prefixed = [
                filename
                for filename in os.listdir(self.path)
                if filename.startswith("positions_output_")
            ]
        # if there's no existing output files start with the leter 'a'
        if len(prefixed) == 0:
            return "a"
        # Split extension from name of file
        split = prefixed[len(prefixed) - 1].split(".")
        # split filename to get letter
        letter = split[len(split) - 2].split("_")
        # letters found
        letters = letter[len(letter) - 1]
        label = self.label_gen()
        while label != letters:
            label = self.label_gen()
        return self.label_gen()