Ejemplo n.º 1
0
def lotus_root_geometry():
    """Tomographic geometry for the lotus root dataset.

    Notes
    -----
    See the article `Tomographic X-ray data of a lotus root filled with
    attenuating objects`_ for further information.

    See Also
    --------
    lotus_root_geometry

    References
    ----------
    .. _Tomographic X-ray data of a lotus root filled with attenuating objects:
       https://arxiv.org/abs/1609.07299
    """
    # To get the same rotation as in the reference article
    a_offset = np.pi / 2
    apart = uniform_partition(a_offset,
                              a_offset + 2 * np.pi * 366. / 360.,
                              366)

    # TODO: Find exact value, determined experimentally
    d_offset = 0.35
    dpart = uniform_partition(d_offset - 60, d_offset + 60, 2240)

    geometry = FanFlatGeometry(apart, dpart,
                               src_radius=540, det_radius=90)

    return geometry
Ejemplo n.º 2
0
def walnut_geometry():
    """Tomographic geometry for the walnut dataset.

    Notes
    -----
    See the article `Tomographic X-ray data of a walnut`_ for further
    information.

    See Also
    --------
    walnut_data

    References
    ----------
    .. _Tomographic X-ray data of a walnut: https://arxiv.org/abs/1502.04064
    """
    # To get the same rotation as in the reference article
    a_offset = -np.pi / 2
    apart = uniform_partition(a_offset, a_offset + 2 * np.pi, 1200)

    # TODO: Find exact value, determined experimentally
    d_offset = -0.279
    dpart = uniform_partition(d_offset - 57.4, d_offset + 57.4, 2296)

    geometry = FanFlatGeometry(apart, dpart,
                               src_radius=110, det_radius=190)

    return geometry
def limited_beam_geometry(space,
                          num_angles=None,
                          max_degree=None,
                          det_shape=None):
    # Find maximum distance from rotation axis
    corners = space.domain.corners()[:, :2]  # corners come from the imagespace
    rho = np.max(np.linalg.norm(corners, axis=1))

    # Find default values according to Nyquist criterion.

    # We assume that the function is bandlimited by a wave along the x or y
    # axis. The highest frequency we can measure is then a standing wave with
    # period of twice the inter-node distance.
    min_side = min(space.partition.cell_sides[:2])
    omega = np.pi / min_side
    num_px_horiz = 2 * int(np.ceil(rho * omega / np.pi)) + 1

    if space.ndim == 2:
        det_min_pt = -rho
        det_max_pt = rho
        if det_shape is None:
            det_shape = num_px_horiz
    elif space.ndim == 3:
        num_px_vert = space.shape[2]
        min_h = space.domain.min_pt[2]
        max_h = space.domain.max_pt[2]
        det_min_pt = [-rho, min_h]
        det_max_pt = [rho, max_h]
        if det_shape is None:
            det_shape = [num_px_horiz, num_px_vert]

    if num_angles is None:
        num_angles = int(np.ceil(omega * rho))

    max_radian = max_degree * (np.pi / 180)

    angle_partition = uniform_partition(0, max_radian, num_angles)
    det_partition = uniform_partition(det_min_pt, det_max_pt, det_shape)

    if space.ndim == 2:
        return Parallel2dGeometry(angle_partition, det_partition)
    elif space.ndim == 3:
        return Parallel3dAxisGeometry(angle_partition, det_partition)
    else:
        raise ValueError('``space.ndim`` must be 2 or 3.')
Ejemplo n.º 4
0
def skimage_proj_space(geometry, volume_space, proj_space):
    """Create a projection space adapted to the skimage radon geometry."""
    padded_size = int(np.ceil(volume_space.shape[0] * np.sqrt(2)))
    det_width = volume_space.domain.extent[0] * np.sqrt(2)
    det_part = uniform_partition(-det_width / 2, det_width / 2, padded_size)

    part = geometry.motion_partition.insert(1, det_part)
    space = uniform_discr_frompartition(part, dtype=proj_space.dtype)
    return space
Ejemplo n.º 5
0
def scikit_sinogram_space(geometry, volume_space, sinogram_space):
    """Create a range adapted to the scikit radon geometry."""

    padded_size = int(np.ceil(volume_space.shape[0] * np.sqrt(2)))
    det_width = volume_space.domain.extent()[0] * np.sqrt(2)
    scikit_detector_part = uniform_partition(-det_width / 2.0, det_width / 2.0,
                                             padded_size)

    scikit_range_part = geometry.motion_partition.insert(
        1, scikit_detector_part)

    scikit_range = uniform_discr_frompartition(scikit_range_part,
                                               interp=sinogram_space.interp,
                                               dtype=sinogram_space.dtype)

    return scikit_range
Ejemplo n.º 6
0
def scikit_sinogram_space(geometry, volume_space, sinogram_space):
    """Create a range adapted to the scikit radon geometry."""

    padded_size = int(np.ceil(volume_space.shape[0] * np.sqrt(2)))
    det_width = volume_space.domain.extent()[0] * np.sqrt(2)
    scikit_detector_part = uniform_partition(-det_width / 2.0,
                                             det_width / 2.0,
                                             padded_size)

    scikit_range_part = geometry.motion_partition.insert(1,
                                                         scikit_detector_part)

    scikit_range = uniform_discr_frompartition(scikit_range_part,
                                               interp=sinogram_space.interp,
                                               dtype=sinogram_space.dtype)

    return scikit_range
