Ejemplo n.º 1
0
    def test_find_center_big(self):
        """
        Test FindCenterCoordinates on large data
        """
        # Note: it's not very clear why, but the center is not exactly the same
        # as with the original data.
        expected_coordinates = [(-0.0003367114224783442,
                                 -0.022941682748052378),
                                (0.42179351215760619, -0.25668360673638801),
                                (0.054153514028894206, -0.046475569488448026),
                                (0.15117193581594143, 0.20813363301021551),
                                (0.1963834856403108, -0.18329597166583256),
                                (0.23159684275306583, 1.3670166271550004),
                                (-1.3363782613242998, 0.20192181693837058),
                                (-0.14978764151902624, 0.66067572281822606),
                                (-0.058984235874285897, 0.13071737132569164),
                                (0.021009283646695891, -0.007037802630523865)]

        for i in range(10):
            data = hdf5.read_data(
                os.path.join(TEST_IMAGE_PATH, "image" + str(i + 1) + ".h5"))[0]
            data.shape = data.shape[-2:]
            Y, X = data.shape
            databig = numpy.zeros((200 + Y, 200 + X), data.dtype)
            databig += numpy.min(data)
            # We put it right at the center, so shouldn't change expected coordinates
            databig[100:100 + Y:, 100:100 + X] = data
            spot_coordinates = spot.FindCenterCoordinates(databig)
            numpy.testing.assert_almost_equal(spot_coordinates,
                                              expected_coordinates[i], 3)
Ejemplo n.º 2
0
 def test_sanity(self):
     """
     Create an image consisting of all zeros and a single pixel with value
     one. FindCenterCoordinates should return the coordinates of this one
     pixel.
     """
     for n in range(5, 12):
         for m in range(5, 12):
             for i in range(2, n - 2):
                 for j in range(2, m - 2):
                     img = numpy.zeros((n, m))
                     img[i, j] = 1
                     xc, yc = spot.FindCenterCoordinates(img)
                     self.assertAlmostEqual(j, xc + 0.5 * (m - 1))
                     self.assertAlmostEqual(i, yc + 0.5 * (n - 1))
Ejemplo n.º 3
0
 def test_find_center_syn(self):
     """
     Test FindCenterCoordinates on synthetic data
     """
     offsets = [
         (0, 0),
         (-1, -1),
         (3, 2),
     ]
     for ofs in offsets:
         data = numpy.zeros((201, 201), numpy.uint16)
         # Just one point, so it should be easy to find
         data[100 + ofs[1], 100 + ofs[0]] = 500
         spot_coordinates = spot.FindCenterCoordinates(data)
         numpy.testing.assert_almost_equal(spot_coordinates, ofs, 3)
Ejemplo n.º 4
0
    def test_divide_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 = [spot.FindCenterCoordinates(i) for i in subimages]
        optical_coordinates = coordinates.ReconstructCoordinates(
            subimage_coordinates, spot_coordinates)

        self.assertEqual(len(subimages), 100)
Ejemplo n.º 5
0
    def test_divide_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 = [spot.FindCenterCoordinates(i) for i in subimages]
        optical_coordinates = coordinates.ReconstructCoordinates(
            subimage_coordinates, spot_coordinates)

        self.assertEqual(len(subimages), 99)
Ejemplo n.º 6
0
    def test_find_center(self):
        """
        Test FindCenterCoordinates
        """
        expected_coordinates = [(-0.00019439548586790034,
                                 -0.023174120210179554),
                                (0.47957790544657719, -0.82786251901339769),
                                (0.05418032832973009, -0.046573726263258203),
                                (0.15117173005078957, 0.20813259555303279),
                                (-0.16400161706684502, 0.12399078936095265),
                                (0.21457123595635252, 1.682698104874774),
                                (-1.3480442345004007, 0.19789183664083154),
                                (-0.13424061744712734, 0.73739434108133217),
                                (-0.063230444692135013, 0.14718269387805094),
                                (0.020941736978718473, -0.0071056828496776324)]

        for i in range(10):
            data = hdf5.read_data(
                os.path.join(TEST_IMAGE_PATH, "image" + str(i + 1) + ".h5"))[0]
            C, T, Z, Y, X = data.shape
            data.shape = Y, X
            spot_coordinates = spot.FindCenterCoordinates(data)
            numpy.testing.assert_almost_equal(spot_coordinates,
                                              expected_coordinates[i], 3)
