Beispiel #1
0
def test_linear_interpolation_2d():
    """Test linear interpolation in 2d."""
    coord_vecs = [[0.125, 0.375, 0.625, 0.875], [0.25, 0.75]]
    f = np.array([[1, 2], [3, 4], [5, 6], [7, 8]], dtype='float64')
    interpolator = linear_interpolator(f, coord_vecs)

    # Evaluate at single point
    val = interpolator([0.3, 0.6])
    l1 = (0.3 - 0.125) / (0.375 - 0.125)
    l2 = (0.6 - 0.25) / (0.75 - 0.25)
    true_val = ((1 - l1) * (1 - l2) * f[0, 0] + (1 - l1) * l2 * f[0, 1] + l1 *
                (1 - l2) * f[1, 0] + l1 * l2 * f[1, 1])
    assert val == pytest.approx(true_val)

    # Input array, with and without output array
    pts = np.array([[0.3, 0.6], [0.1, 0.25], [1.0, 1.0]])
    l1 = (0.3 - 0.125) / (0.375 - 0.125)
    l2 = (0.6 - 0.25) / (0.75 - 0.25)
    true_val_1 = ((1 - l1) * (1 - l2) * f[0, 0] + (1 - l1) * l2 * f[0, 1] +
                  l1 * (1 - l2) * f[1, 0] + l1 * l2 * f[1, 1])
    l1 = (0.125 - 0.1) / (0.375 - 0.125)
    # l2 = 0
    true_val_2 = (1 - l1) * f[0, 0]  # only lower left contributes
    l1 = (1.0 - 0.875) / (0.875 - 0.625)
    l2 = (1.0 - 0.75) / (0.75 - 0.25)
    true_val_3 = (1 - l1) * (1 - l2) * f[3, 1]  # lower left only
    true_arr = [true_val_1, true_val_2, true_val_3]
    assert all_equal(interpolator(pts.T), true_arr)

    out = np.empty(3, dtype='float64')
    interpolator(pts.T, out=out)
    assert all_equal(out, true_arr)

    # Input meshgrid, with and without output array
    mg = sparse_meshgrid([0.3, 1.0], [0.4, 0.75])
    # Indices: (1, 3) x (0, 1)
    lx1 = (0.3 - 0.125) / (0.375 - 0.125)
    lx2 = (1.0 - 0.875) / (0.875 - 0.625)
    ly1 = (0.4 - 0.25) / (0.75 - 0.25)
    # ly2 = 0
    true_val_11 = ((1 - lx1) * (1 - ly1) * f[0, 0] +
                   (1 - lx1) * ly1 * f[0, 1] + lx1 * (1 - ly1) * f[1, 0] +
                   lx1 * ly1 * f[1, 1])
    true_val_12 = ((1 - lx1) * f[0, 1] + lx1 * f[1, 1]  # ly2 = 0
                   )
    true_val_21 = ((1 - lx2) * (1 - ly1) * f[3, 0] +
                   (1 - lx2) * ly1 * f[3, 1]  # high node 1.0, no upper
                   )
    true_val_22 = (1 - lx2) * f[3, 1]  # ly2 = 0, no upper for 1.0
    true_mg = [[true_val_11, true_val_12], [true_val_21, true_val_22]]
    assert all_equal(interpolator(mg), true_mg)
    out = np.empty((2, 2), dtype='float64')
    interpolator(mg, out=out)
    assert all_equal(out, true_mg)
Beispiel #2
0
def test_linear_interpolation_1d():
    """Test linear interpolation in 1d."""
    coord_vecs = [[0.1, 0.3, 0.5, 0.7, 0.9]]
    f = np.array([1, 2, 3, 4, 5], dtype="float64")
    interpolator = linear_interpolator(f, coord_vecs)

    # Evaluate at single point
    val = interpolator(0.35)
    true_val = 0.75 * 2 + 0.25 * 3
    assert val == pytest.approx(true_val)

    # Input array, with and without output array
    pts = np.array([0.4, 0.0, 0.65, 0.95])
    true_arr = [2.5, 0.5, 3.75, 3.75]
    assert all_almost_equal(interpolator(pts), true_arr)
Beispiel #3
0
def test_collocation_interpolation_identity():
    """Check if collocation is left-inverse to interpolation."""
    # Interpolation followed by collocation on the same grid should be
    # the identity
    coord_vecs = [[0.125, 0.375, 0.625, 0.875], [0.25, 0.75]]
    f = np.array([[1, 2], [3, 4], [5, 6], [7, 8]], dtype='float64')
    interpolators = [
        nearest_interpolator(f, coord_vecs),
        linear_interpolator(f, coord_vecs),
        per_axis_interpolator(f, coord_vecs, interp=['linear', 'nearest']),
    ]

    for interpolator in interpolators:
        mg = sparse_meshgrid(*coord_vecs)
        ident_f = point_collocation(interpolator, mg)
        assert all_almost_equal(ident_f, f)
