def FindSpot(image, sensitivity_limit=100): """ This function detects the spot and calculates and returns the coordinates of its center. The algorithms for spot detection and center calculation are similar to the ones that are used in Fine alignment. image (model.DataArray): Optical image sensitivity_limit (int): Limit of sensitivity in spot detection returns (tuple of floats): Position of the spot center in px (from the left-top corner of the image), possibly with sub-pixel resolution. raises: LookupError() if spot was not found """ subimages, subimage_coordinates = coordinates.DivideInNeighborhoods(image, (1, 1), 20, sensitivity_limit) if not subimages: raise LookupError("No spot detected") spot_coordinates = [FindCenterCoordinates(i) for i in subimages] optical_coordinates = coordinates.ReconstructCoordinates(subimage_coordinates, spot_coordinates) # Too many spots detected if len(optical_coordinates) > 10: logging.info("Found %d potential spots on image with data %s -> %s", len(optical_coordinates), image.min(), image.max()) raise LookupError("Too many spots detected") # Pick the brightest one max_intensity = 0 max_pos = optical_coordinates[0] for i in optical_coordinates: x, y = int(round(i[1])), int(round(i[0])) if image[x, y] >= max_intensity: max_pos = i max_intensity = image[x, y] return max_pos
def test_devide_and_find_center_grid(self): """ Test DivideInNeighborhoods combined with FindCenterCoordinates """ grid_data = hdf5.read_data("grid_10x10.h5") C, T, Z, Y, X = grid_data[0].shape grid_data[0].shape = Y, X subimages, subimage_coordinates = coordinates.DivideInNeighborhoods( grid_data[0], (10, 10), 40) spot_coordinates = coordinates.FindCenterCoordinates(subimages) optical_coordinates = coordinates.ReconstructCoordinates( subimage_coordinates, spot_coordinates) self.assertEqual(len(subimages), 100)
def test_divide_and_find_center_grid_noise(self): """ Test DivideInNeighborhoods combined with FindCenterCoordinates for noisy input """ grid_data = hdf5.read_data("grid_10x10.h5") C, T, Z, Y, X = grid_data[0].shape grid_data[0].shape = Y, X # Add Gaussian noise noise = random.normal(0, 40, grid_data[0].size) noise_array = noise.reshape(grid_data[0].shape[0], grid_data[0].shape[1]) noisy_grid_data = grid_data[0] + noise_array subimages, subimage_coordinates = coordinates.DivideInNeighborhoods( noisy_grid_data, (10, 10), 40) spot_coordinates = [spot.FindCenterCoordinates(i) for i in subimages] optical_coordinates = coordinates.ReconstructCoordinates( subimage_coordinates, spot_coordinates) self.assertEqual(len(subimages), 100)
def test_devide_and_find_center_grid_missing_point(self): """ Test DivideInNeighborhoods combined with FindCenterCoordinates for grid that misses one point """ grid_data = hdf5.read_data("grid_missing_point.h5") C, T, Z, Y, X = grid_data[0].shape grid_data[0].shape = Y, X # Add Gaussian noise noise = random.normal(0, 40, grid_data[0].size) noise_array = noise.reshape(grid_data[0].shape[0], grid_data[0].shape[1]) noisy_grid_data = grid_data[0] + noise_array subimages, subimage_coordinates = coordinates.DivideInNeighborhoods( noisy_grid_data, (10, 10), 40) spot_coordinates = coordinates.FindCenterCoordinates(subimages) optical_coordinates = coordinates.ReconstructCoordinates( subimage_coordinates, spot_coordinates) self.assertEqual(len(subimages), 99)
def main(args): """ Handles the command line arguments args is the list of arguments passed return (int): value to return to the OS as program exit code """ # arguments handling parser = argparse.ArgumentParser( description="Automated AR acquisition at multiple spot locations") parser.add_argument( "--repetitions_x", "-x", dest="repetitions_x", required=True, help= "repetitions defines the number of CL spots in the grid (x dimension)") parser.add_argument( "--repetitions_y", "-y", dest="repetitions_y", required=True, help= "repetitions defines the number of CL spots in the grid (y dimension)") parser.add_argument( "--dwell_time", "-t", dest="dwell_time", required=True, help="dwell_time indicates the time to scan each spot (unit: s)") parser.add_argument( "--max_allowed_diff", "-d", dest="max_allowed_diff", required=True, help= "max_allowed_diff indicates the maximum allowed difference in electron coordinates (unit: m)" ) options = parser.parse_args(args[1:]) repetitions = (int(options.repetitions_x), int(options.repetitions_y)) dwell_time = float(options.dwell_time) max_allowed_diff = float(options.max_allowed_diff) try: escan = None detector = None ccd = None # find components by their role for c in model.getComponents(): if c.role == "e-beam": escan = c elif c.role == "se-detector": detector = c elif c.role == "ccd": ccd = c if not all([escan, detector, ccd]): logging.error("Failed to find all the components") raise KeyError("Not all components found") # ccd.data.get() gscanner = GridScanner(repetitions, dwell_time, escan, ccd, detector) # Wait for ScanGrid to finish optical_image, electron_coordinates, electron_scale = gscanner.DoAcquisition( ) hdf5.export("scanned_image.h5", optical_image) logging.debug("electron coord = %s", electron_coordinates) ############## TO BE REMOVED ON TESTING############## # grid_data = hdf5.read_data("scanned_image.h5") # C, T, Z, Y, X = grid_data[0].shape # grid_data[0].shape = Y, X # optical_image = grid_data[0] ##################################################### logging.debug("Isolating spots...") opxs = optical_image.metadata[model.MD_PIXEL_SIZE] optical_dist = escan.pixelSize.value[0] * electron_scale[0] / opxs[0] subimages, subimage_coordinates = coordinates.DivideInNeighborhoods( optical_image, repetitions, optical_dist) logging.debug("Number of spots found: %d", len(subimages)) hdf5.export("spot_found.h5", subimages, thumbnail=None) logging.debug("Finding spot centers...") spot_coordinates = spot.FindCenterCoordinates(subimages) logging.debug("center coord = %s", spot_coordinates) optical_coordinates = coordinates.ReconstructCoordinates( subimage_coordinates, spot_coordinates) logging.debug(optical_coordinates) rgb_optical = img.DataArray2RGB(optical_image) for ta in optical_coordinates: rgb_optical[ta[1] - 1:ta[1] + 1, ta[0] - 1:ta[0] + 1, 0] = 255 rgb_optical[ta[1] - 1:ta[1] + 1, ta[0] - 1:ta[0] + 1, 1] *= 0.5 rgb_optical[ta[1] - 1:ta[1] + 1, ta[0] - 1:ta[0] + 1, 2] *= 0.5 misc.imsave('spots_image.png', rgb_optical) # TODO: Make function for scale calculation sorted_coordinates = sorted(optical_coordinates, key=lambda tup: tup[1]) tab = tuple( map(operator.sub, sorted_coordinates[0], sorted_coordinates[1])) optical_scale = math.hypot(tab[0], tab[1]) scale = electron_scale[0] / optical_scale print(scale) # max_allowed_diff in pixels max_allowed_diff_px = max_allowed_diff / escan.pixelSize.value[0] logging.debug("Matching coordinates...") known_electron_coordinates, known_optical_coordinates, max_diff = coordinates.MatchCoordinates( optical_coordinates, electron_coordinates, scale, max_allowed_diff_px) logging.debug("Calculating transformation...") (calc_translation_x, calc_translation_y), ( calc_scaling_x, calc_scaling_y), calc_rotation = transform.CalculateTransform( known_electron_coordinates, known_optical_coordinates) logging.debug("Electron->Optical: ") print(calc_translation_x, calc_translation_y, calc_scaling_x, calc_scaling_y, calc_rotation) final_electron = coordinates._TransformCoordinates( known_optical_coordinates, (calc_translation_x, calc_translation_y), calc_rotation, (calc_scaling_x, calc_scaling_y)) logging.debug("Overlay done.") # Calculate distance between the expected and found electron coordinates coord_diff = [] for ta, tb in zip(final_electron, known_electron_coordinates): tab = tuple(map(operator.sub, ta, tb)) coord_diff.append(math.hypot(tab[0], tab[1])) mean_difference = numpy.mean(coord_diff) * escan.pixelSize.value[0] variance_sum = 0 for i in range(0, len(coord_diff)): variance_sum += (mean_difference - coord_diff[i])**2 variance = (variance_sum / len(coord_diff)) * escan.pixelSize.value[0] not_found_spots = len(electron_coordinates) - len(final_electron) # Generate overlay image logging.debug("Generating images...") (calc_translation_x, calc_translation_y), ( calc_scaling_x, calc_scaling_y), calc_rotation = transform.CalculateTransform( known_optical_coordinates, known_electron_coordinates) logging.debug("Optical->Electron: ") print(calc_translation_x, calc_translation_y, calc_scaling_x, calc_scaling_y, calc_rotation) overlay_coordinates = coordinates._TransformCoordinates( known_electron_coordinates, (calc_translation_y, calc_translation_x), -calc_rotation, (calc_scaling_x, calc_scaling_y)) for ta in overlay_coordinates: rgb_optical[ta[0] - 1:ta[0] + 1, ta[1] - 1:ta[1] + 1, 1] = 255 misc.imsave('overlay_image.png', rgb_optical) misc.imsave('optical_image.png', optical_image) logging.debug( "Done. Check electron_image.png, optical_image.png and overlay_image.png." ) except: logging.exception("Unexpected error while performing action.") return 127 logging.info( "\n**Overlay precision stats (Resulted to expected electron coordinates comparison)**\n Mean distance: %f (unit: m)\n Variance: %f (unit: m)\n Not found spots: %d", mean_difference, variance, not_found_spots) return 0