def test_astra_cuda_projector_parallel3d(): """Test 3D forward and backward projection functions on the GPU.""" # Create reco space and a phantom reco_space = odl.uniform_discr([-4, -5, -6], [4, 5, 6], (4, 5, 6), dtype='float32') phantom = odl.phantom.cuboid(reco_space, min_pt=[0, 0, 0], max_pt=[4, 5, 6]) # Create parallel geometry angle_part = odl.uniform_partition(0, np.pi, 8) det_part = odl.uniform_partition([-7, -8], [7, 8], (7, 8)) geom = odl.tomo.Parallel3dAxisGeometry(angle_part, det_part) # Make projection space proj_space = odl.uniform_discr_frompartition(geom.partition, dtype='float32') # Forward evaluation proj_data = astra_cuda_forward_projector(phantom, geom, proj_space) assert proj_data.shape == proj_space.shape assert proj_data.norm() > 0 # Backward evaluation backproj = astra_cuda_back_projector(proj_data, geom, reco_space) assert backproj.shape == reco_space.shape assert backproj.norm() > 0
def test_astra_cuda_projector_helical_conebeam(): """Test 3D forward and backward projection functions on the GPU.""" # Create reco space and a phantom reco_space = odl.uniform_discr([-4, -5, -6], [4, 5, 6], (4, 5, 6), dtype='float32') phantom = odl.phantom.cuboid(reco_space, min_pt=[0, 0, 0], max_pt=[4, 5, 6]) # Create circular cone beam geometry with flat detector angle_part = odl.uniform_partition(0, 2 * np.pi, 8) det_part = odl.uniform_partition([-7, -8], [7, 8], (7, 8)) src_rad = 1000 det_rad = 100 pitch = 0.5 geom = odl.tomo.HelicalConeFlatGeometry(angle_part, det_part, src_rad, det_rad, pitch) # Make projection space proj_space = odl.uniform_discr_frompartition(geom.partition, dtype='float32') # Forward evaluation proj_data = astra_cuda_forward_projector(phantom, geom, proj_space) assert proj_data.shape == proj_space.shape assert proj_data.norm() > 0 # Backward evaluation backproj = astra_cuda_back_projector(proj_data, geom, reco_space) assert backproj.shape == reco_space.shape assert backproj.norm() > 0
def test_astra_cpu_projector_parallel2d(): """ASTRA CPU forward and back projection for 2d parallel geometry.""" # Create reco space and a phantom reco_space = odl.uniform_discr([-4, -5], [4, 5], (4, 5), dtype='float32') phantom = odl.phantom.cuboid(reco_space, begin=[0, 0], end=[4, 5]) # Create parallel geometry angle_part = odl.uniform_partition(0, 2 * np.pi, 8) det_part = odl.uniform_partition(-6, 6, 6) geom = odl.tomo.Parallel2dGeometry(angle_part, det_part) # Make projection space proj_space = odl.uniform_discr_frompartition(geom.partition, dtype='float32') # Forward evaluation proj_data = astra_cpu_forward_projector(phantom, geom, proj_space) assert proj_data.shape == proj_space.shape assert proj_data.norm() > 0 # Backward evaluation backproj = astra_cpu_back_projector(proj_data, geom, reco_space) assert backproj.shape == reco_space.shape assert backproj.norm() > 0
def load_data(): """Get the data from disk. Returns ------- mat1_sino, mat2_sino : numpy.ndarray projection of material 1 and 2 geometry : odl.tomo.Geometry Geometry of the data """ current_path = os.path.dirname(os.path.realpath(__file__)) data_path = os.path.join(current_path, 'data', 'aux_corr_in_real_ct_image.mat') try: data_mat = sio.loadmat(data_path) except IOError: raise IOError('data/aux_corr_in_real_ct_image.mat missing, contact ' 'developers for a copy of the data or use another data ' 'source.') data = data_mat['decomposedBasisProjectionsmmObj'] data = data.swapaxes(0, 2) angle_partition = odl.uniform_partition(0, np.pi, 180) detector_partition = odl.uniform_partition(-150 * np.sqrt(2), 150 * np.sqrt(2), 853) geometry = odl.tomo.Parallel2dGeometry(angle_partition, detector_partition) return data, geometry
def test_astra_cpu_projector_fanflat(): """ASTRA CPU forward and back projection for fanflat geometry.""" # Create reco space and a phantom reco_space = odl.uniform_discr([-4, -5], [4, 5], (4, 5), dtype='float32') phantom = odl.phantom.cuboid(reco_space, begin=[0, 0], end=[4, 5]) # Create fan beam geometry with flat detector angle_part = odl.uniform_partition(0, 2 * np.pi, 8) det_part = odl.uniform_partition(-6, 6, 6) src_rad = 100 det_rad = 10 geom = odl.tomo.FanFlatGeometry(angle_part, det_part, src_rad, det_rad) # Make projection space proj_space = odl.uniform_discr_frompartition(geom.partition, dtype='float32') # Forward evaluation proj_data = astra_cpu_forward_projector(phantom, geom, proj_space) assert proj_data.shape == proj_space.shape assert proj_data.norm() > 0 # Backward evaluation backproj = astra_cpu_back_projector(proj_data, geom, reco_space) assert backproj.shape == reco_space.shape assert backproj.norm() > 0
def test_empty_partition(): """Check if empty partitions behave as expected and all methods work.""" part = odl.RectPartition(odl.IntervalProd([], []), odl.uniform_grid([], [], ())) assert part.cell_boundary_vecs == () assert part.nodes_on_bdry is True assert part.nodes_on_bdry_byaxis == () assert part.has_isotropic_cells assert part.boundary_cell_fractions == () assert part.cell_sizes_vecs == () assert np.array_equal(part.cell_sides, []) assert part.cell_volume == 0 same = odl.RectPartition(odl.IntervalProd([], []), odl.uniform_grid([], [], ())) assert part == same assert hash(part) == hash(same) other = odl.uniform_partition(0, 1, 4) assert part != other assert part[[]] == part assert part.insert(0, other) == other assert other.insert(0, part) == other assert other.insert(1, part) == other assert part.squeeze() == part assert part.index([]) == () part.byaxis assert part == odl.uniform_partition([], [], ()) repr(part)
def test_circular_cone_flat(): """Conebeam geometry with circular acquisition and a flat 2D detector.""" # Parameters full_angle = np.pi apart = odl.uniform_partition(0, full_angle, 10) dpart = odl.uniform_partition([0, 0], [1, 1], [10, 10]) src_rad = 10 det_rad = 5 with pytest.raises(TypeError): odl.tomo.CircularConeFlatGeometry([0, 1], dpart, src_rad, det_rad) with pytest.raises(TypeError): odl.tomo.CircularConeFlatGeometry(apart, [0, 1], src_rad, det_rad) with pytest.raises(ValueError): odl.tomo.CircularConeFlatGeometry(apart, dpart, -1, det_rad) with pytest.raises(ValueError): odl.tomo.CircularConeFlatGeometry(apart, dpart, src_rad, -1) # Initialize geom = odl.tomo.CircularConeFlatGeometry(apart, dpart, src_rad, det_rad) assert all_almost_equal(geom.angles, apart.points().ravel()) with pytest.raises(ValueError): geom.det_refpoint(2 * full_angle) assert geom.ndim == 3 assert np.linalg.norm(geom.det_refpoint(0)) == det_rad assert np.linalg.norm(geom.src_position(np.pi)) == src_rad assert isinstance(geom.detector, odl.tomo.Flat2dDetector) # check str and repr work without crashing and return something assert str(geom) assert repr(geom)
def test_fbp_reconstruction_filters(filter_type, frequency_scaling): """Validate that the various filters work as expected.""" apart = odl.uniform_partition(0, np.pi, 500) discr_reco_space = odl.uniform_discr([-20, -20], [20, 20], [100, 100], dtype='float32') # Geometry dpart = odl.uniform_partition(-30, 30, 500) geom = tomo.Parallel2dGeometry(apart, dpart) # Ray transform projector = tomo.RayTransform(discr_reco_space, geom, impl='astra_cuda') # Create Shepp-Logan phantom vol = odl.phantom.shepp_logan(projector.domain, modified=False) # Project data projections = projector(vol) # Create FBP operator with filters and apply to projections fbp_operator = odl.tomo.fbp_op(projector, filter_type=filter_type, frequency_scaling=frequency_scaling) fbp_result = fbp_operator(projections) maxerr = vol.norm() / 5.0 error = vol.dist(fbp_result) assert error < maxerr
def test_parallel_2d_orientation(det_pos_init_2d): """Check if the orientation is positive for any ``det_pos_init``.""" full_angle = np.pi apart = odl.uniform_partition(0, full_angle, 10) dpart = odl.uniform_partition(0, 1, 10) det_pos_init = det_pos_init_2d geom = odl.tomo.Parallel2dGeometry(apart, dpart, det_pos_init=det_pos_init) assert all_almost_equal(geom.det_pos_init, det_pos_init) assert all_almost_equal(geom.det_refpoint(0), det_pos_init) assert all_almost_equal(geom.det_point_position(0, 0), det_pos_init) # The detector-to-source normal should always be perpendicular to the # detector axis and form a positively oriented system for angle in [0, np.pi / 2, 3 * np.pi / 4, np.pi]: dot = np.dot(geom.det_to_src(angle, 0), geom.det_axis(angle)) assert dot == pytest.approx(0) normal_det_to_src = (geom.det_to_src(angle, 0) / np.linalg.norm(geom.det_to_src(angle, 0))) orient = np.linalg.det( np.vstack([normal_det_to_src, geom.det_axis(angle)])) assert orient == pytest.approx(1)
def test_discretizedspace_init(): """Test initialization and basic properties of DiscretizedSpace.""" # Real space part = odl.uniform_partition([0, 0], [1, 1], (2, 4)) tspace = odl.rn(part.shape) discr = DiscretizedSpace(part, tspace) assert discr.tspace == tspace assert discr.partition == part assert discr.exponent == tspace.exponent assert discr.axis_labels == ('$x$', '$y$') assert discr.is_real # Complex space tspace_c = odl.cn(part.shape) discr = DiscretizedSpace(part, tspace_c) assert discr.is_complex # Make sure repr shows something assert repr(discr) != '' # Error scenarios part_1d = odl.uniform_partition(0, 1, 2) with pytest.raises(ValueError): DiscretizedSpace(part_1d, tspace) # wrong dimensionality part_diffshp = odl.uniform_partition([0, 0], [1, 1], (3, 4)) with pytest.raises(ValueError): DiscretizedSpace(part_diffshp, tspace) # shape mismatch
def test_geom_to_vec(): """Create ASTRA projection geometry vectors using ODL geometries.""" apart = odl.uniform_partition(0, 2 * np.pi, 5) dpart = odl.uniform_partition(-40, 40, 10) # Fanbeam flat src_rad = 10 det_rad = 5 geom_ff = odl.tomo.FanFlatGeometry(apart, dpart, src_rad, det_rad) vec = odl.tomo.astra_conebeam_2d_geom_to_vec(geom_ff) assert vec.shape == (apart.size, 6) # Circular cone flat dpart = odl.uniform_partition([-40, -3], [40, 3], (10, 5)) geom_ccf = odl.tomo.CircularConeFlatGeometry(apart, dpart, src_rad, det_rad) vec = odl.tomo.astra_conebeam_3d_geom_to_vec(geom_ccf) assert vec.shape == (apart.size, 12) # Helical cone flat pitch = 1 geom_hcf = odl.tomo.HelicalConeFlatGeometry(apart, dpart, src_rad, det_rad, pitch) vec = odl.tomo.astra_conebeam_3d_geom_to_vec(geom_hcf) assert vec.shape == (apart.size, 12)
def test_astra_cuda_projector_helical_conebeam(): """Test 3D forward and backward projection functions on the GPU.""" # Create reco space and a phantom reco_space = odl.uniform_discr([-4, -5, -6], [4, 5, 6], (4, 5, 6), dtype='float32') phantom = odl.phantom.cuboid(reco_space, begin=[0, 0, 0], end=[4, 5, 6]) # Create circular cone beam geometry with flat detector angle_part = odl.uniform_partition(0, 2 * np.pi, 8) det_part = odl.uniform_partition([-7, -8], [7, 8], (7, 8)) src_rad = 1000 det_rad = 100 pitch = 0.5 geom = odl.tomo.HelicalConeFlatGeometry(angle_part, det_part, src_rad, det_rad, pitch) # Make projection space proj_space = odl.uniform_discr_frompartition(geom.partition, dtype='float32') # Forward evaluation proj_data = astra_cuda_forward_projector(phantom, geom, proj_space) assert proj_data.shape == proj_space.shape assert proj_data.norm() > 0 # Backward evaluation backproj = astra_cuda_back_projector(proj_data, geom, reco_space) assert backproj.shape == reco_space.shape assert backproj.norm() > 0
def test_parallel_3d_frommatrix(): """Test the ``frommatrix`` constructor in 3d parallel geometry.""" full_angle = np.pi apart = odl.uniform_partition(0, full_angle, 10) dpart = odl.uniform_partition([0, 0], [1, 1], (10, 10)) # Need a somewhat simpler rotation in 3d to not go crazy imagining the # rotation. # This one rotates by +pi/2 in phi and -pi/2 in theta, resulting in the # axis remapping x->y, y->-z, z->-x. rot_matrix = np.array([[0, 0, -1], [1, 0, 0], [0, -1, 0]], dtype=float) geom = odl.tomo.Parallel3dAxisGeometry.frommatrix(apart, dpart, rot_matrix) # Axis was [0, 0, 1], gets mapped to [-1, 0, 0] assert all_almost_equal(geom.axis, [-1, 0, 0]) # Detector position starts at [0, 1, 0] and gets mapped to [0, 0, -1] assert all_almost_equal(geom.det_pos_init, [0, 0, -1]) # Detector axes start at [1, 0, 0] and [0, 0, 1], and get mapped to # [0, 1, 0] and [-1, 0, 0] assert all_almost_equal(geom.det_axes_init, ([0, 1, 0], [-1, 0, 0])) # With translation (1, 1, 1) matrix = np.hstack([rot_matrix, [[1], [1], [1]]]) geom = odl.tomo.Parallel3dAxisGeometry.frommatrix(apart, dpart, matrix) assert all_almost_equal(geom.translation, (1, 1, 1)) assert all_almost_equal(geom.det_pos_init, geom.translation + [0, 0, -1])
def test_parallel_2d_slanted_detector(): """Check if non-standard detector axis is handled correctly.""" full_angle = np.pi apart = odl.uniform_partition(0, full_angle, 10) dpart = odl.uniform_partition(0, 1, 10) # Detector forms a 45 degree angle with the x axis at initial position, # with positive direction upwards init_axis = [1, 1] geom = odl.tomo.Parallel2dGeometry(apart, dpart, det_pos_init=[0, 1], det_axis_init=init_axis) assert all_almost_equal(geom.det_pos_init, [0, 1]) norm_axis = np.array(init_axis, dtype=float) norm_axis /= np.linalg.norm(norm_axis) assert all_almost_equal(geom.det_axis_init, norm_axis) sqrt_1_2 = np.sqrt(1.0 / 2.0) # At angle 0: detector position at param 1 should be # [0, 1] + [sqrt(1/2), sqrt(1/2)] assert all_almost_equal(geom.det_point_position(0, 1), [sqrt_1_2, 1 + sqrt_1_2]) # At angle pi/2: detector position at param 1 should be # [-1, 0] + [-sqrt(1/2), +sqrt(1/2)] assert all_almost_equal(geom.det_point_position(np.pi / 2, 1), [-1 - sqrt_1_2, sqrt_1_2])
def test_parallel_3d_orientation(axis): """Check if the orientation is positive for any ``axis``.""" full_angle = np.pi apart = odl.uniform_partition(0, full_angle, 10) dpart = odl.uniform_partition([0, 0], [1, 1], (10, 10)) geom = odl.tomo.Parallel3dAxisGeometry(apart, dpart, axis=axis) norm_axis = np.array(axis, dtype=float) / np.linalg.norm(axis) assert all_almost_equal(geom.axis, norm_axis) # The symmetry axis should be the second detector axis by default assert all_almost_equal(geom.axis, geom.det_axes_init[1]) # The detector-to-source normal should always be perpendicular to both # detector axes, and the triplet (normal, det_ax0, det_ax1) should form # a positively oriented system for angle in [0, np.pi / 2, 3 * np.pi / 4, np.pi]: dot0 = np.dot(geom.det_to_src(angle, [0, 0]), geom.det_axes(angle)[0]) dot1 = np.dot(geom.det_to_src(angle, [0, 0]), geom.det_axes(angle)[1]) assert dot0 == pytest.approx(0) assert dot1 == pytest.approx(0) normal_det_to_src = (geom.det_to_src(angle, [0, 0]) / np.linalg.norm(geom.det_to_src(angle, [0, 0]))) axis_0, axis_1 = geom.det_axes(angle) orient = np.linalg.det(np.vstack([normal_det_to_src, axis_0, axis_1])) assert orient == pytest.approx(1) # check str and repr work without crashing and return a non-empty string assert str(geom) assert repr(geom)
def test_scikit_radon_projector_parallel2d(): """Parallel 2D forward and backward projectors on the GPU.""" # Create reco space and a phantom reco_space = odl.uniform_discr([-5, -5], [5, 5], (5, 5), dtype='float32') phantom = odl.util.phantom.cuboid(reco_space, begin=0.5, end=1) # Create parallel geometry angle_part = odl.uniform_partition(0, 2 * np.pi, 8) det_part = odl.uniform_partition(-6, 6, 6) geom = odl.tomo.Parallel2dGeometry(angle_part, det_part) # Make projection space proj_space = odl.uniform_discr_frompartition(geom.partition, dtype='float32') # Forward evaluation proj_data = scikit_radon_forward(phantom, geom, proj_space) assert proj_data.shape == proj_space.shape assert proj_data.norm() > 0 # Backward evaluation backproj = scikit_radon_back_projector(proj_data, geom, reco_space) assert backproj.shape == reco_space.shape assert backproj.norm() > 0
def geometry(request): geom = request.param m = 100 n_angles = 100 if geom == 'par2d': apart = odl.uniform_partition(0, np.pi, n_angles) dpart = odl.uniform_partition(-30, 30, m) return odl.tomo.Parallel2dGeometry(apart, dpart) elif geom == 'par3d': apart = odl.uniform_partition(0, np.pi, n_angles) dpart = odl.uniform_partition([-30, -30], [30, 30], (m, m)) return odl.tomo.Parallel3dAxisGeometry(apart, dpart) elif geom == 'cone2d': apart = odl.uniform_partition(0, 2 * np.pi, n_angles) dpart = odl.uniform_partition(-30, 30, m) return odl.tomo.FanFlatGeometry(apart, dpart, src_radius=200, det_radius=100) elif geom == 'cone3d': apart = odl.uniform_partition(0, 2 * np.pi, n_angles) dpart = odl.uniform_partition([-60, -60], [60, 60], (m, m)) return odl.tomo.ConeFlatGeometry(apart, dpart, src_radius=200, det_radius=100) elif geom == 'helical': apart = odl.uniform_partition(0, 8 * 2 * np.pi, n_angles) dpart = odl.uniform_partition([-30, -3], [30, 3], (m, m)) return odl.tomo.ConeFlatGeometry(apart, dpart, pitch=5.0, src_radius=200, det_radius=100) else: raise ValueError('geom not valid')
def test_scikit_radon_projector_parallel2d(): """Parallel 2D forward and backward projectors on the GPU.""" # Create reco space and a phantom reco_space = odl.uniform_discr([-5, -5], [5, 5], (5, 5), dtype='float32') phantom = odl.phantom.cuboid(reco_space, begin=[0, 0], end=[5, 5]) # Create parallel geometry angle_part = odl.uniform_partition(0, 2 * np.pi, 8) det_part = odl.uniform_partition(-6, 6, 6) geom = odl.tomo.Parallel2dGeometry(angle_part, det_part) # Make projection space proj_space = odl.uniform_discr_frompartition(geom.partition, dtype='float32') # Forward evaluation proj_data = scikit_radon_forward(phantom, geom, proj_space) assert proj_data.shape == proj_space.shape assert proj_data.norm() > 0 # Backward evaluation backproj = scikit_radon_back_projector(proj_data, geom, reco_space) assert backproj.shape == reco_space.shape assert backproj.norm() > 0
def test_astra_cuda_projector_parallel3d(): """Test 3D forward and backward projection functions on the GPU.""" # Create reco space and a phantom reco_space = odl.uniform_discr([-4, -5, -6], [4, 5, 6], (4, 5, 6), dtype='float32') phantom = odl.phantom.cuboid(reco_space, begin=[0, 0, 0], end=[4, 5, 6]) # Create parallel geometry angle_part = odl.uniform_partition(0, 2 * np.pi, 8) det_part = odl.uniform_partition([-7, -8], [7, 8], (7, 8)) geom = odl.tomo.Parallel3dAxisGeometry(angle_part, det_part) # Make projection space proj_space = odl.uniform_discr_frompartition(geom.partition, dtype='float32') # Forward evaluation proj_data = astra_cuda_forward_projector(phantom, geom, proj_space) assert proj_data.shape == proj_space.shape assert proj_data.norm() > 0 # Backward evaluation backproj = astra_cuda_back_projector(proj_data, geom, reco_space) assert backproj.shape == reco_space.shape assert backproj.norm() > 0
def test_geom_to_vec(): """Create ASTRA projection geometry vectors using ODL geometries.""" apart = odl.uniform_partition(0, 2 * np.pi, 5) dpart = odl.uniform_partition(-40, 40, 10) # Fanbeam flat src_rad = 10 det_rad = 5 geom_ff = odl.tomo.FanBeamGeometry(apart, dpart, src_rad, det_rad) vec = odl.tomo.astra_conebeam_2d_geom_to_vec(geom_ff) assert vec.shape == (apart.size, 6) # Circular cone flat dpart = odl.uniform_partition([-40, -3], [40, 3], (10, 5)) geom_ccf = odl.tomo.ConeBeamGeometry(apart, dpart, src_rad, det_rad) vec = odl.tomo.astra_conebeam_3d_geom_to_vec(geom_ccf) assert vec.shape == (apart.size, 12) # Helical cone flat pitch = 1 geom_hcf = odl.tomo.ConeBeamGeometry(apart, dpart, src_rad, det_rad, pitch=pitch) vec = odl.tomo.astra_conebeam_3d_geom_to_vec(geom_hcf) assert vec.shape == (apart.size, 12)
def test_astra_cpu_projector_fanflat(): """ASTRA CPU forward and back projection for fanflat geometry.""" # Create reco space and a phantom reco_space = odl.uniform_discr([-4, -5], [4, 5], (4, 5), dtype='float32') phantom = odl.phantom.cuboid(reco_space, min_pt=[0, 0], max_pt=[4, 5]) # Create fan beam geometry with flat detector angle_part = odl.uniform_partition(0, 2 * np.pi, 8) det_part = odl.uniform_partition(-6, 6, 6) src_rad = 100 det_rad = 10 geom = odl.tomo.FanFlatGeometry(angle_part, det_part, src_rad, det_rad) # Make projection space proj_space = odl.uniform_discr_frompartition(geom.partition, dtype='float32') # Forward evaluation proj_data = astra_cpu_forward_projector(phantom, geom, proj_space) assert proj_data.shape == proj_space.shape assert proj_data.norm() > 0 # Backward evaluation backproj = astra_cpu_back_projector(proj_data, geom, reco_space) assert backproj.shape == reco_space.shape assert backproj.norm() > 0
def test_astra_cpu_projector_parallel2d(): """ASTRA CPU forward and back projection for 2d parallel geometry.""" # Create reco space and a phantom reco_space = odl.uniform_discr([-4, -5], [4, 5], (4, 5), dtype='float32') phantom = odl.phantom.cuboid(reco_space, min_pt=[0, 0], max_pt=[4, 5]) # Create parallel geometry angle_part = odl.uniform_partition(0, 2 * np.pi, 8) det_part = odl.uniform_partition(-6, 6, 6) geom = odl.tomo.Parallel2dGeometry(angle_part, det_part) # Make projection space proj_space = odl.uniform_discr_frompartition(geom.partition, dtype='float32') # Forward evaluation proj_data = astra_cpu_forward_projector(phantom, geom, proj_space) assert proj_data.shape == proj_space.shape assert proj_data.norm() > 0 # Backward evaluation backproj = astra_cpu_back_projector(proj_data, geom, reco_space) assert backproj.shape == reco_space.shape assert backproj.norm() > 0
def prj_factory(Nx, Ny, Np, Nd, ret_A=False): ''' returns cuda fwd and bwd projectors for given 2d geometry. Inputs Nx,Ny -> voxels in x and y dims Np -> number of angles Nd -> number of det elements Outputs fwd -> forward projector, calculates Ax Input Nx*Ny matrix Output Np*Nd matrix bwd -> backward projector, calculates A^Tx Input Nx*Ny matrix Output Np*Nd matrix ''' reco_space = odl.uniform_discr([0, 0], [1, 1], [Nx, Ny], dtype='float32') angle_partition = odl.uniform_partition(0, 2 * pi, Np) detector_partition = odl.uniform_partition(-0.1, 1.1, Nd) geometry = odl.tomo.Parallel2dGeometry(angle_partition, detector_partition) ray_trafo = odl.tomo.RayTransform(reco_space, geometry, impl='astra_cuda') fwd = lambda X: ray_trafo(X).asarray() bwd = lambda X: ray_trafo.adjoint(X).asarray() return fwd, bwd
def test_parallel_3d_single_axis_geometry(): """General parallel 3D geometries.""" # Parameters full_angle = np.pi apart = odl.uniform_partition(0, full_angle, 10) dpart = odl.uniform_partition([0, 0], [1, 1], [10, 10]) # Bad init with pytest.raises(TypeError): odl.tomo.Parallel3dAxisGeometry([0, 1], dpart) with pytest.raises(TypeError): odl.tomo.Parallel3dAxisGeometry(apart, [0, 1]) # Initialize geom = odl.tomo.Parallel3dAxisGeometry(apart, dpart, axis=[0, 0, 1]) with pytest.raises(ValueError): geom.rotation_matrix(2 * full_angle) # rotation of cartesian basis vectors about each other coords = np.eye(3) geom = odl.tomo.Parallel3dAxisGeometry(apart, dpart, axis=[1, 0, 0]) rot_mat = geom.rotation_matrix(np.pi / 2) assert all_almost_equal(rot_mat.dot(coords), [[1, 0, 0], [0, 0, -1], [0, 1, 0]]) geom = odl.tomo.Parallel3dAxisGeometry(apart, dpart, axis=[0, 1, 0]) rot_mat = geom.rotation_matrix(np.pi / 2) assert all_almost_equal(rot_mat.dot(coords), [[0, 0, 1], [0, 1, 0], [-1, 0, 0]]) geom = odl.tomo.Parallel3dAxisGeometry(apart, dpart, axis=[0, 0, 1]) rot_mat = geom.rotation_matrix(np.pi / 2) assert all_almost_equal(rot_mat.dot(coords), [[0, -1, 0], [1, 0, 0], [0, 0, 1]]) # rotation axis geom = odl.tomo.Parallel3dAxisGeometry(apart, dpart, axis=[1, 0, 0]) assert all_equal(geom.axis, np.array([1, 0, 0])) geom = odl.tomo.Parallel3dAxisGeometry(apart, dpart, axis=[0, 1, 0]) assert all_equal(geom.axis, np.array([0, 1, 0])) geom = odl.tomo.Parallel3dAxisGeometry(apart, dpart, axis=[0, 0, 1]) assert all_equal(geom.axis, np.array([0, 0, 1])) geom = odl.tomo.Parallel3dAxisGeometry(apart, dpart, axis=[1, 2, 3]) assert all_equal(geom.axis, np.array([1, 2, 3]) / np.linalg.norm([1, 2, 3])) with pytest.raises(ValueError): odl.tomo.Parallel3dAxisGeometry(apart, dpart, axis=(1, )) with pytest.raises(ValueError): odl.tomo.Parallel3dAxisGeometry(apart, dpart, axis=(1, 2)) with pytest.raises(ValueError): odl.tomo.Parallel3dAxisGeometry(apart, dpart, axis=(1, 2, 3, 4)) # check str and repr work without crashing and return something assert str(geom) assert repr(geom)
def _build_forward_op(self, upscale_shape, impl, num_angles): reco_space = self.space if self.inner_circle: space = odl.uniform_discr(min_pt=reco_space.min_pt, max_pt=reco_space.max_pt, shape=(upscale_shape, upscale_shape), dtype=np.float32) min_pt = reco_space.min_pt max_pt = reco_space.max_pt proj_space = odl.uniform_discr( min_pt, max_pt, 2 * (2 * int(reco_space.max_pt[0]) - 1, ), dtype=np.float32) detector_length = get_detector_length(proj_space) det_partition = odl.uniform_partition( -np.sqrt((reco_space.shape[0] / 2.)**2 / 2), np.sqrt((reco_space.shape[0] / 2.)**2 / 2), detector_length) else: space = odl.uniform_discr(min_pt=reco_space.min_pt, max_pt=reco_space.max_pt, shape=(upscale_shape, upscale_shape), dtype=np.float32) min_pt = reco_space.min_pt max_pt = reco_space.max_pt proj_space = odl.uniform_discr(min_pt, max_pt, 2 * (reco_space.shape[0], ), dtype=np.float32) detector_length = get_detector_length(proj_space) det_partition = odl.uniform_partition(-reco_space.shape[0] / 2., reco_space.shape[0] / 2., detector_length) angle_partition = odl.uniform_partition(0, np.pi, num_angles) reco_geometry = odl.tomo.Parallel2dGeometry(angle_partition, det_partition) ray_trafo = odl.tomo.RayTransform(space, reco_geometry, impl=impl) def get_reco_ray_trafo(**kwargs): return odl.tomo.RayTransform(reco_space, reco_geometry, **kwargs) reco_ray_trafo = get_reco_ray_trafo(impl=impl) class _ResizeOperator(odl.Operator): def __init__(self): super().__init__(reco_space, space) def _call(self, x, out, **kwargs): out.assign( space.element( resize(x, (upscale_shape, upscale_shape), order=1))) # forward operator resize_op = _ResizeOperator() forward_op = ray_trafo * resize_op return forward_op, get_reco_ray_trafo, reco_ray_trafo
def __init__(self, data_obj, data_struct=True, **kwargs): # %% Store the input in the object # BE F*****G CONSISTENT WITH RENAMING ETC. self.pix = data_obj.voxels[0] self.angles = data_obj.angles self.src_rad = data_obj.src_rad self.det_rad = data_obj.det_rad self.magn = self.src_rad / (self.src_rad + self.det_rad) self.data_struct = data_struct self.rec_methods = [] # %% Set up the geometry self.phantom = data_obj # Make the reconstruction space self.reco_space = self.phantom.reco_space voxels = self.phantom.voxels factor = 2 dpix = [int(factor * voxels[0]), voxels[1]] self.w_detu = (2 * self.phantom.detecsize[0]) / dpix[0] self.w_detv = (2 * self.phantom.detecsize[1]) / dpix[1] if self.phantom.data_type == 'simulated': self.PH = data_obj.PH self.noise = data_obj.noise # Do we need a mask? src_radius = self.src_rad * self.phantom.volumesize[0] * 2 det_radius = self.det_rad * self.phantom.volumesize[0] * 2 # Make a circular scanning geometry angle_partition = odl.uniform_partition(0, 2 * np.pi, self.angles) self.angle_space = odl.uniform_discr_frompartition(angle_partition) # Make a flat detector space det_partition = odl.uniform_partition(-self.phantom.detecsize, self.phantom.detecsize, dpix) self.det_space = odl.uniform_discr_frompartition(det_partition, dtype='float32') # Create geometry self.geometry = odl.tomo.ConeFlatGeometry(angle_partition, det_partition, src_radius=src_radius, det_radius=det_radius, axis=[0, 0, 1]) else: src_radius = self.phantom.src_rad det_radius = self.phantom.det_rad self.pix_size = self.phantom.pix_size self.angle_space = self.phantom.angle_space self.angles = np.size(self.angle_space) self.det_space = self.phantom.det_space self.geometry = self.phantom.geometry # %% Create the FP and BP and the data # Forward Projection self.FwP = odl.tomo.RayTransform(self.reco_space, self.geometry, use_cache=False) self.rs_detu = int(2**(np.ceil(np.log2(dpix[0])) + 1)) self.g = self.phantom.g
def test_astra_projection_geometry(): """Create ASTRA projection geometry from geometry objects.""" with pytest.raises(TypeError): odl.tomo.astra_projection_geometry(None) apart = odl.uniform_partition(0, 2 * np.pi, 5) dpart = odl.uniform_partition(-40, 40, 10) # motion sampling grid, detector sampling grid but not uniform dpart_0 = odl.RectPartition(odl.IntervalProd(0, 3), odl.RectGrid([0, 1, 3])) geom_p2d = odl.tomo.Parallel2dGeometry(apart, dpart=dpart_0) with pytest.raises(ValueError): odl.tomo.astra_projection_geometry(geom_p2d) # detector sampling grid, motion sampling grid geom_p2d = odl.tomo.Parallel2dGeometry(apart, dpart) odl.tomo.astra_projection_geometry(geom_p2d) # Parallel 2D geometry geom_p2d = odl.tomo.Parallel2dGeometry(apart, dpart) astra_geom = odl.tomo.astra_projection_geometry(geom_p2d) assert astra_geom['type'] == 'parallel' # Fan flat src_rad = 10 det_rad = 5 geom_ff = odl.tomo.FanFlatGeometry(apart, dpart, src_rad, det_rad) astra_geom = odl.tomo.astra_projection_geometry(geom_ff) assert astra_geom['type'] == 'fanflat_vec' dpart = odl.uniform_partition([-40, -3], [40, 3], (10, 5)) # Parallel 3D geometry geom_p3d = odl.tomo.Parallel3dAxisGeometry(apart, dpart) odl.tomo.astra_projection_geometry(geom_p3d) astra_geom = odl.tomo.astra_projection_geometry(geom_p3d) assert astra_geom['type'] == 'parallel3d_vec' # Circular conebeam flat geom_ccf = odl.tomo.ConeFlatGeometry(apart, dpart, src_rad, det_rad) astra_geom = odl.tomo.astra_projection_geometry(geom_ccf) assert astra_geom['type'] == 'cone_vec' # Helical conebeam flat pitch = 1 geom_hcf = odl.tomo.ConeFlatGeometry(apart, dpart, src_rad, det_rad, pitch=pitch) astra_geom = odl.tomo.astra_projection_geometry(geom_hcf) assert astra_geom['type'] == 'cone_vec'
def test_detector_shifts_2d(): """Check that detector shifts are handled correctly. We forward project a cubic phantom and check that ray transform and back-projection with and without detector shifts are numerically close (the error depends on domain discretization). """ if not odl.tomo.ASTRA_AVAILABLE: pytest.skip(msg='ASTRA not available, skipping 2d test') d = 10 space = odl.uniform_discr([-1] * 2, [1] * 2, [d] * 2) phantom = odl.phantom.cuboid(space, [-1 / 3] * 2, [1 / 3] * 2) full_angle = 2 * np.pi n_angles = 2 * 10 src_rad = 2 det_rad = 2 apart = odl.uniform_partition(0, full_angle, n_angles) dpart = odl.uniform_partition(-4, 4, 8 * d) geom = odl.tomo.FanBeamGeometry(apart, dpart, src_rad, det_rad) k = 3 shift = k * dpart.cell_sides[0] geom_shift = odl.tomo.FanBeamGeometry( apart, dpart, src_rad, det_rad, det_shift_func=lambda angle: [0.0, shift] ) assert all_almost_equal(geom.angles, geom_shift.angles) angles = geom.angles assert all_almost_equal(geom.src_position(angles), geom_shift.src_position(angles)) assert all_almost_equal(geom.det_axis(angles), geom_shift.det_axis(angles)) assert all_almost_equal(geom.det_refpoint(angles), geom_shift.det_refpoint(angles) + shift * geom_shift.det_axis(angles)) # check ray transform op = odl.tomo.RayTransform(space, geom) op_shift = odl.tomo.RayTransform(space, geom_shift) y = op(phantom).asarray() y_shift = op_shift(phantom).asarray() # projection on the shifted detector is shifted regular projection data_error = np.max(np.abs(y[:, :-k] - y_shift[:, k:])) assert data_error < space.cell_volume # check back-projection im = op.adjoint(y).asarray() im_shift = op_shift.adjoint(y_shift).asarray() error = np.abs(im_shift - im) rel_error = np.max(error[im > 0] / im[im > 0]) assert rel_error < space.cell_volume
def test_partition_byaxis(): """Test indexing a partition along the axes.""" part = odl.uniform_partition([-1, -2, -3, -4], [1, 2, 3, 4], (1, 2, 4, 5)) assert part.byaxis[0] == odl.uniform_partition(-1, 1, 1) assert part.byaxis[1:3] == odl.uniform_partition([-2, -3], [2, 3], (2, 4)) assert (part.byaxis[[1, 3, 1]] == odl.uniform_partition([-2, -4, -2], [2, 4, 2], (2, 5, 2))) assert part.byaxis[[1]] == part.byaxis[1] assert part.byaxis[...] == part assert part.byaxis[:] == part
def test_detector_shifts_3d(): """Check that detector shifts are handled correctly. We forward project a cubic phantom and check that ray transform and back-projection with and without detector shifts are numerically close (the error depends on domain discretization). """ if not odl.tomo.ASTRA_AVAILABLE: pytest.skip(msg='ASTRA not available, skipping 2d test') d = 100 space = odl.uniform_discr([-1] * 3, [1] * 3, [d] * 3) phantom = odl.phantom.cuboid(space, [-1/3] * 3, [1/3] * 3) full_angle = 2 * np.pi n_angles = 2 * 100 src_rad = 2 det_rad = 2 apart = odl.uniform_partition(0, full_angle, n_angles) dpart = odl.uniform_partition([-4] * 2, [4] * 2, [8 * d] * 2) geom = odl.tomo.ConeBeamGeometry(apart, dpart, src_rad, det_rad) k = 3 l = 2 shift = np.array([0, k, l]) * dpart.cell_sides[0] geom_shift = odl.tomo.ConeBeamGeometry(apart, dpart, src_rad, det_rad, det_shift_func=lambda angle: shift) angles = geom.angles assert all_almost_equal(angles, geom_shift.angles) assert all_almost_equal(geom.src_position(angles), geom_shift.src_position(angles)) assert all_almost_equal(geom.det_axes(angles), geom_shift.det_axes(angles)) assert all_almost_equal(geom.det_refpoint(angles), geom_shift.det_refpoint(angles) + geom_shift.det_axes(angles)[:, 0] * shift[1] - geom_shift.det_axes(angles)[:, 1] * shift[2]) # check forward pass op = odl.tomo.RayTransform(space, geom) op_shift = odl.tomo.RayTransform(space, geom_shift) y = op(phantom).asarray() y_shift = op_shift(phantom).asarray() data_error = np.max(np.abs(y[:, :-k, l:] - y_shift[:, k:, :-l])) assert data_error < 1e-3 # check back-projection im = op.adjoint(y).asarray() im_shift = op_shift.adjoint(y_shift).asarray() error = np.max(np.abs(im_shift-im)) assert error < 1e-3
def limited_view_parallel_beam_geometry(space, beam_num_angle): corners = space.domain.corners()[:, :2] rho = np.max(np.linalg.norm(corners, axis=1)) 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 det_min_pt = -rho det_max_pt = rho det_shape = num_px_horiz angle_partition = odl.uniform_partition(0, beam_num_angle * (np.pi / 180), beam_num_angle) det_partition = odl.uniform_partition(det_min_pt, det_max_pt, det_shape) return odl.tomo.Parallel2dGeometry(angle_partition, det_partition)
def generate_data(self, voxels_up, reco_space_up, f_up, **kwargs): factor = 2 dpix_up = [factor * voxels_up[0], voxels_up[1]] dpix = [int(factor * self.voxels[0]), self.voxels[0]] src_radius = self.src_rad * self.volumesize[0] * 2 det_radius = self.det_rad * self.volumesize[0] * 2 # Make a circular scanning geometry angle_partition = odl.uniform_partition(0, 2 * np.pi, self.angles) # Make a flat detector space det_partition = odl.uniform_partition(-self.detecsize, self.detecsize, dpix_up) # Create data_space_up and data_space data_space = odl.uniform_discr((0, *-self.detecsize), (2 * np.pi, *self.detecsize), [self.angles, *dpix], dtype='float32') data_space_up = odl.uniform_discr((0, *-self.detecsize), (2 * np.pi, *self.detecsize), [self.angles, *dpix_up], dtype='float32') # Create geometry geometry = odl.tomo.ConeFlatGeometry(angle_partition, det_partition, src_radius=src_radius, det_radius=det_radius, axis=[0, 0, 1]) FP = odl.tomo.RayTransform(reco_space_up, geometry, use_cache=False) resamp = odl.Resampling(data_space_up, data_space) if 'load_data_g' in kwargs: if type(kwargs['load_data_g']) == str: self.g = data_space.element(np.load(kwargs['load_data_g'])) else: self.g = data_space.element(kwargs['load_data_g']) else: self.g = resamp(FP(f_up)) if self.noise == None: pass elif self.noise[0] == 'Gaussian': self.g += data_space.element( odl.phantom.white_noise(resamp.range) * \ np.mean(self.g) * self.noise[1]) elif self.noise[0] == 'Poisson': # 2**8 seems to be the minimal accepted I_0 self.g = data_space.element( self.add_poisson_noise(self.noise[1])) else: raise ValueError('unknown `noise type` ({})' ''.format(self.noise[0]))
def operators_smooth(): size = 512 space = odl.uniform_discr([-256, -256], [256, 256], [size, size], dtype='float32', weighting=1.0) angle_partition = odl.uniform_partition(0, 2 * np.pi, 1000) detector_partition = odl.uniform_partition(-360, 360, 1000) geometry = odl.tomo.Parallel2dGeometry(angle_partition, detector_partition) T = odl.tomo.RayTransform(space, geometry) fbp = odl.tomo.fbp_op(T, frequency_scaling=0.45, filter_type='Hann') T_norm = T.norm(estimate=True) T = (1 / T_norm) * T W = odl.Gradient(space) return [T, W]
def test_spect(): det_nx_pix = 64 det_ny_pix = 64 det_nx_mm = 4 det_radius = 200 n_proj = 180 det_param = det_nx_mm * det_nx_pix dpart = odl.uniform_partition([-det_param, -det_param], [det_param, det_param], [det_nx_pix, det_ny_pix]) apart = odl.uniform_partition(0, 2 * np.pi, n_proj) geom = ParallelHoleCollimatorGeometry(apart, dpart, det_radius) assert isinstance(geom.detector, odl.tomo.Flat2dDetector) assert all_equal(geom.det_radius, det_radius)
def test_proj_geom_parallel_2d(): """Create ASTRA 2D projection geometry.""" apart = odl.uniform_partition(0, 2, 5) dpart = odl.uniform_partition(-1, 1, 10) geom = odl.tomo.Parallel2dGeometry(apart, dpart) proj_geom = astra_projection_geometry(geom) correct_subdict = { 'type': 'parallel', 'DetectorCount': 10, 'DetectorWidth': 0.2} assert is_subdict(correct_subdict, proj_geom) assert 'ProjectionAngles' in proj_geom
def xray(nviews=15, ndectectors=100): gtruth = resolution_phantom(case=1, nsamples=200, highres=800) sinfo = resolution_phantom(case=2, nsamples=200, highres=800) angle_partition = odl.uniform_partition(0, np.pi, nviews) detector_partition = odl.uniform_partition(-1.5, 1.5, ndectectors) geometry = odl.tomo.Parallel2dGeometry(angle_partition, detector_partition) operator = odl.tomo.RayTransform(gtruth.space, geometry, impl='astra_cpu') data = odl.phantom.salt_pepper_noise(operator(gtruth), fraction=0.05, seed=10) return gtruth, sinfo, operator, data