Source code for fibsem.milling

"""FIB-SEM semi-aautomated creation of ion beam milling patterns."""
from collections import namedtuple
import logging

import matplotlib.pyplot as plt
import numpy as np

import fibsem
from fibsem.user_input import response_yes, response_no, response_cancel

log = logging.getLogger(__name__)


def _n_rois(axis_coords, roi_width, roi_overlap):
    """Number of ROIs needed to cover the sample.

    Parameters
    ----------
    axis_coords : tuple of coordinates
        Coordinates are in row, column format.
    roi_width : float
        Width of ROI milling regions, in units of meters.
    roi_overlap : float
        Amount each milling region should overlap with the last. Meter units.

    Returns
    -------
    n_rois
        Integer. How many ROIs are needed to cover the sample.
    """
    length = fibsem.orientation.line_length(axis_coords)
    n_rois = int(np.ceil(length / (roi_width - roi_overlap)))
    return n_rois


def _single_roi_centroid_position(i, axis_coords, roi_width, roi_overlap):
    """Return center point coordinate for ROI.

    Parameters
    ----------
    i : ROI index
        i.e. which number ROI in the sequence this one is.
    axis_coords :
        Coordinates are in row, column format.
    roi_width : float
        Width of ROI milling regions, in units of meters.
    roi_overlap : float
        Amount each milling region should overlap with the last. Meter units.

    Returns
    -------
    center_coordinate : numpy array
        Coordinate is in row, column format.
    """
    theta = fibsem.orientation.rotation_angle(axis_coords)
    distance = (i * (roi_width - roi_overlap)) + (roi_width / 2)
    center_coordinate = fibsem.orientation._point_given_angle_and_distance(
        axis_coords[0], theta, distance)
    return center_coordinate