def angle_beam_geometry(
    space,
    angle_array,
    det_shape=None
):  #this function allows to specify a new geometry by providing the projection angles
    # Find maximum distance from rotation axis
    corners = space.domain.corners()[:, :2]
    rho = np.max(np.linalg.norm(corners, axis=1))

    # Find default values according to Nyquist criterion.

    # We assume that the function is bandlimited by a wave along the x or y
    # axis. The highest frequency we can measure is then a standing wave with
    # period of twice the inter-node distance.
    min_side = min(space.partition.cell_sides[:2])
    omega = np.pi / min_side
    num_px_horiz = 2 * int(np.ceil(rho * omega / np.pi)) + 1

    if space.ndim == 2:
        det_min_pt = -rho
        det_max_pt = rho
        if det_shape is None:
            det_shape = num_px_horiz
    elif space.ndim == 3:
        num_px_vert = space.shape[2]
        min_h = space.domain.min_pt[2]
        max_h = space.domain.max_pt[2]
        det_min_pt = [-rho, min_h]
        det_max_pt = [rho, max_h]
        if det_shape is None:
            det_shape = [num_px_horiz, num_px_vert]

    angle_partition = nonuniform_partition(angle_array, min_pt=0, max_pt=np.pi)
    det_partition = uniform_partition(det_min_pt, det_max_pt, det_shape)

    if space.ndim == 2:
        return Parallel2dGeometry(angle_partition, det_partition)
    elif space.ndim == 3:
        return Parallel3dAxisGeometry(angle_partition, det_partition)
    else:
        raise ValueError('``space.ndim`` must be 2 or 3.')
Ejemplo n.º 8
0
def _resize_discr(discr, newshp, offset, discr_kwargs):
    """Return a space based on ``discr`` and ``newshp``.

    Use the domain of ``discr`` and its partition to create a new
    uniformly discretized space with ``newshp`` as shape. In axes where
    ``offset`` is given, it determines the number of added/removed cells to
    the left. Where ``offset`` is ``None``, the points are distributed
    evenly to left and right. The ``discr_kwargs`` parameter is passed
    to `uniform_discr` for further specification of discretization
    parameters.
    """
    nodes_on_bdry = discr_kwargs.get('nodes_on_bdry', False)
    if np.shape(nodes_on_bdry) == ():
        nodes_on_bdry = ([(bool(nodes_on_bdry), bool(nodes_on_bdry))] *
                         discr.ndim)
    elif discr.ndim == 1 and len(nodes_on_bdry) == 2:
        nodes_on_bdry = [nodes_on_bdry]
    elif len(nodes_on_bdry) != discr.ndim:
        raise ValueError('`nodes_on_bdry` has length {}, expected {}'
                         ''.format(len(nodes_on_bdry), discr.ndim))

    dtype = discr_kwargs.pop('dtype', discr.dtype)
    impl = discr_kwargs.pop('impl', discr.impl)
    exponent = discr_kwargs.pop('exponent', discr.exponent)
    interp = discr_kwargs.pop('interp', discr.interp)
    weighting = discr_kwargs.pop('weighting', discr.weighting)

    affected = np.not_equal(newshp, discr.shape)
    ndim = discr.ndim
    for i in range(ndim):
        if affected[i] and not discr.is_uniform_byaxis[i]:
            raise ValueError('cannot resize in non-uniformly discretized '
                             'axis {}'.format(i))

    grid_min, grid_max = discr.grid.min(), discr.grid.max()
    cell_size = discr.cell_sides
    new_minpt, new_maxpt = [], []

    for axis, (n_orig, n_new, off, on_bdry) in enumerate(
            zip(discr.shape, newshp, offset, nodes_on_bdry)):

        if not affected[axis]:
            new_minpt.append(discr.min_pt[axis])
            new_maxpt.append(discr.max_pt[axis])
            continue

        n_diff = n_new - n_orig
        if off is None:
            num_r = n_diff // 2
            num_l = n_diff - num_r
        else:
            num_r = n_diff - off
            num_l = off

        try:
            on_bdry_l, on_bdry_r = on_bdry
        except TypeError:
            on_bdry_l = on_bdry
            on_bdry_r = on_bdry

        if on_bdry_l:
            new_minpt.append(grid_min[axis] - num_l * cell_size[axis])
        else:
            new_minpt.append(grid_min[axis] - (num_l + 0.5) * cell_size[axis])

        if on_bdry_r:
            new_maxpt.append(grid_max[axis] + num_r * cell_size[axis])
        else:
            new_maxpt.append(grid_max[axis] + (num_r + 0.5) * cell_size[axis])

    fspace = FunctionSpace(IntervalProd(new_minpt, new_maxpt), out_dtype=dtype)
    tspace = tensor_space(newshp,
                          dtype=dtype,
                          impl=impl,
                          exponent=exponent,
                          weighting=weighting)

    # Stack together the (unchanged) nonuniform axes and the (new) uniform
    # axes in the right order
    part = uniform_partition([], [], ())
    for i in range(ndim):
        if discr.is_uniform_byaxis[i]:
            part = part.append(
                uniform_partition(new_minpt[i],
                                  new_maxpt[i],
                                  newshp[i],
                                  nodes_on_bdry=nodes_on_bdry[i]))
        else:
            part = part.append(discr.partition.byaxis[i])

    return DiscreteLp(fspace, part, tspace, interp=interp)
