def test_dimension(self): image_dim = (50, 50, 3) image_3d = np.random.default_rng().random(image_dim) image_2d = np.copy(image_3d[..., 0]) spline_3d = spl.radon(image_3d) spline_2d = spl.radon(image_2d) np.testing.assert_allclose( spline_2d, spline_3d[..., 0], rtol=1e-12, atol=1e-12) out_3d = spl.iradon(image_3d) out_2d = spl.iradon(image_2d) np.testing.assert_allclose( out_2d, out_3d[..., 0], rtol=1e-12, atol=1e-12)
def test_contiguous(self): image_dim = (50, 50, 3) image_3d = np.random.default_rng().random(image_dim) image_3d = np.transpose(image_3d, (1, 0, 2)) image_2d = np.copy(image_3d[..., 0]) spline_3d = spl.radon(image_3d) spline_2d = spl.radon(image_2d) np.testing.assert_allclose( spline_2d, spline_3d[..., 0], rtol=1e-9, atol=1e-12) out_3d = spl.iradon(spline_3d) out_2d = spl.iradon(spline_2d) np.testing.assert_allclose( out_2d, out_3d[..., 0], rtol=1e-9, atol=1e-12)
def test_padding(self): theta = np.arange(10) for size in range(5, 25): shape = (size, size) image = np.random.default_rng().random(shape) for circle in (True, False): rd = spl.radon(image, theta, circle=circle, b_spline_deg=(0, 0)) ird = spl.iradon(rd, theta, circle=circle, b_spline_deg=(0, 0)) np.testing.assert_array_equal(shape, ird.shape)
def test_deconv(self): size = 100 sample = primitives.boccia(size, radius=(0.5 * size) // 2, n_stripes=4) theta = np.arange(90) radon = spl.radon(sample, theta=theta, circle=True) for psf in self.psfs: s_fpsopt = imaging.fps_opt(sample, psf, theta=theta) decon = fpsopt.deconvolve_sinogram(s_fpsopt, psf, l=1e-12) snr = psnr(radon, decon) self.assertGreater(snr, 60)
def fps_opt(obj, psf, pad=False, **kwargs): """ Simulate the FPS-OPT (focal plane scanning) imaging of an object Parameters ---------- obj : array [ZXY] the object to be imaged psf : array [ZXY] or array [XY] the PSF of the system, or projected PSF along the Z axis pad : bool, optional pad the vield of view to see all contributions (required if the sample is not contained in the inner cylinder of the object), by default False **kwargs : to be passed to the radon call Returns ------- array [TPY] the imaged sinogram Raises ------ ValueError if the PSF is not 2D or 3D """ if psf.ndim == 3: psf = psf.sum(0, keepdims=True) elif psf.ndim == 2: psf = psf[None, ...] else: raise ValueError("Invalid dimensions for PSF: {}".format(psf.ndim)) sinogram = spl.radon(obj, circle=(not pad), **kwargs) mode = 'full' if pad else 'same' image = sig.fftconvolve(sinogram, psf, axes=(1, 2), mode=mode) return image
TEST_SIZE = 64 s_psf = optics.gaussian_psf(numerical_aperture=0.3, npix_axial=TEST_SIZE + 1, npix_lateral=TEST_SIZE + 1) i_psf = inverse_psf_rfft(s_psf, l=1e-15, mode='constant') psfft = fft.rfft2(s_psf.sum(0)) dirac = fft.irfft2(psfft * i_psf, s=s_psf.shape[1:]) sample = primitives.boccia(TEST_SIZE, radius=(0.8 * TEST_SIZE) // 2, n_stripes=4) s_theta = np.arange(90) s_radon = spl.radon(sample, theta=s_theta, circle=True) s_fpsopt = imaging.fps_opt(sample, s_psf, theta=s_theta) s_deconv = deconvolve_sinogram(s_fpsopt, s_psf, l=0) viewer = napari.view_image(s_radon) viewer.add_image(s_fpsopt) viewer.add_image(s_deconv) viewer = napari.view_image(fft.fftshift(np.abs(i_psf), 0), name='inverse PSF FFT') viewer.add_image(dirac) napari.run()
# This file is part of CBI Toolbox. # # CBI Toolbox is free software: you can redistribute it and/or modify # it under the terms of the 3-Clause BSD License. # # CBI Toolbox is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # 3-Clause BSD License for more details. # # You should have received a copy of the 3-Clause BSD License along # with CBI Toolbox. If not, see https://opensource.org/licenses/BSD-3-Clause. # # SPDX-License-Identifier: BSD-3-Clause import os import numpy as np import cbi_toolbox.splineradon as spl path = os.environ['OVC_PATH'] ipath = os.path.join(path, 'imaging') phantom = np.load(os.path.join(path, 'arrays', 'phantom.npy')) theta = np.linspace(0, 180, 360, endpoint=False) radon = spl.radon(phantom, theta=theta, circle=True) np.save(os.path.join(ipath, 'radon.npy'), radon) print('Saved Radon')
image = np.zeros((71, 71, 3)) image[2, 2, 0] = 1 image[50, 50, 0] = 1 image[10, 50, 1] = 1 image[60, 20, 1] = 1 image[30, 60, 2] = 1 image[30, 20, 2] = 1 image[35, 35, :] = 1 use_cuda = spl.is_cuda_available() if use_cuda: print('Running with GPU acceleration') else: print('Running on CPU') sinogram = spl.radon(image, use_cuda=use_cuda) reconstruct = spl.iradon(sinogram, use_cuda=use_cuda) reconstruct -= reconstruct.min() reconstruct /= reconstruct.max() sinogram[sinogram < 0] = 0 sinogram /= sinogram.max() plt.figure() plt.subplot(131) plt.imshow(image) plt.subplot(132) plt.imshow(sinogram) plt.subplot(133)
def fss_opt(obj, psf, illu, pad=False, **kwargs): """ Simulate the FSS-OPT (focal sheet scanning) imaging of an object Parameters ---------- obj : array [ZXY] the object to be imaged psf : array [ZXY] or array [XY] the PSF of the system illu : array [ZXY] the illumination function of the SPIM pad : bool, optional pad the vield of view to see all contributions if used, illu will be required to be bigger (required if the sample is not contained in the inner cylinder of the object), by default False **kwargs : to be passed to the radon call Returns ------- array [TPY] the imaged sinogram Raises ------ ValueError if the PSF dimensions do not fit the illumination if the illumination function has an incorrect shape """ if pad: # TODO use bigger SPIM illumination function to fit padded sinogram raise NotImplementedError() if psf.shape[0] % 2 != illu.shape[0] % 2: raise ValueError( 'In order to correctly center the illumination on the PSF,' ' please profide a PSF with the same Z axis parity' 'as the illumination Z axis') if not (psf.shape[1] % 2 and psf.shape[2] % 2): raise ValueError( 'In order to correctly center the PSF on the pixels,' ' please provide a PSF with odd parity on X and Y axis') crop = (psf.shape[0] - illu.shape[0]) // 2 if crop: psf = psf[crop:-crop, ...] psf = np.flip(psf) sinogram = spl.radon(obj, circle=(not pad), **kwargs) psf_xw = psf.shape[1] psf_yw = psf.shape[2] pad_x = psf_xw // 2 pad_y = psf_yw // 2 illu = np.pad(illu, ((0, ), (pad_x, ), (pad_y, ))) image = np.zeros((sinogram.shape[0], sinogram.shape[1] + psf_xw - 1, sinogram.shape[2] + psf_yw - 1), dtype=sinogram.dtype) for x in range(sinogram.shape[1]): for y in range(sinogram.shape[2]): local = np.sum(illu[:, x:x + psf_xw, y:y + psf_yw] * psf, 0) pixel = sinogram[:, x, y] spread = pixel[:, None, None] * local[None, ...] image[:, x:x + psf_xw, y:y + psf_xw] += spread if not pad: image = image[:, pad_x:-pad_x, pad_y:-pad_y] return image