Ejemplo n.º 7
0
    def test_find_center(self):
        """
        Test FindCenterCoordinates
        """
        expected_coordinates = [(-0.00019439548586790034,
                                 -0.023174120210179554),
                                (0.41813787193469681, -0.77556146879261101),
                                (0.05418032832973009, -0.046573726263258203),
                                (0.15117173005078957, 0.20813259555303279),
                                (0.15372338817998937, -0.071307409462406962),
                                (0.22214464176322843, 1.5448851668913044),
                                (-1.3567379189595801, 0.20634334863259929),
                                (-0.068717256379618827, 0.76902400758882417),
                                (-0.064496044288789064, 0.14000630665134439),
                                (0.020941736978718473, -0.0071056828496776324)]

        for i in range(10):
            data = hdf5.read_data(
                os.path.join(TEST_IMAGE_PATH, "image" + str(i + 1) + ".h5"))[0]
            C, T, Z, Y, X = data.shape
            data.shape = Y, X
            spot_coordinates = spot.FindCenterCoordinates(data)
            numpy.testing.assert_almost_equal(spot_coordinates,
                                              expected_coordinates[i], 3)
Ejemplo n.º 8
0
def _DoFindOverlay(future,
                   repetitions,
                   dwell_time,
                   max_allowed_diff,
                   escan,
                   ccd,
                   detector,
                   skew=False):
    """
    Scans a spots grid using the e-beam and captures the CCD image, isolates the
    spots in the CCD image and finds the coordinates of their centers, matches the
    coordinates of the spots in the CCD image to those of SEM image and calculates
    the transformation values from optical to electron image (i.e. ScanGrid->
    DivideInNeighborhoods->FindCenterCoordinates-> ReconstructCoordinates->MatchCoordinates->
    CalculateTransform). In case matching the coordinates is infeasible, it automatically
    repeats grid scan -and thus all steps until matching- with different parameters.
    future (model.ProgressiveFuture): Progressive future provided by the wrapper
    repetitions (tuple of ints): The number of CL spots are used
    dwell_time (float): Time to scan each spot (in s)
    max_allowed_diff (float): Maximum allowed difference (in m) between the spot
      coordinates and the estimated spot position based on the computed
      transformation (in m). If no transformation can be found to fit this
      limit, the procedure will fail.
    escan (model.Emitter): The e-beam scanner
    ccd (model.DigitalCamera): The CCD
    detector (model.Detector): The electron detector
    skew (boolean): If True, also compute skew
    returns tuple: Transformation parameters
                translation (Tuple of 2 floats)
                scaling (Float)
                rotation (Float)
            dict : Transformation metadata
    raises:
            CancelledError if cancelled
            ValueError if procedure failed
    """
    # TODO: drop the "skew" argument (to always True) once we are convinced it
    # works fine
    # TODO: take the limits of the acceptable values for the metadata, and raise
    # an error when the data is not within range (or retry)
    logging.debug("Starting Overlay...")

    try:
        _set_blanker(escan, False)

        # Repeat until we can find overlay (matching coordinates is feasible)
        for trial in range(MAX_TRIALS_NUMBER):
            logging.debug("Trying with dwell time = %g s...",
                          future._gscanner.dwell_time)
            # For making a report when a failure happens
            report = OrderedDict()  # Description (str) -> value (str()'able)
            optical_image = None
            report["Grid size"] = repetitions
            report["SEM magnification"] = escan.magnification.value
            report["SEM pixel size"] = escan.pixelSize.value
            report["SEM FoV"] = tuple(
                s * p for s, p in zip(escan.shape, escan.pixelSize.value))
            report["Maximum difference allowed"] = max_allowed_diff
            report["Dwell time"] = dwell_time
            subimages = []

            try:
                # Grid scan
                if future._find_overlay_state == CANCELLED:
                    raise CancelledError()

                # Update progress of the future (it may be the second trial)
                future.set_progress(end=time.time() + estimateOverlayTime(
                    future._gscanner.dwell_time, repetitions))

                # Wait for ScanGrid to finish
                optical_image, electron_coordinates, electron_scale = future._gscanner.DoAcquisition(
                )
                report["Spots coordinates in SEM ref"] = electron_coordinates

                if future._find_overlay_state == CANCELLED:
                    raise CancelledError()

                # Update remaining time to 6secs (hardcoded estimation)
                future.set_progress(end=time.time() + 6)

                # Check if ScanGrid gave one image or list of images
                # If it is a list, follow the "one image per spot" procedure
                logging.debug("Isolating spots...")
                if isinstance(optical_image, list):
                    report["Acquisition method"] = "One image per spot"
                    opxs = optical_image[0].metadata[model.MD_PIXEL_SIZE]
                    opt_img_shape = optical_image[0].shape
                    subimage_coordinates = []
                    for oimg in optical_image:
                        subspots, subspot_coordinates = coordinates.DivideInNeighborhoods(
                            oimg, (1, 1), oimg.shape[0] / 2)
                        subimages.append(subspots[0])
                        subimage_coordinates.append(subspot_coordinates[0])
                else:
                    report["Acquisition method"] = "Whole image"
                    # Distance between spots in the optical image (in optical pixels)
                    opxs = optical_image.metadata[model.MD_PIXEL_SIZE]
                    optical_dist = escan.pixelSize.value[0] * electron_scale[
                        0] / opxs[0]
                    opt_img_shape = optical_image.shape

                    # Isolate spots
                    if future._find_overlay_state == CANCELLED:
                        raise CancelledError()

                    subimages, subimage_coordinates = coordinates.DivideInNeighborhoods(
                        optical_image, repetitions, optical_dist)

                if not subimages:
                    raise OverlayError(
                        "Overlay failure: failed to partition image")
                report["Optical pixel size"] = opxs
                report["Optical FoV"] = tuple(
                    s * p for s, p in zip(opt_img_shape[::-1], opxs))
                report[
                    "Coordinates of partitioned optical images"] = subimage_coordinates

                if max_allowed_diff < opxs[0] * 4:
                    logging.warning(
                        "The maximum distance is very small compared to the optical pixel size: "
                        "%g m vs %g m", max_allowed_diff, opxs[0])

                # Find the centers of the spots
                if future._find_overlay_state == CANCELLED:
                    raise CancelledError()
                logging.debug("Finding spot centers with %d subimages...",
                              len(subimages))
                spot_coordinates = [
                    spot.FindCenterCoordinates(i) for i in subimages
                ]

                # Reconstruct the optical coordinates
                if future._find_overlay_state == CANCELLED:
                    raise CancelledError()
                optical_coordinates = coordinates.ReconstructCoordinates(
                    subimage_coordinates, spot_coordinates)

                # Check if SEM calibration is correct. If this is not the case
                # generate a warning message and provide the ratio of X/Y scale.
                ratio = _computeGridRatio(optical_coordinates, repetitions)
                report["SEM X/Y ratio"] = ratio
                if not (0.9 < ratio < 1.1):
                    logging.warning(
                        "SEM may needs calibration. X/Y ratio is %f.", ratio)
                else:
                    logging.info("SEM X/Y ratio is %f.", ratio)

                opt_offset = (opt_img_shape[1] / 2, opt_img_shape[0] / 2)
                optical_coordinates = [(x - opt_offset[0], y - opt_offset[1])
                                       for x, y in optical_coordinates]
                report[
                    "Spots coordinates in Optical ref"] = optical_coordinates

                # Estimate the scale by measuring the distance between the closest
                # two spots in optical and electron coordinates.
                #  * For electrons, it's easy as we've placed them.
                #  * For optical, we pick one spot, and measure the distance to the
                #    closest spot.
                p1 = optical_coordinates[0]

                def dist_to_p1(p):
                    return math.hypot(p1[0] - p[0], p1[1] - p[1])

                optical_dist = min(
                    dist_to_p1(p) for p in optical_coordinates[1:])
                scale = electron_scale[0] / optical_dist
                report["Estimated scale"] = scale

                # max_allowed_diff in pixels
                max_allowed_diff_px = max_allowed_diff / escan.pixelSize.value[
                    0]

                # Match the electron to optical coordinates
                if future._find_overlay_state == CANCELLED:
                    raise CancelledError()

                logging.debug("Matching coordinates...")
                try:
                    known_ec, known_oc, max_diff = coordinates.MatchCoordinates(
                        optical_coordinates, electron_coordinates, scale,
                        max_allowed_diff_px)
                except LookupError as exp:
                    raise OverlayError(
                        "Failed to match SEM and optical coordinates: %s" %
                        (exp, ))

                report["Matched coordinates in SEM ref"] = known_ec
                report["Matched coordinates in Optical ref"] = known_oc
                report["Maximum distance between matches"] = max_diff

                # Calculate transformation parameters
                if future._find_overlay_state == CANCELLED:
                    raise CancelledError()

                # We are almost done... about 1 s left
                future.set_progress(end=time.time() + 1)

                logging.debug("Calculating transformation...")
                try:
                    ret = transform.CalculateTransform(known_ec, known_oc,
                                                       skew)
                except ValueError as exp:
                    raise OverlayError(
                        "Failed to calculate transformation: %s" % (exp, ))

                if future._find_overlay_state == CANCELLED:
                    raise CancelledError()

                logging.debug("Calculating transform metadata...")
                if skew is True:
                    transform_d, skew_d = _transformMetadata(
                        optical_image, ret, escan, ccd, skew)
                    transform_data = (transform_d, skew_d)
                else:
                    transform_d = _transformMetadata(
                        optical_image, ret, escan, ccd, skew
                    )  # Also indicate which dwell time eventually worked
                    transform_data = transform_d
                transform_d[model.MD_DWELL_TIME] = dwell_time

                # Everything went fine
                # _MakeReport("No problem", report, optical_image, subimages)  # DEBUG
                logging.debug("Overlay done.")
                return ret, transform_data
            except OverlayError as exp:
                # Make failure report
                _MakeReport(str(exp), report, optical_image, subimages)
                # Maybe it's just due to a bad SNR => retry with longer dwell time
                future._gscanner.dwell_time = future._gscanner.dwell_time * 1.2 + 0.1
        else:
            raise ValueError("Overlay failure after %d attempts" %
                             (MAX_TRIALS_NUMBER, ))

    except CancelledError:
        pass
    except Exception as exp:
        logging.debug("Finding overlay failed", exc_info=1)
        raise exp
    finally:
        _set_blanker(escan, True)
        with future._overlay_lock:
            future._done.set()
            if future._find_overlay_state == CANCELLED:
                raise CancelledError()
            future._find_overlay_state = FINISHED
