Example #1
0
def test_resize_array_adj(resize_setup, odl_floating_dtype):
    dtype = odl_floating_dtype
    pad_mode, pad_const, newshp, offset, array, _ = resize_setup

    if pad_const != 0:
        # Not well-defined
        return

    array = np.array(array, dtype=dtype)
    if is_real_dtype(dtype):
        other_arr = np.random.uniform(-10, 10, size=newshp)
    else:
        other_arr = (np.random.uniform(-10, 10, size=newshp) +
                     1j * np.random.uniform(-10, 10, size=newshp))

    resized = resize_array(array,
                           newshp,
                           offset,
                           pad_mode,
                           pad_const,
                           direction='forward')
    resized_adj = resize_array(other_arr,
                               array.shape,
                               offset,
                               pad_mode,
                               pad_const,
                               direction='adjoint')

    assert (np.vdot(resized.ravel(),
                    other_arr.ravel()) == pytest.approx(np.vdot(
                        array.ravel(), resized_adj.ravel()),
                                                        rel=dtype_tol(dtype)))
Example #2
0
 def _call(self, x, out):
     """Implement ``self(x, out)``."""
     # TODO: simplify once context manager is available
     out[:] = resize_array(
         x.asarray(), self.range.shape, offset=self.offset,
         pad_mode=self.pad_mode, pad_const=0, direction='adjoint',
         out=out.asarray())
Example #3
0
def test_resize_array_corner_cases(odl_scalar_dtype, padding):
    # Test extreme cases of resizing that are still valid for several
    # `pad_mode`s
    dtype = odl_scalar_dtype
    pad_mode, pad_const = padding

    # Test array
    arr = np.arange(12, dtype=dtype).reshape((3, 4))

    # Resize to and from 0 total size
    squashed_arr = resize_array(arr, (3, 0), pad_mode=pad_mode)
    assert squashed_arr.size == 0

    squashed_arr = resize_array(arr, (0, 0), pad_mode=pad_mode)
    assert squashed_arr.size == 0

    if pad_mode == 'constant':
        # Blowing up from size 0 only works with constant padding
        true_blownup_arr = np.empty_like(arr)
        true_blownup_arr.fill(pad_const)

        blownup_arr = resize_array(np.ones((3, 0), dtype=dtype), (3, 4),
                                   pad_mode=pad_mode,
                                   pad_const=pad_const)
        assert np.array_equal(blownup_arr, true_blownup_arr)

        blownup_arr = resize_array(np.ones((0, 0), dtype=dtype), (3, 4),
                                   pad_mode=pad_mode,
                                   pad_const=pad_const)
        assert np.array_equal(blownup_arr, true_blownup_arr)

    # Resize from 0 axes to 0 axes
    zero_axes_arr = resize_array(np.array(0, dtype=dtype), (),
                                 pad_mode=pad_mode)
    assert zero_axes_arr == np.array(0, dtype=dtype)

    if pad_mode == 'periodic':
        # Resize with periodic padding, using all values from the original
        # array on both sides
        max_per_shape = (9, 12)
        res_arr = resize_array(arr,
                               max_per_shape,
                               pad_mode='periodic',
                               offset=arr.shape)
        assert np.array_equal(res_arr, np.tile(arr, (3, 3)))

    elif pad_mode == 'symmetric':
        # Symmetric padding, maximum number is one less compared to periodic
        # padding since the boundary value is not repeated
        arr = np.arange(6).reshape((2, 3))
        max_sym_shape = (4, 7)
        res_arr = resize_array(arr,
                               max_sym_shape,
                               pad_mode='symmetric',
                               offset=[1, 2])
        true_arr = np.array([[5, 4, 3, 4, 5, 4, 3], [2, 1, 0, 1, 2, 1, 0],
                             [5, 4, 3, 4, 5, 4, 3], [2, 1, 0, 1, 2, 1, 0]])
        assert np.array_equal(res_arr, true_arr)