Beispiel #4
0
 def interpolator(x, out=None):
     x = (x[0], np.clip(x[1], min_x, max_x))
     interpolator = linear_interpolator(
         sinogram, skimage_range.grid.coord_vectors
     )
     return interpolator(x, out=out)
Beispiel #5
0
def load_projections(folder, indices=None):
    """Load geometry and data stored in Mayo format from folder.

    Parameters
    ----------
    folder : str
        Path to the folder where the Mayo DICOM files are stored.
    indices : optional
        Indices of the projections to load.
        Accepts advanced indexing such as slice or list of indices.

    Returns
    -------
    geometry : ConeBeamGeometry
        Geometry corresponding to the Mayo projector.
    proj_data : `numpy.ndarray`
        Projection data, given as the line integral of the linear attenuation
        coefficient (g/cm^3). Its unit is thus g/cm^2.
    """
    datasets, data_array = _read_projections(folder, indices)

    # Get the angles
    angles = [d.DetectorFocalCenterAngularPosition for d in datasets]
    angles = -np.unwrap(angles) - np.pi  # different definition of angles

    # Set minimum and maximum corners
    shape = np.array([
        datasets[0].NumberofDetectorColumns, datasets[0].NumberofDetectorRows
    ])
    pixel_size = np.array([
        datasets[0].DetectorElementTransverseSpacing,
        datasets[0].DetectorElementAxialSpacing
    ])

    # Correct from center of pixel to corner of pixel
    minp = -(np.array(datasets[0].DetectorCentralElement) - 0.5) * pixel_size
    maxp = minp + shape * pixel_size

    # Select geometry parameters
    src_radius = datasets[0].DetectorFocalCenterRadialDistance
    det_radius = (datasets[0].ConstantRadialDistance -
                  datasets[0].DetectorFocalCenterRadialDistance)

    # For unknown reasons, mayo does not include the tag
    # "TableFeedPerRotation", which is what we want.
    # Instead we manually compute the pitch
    pitch = ((datasets[-1].DetectorFocalCenterAxialPosition -
              datasets[0].DetectorFocalCenterAxialPosition) /
             ((np.max(angles) - np.min(angles)) / (2 * np.pi)))

    # Get flying focal spot data
    offset_axial = np.array([d.SourceAxialPositionShift for d in datasets])
    offset_angular = np.array([d.SourceAngularPositionShift for d in datasets])
    offset_radial = np.array([d.SourceRadialDistanceShift for d in datasets])

    # TODO(adler-j): Implement proper handling of flying focal spot.
    # Currently we do not fully account for it, merely making some "first
    # order corrections" to the detector position and radial offset.

    # Update angles with flying focal spot (in plane direction).
    # This increases the resolution of the reconstructions.
    angles = angles - offset_angular

    # We correct for the mean offset due to the rotated angles, we need to
    # shift the detector.
    offset_detector_by_angles = det_radius * np.mean(offset_angular)
    minp[0] -= offset_detector_by_angles
    maxp[0] -= offset_detector_by_angles

    # We currently apply only the mean of the offsets
    src_radius = src_radius + np.mean(offset_radial)

    # Partially compensate for a movement of the source by moving the object
    # instead. We need to rescale by the magnification to get the correct
    # change in the detector. This approximation is only exactly valid on the
    # axis of rotation.
    mean_offset_along_axis_for_ffz = np.mean(offset_axial) * (
        src_radius / (src_radius + det_radius))

    # Create partition for detector
    detector_partition = odl.uniform_partition(minp, maxp, shape)

    # Convert offset to odl definitions
    offset_along_axis = (mean_offset_along_axis_for_ffz +
                         datasets[0].DetectorFocalCenterAxialPosition -
                         angles[0] / (2 * np.pi) * pitch)

    # Assemble geometry
    angle_partition = odl.nonuniform_partition(angles)
    geometry = odl.tomo.ConeBeamGeometry(angle_partition,
                                         detector_partition,
                                         src_radius=src_radius,
                                         det_radius=det_radius,
                                         pitch=pitch,
                                         offset_along_axis=offset_along_axis)

    # Create a *temporary* ray transform (we need its range)
    spc = odl.uniform_discr([-1] * 3, [1] * 3, [32] * 3)
    ray_trafo = odl.tomo.RayTransform(spc, geometry, interp='linear')

    # convert coordinates
    theta, up, vp = ray_trafo.range.grid.meshgrid
    d = src_radius + det_radius
    u = d * np.arctan(up / d)
    v = d / np.sqrt(d**2 + up**2) * vp

    # Calculate projection data in rectangular coordinates since we have no
    # backend that supports cylindrical
    interpolator = linear_interpolator(data_array,
                                       ray_trafo.range.coord_vectors)
    proj_data = interpolator((theta, u, v))

    return geometry, proj_data.asarray()
Beispiel #6
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