Ejemplo n.º 9
0
def _DoFindOverlay(future,
                   repetitions,
                   dwell_time,
                   max_allowed_diff,
                   escan,
                   ccd,
                   detector,
                   skew=False):
    """
    Scans a spots grid using the e-beam and captures the CCD image, isolates the
    spots in the CCD image and finds the coordinates of their centers, matches the
    coordinates of the spots in the CCD image to those of SEM image and calculates
    the transformation values from optical to electron image (i.e. ScanGrid->
    DivideInNeighborhoods->FindCenterCoordinates-> ReconstructCoordinates->MatchCoordinates->
    CalculateTransform). In case matching the coordinates is infeasible, it automatically
    repeats grid scan -and thus all steps until matching- with different parameters.
    future (model.ProgressiveFuture): Progressive future provided by the wrapper
    repetitions (tuple of ints): The number of CL spots are used
    dwell_time (float): Time to scan each spot (in s)
    max_allowed_diff (float): Maximum allowed difference in electron coordinates #m
    escan (model.Emitter): The e-beam scanner
    ccd (model.DigitalCamera): The CCD
    detector (model.Detector): The electron detector
    skew (boolean): If True, also compute skew
    returns tuple: Transformation parameters
                translation (Tuple of 2 floats)
                scaling (Float)
                rotation (Float)
            dict : Transformation metadata
    raises:
            CancelledError if cancelled
            ValueError if procedure failed
    """
    # TODO: drop the "skew" argument (to always True) once we are convinced it
    # works fine
    logging.debug("Starting Overlay...")

    try:
        # Repeat until we can find overlay (matching coordinates is feasible)
        for trial in range(MAX_TRIALS_NUMBER):
            # Grid scan
            if future._find_overlay_state == CANCELLED:
                raise CancelledError()

            # Update progress of the future (it may be the second trial)
            future.set_progress(
                end=time.time() +
                estimateOverlayTime(future._scanner.dwell_time, repetitions))

            # Wait for ScanGrid to finish
            optical_image, electron_coordinates, electron_scale = future._scanner.DoAcquisition(
            )
            if future._find_overlay_state == CANCELLED:
                raise CancelledError()

            # Update remaining time to 6secs (hardcoded estimation)
            future.set_progress(end=time.time() + 6)

            # Check if ScanGrid gave one image or list of images
            # If it is a list, follow the "one image per spot" procedure
            logging.debug("Isolating spots...")
            if isinstance(optical_image, list):
                opt_img_shape = optical_image[0].shape
                subimages = []
                subimage_coordinates = []
                for img in optical_image:
                    subspots, subspot_coordinates = coordinates.DivideInNeighborhoods(
                        img, (1, 1), img.shape[0] / 2)
                    subimages.append(subspots[0])
                    subimage_coordinates.append(subspot_coordinates[0])
            else:
                # Distance between spots in the optical image (in optical pixels)
                optical_dist = escan.pixelSize.value[0] * electron_scale[
                    0] / optical_image.metadata[model.MD_PIXEL_SIZE][0]
                opt_img_shape = optical_image.shape

                # Isolate spots
                if future._find_overlay_state == CANCELLED:
                    raise CancelledError()
                subimages, subimage_coordinates = coordinates.DivideInNeighborhoods(
                    optical_image, repetitions, optical_dist)

            if not subimages:
                raise ValueError("Overlay failure")

            # Find the centers of the spots
            if future._find_overlay_state == CANCELLED:
                raise CancelledError()
            logging.debug("Finding spot centers with %d subimages...",
                          len(subimages))
            spot_coordinates = [
                spot.FindCenterCoordinates(i) for i in subimages
            ]

            # Reconstruct the optical coordinates
            if future._find_overlay_state == CANCELLED:
                raise CancelledError()
            optical_coordinates = coordinates.ReconstructCoordinates(
                subimage_coordinates, spot_coordinates)

            # Check if SEM calibration is correct. If this is not the case
            # generate a warning message and provide the ratio of X/Y scale.
            ratio = _computeGridRatio(optical_coordinates, repetitions)
            if not (0.9 < ratio < 1.1):
                logging.warning("SEM may needs calibration. X/Y ratio is %f.",
                                ratio)
            else:
                logging.info("SEM X/Y ratio is %f.", ratio)

            opt_offset = (opt_img_shape[1] / 2, opt_img_shape[0] / 2)

            optical_coordinates = [(x - opt_offset[0], y - opt_offset[1])
                                   for x, y in optical_coordinates]

            # Estimate the scale by measuring the distance between the closest
            # two spots in optical and electron coordinates.
            #  * For electrons, it's easy as we've placed them.
            #  * For optical, we pick one spot, and measure the distance to the
            #    closest spot.
            p1 = optical_coordinates[0]

            def dist_to_p1(p):
                return math.hypot(p1[0] - p[0], p1[1] - p[1])

            optical_dist = min(dist_to_p1(p) for p in optical_coordinates[1:])
            scale = electron_scale[0] / optical_dist

            # max_allowed_diff in pixels
            max_allowed_diff_px = max_allowed_diff / escan.pixelSize.value[0]

            # Match the electron to optical coordinates
            if future._find_overlay_state == CANCELLED:
                raise CancelledError()

            logging.debug("Matching coordinates...")
            known_ec, known_oc = coordinates.MatchCoordinates(
                optical_coordinates, electron_coordinates, scale,
                max_allowed_diff_px)
            if known_ec:
                break
            else:
                if trial < MAX_TRIALS_NUMBER - 1:
                    future._scanner.dwell_time = future._scanner.dwell_time * 1.2 + 0.1
                    logging.warning("Trying with dwell time = %g s...",
                                    future._scanner.dwell_time)
        else:
            # Make failure report
            _MakeReport(optical_image, repetitions, escan.magnification.value,
                        escan.pixelSize.value, dwell_time,
                        electron_coordinates)
            raise ValueError("Overlay failure")

        # Calculate transformation parameters
        if future._find_overlay_state == CANCELLED:
            raise CancelledError()

        # We are almost done... about 1 s left
        future.set_progress(end=time.time() + 1)

        logging.debug("Calculating transformation...")
        try:
            ret = transform.CalculateTransform(known_ec, known_oc, skew)
        except ValueError as exp:
            # Make failure report
            _MakeReport(optical_image, repetitions, escan.magnification.value,
                        escan.pixelSize.value, dwell_time,
                        electron_coordinates)
            raise ValueError("Overlay failure: %s" % (exp, ))

        if future._find_overlay_state == CANCELLED:
            raise CancelledError()
        logging.debug("Calculating transform metadata...")

        if skew is True:
            transform_d, skew_d = _transformMetadata(optical_image, ret, escan,
                                                     ccd, skew)
            transform_data = (transform_d, skew_d)
        else:
            transform_d = _transformMetadata(
                optical_image, ret, escan, ccd,
                skew)  # Also indicate which dwell time eventually worked
            transform_data = transform_d
        transform_d[model.MD_DWELL_TIME] = dwell_time

        logging.debug("Overlay done.")
        return ret, transform_data
    except CancelledError:
        pass
    except Exception as exp:
        logging.debug("Finding overlay failed", exc_info=1)
        raise exp
    finally:
        with future._overlay_lock:
            future._done.set()
            if future._find_overlay_state == CANCELLED:
                raise CancelledError()
            future._find_overlay_state = FINISHED