Ejemplo n.º 9
0
def parallel_beam_geometry(space, num_angles=None, det_shape=None):
    """Create default parallel beam geometry from ``space``.

    This is intended for simple test cases where users do not need the full
    flexibility of the geometries, but simply want a geometry that works.

    This default geometry gives a fully sampled sinogram according to the
    Nyquist criterion, which in general results in a very large number of
    samples. In particular, a ``space`` that is not centered at the origin
    can result in very large detectors.

    Parameters
    ----------
    space : `DiscreteLp`
        Reconstruction space, the space of the volumetric data to be projected.
        Needs to be 2d or 3d.
    num_angles : int, optional
        Number of angles.
        Default: Enough to fully sample the data, see Notes.
    det_shape : int or sequence of int, optional
        Number of detector pixels.
        Default: Enough to fully sample the data, see Notes.

    Returns
    -------
    geometry : `ParallelBeamGeometry`
        If ``space`` is 2d, return a `Parallel2dGeometry`.
        If ``space`` is 3d, return a `Parallel3dAxisGeometry`.

    Examples
    --------
    Create a parallel beam geometry from a 2d space:

    >>> space = odl.uniform_discr([-1, -1], [1, 1], (20, 20))
    >>> geometry = parallel_beam_geometry(space)
    >>> geometry.angles.size
    45
    >>> geometry.detector.size
    31

    Notes
    -----
    According to [NW2001]_, pages 72--74, a function
    :math:`f : \\mathbb{R}^2 \\to \\mathbb{R}` that has compact support

    .. math::
        \| x \| > \\rho  \implies f(x) = 0,

    and is essentially bandlimited

    .. math::
       \| \\xi \| > \\Omega \implies \\hat{f}(\\xi) \\approx 0,

    can be fully reconstructed from a parallel beam ray transform
    if (1) the projection angles are sampled with a spacing of
    :math:`\\Delta \psi` such that

    .. math::
        \\Delta \psi \leq \\frac{\\pi}{\\rho \\Omega},

    and (2) the detector is sampled with an interval :math:`\\Delta s`
    that satisfies

    .. math::
        \\Delta s \leq \\frac{\\pi}{\\Omega}.

    The geometry returned by this function satisfies these conditions exactly.

    If the domain is 3-dimensional, the geometry is "separable", in that each
    slice along the z-dimension of the data is treated as independed 2d data.

    References
    ----------
    .. [NW2001] Natterer, F and Wuebbeling, F.
       *Mathematical Methods in Image Reconstruction*.
       SIAM, 2001.
       https://dx.doi.org/10.1137/1.9780898718324
    """
    # Find maximum distance from rotation axis
    corners = space.domain.corners()[:, :2]
    rho = np.max(np.linalg.norm(corners, axis=1))

    # Find default values according to Nyquist criterion.

    # We assume that the function is bandlimited by a wave along the x or y
    # axis. The highest frequency we can measure is then a standing wave with
    # period of twice the inter-node distance.
    min_side = min(space.partition.cell_sides[:2])
    omega = np.pi / min_side
    num_px_horiz = 2 * int(np.ceil(rho * omega / np.pi)) + 1

    if space.ndim == 2:
        det_min_pt = -rho
        det_max_pt = rho
        if det_shape is None:
            det_shape = num_px_horiz
    elif space.ndim == 3:
        num_px_vert = space.shape[2]
        min_h = space.domain.min_pt[2]
        max_h = space.domain.max_pt[2]
        det_min_pt = [-rho, min_h]
        det_max_pt = [rho, max_h]
        if det_shape is None:
            det_shape = [num_px_horiz, num_px_vert]

    if num_angles is None:
        num_angles = int(np.ceil(omega * rho))

    angle_partition = uniform_partition(0, np.pi, num_angles)
    det_partition = uniform_partition(det_min_pt, det_max_pt, det_shape)

    if space.ndim == 2:
        return Parallel2dGeometry(angle_partition, det_partition)
    elif space.ndim == 3:
        return Parallel3dAxisGeometry(angle_partition, det_partition)
    else:
        raise ValueError('``space.ndim`` must be 2 or 3.')
Ejemplo n.º 10
0
print("generating adjoint operator tensorflow layer")
odl_op_layer_adjoint = odl.contrib.tensorflow.as_tensorflow_layer(
    operator.adjoint, 'RayTransformAdjoint')

partial_op = partial.PartialRay(space, impl='astra_cuda')

print("preparing the partial layer")
odl_op_partial_layer = partial.tensor_partial_layer(partial_op,
                                                    'PartialRayTransform')
odl_op_partial_layer_adjoint = partial.tensor_partial_layer(
    partial_op.adjoint, 'PartialRayTransformAdjoint')

# Retrieving the angle projections
max_radian = max_degree * (np.pi / 180)
print(max_radian)  # the selected degree in radians
angle_partition = uniform_partition(0, max_radian, n_angles)
projections_array = (np.array(angle_partition.grid.coord_vectors)).ravel()
selection_array = np.arange(
    n_angles)  # this is the array, only going from 0,1,2,3,...n_angles

n_data = 5
n_primal = 5
n_dual = 5


