Exemple #1
0
def FindGridSpots(image, repetition):
    """
    Find the coordinates of a grid of spots in an image. And find the
    corresponding transformation to transform a grid centered around the origin
    to the spots in an image.

    Parameters
    ----------
    image : array like
        Data array containing the greyscale image.
    repetition : tuple of ints
        Number of expected spots in (X, Y).

    Returns
    -------
    spot_coordinates : array like
        A 2D array of shape (N, 2) containing the coordinates of the spots,
        in respect to the top left of the image.
    translation : tuple of two floats
        Translation from the origin to the center of the grid in image space,
        origin is top left of the image. Primary axis points right and the
        secondary axis points down.
    scaling : tuple of two floats
        Scaling factors for primary and secondary axis.
    rotation : float
        Rotation in image space, positive rotation is clockwise.
    shear : float
        Horizontal shear factor. A positive shear factor transforms a coordinate
        in the positive x direction parallel to the x axis.

    """
    # Find the center coordinates of the spots in the image.
    spot_positions = MaximaFind(image, repetition[0] * repetition[1])
    if len(spot_positions) < repetition[0] * repetition[1]:
        logging.warning(
            'Not enough spots found, returning only the found spots.')
        return spot_positions, None, None, None
    # Estimate the two most common (orthogonal) directions in the grid of spots, defined in the image coordinate system.
    lattice_constants = EstimateLatticeConstant(spot_positions)
    # Each row in the lattice_constants array corresponds to one direction. By transposing the array the direction
    # vectors are on the columns of the array. This allows us to directly use them as a transformation matrix.
    transformation_matrix = numpy.transpose(lattice_constants)

    # Translation is the mean of the spots, which is the distance from the origin to the center of the grid of spots.
    translation = numpy.mean(spot_positions, axis=0)
    transform_to_spot_positions = AffineTransform(matrix=transformation_matrix,
                                                  translation=translation)
    # Iterative closest point algorithm - single iteration, to fit a grid to the found spot positions
    grid = GridPoints(*repetition)
    spot_grid = transform_to_spot_positions(grid)
    tree = KDTree(spot_positions)
    dd, ii = tree.query(spot_grid, k=1)
    # Sort the original spot positions by mapping them to the order of the GridPoints.
    pos_sorted = spot_positions[ii.ravel(), :]
    # Find the transformation from a grid centered around the origin to the sorted positions.
    transformation = AffineTransform.from_pointset(grid, pos_sorted)
    spot_coordinates = transformation(grid)
    return spot_coordinates, translation, transformation.scale, transformation.rotation, transformation.shear
Exemple #2
0
 def test_affine_transform_apply_known_values(self):
     """
     AffineTransform should return known result with known input for affine
     transformations.
     """
     src = AFFINE_KNOWN_VALUES[0][-1]
     for rotation, scale, shear, translation, dst in AFFINE_KNOWN_VALUES:
         tform = AffineTransform(rotation=rotation, scale=scale,
                                 shear=shear, translation=translation)
         numpy.testing.assert_array_almost_equal(dst, tform.apply(src))
Exemple #3
0
    def test_transform_equal_rotation(self):
        """
        When estimating the transform using `from_pointset()` where the source
        coordinates have zero moments, the estimated rotation should be equal
        for transforms of class `AffineTransform`, `SimilarityTransform`, and
        `RigidTransform`. Note: this list excludes `ScalingTransform`.

        """
        params = {
            "scale": 1 + 0.5 * (2 * numpy.random.random_sample() - 1),
            "rotation": numpy.pi * (2 * numpy.random.random_sample() - 1),
            "squeeze": 1 + 0.2 * (2 * numpy.random.random_sample() - 1),
            "shear": 0.2 * (2 * numpy.random.random_sample() - 1),
        }
        src = GridPoints(8, 8)
        noise = 0.1 * (2 * numpy.random.random_sample(src.shape) - 1)
        dst = AffineTransform(**params).apply(src + noise)
        rotation = None
        for transform_type in (AffineTransform, SimilarityTransform,
                               RigidTransform):
            tform = transform_type.from_pointset(src, dst)
            if rotation is None:
                rotation = tform.rotation
            else:
                self.assertAlmostEqual(rotation, tform.rotation)
