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)))
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())
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)
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)
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))
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')