def generate_data(validation=False):
    """Generate a set of random data."""
    n_generate = 1 if validation else n_data

    y_arr = np.empty(
        (n_generate, operator.range.shape[0], operator.range.shape[1], 1),
Ejemplo n.º 11
0
def project_data(data, old_detector, new_detector):
    """Transforms data one detector to another using linear interpolation.

    Parameters
    ----------
    data : `numpy.ndarray`
        Data sampled on a partition of the detector.
    old_detector : Detector
        The detector on which the data was sampled.
    new_detector : Detector
        The detector to which the data is projected.

    Returns
    -------
    resampled_data : `numpy.ndarray`
        Resampled data, which corresponds to a new detector

    Examples
    --------

    Transforming a flat detector to a circular. In this example
    a flat detector has a range [-1, 1], with uniform discretization
    [-1, -0.5, 0, 0.5, 1]. The circular detector has a range [-pi/4, pi/4]
    with uniform discretization [-pi/4, -pi/8, 0, pi/8, pi/4], which
    corresponds to [-1, -0.41, 0, 0.41, 1] on the flat detector,
    since tg(pi/8) approx. 0.41. Thus, values at points -pi/8 and pi/8 obtained
    through interpolation are -0.83 and 0.83 (approx. 0.41/0.5).

    >>> part = odl.uniform_partition(-1, 1, 5, nodes_on_bdry=True)
    >>> det = odl.tomo.Flat1dDetector(part, axis=[1, 0])
    >>> det.partition.meshgrid[0]
        array([-1. , -0.5,  0. ,  0.5,  1. ])
    >>> data = np.arange(-2, 3)
    >>> new_det = flat_to_curved(det, radius=1)
    >>> new_data = project_data(data, det, new_det)
    >>> np.round(new_data, 2)
    array([-2.  , -0.83,  0.  ,  0.83,  2.  ])

    Transforming a circular detector to a flat. In this example
    a circular detector has a range [-pi/4, pi/4] with uniform discretization
    [-0.79, -0.39, 0, 0.39, 0.79]. The corresponding flat detector has
    uniform discretization [-1, -0.5, 0, 0.5, 1], which corresponds to points
    [-0.79, -0.46, 0, 0.46, 0.79] on the circular detector,
    since arctg(0.5) = 0.46. Thus, values at points -0.5 and 0.5 obtained
    through interpolation are -1.18 and 1.18
    (approx. (0.79-0.46)/0.39*1 + (0.46-0.39)/0.39*2).

    >>> part = odl.uniform_partition(-np.pi / 4, np.pi / 4, 5,
    ...                              nodes_on_bdry=True)
    >>> det = odl.tomo.CircularDetector(part, axis=[1, 0], radius=1)
    >>> data = np.arange(-2, 3)
    >>> new_det = curved_to_flat(det)
    >>> new_data = project_data(data, det, new_det)
    >>> np.round(new_data, 2)
    array([-2.  ,  -1.18, 0.  , 1.18,  2.  ])

    Previous example extended to 2D cylindrical detector with height 2 and
    uniform partition along height axis [-1, -0.5, 0, 0.5, 1].
    The height partition of corresponding 2D flat detector is
    [-1.41, -0.71, 0., 0.71, 1.41].
    We can see that points that are closer to side edges of the cylinder are
    are projected higher on the flat detector.
    >>> part = odl.uniform_partition([-np.pi / 4, -1], [np.pi / 4, 1], (5, 5),
    ...                              nodes_on_bdry=True)
    >>> det = odl.tomo.CylindricalDetector(part,
    ...                                    axes=[[1, 0, 0], [0, 0, 1]],
    ...                                    radius=1)
    >>> data = np.zeros((5,5))
    >>> data[:, 1:4] = 1
    >>> new_det = curved_to_flat(det)
    >>> np.round(new_det.partition.meshgrid[1], 2)
    array([[-1.41, -0.71,  0.  ,  0.71,  1.41]])
    >>> new_data = project_data(data, det, new_det)
    >>> np.round(new_data.T, 2)
    array([[ 0.  ,  0.  ,  0.  ,  0.  ,  0.  ],
           [ 1.  ,  0.74,  0.59,  0.74,  1.  ],
           [ 1.  ,  1.  ,  1.  ,  1.  ,  1.  ],
           [ 1.  ,  0.74,  0.59,  0.74,  1.  ],
           [ 0.  ,  0.  ,  0.  ,  0.  ,  0.  ]])

    Now projecting this back to curved detector.
    >>> new_data = project_data(new_data, new_det, det)
    >>> np.round(new_data.T, 2)
    array([[ 0.  ,  0.33,  0.34,  0.33,  0.  ],
           [ 1.  ,  0.78,  0.71,  0.78,  1.  ],
           [ 1.  ,  1.  ,  1.  ,  1.  ,  1.  ],
           [ 1.  ,  0.78,  0.71,  0.78,  1.  ],
           [ 0.  ,  0.33,  0.34,  0.33,  0.  ]])

    The method is vectorized, i.e., it can be called for multiple observations
    of values on the detector (most often corresponding to different angles):

    >>> part = odl.uniform_partition(-1, 1, 3, nodes_on_bdry=True)
    >>> det = odl.tomo.Flat1dDetector(part, axis=[1, 0])
    >>> data_row = np.arange(-1, 2)
    >>> data = np.stack([data_row] * 2, axis=0)
    >>> new_det = flat_to_curved(det, 1)
    >>> new_data = project_data(data, det, new_det)
    >>> np.round(new_data, 2)
    array([[-1.,  0.,  1.],
           [-1.,  0.,  1.]])
    """

    assert isinstance(old_detector, Detector)
    assert isinstance(new_detector, Detector)
    part = old_detector.partition
    d = len(part.shape)
    if d == 1 and any(old_detector.axis != new_detector.axis):
        NotImplementedError('Detectors are axis not the same, {} and {}'
                            ''.format(old_detector.axis, new_detector.axis))
    elif d > 1 and (any(old_detector.axes[0] != new_detector.axes[0])
                    or any(old_detector.axes[1] != new_detector.axes[1])):
        NotImplementedError('Detectors are axis not the same, {} and {}'
                            ''.format(old_detector.axes, new_detector.axes))
    data = np.asarray(data, dtype=float)
    if data.shape[-d:] != part.shape:
        raise ValueError('Last dimensions of `data.shape` must '
                         'correspond to the detector partitioning, '
                         'got {} and {}'.format(data.shape[-d:], part.shape))

    # find out if there are multiple data points
    if d < len(data.shape):
        n = data.shape[0]
    else:
        n = 1

    if n > 1:
        # extend detectors partition for multiple samples
        data_part = uniform_partition(np.append([0], part.min_pt),
                                      np.append([n], part.max_pt),
                                      np.append([n], part.shape),
                                      nodes_on_bdry=part.nodes_on_bdry)
    else:
        data_part = part

    if isinstance(old_detector, Flat1dDetector):
        assert isinstance(new_detector, CircularDetector)

        phi = new_detector.partition.meshgrid[0]
        u = new_detector.radius * np.tan(phi)
        interpolator = linear_interpolator(data, data_part.coord_vectors)
        if n > 1:
            i = data_part.meshgrid[0]
            u = u.reshape(1, -1)
            resampled_data = interpolator((i, u))
        else:
            resampled_data = interpolator(u)

    elif isinstance(old_detector, CircularDetector):
        assert isinstance(new_detector, Flat1dDetector)

        u = new_detector.partition.meshgrid
        phi = np.arctan2(u, old_detector.radius)
        interpolator = linear_interpolator(data, data_part.coord_vectors)
        if n > 1:
            i = data_part.meshgrid[0]
            phi = phi.reshape(-1, 1)
            resampled_data = interpolator((i, phi))
        else:
            resampled_data = interpolator(phi)

    elif isinstance(old_detector, Flat2dDetector):
        assert isinstance(new_detector,
                          (CylindricalDetector, SphericalDetector))

        r = new_detector.radius
        if isinstance(new_detector, CylindricalDetector):
            phi, h = new_detector.partition.meshgrid
            u = r * np.tan(phi)
            v = h / r * np.sqrt(r * r + u * u)
        else:
            phi, theta = new_detector.partition.meshgrid
            u = r * np.tan(phi)
            v = np.tan(theta) * np.sqrt(r * r + u * u)
        interpolator = linear_interpolator(data, data_part.coord_vectors)
        if n > 1:
            i = data_part.meshgrid[0]
            u = np.expand_dims(u, axis=0)
            v = np.expand_dims(v, axis=0)
            resampled_data = interpolator((i, u, v))
        else:
            u = np.repeat(u, v.shape[1], axis=-1)
            coord = np.stack([u, v], axis=-1).reshape((-1, 2))
            resampled_data = interpolator(coord.T).reshape(
                new_detector.partition.shape)

    elif isinstance(old_detector, CylindricalDetector):
        assert isinstance(new_detector, (Flat2dDetector, SphericalDetector))

        r = old_detector.radius
        if isinstance(new_detector, Flat2dDetector):
            u, v = new_detector.partition.meshgrid
            phi = np.arctan2(u, r)
            h = v * r / np.sqrt(r * r + u * u)
        else:
            phi, theta = new_detector.partition.meshgrid
            h = r * np.tan(theta)
        interpolator = linear_interpolator(data, data_part.coord_vectors)
        if n > 1:
            i = data_part.meshgrid[0]
            phi = np.expand_dims(phi, axis=0)
            h = np.expand_dims(h, axis=0)
            resampled_data = interpolator((i, phi, h))
        else:
            phi = np.repeat(phi, h.shape[1], axis=-1)
            if h.shape[0] != phi.shape[0]:
                h = np.repeat(h, phi.shape[0], axis=0)
            coord = np.stack([phi, h], axis=-1).reshape((-1, 2))
            resampled_data = interpolator(coord.T).reshape(
                new_detector.partition.shape)

    elif isinstance(old_detector, SphericalDetector):
        assert isinstance(new_detector, (Flat2dDetector, CylindricalDetector))

        r = old_detector.radius
        if isinstance(new_detector, Flat2dDetector):
            u, v = new_detector.partition.meshgrid
            phi = np.arctan2(u, r)
            theta = np.arctan2(v, np.sqrt(r * r + u * u))
        else:
            phi, h = new_detector.partition.meshgrid
            theta = np.arctan2(h, r)
        interpolator = linear_interpolator(data, data_part.coord_vectors)
        if n > 1:
            i = data_part.meshgrid[0]
            phi = np.expand_dims(phi, axis=0)
            theta = np.expand_dims(theta, axis=0)
            resampled_data = interpolator((i, phi, theta))
        else:
            phi = np.repeat(phi, theta.shape[1], axis=-1)
            if theta.shape[0] != phi.shape[0]:
                theta = np.repeat(theta, phi.shape[0], axis=0)
            coord = np.stack([phi, theta], axis=-1).reshape((-1, 2))
            resampled_data = interpolator(coord.T).reshape(
                new_detector.partition.shape)

    else:
        NotImplementedError('Data transformation between detectors {} and {}'
                            'is not implemented'.format(
                                old_detector, new_detector))

    return resampled_data
Ejemplo n.º 12
0
def flat_to_curved(detector, radius):
    """Transforms a flat detector to a curved.

    Parameters
    ----------
    detector : `Flat1dDetector` or `Flat2dDetector`
        The detector on which the data was sampled.
    radius :  nonnegatice float or 2-tuple of nonnegative floats
        Radius or radii of the detector curvature.
        If ``r`` a circular or cylindrical detector is created.
        If ``(r, None)`` or ``(r, float('inf'))``, a cylindrical
        detector is created.
        If ``(r1, r2)``, a spherical detector is created.

    Returns
    -------
    transformed_detector : `CircularDetector`, `CylindricalDetector`
        or `SphericalDetector`
        A new transformed detector.

    Examples
    --------
    Transforming a flat detector with range [-1, 1] to
    a circular with radius 1 and range [-pi/4, pi/4].

    >>> part = odl.uniform_partition(-1, 1, 3)
    >>> det = odl.tomo.Flat1dDetector(part, axis=[1, 0])
    >>> new_det = flat_to_curved(det, radius=1)
    >>> new_det
    CircularDetector(
        uniform_partition(-0.7854, 0.7854, 3),
        axis='[ 1.,  0.]',
        radius='1.0'
    )

    Transforming a flat detector with height 2 to
    a cylindrical with radius 1 and height 2.
    >>> part = odl.uniform_partition([-1, -1], [1, 1], (3, 2))
    >>> det = odl.tomo.Flat2dDetector(part, axes=[[1, 0, 0], [0, 0, 1]])
    >>> new_det = flat_to_curved(det, radius=1)
    >>> new_det
    CylindricalDetector(
        uniform_partition([-0.7854, -1.    ], [ 0.7854,  1.    ], (3, 2)),
        axes=('[ 1.,  0.,  0.]', '[ 0.,  0.,  1.]'),
        radius='1.0'
    )
    """

    part = detector.partition
    radius = np.array(radius, dtype=np.float, ndmin=1)
    if isinstance(detector, Flat1dDetector):

        if radius <= 0 or radius.size > 1:
            raise ValueError('`radius` must be positive float')

        min_pt = np.arctan2(part.min_pt, radius)
        max_pt = np.arctan2(part.max_pt, radius)
        transformed_part = uniform_partition(min_pt,
                                             max_pt,
                                             part.shape,
                                             nodes_on_bdry=part.nodes_on_bdry)
        transformed_det = CircularDetector(transformed_part,
                                           detector.axis,
                                           radius,
                                           check_bounds=detector.check_bounds)

    elif isinstance(detector, Flat2dDetector):

        if radius.size == 1 or radius[1] is None or radius[1] == float('inf'):

            if radius[0] <= 0:
                raise ValueError('`radius` must be positive float')

            min_pt = [np.arctan2(part.min_pt[0], radius[0]), part.min_pt[1]]
            max_pt = [np.arctan2(part.max_pt[0], radius[0]), part.max_pt[1]]
            transformed_part = uniform_partition(
                min_pt, max_pt, part.shape, nodes_on_bdry=part.nodes_on_bdry)
            transformed_det = CylindricalDetector(
                transformed_part,
                radius=radius[0],
                axes=detector.axes,
                check_bounds=detector.check_bounds)

        elif radius[0] == radius[1]:

            if radius[0] <= 0:
                raise ValueError('`radius` must be positive float')

            min_pt = np.arctan2(part.min_pt, radius)
            max_pt = np.arctan2(part.max_pt, radius)
            transformed_part = uniform_partition(
                min_pt, max_pt, part.shape, nodes_on_bdry=part.nodes_on_bdry)
            transformed_det = SphericalDetector(
                transformed_part,
                radius=radius[0],
                axes=detector.axes,
                check_bounds=detector.check_bounds)

        else:
            raise NotImplementedError('Curved detector with different '
                                      'curvature radii')
    else:
        raise ValueError('Detector must be flat, got '.format(detector))

    return transformed_det
Ejemplo n.º 13
0
def curved_to_flat(detector):
    """Transforms a curved detector to a flat.

    Parameters
    ----------
    detector : `CircularDetector`, `CylindricalDetector`
        or `SphericalDetector`
        The detector on which the data was sampled.

    Returns
    -------
    transformed_detector : `Flat1dDetector` or `Flat2dDetector`
        A new transformed detector.

    Examples
    --------
    Transforming a circular detector with range [-pi/4, pi/4]
    to a flat with range [-1, 1].

    >>> part = odl.uniform_partition(-np.pi / 4, np.pi / 4, 3,
    ...                              nodes_on_bdry=True)
    >>> det = odl.tomo.CircularDetector(part, axis=[1, 0], radius=1)
    >>> new_det = curved_to_flat(det)
    >>> new_det
    Flat1dDetector(
        uniform_partition(-1.0, 1.0, 3, nodes_on_bdry=True),
        axis='[ 1.,  0.]'
    )

    Transforming a cylindrical detector with radius 1 and height 2 to
    a flat with height 2sqrt(2) (since  edge point are projected higher).
    >>> part = odl.uniform_partition([-np.pi / 4, -1], [np.pi / 4, 1], (3, 2),
    ...                              nodes_on_bdry=True)
    >>> det = odl.tomo.CylindricalDetector(part,
    ...                                    axes=[[1, 0, 0], [0, 0, 1]],
    ...                                    radius=1)
    >>> new_det = curved_to_flat(det)
    >>> new_det
    Flat2dDetector(
        uniform_partition([-1.    , -1.4142], [ 1.    ,  1.4142], (3, 2),
            nodes_on_bdry=True),
        axes=('[ 1.,  0.,  0.]', '[ 0.,  0.,  1.]')
    )
    """

    part = detector.partition
    if isinstance(detector, CircularDetector):

        min_pt = detector.radius * np.tan(part.min_pt)
        max_pt = detector.radius * np.tan(part.max_pt)
        transformed_part = uniform_partition(min_pt,
                                             max_pt,
                                             part.shape,
                                             nodes_on_bdry=part.nodes_on_bdry)
        transformed_det = Flat1dDetector(transformed_part,
                                         detector.axis,
                                         check_bounds=detector.check_bounds)

    elif isinstance(detector, CylindricalDetector):

        r = detector.radius
        R = r / np.minimum(np.cos(part.min_pt[0]), np.cos(part.max_pt[0]))
        min_pt = [r * np.tan(part.min_pt[0]), R / r * part.min_pt[1]]
        max_pt = [r * np.tan(part.max_pt[0]), R / r * part.max_pt[1]]
        transformed_part = uniform_partition(min_pt,
                                             max_pt,
                                             part.shape,
                                             nodes_on_bdry=part.nodes_on_bdry)
        transformed_det = Flat2dDetector(transformed_part,
                                         detector.axes,
                                         check_bounds=detector.check_bounds)

    elif isinstance(detector, SphericalDetector):

        r = detector.radius
        R = r / np.minimum(np.cos(part.min_pt[0]), np.cos(part.max_pt[0]))
        min_pt = [r * np.tan(part.min_pt[0]), R * np.tan(part.min_pt[1])]
        max_pt = [r * np.tan(part.max_pt[0]), R * np.tan(part.max_pt[1])]
        transformed_part = uniform_partition(min_pt,
                                             max_pt,
                                             part.shape,
                                             nodes_on_bdry=part.nodes_on_bdry)
        transformed_det = Flat2dDetector(transformed_part,
                                         detector.axes,
                                         check_bounds=detector.check_bounds)

    else:
        raise ValueError('Detector must be curved, got '.format(detector))

    return transformed_det
# Create tensorflow layer from odl operator
print("\ngenerating operator tensorflow layer")
odl_op_layer = odl.contrib.tensorflow.as_tensorflow_layer(operator, 'RayTransform')

print("generating adjoint operator tensorflow layer")
odl_op_layer_adjoint = odl.contrib.tensorflow.as_tensorflow_layer(operator.adjoint, 'RayTransformAdjoint')

partial_op = partial.PartialRay(space, impl=astra_impl)

print("preparing the partial layer")
odl_op_partial_layer = partial.tensor_partial_layer(partial_op, 'PartialRayTransform')
odl_op_partial_layer_adjoint = partial.tensor_partial_layer(partial_op.adjoint, 'PartialRayTransformAdjoint')


# Retrieving the angle projections
angle_partition = uniform_partition(0, np.pi, n_angles)
projections_array = (np.array(angle_partition.grid.coord_vectors)).ravel()
selection_array = np.arange(n_angles)

n_data = 5
n_iter = 2
n_primal = 5
n_dual = 5


def generate_data(validation=False):
    """Generate a set of random data."""
    n_generate = 1 if validation else n_data

    y_arr = np.empty((n_generate, operator.range.shape[0], operator.range.shape[1], 1), dtype='float64')
    x_true_arr = np.empty((n_generate, space.shape[0], space.shape[1], 1), dtype='float64')
Ejemplo n.º 15
0
def cone_beam_geometry(space,
                       src_radius,
                       det_radius,
                       num_angles=None,
                       short_scan=False,
                       det_shape=None):
    """Create a default fan or cone beam geometry from ``space``.

    This function is intended for simple test cases where users do not
    need the full flexibility of the geometries, but simply wants a
    geometry that works.

    The geometry returned by this function has equidistant angles
    that lie (strictly) between 0 and either ``2 * pi`` (full scan)
    or ``pi + fan_angle`` (short scan).
    The detector is centered around 0, and its size is chosen such that
    the whole ``space`` is covered with lines.

    The number of angles and detector elements is chosen such that
    the resulting sinogram is fully sampled according to the
    Nyquist criterion, which in general results in a very large number of
    samples. In particular, a ``space`` that is not centered at the origin
    can result in very large detectors since the latter is always
    origin-centered.

    Parameters
    ----------
    space : `DiscreteLp`
        Reconstruction space, the space of the volumetric data to be
        projected. Must be 2- or 3-dimensional.
    src_radius : nonnegative float
        Radius of the source circle. Must be larger than the radius of
        the smallest vertical cylinder containing ``space.domain``,
        i.e., the source must be outside the volume for all rotations.
    det_radius : nonnegative float
        Radius of the detector circle.
    short_scan : bool, optional
        Use the minimum required angular range ``[0, pi + fan_angle]``.
        For ``True``, the `parker_weighting` should be used in FBP.
        By default, the range ``[0, 2 * pi]`` is used.
    num_angles : int, optional
        Number of angles.
        Default: Enough to fully sample the data, see Notes.
    det_shape : int or sequence of ints, optional
        Number of detector pixels.
        Default: Enough to fully sample the data, see Notes.

    Returns
    -------
    geometry : `DivergentBeamGeometry`
        Projection geometry with equidistant angles and zero-centered
        detector as determined by sampling criteria.

            - If ``space`` is 2D, the result is a `FanFlatGeometry`.
            - If ``space`` is 3D, the result is a `ConeFlatGeometry`.

    Examples
    --------
    Create a fan beam geometry from a 2d space:

    >>> space = odl.uniform_discr([-1, -1], [1, 1], (20, 20))
    >>> geometry = cone_beam_geometry(space, src_radius=5, det_radius=5)
    >>> geometry.angles.size
    78
    >>> geometry.detector.size
    57

    For a short scan geometry (from 0 to ``pi + fan_angle``), the
    ``short_scan`` flag can be set, resulting in a smaller number of
    angles:

    >>> geometry = cone_beam_geometry(space, src_radius=5, det_radius=5,
    ...                               short_scan=True)
    >>> geometry.angles.size
    46

    If the source is close to the object, the detector becomes larger due
    to more magnification:

    >>> geometry = cone_beam_geometry(space, src_radius=3, det_radius=9)
    >>> geometry.angles.size
    80
    >>> geometry.detector.size
    105

    Notes
    -----
    According to [NW2001]_, pages 75--76, a function
    :math:`f : \\mathbb{R}^2 \\to \\mathbb{R}` that has compact support

    .. math::
        \| x \| > \\rho  \implies f(x) = 0,

    and is essentially bandlimited

    .. math::
       \| \\xi \| > \\Omega \implies \\hat{f}(\\xi) \\approx 0,

    can be fully reconstructed from a fan beam ray transform with
    source-detector distance :math:`r` (assuming all detector
    points have the same distance to the source) if (1) the projection
    angles are sampled with a spacing of :math:`\\Delta \psi` such that

    .. math::
        \\Delta \psi \leq \\frac{r + \\rho}{r}\, \\frac{\\pi}{\\rho \\Omega},

    and (2) the detector is sampled with an angular interval
    :math:`\\Delta \\alpha` that satisfies

    .. math::
        \\Delta \\alpha \leq \\frac{\\pi}{r \\Omega}.

    For a flat detector, the angular interval is smallest in the center
    of the fan and largest at the boundaries. The worst-case relation
    between the linear and angular sampling intervals are

    .. math::
        \\Delta s = R \\Delta \\alpha, \quad R^2 = r^2 + (w / 2)^2,

    where :math:`w` is the width of the detector.
    Thus, to satisfy the angular detector condition one can choose

    .. math::
        \\Delta s \leq \\frac{\\pi \sqrt{r^2 + (w / 2)^2}}{r \\Omega}.

    The geometry returned by this function satisfies these conditions exactly.

    If the domain is 3-dimensional, a circular cone beam geometry is
    created with the third coordinate axis as rotation axis. This does,
    of course, not yield complete data, but is equivalent to the
    2D fan beam case in the :math:`z = 0` slice. The vertical size of
    the detector is chosen such that it covers the object vertically
    with rays, using a containing cuboid
    :math:`[-\\rho, \\rho]^2 \\times [z_{\mathrm{min}}, z_{\mathrm{min}}]`
    to compute the cone angle.

    References
    ----------
    .. [NW2001] Natterer, F and Wuebbeling, F.
       *Mathematical Methods in Image Reconstruction*.
       SIAM, 2001.
       https://dx.doi.org/10.1137/1.9780898718324
    """
    # Find maximum distance from rotation axis
    corners = space.domain.corners()[:, :2]
    rho = np.max(np.linalg.norm(corners, axis=1))

    # Find default values according to Nyquist criterion.

    # We assume that the function is bandlimited by a wave along the x or y
    # axis. The highest frequency we can measure is then a standing wave with
    # period of twice the inter-node distance.
    min_side = min(space.partition.cell_sides[:2])
    omega = np.pi / min_side

    # Compute minimum width of the detector to cover the object. The relation
    # used here is (w/2)/(rs+rd) = rho/rs since both are equal to tan(alpha),
    # where alpha is the half fan angle.
    rs = float(src_radius)
    if (rs <= rho):
        raise ValueError('source too close to the object, resulting in '
                         'infinite detector for full coverage')
    rd = float(det_radius)
    r = src_radius + det_radius
    w = 2 * rho * (rs + rd) / rs

    # Compute minimum number of pixels given the constraint on the
    # sampling interval and the computed width
    rb = np.hypot(r, w / 2)  # length of the boundary ray to the flat detector
    num_px_horiz = 2 * int(np.ceil(w * omega * r / (2 * np.pi * rb))) + 1

    if space.ndim == 2:
        det_min_pt = -w / 2
        det_max_pt = w / 2
        if det_shape is None:
            det_shape = num_px_horiz
    elif space.ndim == 3:
        # Compute number of vertical pixels required to cover the object,
        # using the same sampling interval vertically as horizontally.
        # The reasoning is the same as for the computation of w.

        # Minimum distance of the containing cuboid edges to the source
        dist = rs - rho
        # Take angle of the rays going through the top and bottom corners
        # in that edge
        half_cone_angle = max(np.arctan(abs(space.partition.min_pt[2]) / dist),
                              np.arctan(abs(space.partition.max_pt[2]) / dist))
        h = 2 * np.sin(half_cone_angle) * (rs + rd)

        # Use the vertical spacing from the reco space, corrected for
        # magnification at the "back" of the object, i.e., where it is
        # minimal
        min_mag = (rs + rd) / (rs + rho)
        delta_h = min_mag * space.cell_sides[2]
        num_px_vert = int(np.ceil(h / delta_h))
        h = num_px_vert * delta_h  # make multiple of spacing

        det_min_pt = [-w / 2, -h / 2]
        det_max_pt = [w / 2, h / 2]
        if det_shape is None:
            det_shape = [num_px_horiz, num_px_vert]

    fan_angle = 2 * np.arctan(rho / rs)
    if short_scan:
        max_angle = min(np.pi + fan_angle, 2 * np.pi)
    else:
        max_angle = 2 * np.pi

    if num_angles is None:
        num_angles = int(
            np.ceil(max_angle * omega * rho / np.pi * r / (r + rho)))

    angle_partition = uniform_partition(0, max_angle, num_angles)
    det_partition = uniform_partition(det_min_pt, det_max_pt, det_shape)

    if space.ndim == 2:
        return FanFlatGeometry(angle_partition, det_partition, src_radius,
                               det_radius)
    elif space.ndim == 3:
        return ConeFlatGeometry(angle_partition, det_partition, src_radius,
                                det_radius)
    else:
        raise ValueError('``space.ndim`` must be 2 or 3.')
Ejemplo n.º 16
0
def parallel_beam_geometry(space, angles=None, det_shape=None):
    """Create default parallel beam geometry from ``space``.

    This is intended for simple test cases where users do not need the full
    flexibility of the geometries, but simply want a geometry that works.

    This default geometry gives a fully sampled sinogram according to the
    Nyquist criterion, which in general results in a very large number of
    samples.

    Parameters
    ----------
    space : `DiscreteLp`
        Reconstruction space, the space of the volumetric data to be projected.
        Needs to be 2d or 3d.
    angles : int, optional
        Number of angles.
        Default: Enough to fully sample the data, see Notes.
    det_shape : int or sequence of int, optional
        Number of detector pixels.
        Default: Enough to fully sample the data, see Notes.

    Returns
    -------
    geometry : `ParallelGeometry`
        If ``space`` is 2d, returns a `Parallel2dGeometry`.
        If ``space`` is 3d, returns a `Parallel3dAxisGeometry`.

    Examples
    --------
    Create geometry from 2d space and check the number of data points:

    >>> space = odl.uniform_discr([-1, -1], [1, 1], [20, 20])
    >>> geometry = parallel_beam_geometry(space)
    >>> geometry.angles.size
    45
    >>> geometry.detector.size
    29

    Notes
    -----
    According to `Mathematical Methods in Image Reconstruction`_ (page 72), for
    a function :math:`f : \\mathbb{R}^2 \\to \\mathbb{R}` that has compact
    support

    .. math::
        \| x \| > \\rho  \implies f(x) = 0,

    and is essentially bandlimited

    .. math::
       \| \\xi \| > \\Omega \implies \\hat{f}(\\xi) \\approx 0,

    then, in order to fully reconstruct the function from a parallel beam ray
    transform the function should be sampled at an angular interval
    :math:`\\Delta \psi` such that

    .. math::
        \\Delta \psi \leq \\frac{\\pi}{\\rho \\Omega},

    and the detector should be sampled with an interval :math:`Delta s` that
    satisfies

    .. math::
        \\Delta s \leq \\frac{\\pi}{\\Omega}.

    The geometry returned by this function satisfies these conditions exactly.

    If the domain is 3-dimensional, the geometry is "separable", in that each
    slice along the z-dimension of the data is treated as independed 2d data.

    References
    ----------
    .. _Mathematical Methods in Image Reconstruction: \
http://dx.doi.org/10.1137/1.9780898718324
    """
    # Find maximum distance from rotation axis
    corners = space.domain.corners()[:, :2]
    rho = np.max(np.linalg.norm(corners, axis=1))

    # Find default values according to Nyquist criterion.

    # We assume that the function is bandlimited by a wave along the x or y
    # axis. The highest frequency we can measure is then a standing wave with
    # period of twice the inter-node distance.
    min_side = min(space.partition.cell_sides[:2])
    omega = np.pi / min_side

    if det_shape is None:
        if space.ndim == 2:
            det_shape = int(2 * rho * omega / np.pi) + 1
        elif space.ndim == 3:
            width = int(2 * rho * omega / np.pi) + 1
            height = space.shape[2]
            det_shape = [width, height]

    if angles is None:
        angles = int(omega * rho) + 1

    # Define angles
    angle_partition = uniform_partition(0, np.pi, angles)

    if space.ndim == 2:
        det_partition = uniform_partition(-rho, rho, det_shape)
        return Parallel2dGeometry(angle_partition, det_partition)
    elif space.ndim == 3:
        min_h = space.domain.min_pt[2]
        max_h = space.domain.max_pt[2]

        det_partition = uniform_partition([-rho, min_h], [rho, max_h],
                                          det_shape)
        return Parallel3dAxisGeometry(angle_partition, det_partition)
    else:
        raise ValueError('``space.ndim`` must be 2 or 3.')
from odl.discr import uniform_partition
import xlsxwriter

np.random.seed(0)
name = 'learned_random_sub'

sess = tf.InteractiveSession()

astra_impl = 'astra_cuda'
optimal_number = 202  # number optimal projection points
n_angles = 60  # number of projection angles

selection_projection = np.arange(
    optimal_number)  # going from [0,1,2,3, ... , optinumber]
opti_partition = uniform_partition(
    0, np.pi,
    optimal_number)  # uniform partition from the optimal number of projections
opti_projection_points = (np.array(
    opti_partition.grid.coord_vectors)).ravel()  # changing into an array

print("defining user selected parameters")
# User selected paramters
angles_in_batch = 10
if (n_angles % angles_in_batch != 0):
    print(
        "number of angles must be divisible by the number of angles in the batch"
    )
    exit()
else:
    n_batches = int(n_angles / angles_in_batch)