Exemple #4
0
    def test_from_pointset_optimal(self):
        """
        The transform returned by `GeometricTransform.from_pointset()` should
        minimize the fiducial registration error (FRE). Here we test that a
        pertubation of any of the non-constrained parameters results in an
        increased FRE.

        """
        params = {
            "scale": 1 + 0.5 * (2 * numpy.random.random_sample() - 1),
            "rotation": numpy.pi * (2 * numpy.random.random_sample() - 1),
            "squeeze": 1 + 0.2 * (2 * numpy.random.random_sample() - 1),
            "shear": 0.2 * (2 * numpy.random.random_sample() - 1),
        }
        src = GridPoints(8, 8)
        noise = 0.1 * (2 * numpy.random.random_sample(src.shape) - 1)
        dst = AffineTransform(**params).apply(src + noise)
        tform0 = self.transform_type.from_pointset(src, dst)
        fre0 = tform0.fre(src, dst)
        for param in ("scale", "rotation", "squeeze", "shear"):
            if getattr(self.transform_type, param).constrained:
                continue
            val = getattr(tform0, param)
            tform = copy.copy(tform0)
            for op in (operator.add, operator.sub):
                setattr(tform, param, op(val, 1.0e-6))
                fre = tform.fre(src, dst)
                self.assertGreater(fre, fre0)
Exemple #5
0
    def test_angle_difference(self):
        """
        When provided with an input transformation with positive determinant
        (i.e. no reflection), the resulting change in rotation should be a
        multiple of 90°.

        """
        for i in range(50):
            matrix = self._rng.standard_normal((2, 2))
            if numpy.linalg.det(matrix) < 0:
                matrix = numpy.fliplr(matrix)
            with self.subTest(matrix=matrix, i=i):
                tform1 = AffineTransform(matrix)
                tform2 = AffineTransform(_canonical_matrix_form(matrix))
                delta = tform2.rotation - tform1.rotation
                n = int(round(delta / (0.5 * numpy.pi)))
                self.assertAlmostEqual(delta - n * 0.5 * numpy.pi, 0)
Exemple #6
0
 def test_affine_transform_matrix_known_values(self):
     for rotation, scale, shear, _, _ in AFFINE_KNOWN_VALUES:
         tform = AffineTransform(rotation=rotation, scale=scale,
                                 shear=shear)
         R = _rotation_matrix_from_angle(rotation)
         S = scale * numpy.eye(2)
         L = numpy.array([(1., shear), (0., 1.)])
         matrix = numpy.dot(numpy.dot(R, S), L)
         numpy.testing.assert_array_almost_equal(matrix, tform.transformation_matrix)
Exemple #7
0
def FindGridSpots(image, repetition):
    """
    Find a grid of spots in an image.

    Parameters
    ----------
    image : array like
        Data array containing the greyscale image.
    repetition : tuple of ints
        Number of expected spots in (X, Y).

    Returns
    -------
    spot_coordinates : array like
        A 2D array of shape (N, 2) containing the coordinates of the spots.
    translation : tuple of two floats
    scaling : tuple of two floats
    rotation : float
    shear : float

    """
    spot_positions = MaximaFind(image, repetition[0] * repetition[1])
    if len(spot_positions) < repetition[0] * repetition[1]:
        logging.warning(
            'Not enough spots found, returning only the found spots.')
        return spot_positions, None, None, None
    # Estimate transformation
    lattice_constants = EstimateLatticeConstant(spot_positions)
    transformation_matrix = numpy.transpose(lattice_constants)
    if numpy.linalg.det(lattice_constants) < 0.:
        transformation_matrix = numpy.fliplr(transformation_matrix)
    translation = numpy.mean(spot_positions, axis=0)
    transform_to_spot_positions = AffineTransform(matrix=transformation_matrix,
                                                  translation=translation)
    # Iterative closest point algorithm - single iteration, to fit a grid to the found spot positions
    grid = GridPoints(*repetition)
    spot_grid = transform_to_spot_positions(grid)
    tree = KDTree(spot_positions)
    dd, ii = tree.query(spot_grid, k=1)

    pos_sorted = spot_positions[ii.ravel(), :]
    transformation = AffineTransform.from_pointset(grid, pos_sorted)
    spot_coordinates = transformation(grid)
    return spot_coordinates, translation, transformation.scale, transformation.rotation, transformation.shear
Exemple #8
0
 def test_affine_transform_from_pointset_non_negative_scaling(self):
     """
     AffineTransform.from_pointset should not return a negative value for
     the scaling when there is a reflection in the point set for affine
     transformations.
     """
     src = numpy.array([(0., 0.), (1., 0.), (0., 2.)])
     dst = numpy.array([(0., 0.), (-1., 0.), (0., 2.)])
     tform = AffineTransform.from_pointset(src, dst)
     self.assertTrue(numpy.all(tform.scale > 0.))
