예제 #1
0
def test_cylinder_xaxis():
    # test single axis cylinder, parallel to the x axis
    shape = (64, 64, 64)
    halfshape = tuple(s // 2 for s in shape)
    p1 = (1, 0, 0)
    p2 = (-1, 0, 0)
    r = 0.5  # in base coordinates; radius will be 1/4 the shape
    volume = phantom_base.cylinder(shape, p1, p2, r)

    # check the centroid and radius
    xslice = volume[halfshape[0], :, :]
    row = xslice[halfshape[1], :]
    col = xslice[:, halfshape[2]]
    # given a radius in real numbers, calculate that expected radius in pixels
    expected_diameters = tuple(int(s * r) for s in shape)
    # assert that a row slice through the object centroid has the correct radius
    nose.tools.assert_equals(_find_diameter(row), expected_diameters[0])
    # assert that a column slice through the object centroid has the correct radius
    nose.tools.assert_equals(_find_diameter(col), expected_diameters[1])
    # make sure that the center line of the cylinder exists across the volume
    expected_line = np.ones(shape[0], dtype=bool)
    np.testing.assert_array_equal(volume[:, halfshape[1], halfshape[2]],
                                  expected_line)

    # check that cap == "none" is identical with full axis
    p1 = (-0.5, 0, 0)
    p2 = (0.5, 0, 0)
    r = 0.5  # in base coordinates; radius will be 1/4 the shape
    vol_nocap = phantom_base.cylinder(shape, p1, p2, r, cap="none")
    np.testing.assert_array_equal(vol_nocap, volume)
예제 #2
0
def test_cylinder_tiny_radius():
    # check that we are logging a warning for radii smaller than grid resolution
    shape = (10, 10, 10)
    p1 = (-0.5, 0, 0)
    p2 = (0.5, 0, 0)
    r = 0.05  # use an epsilon-sized radius, should be smaller than the grid size
    volume = phantom_base.cylinder(shape, p1, p2, r)
    # volume should be all zeros
    np.testing.assert_array_equal(np.unique(volume), np.zeros(1))

    r = 1 / min(shape)  # use the grid size, see what happens
    volume = phantom_base.cylinder(shape, p1, p2, r)
    # volume should be all zeros
    np.testing.assert_array_equal(np.unique(volume), np.zeros(1))
예제 #3
0
def test_cylinder_big_radius():
    shape = (10, 10, 10)
    p1 = (0, 0, -1)
    p2 = (0, 0, 1)
    r = 2
    volume = phantom_base.cylinder(shape, p1, p2, r)
    np.testing.assert_array_equal(np.unique(volume), np.ones(1))
예제 #4
0
def test_cylinder_xyz_inf():
    # make sure that we can generate an arbitrary cylinder across 3 dimensions
    shape = (64, 64, 64)
    p1 = (-1, -1, -1)
    p2 = (1, 1, 1)
    r = 0.5  # in base coordinates; radius will be 1/4 the shape
    volume = phantom_base.cylinder(shape, p1, p2, r, cap="none")
    np.testing.assert_array_equal(volume, volume[::-1, ::-1, ::-1])
예제 #5
0
def test_cylinder_xaxis_flatcap():
    # cylinder along the xaxis, but with flat caps
    shape = (64, 64, 64)
    halfshape = tuple(s // 2 for s in shape)
    p1 = (-0.5, 0, 0)
    p2 = (0.5, 0, 0)
    r = 0.5  # in base coordinates; radius will be 1/4 the shape
    volume = phantom_base.cylinder(shape, p1, p2, r, cap="flat")
    # get the expected centerline of the cylinder, with a planar cap
    # Parts of the line outside of the planes defined by the two points
    # through the center line should not be turned on.
    expected_line = np.zeros(shape[0], dtype=bool)
    expected_line[(halfshape[0] // 2):(halfshape[0] + halfshape[0] // 2)] = 1
    np.testing.assert_array_equal(volume[:, 31, 31], expected_line)
예제 #6
0
def test_cylinder_xyz_flatcap():
    # test the planar cut on generating a cylinder across 3 dimensions
    shape = (64, 64, 64)
    quarter = np.array([s // 4 for s in shape])
    p1 = (-0.5, -0.5, -0.5)
    p2 = (0.5, 0.5, 0.5)
    r = 0.25  # in base coordinates; radius will be 1/4 the shape
    volume = phantom_base.cylinder(shape, p1, p2, r)
    np.testing.assert_array_equal(volume, volume[::-1, ::-1, ::-1])

    # make sure we have flattened at the edge of the cylinder
    # get the diagonal line through the cylinder, then check bounds
    line = np.array([volume[x, x, x] for x in range(shape[0])])
    nose.tools.assert_equal(phantom_base.nonzero_bounds(line),
                            (quarter[0], shape[0] - quarter[0]))
예제 #7
0
def test_cylinder_xaxis_spherecap():
    # cylinder along the xaxis, but with flat caps
    shape = (64, 64, 64)
    halfshape = tuple(s // 2 for s in shape)
    p1 = (-0.5, 0, 0)
    p2 = (0.5, 0, 0)
    r = 0.25  # in base coordinates; radius will be 1/4 the shape
    # radius in pixels; divide by 2 since space is [-1, 1]
    rpix = int(shape[0] * r / 2)
    volume = phantom_base.cylinder(shape, p1, p2, r, cap="sphere")
    center_line = volume[:, halfshape[1], halfshape[2]]
    # create the expected centerline between point1 and point2, plus the
    # spherical radius of each of the endcaps at both endpoints
    expected_line = np.zeros(shape[0], dtype=bool)
    expected_line[(halfshape[0] // 2 - rpix):(halfshape[0] +
                                              halfshape[0] // 2 + rpix)] = 1
    np.testing.assert_array_equal(center_line, expected_line)
예제 #8
0
def add_cylinder_basecoord(volume,
                           point1: (float, float, float),
                           point2: (float, float, float),
                           radius: float,
                           cap="sphere"):
    """
    Given two points and a radius in pixel space, allocate a volume with the target cylinder
    Internally, radius is normalized to the minimum dimension of the volume shape

    Inputs:
        volume: ndarray, grayscale volume (unboxed)
        point1: tuple, describes starting point of cylinder in volume, in base volume floats [-1, 1]
            eg, (-1, 0.56, 0.98)
        point2: tuple, describes starting point of cylinder in volume, in base volume floats [-1, 1]
        radius: float, the cylinder radius in base volume coordinates
        cap: type of cap on the cylinder, eg "flat", "sphere", or None.
            Types are defined in phantoms.base.CAP_TYPES

    Return:
        ndarray, dtype=bool, same shape as input `volume`

    TODO: Make this smarter:
        1) only allocate volume for minimum bounding box for the cylinder we are adding
        2) update original input `volume` slice with allocated cylinder volume
        Even better, mkae a cylinder as a primitive, instead of making cylinder out of spheres
    """
    # TODO add isotropy back to base cylinder call
    # # scale the isotropy
    # shape_min = min(volume.shape)  # used as scaling factor
    # isotropy = tuple(shape_min / s for s in volume.shape)

    # A smarter way to do this would be to allocate the minimum bounding box
    # necessary to enclose the cylinder, and insert that volume into a slice
    # of the original volume. Hard part is getting the scale units correct.
    newvolume = phantom_base.cylinder(volume.shape,
                                      point1,
                                      point2,
                                      radius,
                                      cap=cap)
    return np.logical_or(volume, newvolume)
예제 #9
0
def test_cylinder_offset_from_axis():
    # test cylinder line in negative quadrant
    shape = (64, 64, 64)
    quartershape = tuple(s // 4 for s in shape)
    p1 = (-.9, -0.5, -0.5)
    p2 = (-0.2, -0.5, -0.5)
    r = 0.2
    volume = phantom_base.cylinder(shape, p1, p2, r, cap="flat")
    expected_line = np.zeros(shape[0], dtype=bool)
    expected_line[4:26] = 1
    np.testing.assert_array_equal(volume[:, quartershape[1], quartershape[2]],
                                  expected_line)

    xindex = quartershape[0]
    xslice = volume[xindex, :, :]
    row = xslice[quartershape[1], :]
    col = xslice[:, quartershape[2]]
    expected_diameters = tuple(int(s * r) for s in shape)
    nose.tools.assert_almost_equal(_find_diameter(row),
                                   expected_diameters[0],
                                   delta=1)
    nose.tools.assert_almost_equal(_find_diameter(col),
                                   expected_diameters[1],
                                   delta=1)
예제 #10
0
def test_cylinder_cap_strmatch_assertion():
    # invalid cap types should fail
    shape = (4, 4, 4)
    cyl = ((0, 0, 0), (1, 1, 1), .5)
    with nose.tools.assert_raises(AssertionError):
        phantom_base.cylinder(shape, *cyl, cap="foo")