Example #4
0
def test_resize_array_fwd(resize_setup, odl_scalar_dtype):
    dtype = odl_scalar_dtype
    pad_mode, pad_const, newshp, offset, array_in, true_out = resize_setup
    array_in = np.array(array_in, dtype=dtype)
    true_out = np.array(true_out, dtype=dtype)

    resized = resize_array(array_in,
                           newshp,
                           offset,
                           pad_mode,
                           pad_const,
                           direction='forward')
    out = np.empty(newshp, dtype=dtype)
    resize_array(array_in,
                 newshp,
                 offset,
                 pad_mode,
                 pad_const,
                 direction='forward',
                 out=out)

    assert np.array_equal(resized, true_out)
    assert np.array_equal(out, true_out)
Example #5
0
def ellipsoid_phantom(space, ellipsoids, min_pt=None, max_pt=None):
    """Return a phantom given by ellipsoids.

    Parameters
    ----------
    space : `DiscreteLp`
        Space in which the phantom should be created, must be 2- or
        3-dimensional. If ``space.shape`` is 1 in an axis, a corresponding
        slice of the phantom is created (instead of squashing the whole
        phantom into the slice).
    ellipsoids : sequence of sequences
        If ``space`` is 2-dimensional, each row should contain the entries ::

            'value',
            'axis_1', 'axis_2',
            'center_x', 'center_y',
            'rotation'

        If ``space`` is 3-dimensional, each row should contain the entries ::

            'value',
            'axis_1', 'axis_2', 'axis_3',
            'center_x', 'center_y', 'center_z',
            'rotation_phi', 'rotation_theta', 'rotation_psi'

        The provided ellipsoids need to be specified relative to the
        reference rectangle ``[-1, -1] x [1, 1]``, or analogously in 3d.
        The angles are to be given in radians.

    min_pt, max_pt : array-like, optional
        If provided, use these vectors to determine the bounding box of the
        phantom instead of ``space.min_pt`` and ``space.max_pt``.
        It is currently required that ``min_pt >= space.min_pt`` and
        ``max_pt <= space.max_pt``, i.e., shifting or scaling outside the
        original space is not allowed.

        Providing one of them results in a shift, e.g., for ``min_pt``::

            new_min_pt = min_pt
            new_max_pt = space.max_pt + (min_pt - space.min_pt)

        Providing both results in a scaled version of the phantom.

    Notes
    -----
    The phantom is created by adding the values of each ellipse. The
    ellipses are defined by a center point
    ``(center_x, center_y, [center_z])``, the lengths of its principial
    axes ``(axis_1, axis_2, [axis_2])``, and a rotation angle ``rotation``
    in 2D or Euler angles ``(rotation_phi, rotation_theta, rotation_psi)``
    in 3D.

    This function is heavily optimized, achieving runtimes about 20 times
    faster than "trivial" implementations. It is therefore recommended to use
    it in all phantoms where applicable.

    The main optimization is that it only considers a subset of all the
    points when updating for each ellipse. It does this by first finding
    a subset of points that could possibly be inside the ellipse. This
    optimization is very good for "spherical" ellipsoids, but not so
    much for elongated or rotated ones.

    It also does calculations wherever possible on the meshgrid instead of
    individual points.

    Examples
    --------
    Create a circle with a smaller circle inside:

    >>> space = odl.uniform_discr([-1, -1], [1, 1], [5, 5])
    >>> ellipses = [[1.0, 1.0, 1.0, 0.0, 0.0, 0.0],
    ...             [1.0, 0.6, 0.6, 0.0, 0.0, 0.0]]
    >>> print(ellipsoid_phantom(space, ellipses))
    [[ 0.,  0.,  1.,  0.,  0.],
     [ 0.,  1.,  2.,  1.,  0.],
     [ 1.,  2.,  2.,  2.,  1.],
     [ 0.,  1.,  2.,  1.,  0.],
     [ 0.,  0.,  1.,  0.,  0.]]

    See Also
    --------
    odl.phantom.transmission.shepp_logan : Classical Shepp-Logan phantom,
        typically used for transmission imaging
    odl.phantom.transmission.shepp_logan_ellipses : Ellipses for the
        Shepp-Logan phantom
    odl.phantom.geometric.defrise_ellipses : Ellipses for the
        Defrise phantom
    """
    if space.ndim == 2:
        _phantom = _ellipse_phantom_2d
    elif space.ndim == 3:
        _phantom = _ellipsoid_phantom_3d
    else:
        raise ValueError('dimension not 2 or 3, no phantom available')

    if min_pt is None and max_pt is None:
        return _phantom(space, ellipsoids)

    else:
        # Generate a temporary space with given `min_pt` and `max_pt`
        # (snapped to the cell grid), create the phantom in that space and
        # resize to the target size for `space`.
        # The snapped points are constructed by finding the index of
        # `min/max_pt` in the space partition, indexing the partition with
        # that index, yielding a single-cell partition, and then taking
        # the lower-left/upper-right corner of that cell.
        if min_pt is None:
            snapped_min_pt = space.min_pt
        else:
            min_pt_cell = space.partition[space.partition.index(min_pt)]
            snapped_min_pt = min_pt_cell.min_pt

        if max_pt is None:
            snapped_max_pt = space.max_pt
        else:
            max_pt_cell = space.partition[space.partition.index(max_pt)]
            snapped_max_pt = max_pt_cell.max_pt
            # Avoid snapping to the next cell where max_pt falls exactly on
            # a boundary
            for i in range(space.ndim):
                if max_pt[i] in space.partition.cell_boundary_vecs[i]:
                    snapped_max_pt[i] = max_pt[i]

        tmp_space = uniform_discr_fromdiscr(space,
                                            min_pt=snapped_min_pt,
                                            max_pt=snapped_max_pt,
                                            cell_sides=space.cell_sides)

        tmp_phantom = _phantom(tmp_space, ellipsoids)
        offset = space.partition.index(tmp_space.min_pt)
        return space.element(resize_array(tmp_phantom, space.shape, offset))