[docs]def all_roi_coordinates(sample, settings): """"Calculate all coordinates for rectangle milling patterns. Parameters ---------- sample : Embryo sample object. settings : dict User input arguments from config yml file. Returns ------- all_roi_coords List of all ROI coordinates. """ head2tail_axis, tail2head_axis = _dual_milling_directions( sample.major_axis, settings) length_head2tail = fibsem.orientation.line_length(head2tail_axis) length_tail2head = fibsem.orientation.line_length(tail2head_axis) if length_head2tail > settings['roi_width'] * 0.5: head2tail_coords = _roi_coordinates(head2tail_axis, sample, settings) for head_coords in head2tail_coords: head_coords[-1] += np.pi / 2 # effectively a LeftToRight scan dir else: head2tail_coords = [] log.info("Head to tail milling axis is too short, don't mill here.") if length_tail2head > settings['roi_width'] * 0.5: tail2head_coords = _roi_coordinates(tail2head_axis, sample, settings) for tail_coords in tail2head_coords: tail_coords[-1] -= np.pi / 2 # effectively a RightToLeft scan dir else: tail2head_coords = [] log.info("Tail to head milling axis is too short, don't mill here.") all_roi_coords = head2tail_coords + tail2head_coords return all_roi_coords
def _roi_centroid_positions(milling_axis, args): """Return list of ROI centroid coordinates for the milling axis. ROI centroids are calculated from the outside in, so the edge is always in the right position closest to the lamella (we don't care if we mill a little extra on the outside edge). The returned list is flipped, so ROIs are created from the outside in. Parameters ---------- milling_axis : Axis coordinates; in row, column format. args : dict User input settings from config yml file. Returns ------- list of coordinates Centroid coordinates are in row, column format. """ n_rois = _n_rois(milling_axis, args['roi_width'], args['roi_overlap']) reversed_axis = np.flip(milling_axis, axis=0) results = [] for i in range(n_rois): roi_centroid = _single_roi_centroid_position( i, reversed_axis, args['roi_width'], args['roi_overlap']) results.append(roi_centroid) results = np.flip(results, axis=0) return results def _roi_coordinates(milling_axis, sample, args): """Calculate coordinates for rectangle milling patterns. start_coord is in row, column format. """ theta = fibsem.orientation.rotation_angle(sample.major_axis) roi_centroids = _roi_centroid_positions(milling_axis, args) results = [] for roi_centroid in roi_centroids: center_y, center_x = roi_centroid roi_height = fibsem.orientation._calculate_roi_height(roi_centroid, sample) roi_height_buffer = 0.1 * \ fibsem.orientation.line_length(sample.minor_axis) roi_height += roi_height_buffer result = [center_x, center_y, roi_height, args['roi_width'], args['depth'], theta] # swap width/height, scan_dir bugfix results.append(result) return results def _dual_milling_directions(major_axis, settings): """Milling goes from the outside edges in, create axis for this. Parameters ---------- major_axis : major axis of sample segmentation. Coordinates are in row, column format. settings : User input settings. Includes: start_lamella : float, in range 0 to 1.0 (1 = 100%) Where the lamella starts, as percentage of sample length from head. end_lamella : float, in range 0 to 1.0 (1 = 100%) Where the lamella ends, as a percentage of sample length from head. buffer_around_lamella : Returns ------- head_to_tail_axis, tail_to_head_to_axis """ start_lamella = settings['start_lamella'] end_lamella = settings['end_lamella'] subtract_dist = settings['buffer_around_lamella'] + settings['roi_overlap'] theta = fibsem.orientation.rotation_angle(major_axis) sample_length = fibsem.orientation.line_length(major_axis) distance_to_start = (start_lamella * sample_length) - subtract_dist distance_to_stop = (end_lamella * sample_length) - subtract_dist # head_to_tail start_coord = fibsem.orientation._point_given_angle_and_distance( major_axis[0], theta, distance_to_start) head_to_tail_axis = (major_axis[0], start_coord) # tail_to_head stop_coord = fibsem.orientation._point_given_angle_and_distance( major_axis[0], theta, distance_to_stop) tail_to_head_to_axis = (major_axis[1], stop_coord) return head_to_tail_axis, tail_to_head_to_axis
[docs]def mill_single_sample(sample, microscope, settings, wait_for_user=False): """Ion beam milling a single sample. Parameters ---------- sample : fibsem.sample.Sample() Sample object for ion beam milling. microscope : autoscript_sdb_microscope_client.SdbMicroscopeClient() Autoscript microscope client. settings : dict User input settings from config yml file. wait_for_user : bool, optional Ask the user to confirm (the default is False, which cancels milling). Returns ------- milled_image The ion beam image of the sample after milling is finished. """ import fibsem.autoscript fibsem.autoscript.reset_patterning_state(microscope) fibsem.autoscript.create_milling_patterns(microscope, sample.milling_rois, settings) if wait_for_user: user_continue = fibsem.user_input.response( 'Do you want to mill these patterns? y/n') if user_continue in response_no or user_continue in response_cancel: print('Aborting ion beam milling') microscope.patterning.clear_patterns() return log.info('Ion beam milling in progress, this may take some time...') current_for_milling = settings['current_for_milling'] if microscope.beams.ion_beam.beam_current.value != current_for_milling: microscope.beams.ion_beam.beam_current.value = current_for_milling microscope.patterning.run() microscope.patterning.clear_patterns() # inconsistent in clearing patterns log.info('Single sample milled.')
[docs]def mill_all_samples(samples, microscope, settings): """Ion beam milling for all samples in list. Parameters ---------- samples : list List of sample objects for ion beam milling. microscope : autoscript_sdb_microscope_client.SdbMicroscopeClient() Autoscript microscope client. settings : dict User input arguments from config yml file. """ message = 'Do you want to mill all samples? y/n' user_continue = fibsem.user_input.response(message) if user_continue in response_yes: for i, sample in enumerate(samples): log.info('Sample {}'.format(i)) if settings['display_matplotlib'] is True: sample.display_milling_rois() sample.move_to_stage_position(microscope) mill_single_sample(sample, microscope, settings, wait_for_user=True) log.info('All ion beam milling complete.') else: log.info('Cancelling ion milling.')