Exemple #9
0
 def test_affine_transform_from_pointset_known_values(self):
     """
     AffineTransform.from_pointset should return known result with known
     input for affine transformations.
     """
     src = AFFINE_KNOWN_VALUES[0][-1]
     for rotation, scale, shear, translation, dst in AFFINE_KNOWN_VALUES:
         tform = AffineTransform.from_pointset(src, dst)
         self.assertAlmostEqual(0., _angle_diff(rotation, tform.rotation))
         numpy.testing.assert_array_almost_equal(scale, tform.scale)
         self.assertAlmostEqual(shear, tform.shear)
         numpy.testing.assert_array_almost_equal(translation, tform.translation)
Exemple #10
0
 def test_anamorphosis_transform_comparison_known_values(self):
     """
     The transform returned by AnamorphosisTransform.from_pointset should be
     an affine transform if the input coordinates contain no higher order
     aberrations.
     """
     src = GridPoints(8, 8)
     rotationList = [0., ROT45, ROT90, ROT135, ROT180, -ROT135, -ROT90, -ROT45]
     scaleList = [(1, 1), (SQ05, SQ05), (SQ2, SQ2), S23, SQ2S23]
     shearList = [-1., 0., 1.]
     translationList = [T0, TX, TY, TXY]
     iterator = itertools.product(rotationList, scaleList, shearList, translationList)
     for rotation, scale, shear, translation in iterator:
         affine = AffineTransform(rotation=rotation, scale=scale,
                                  shear=shear, translation=translation)
         dst = affine.apply(src)
         tform = AnamorphosisTransform.from_pointset(src, dst)
         self.assertAlmostEqual(0., _angle_diff(rotation, tform.rotation))
         numpy.testing.assert_array_almost_equal(scale, tform.scale)
         self.assertAlmostEqual(shear, tform.shear)
         numpy.testing.assert_array_almost_equal(translation, tform.translation)
         numpy.testing.assert_array_almost_equal(0., tform.coeffs[3:])
Exemple #11
0
    def test_random_input(self):
        """
        `_canonical_matrix_form()` should accept any 2x2 transformation matrix.
        The resulting matrix should not contain a reflection (have a positive
        determinant) and have a rotation between ±45°.

        """
        for i in range(50):
            matrix = self._rng.standard_normal((2, 2))
            with self.subTest(matrix=matrix, i=i):
                matrix = _canonical_matrix_form(matrix)
                tform = AffineTransform(matrix)
                self.assertGreater(numpy.linalg.det(matrix), 0)
                self.assertGreater(tform.rotation, -0.25 * numpy.pi)
                self.assertLess(tform.rotation, 0.25 * numpy.pi)
Exemple #12
0
def _initial_estimate_grid_orientation(xy: numpy.ndarray) -> AffineTransform:
    """
    Provide an initial estimate for the orientation of a square grid of points.

    Parameters
    ----------
    xy : ndarray of shape (n, 2)
        Array with the determined coordinates of the grid points.

    Returns
    -------
    tform : AffineTransform

    """
    graph = nearest_neighbor_graph(xy)

    # Create an array `dxy` containing the displacement vectors for all edges
    # in the graph.
    a, b = zip(*graph.iter_edges(False))
    dxy = xy[b, ...] - xy[a, ...]

    # The array `dxy` typically contains the lattice vectors for the 'left',
    # 'right', 'up', and 'down' directions, but it is not guaranteed that it
    # will contain all of them, nor that there are equal amounts of edges in
    # each direction. The solution is to not make any distinction between
    # 'left' and 'right', or 'up' and 'down'. This is done using a mapping of
    # `(ρ, θ)` to `(ρ, 2*θ)`. The clustering itself is done in Cartesian
    # coordinates to not be impacted by the branch cut at `θ = ±π`.
    rho, theta = cartesian_to_polar(dxy)
    dpq = polar_to_cartesian(rho, 2 * theta)
    # minit="++" is only available in SciPy v1.2.0 and above
    rng = check_random_state(None)
    code_book = _kpp(dpq, 2, rng)
    centroid, _ = scipy.cluster.vq.kmeans2(dpq, code_book, minit="matrix")
    rho, theta = cartesian_to_polar(centroid)
    dxy = polar_to_cartesian(rho, 0.5 * theta)

    # Each rows of the array `dxy` now contains a lattice vector (direction).
    # By transposing the array, these lattice vectors are the columns of the
    # new matrix. This allows to directly use them as a transformation matrix.
    # For example: let `x₁` and `x₂` be the two 2-by-1 lattice vectors, then
    # `(x₁, x₂) * (n, m)ᵀ = n * x₁ + m * x₂`.
    matrix = _canonical_matrix_form(numpy.transpose(dxy))

    # The estimated translated is the centroid of the input coordinates.
    translation = numpy.mean(xy, axis=0)

    return AffineTransform(matrix, translation)