Example #6
0
def test_resize_array_raise():

    arr_1d = np.arange(6)
    arr_2d = np.arange(6).reshape((2, 3))

    # Shape not a sequence
    with pytest.raises(TypeError):
        resize_array(arr_1d, 19)

    # out given, but not an ndarray
    with pytest.raises(TypeError):
        resize_array(arr_1d, (10, ), out=[])

    # out has wrong shape
    with pytest.raises(ValueError):
        out = np.empty((4, 5))
        resize_array(arr_2d, (5, 5), out=out)

    # Input and output arrays differ in dimensionality
    with pytest.raises(ValueError):
        out = np.empty((4, 5))
        resize_array(arr_1d, (4, 5))
    with pytest.raises(ValueError):
        out = np.empty((4, 5))
        resize_array(arr_1d, (4, 5), out=out)

    # invalid pad mode
    with pytest.raises(ValueError):
        resize_array(arr_1d, (10, ), pad_mode='madeup_mode')

    # padding constant cannot be cast to output data type
    with pytest.raises(ValueError):
        resize_array(arr_1d, (10, ), pad_const=1.0)  # arr_1d has dtype int
    with pytest.raises(ValueError):
        arr_1d_float = arr_1d.astype(float)
        resize_array(arr_1d_float, (10, ), pad_const=1.0j)

    # Too few entries for order 0 or 1 padding modes
    empty_arr = np.ones((3, 0))
    with pytest.raises(ValueError):
        resize_array(empty_arr, (3, 1), pad_mode='order0')

    small_arr = np.ones((3, 1))
    with pytest.raises(ValueError):
        resize_array(small_arr, (3, 3), pad_mode='order1')

    # Too large padding sizes for symmetric
    small_arr = np.ones((3, 1))
    with pytest.raises(ValueError):
        resize_array(small_arr, (3, 2), pad_mode='symmetric')
    with pytest.raises(ValueError):
        resize_array(small_arr, (6, 1), pad_mode='symmetric')
    with pytest.raises(ValueError):
        resize_array(small_arr, (4, 3), offset=(0, 1), pad_mode='symmetric')

    # Too large padding sizes for periodic
    small_arr = np.ones((3, 1))
    with pytest.raises(ValueError):
        resize_array(small_arr, (3, 3), pad_mode='periodic')
    with pytest.raises(ValueError):
        resize_array(small_arr, (7, 1), pad_mode='periodic')
    with pytest.raises(ValueError):
        resize_array(small_arr, (3, 4), offset=(0, 1), pad_mode='periodic')