Exemple #13
0
def random_affine_transform(seed=None):
    """
    Return a randomized affine transform.

    Parameters
    ----------
    rng : numpy.random.Generator, optional
        Random number generator

    Returns
    -------
    tform : AffineTransform
        Randomized affine transform.

    """
    rng = check_random_state(seed)
    params = {
        "scale": 1 + 0.5 * rng.uniform(-1, 1),
        "rotation": 0.25 * numpy.pi * rng.uniform(-1, 1),
        "squeeze": 1 + 0.1 * rng.uniform(-1, 1),
        "shear": 0.1 * rng.uniform(-1, 1),
        "translation": rng.uniform(-1, 1, (2, )),
    }
    return AffineTransform(**params)
Exemple #14
0
def FindGridSpots(image, repetition, spot_size=18, method=GRID_AFFINE):
    """
    Find the coordinates of a grid of spots in an image. And find the
    corresponding transformation to transform a grid centered around the origin
    to the spots in an image.

    Parameters
    ----------
    image : array like
        Data array containing the greyscale image.
    repetition : tuple of ints
        Number of expected spots in (X, Y). Where the total number of expected spots must be at least 6.
    spot_size : int
        A length in pixels somewhat larger than a typical spot.
    method : GRID_AFFINE or GRID_SIMILARITY
        The transformation method used to get the returned grid of spots.
        If the similarity method is used the returned grid has 90 degree angles with equal scaling in x and y.
        It the affine method is used the returned grid contains a shear component, therefore the angles in the grid
        do not have to be 90 degrees. The grid can also have different scaling in x and y.

    Returns
    -------
    spot_coordinates : array like
        A 2D array of shape (N, 2) containing the coordinates of the spots,
        in respect to the top left of the image.
    translation : tuple of two floats
        Translation from the origin to the center of the grid in image space,
        origin is top left of the image. Primary axis points right and the
        secondary axis points down.
    scaling : tuple of two floats or float
        Scaling factors for primary and secondary axis when the affine method is used.
        Single scaling factor when the similarity method is used.
    rotation : float
        Rotation in image space, positive rotation is clockwise.
    shear : float
        Horizontal shear factor. A positive shear factor transforms a coordinate
        in the positive x direction parallel to the x axis. The shear is None
        when similarity method is used.

    """
    if repetition[0] * repetition[1] < 6:
        raise ValueError(
            "Need at least 6 expected points to properly find the grid.")
    # Find the center coordinates of the spots in the image.
    spot_positions = MaximaFind(image,
                                repetition[0] * repetition[1],
                                len_object=spot_size)
    if len(spot_positions) < repetition[0] * repetition[1]:
        logging.warning(
            'Not enough spots found, returning only the found spots.')
        return spot_positions, None, None, None, None
    # Estimate the two most common (orthogonal) directions in the grid of spots, defined in the image coordinate system.
    lattice_constants = EstimateLatticeConstant(spot_positions)
    # Each row in the lattice_constants array corresponds to one direction. By transposing the array the direction
    # vectors are on the columns of the array. This allows us to directly use them as a transformation matrix.
    transformation_matrix = numpy.transpose(lattice_constants)

    # Translation is the mean of the spots, which is the distance from the origin to the center of the grid of spots.
    translation = numpy.mean(spot_positions, axis=0)
    transform_to_spot_positions = AffineTransform(transformation_matrix,
                                                  translation)
    # Iterative closest point algorithm - single iteration, to fit a grid to the found spot positions
    grid = GridPoints(*repetition)
    spot_grid = transform_to_spot_positions.apply(grid)
    tree = KDTree(spot_positions)
    dd, ii = tree.query(spot_grid, k=1)
    # Sort the original spot positions by mapping them to the order of the GridPoints.
    pos_sorted = spot_positions[ii.ravel(), :]
    # Find the transformation from a grid centered around the origin to the sorted positions.
    if method == GRID_AFFINE:
        transformation = AffineTransform.from_pointset(grid, pos_sorted)
        scale, rotation, shear = alt_transformation_matrix_to_implicit(
            transformation.matrix, "RSU")
    elif method == GRID_SIMILARITY:
        transformation = SimilarityTransform.from_pointset(grid, pos_sorted)
        scale, rotation, _ = alt_transformation_matrix_to_implicit(
            transformation.matrix, "RSU")
        shear = None  # The similarity transform does not have a shear component.
    else:
        raise ValueError(
            "Method: %s is unknown, should be 'affine' or 'similarity'." %
            method)
    spot_coordinates = transformation.apply(grid)
    return spot_coordinates, translation, scale, rotation, shear