def testArrayImageSourceRotsSetGet(self): """ Test ArrayImageSource `rots` property, setter and getter function. """ # Construct the source for testing src = ArrayImageSource(self.im) # Get some random angles, can use from sim. angles = self.sim.angles self.assertTrue(angles.shape == (src.n, 3)) # Convert to rotation matrix (n,3,3) rotations = R.from_euler("ZYZ", angles).as_matrix() self.assertTrue(rotations.shape == (src.n, 3, 3)) # Excercise the setter src.rots = rotations # Test Rots Getter self.assertTrue( np.allclose(rotations, src.rots, atol=utest_tolerance(self.dtype)) ) # Test Angles Getter self.assertTrue( np.allclose(angles, src.angles, atol=utest_tolerance(self.dtype)) )
def testArrayImageSource(self): """ An Image can be wrapped in an ArrayImageSource when we need to deal with ImageSource objects. This checks round trip conversion does not crash and returns identity. """ src = ArrayImageSource(self.im) im = src.images(start=0, num=np.inf) # returns Image instance self.assertTrue(np.allclose(im.asnumpy(), self.ims_np))
def testInvertContrast(self): sim1 = self.sim imgs1 = sim1.images(start=0, num=128) sim1.invert_contrast() imgs1_rc = sim1.images(start=0, num=128) # need to set the negative images to the second simulation object sim2 = ArrayImageSource(-imgs1) sim2.invert_contrast() imgs2_rc = sim2.images(start=0, num=128) # all images should be the same after inverting contrast self.assertTrue(np.allclose(imgs1_rc.asnumpy(), imgs2_rc.asnumpy()))
def testArrayImageSourceFromNumpy(self): """ An Array can be wrapped in an ArrayImageSource when we need to deal with ImageSource objects. This checks round trip conversion does not crash and returns identity. """ # Create an ArrayImageSource directly from Numpy array src = ArrayImageSource(self.ims_np) # Ask the Source for all images in the stack as a Numpy array ims_np = src.images(start=0, num=np.inf).asnumpy() # Comparison should be yield identity self.assertTrue(np.allclose(ims_np, self.ims_np))
def testArrayImageSourceMeanVol(self): """ Test that ArrayImageSource can be consumed by mean/volume codes. Checks that the estimate is consistent with Simulation source. """ # Run estimator with a Simulation source as a reference. sim_estimator = MeanEstimator(self.sim, self.basis, preconditioner="none") sim_est = sim_estimator.estimate() logger.info("Simulation source checkpoint") # Construct the source for testing src = ArrayImageSource(self.im, angles=self.sim.angles) # Instantiate a volume estimator using ArrayImageSource estimator = MeanEstimator(src, self.basis, preconditioner="none") # Get estimate consuming ArrayImageSource est = estimator.estimate() # Compute RMS error and log it for debugging. delta = np.sqrt(np.mean(np.square(est - sim_est))) logger.info(f"Simulation vs ArrayImageSource estimates MRSE: {delta}") # Estimate RMSE should be small. self.assertTrue(delta <= utest_tolerance(self.dtype)) # And the estimate themselves should be close (virtually same inputs). # We should be within same neighborhood as generating sim_est multiple times... self.assertTrue( np.allclose(est, sim_est, atol=10 * utest_tolerance(self.dtype)) )
def testArrayImageSourceNumpyError(self): """ Test that ArrayImageSource when instantiated with incorrect input gives appropriate error. """ # Test we raise with expected message from getter. with raises(RuntimeError, match=r"Creating Image object from Numpy.*"): _ = ArrayImageSource(np.empty((3, 2, 1)))
def testArrayImageSourceAnglesShape(self): """ Test ArrayImageSource `angles` argument shapes. """ # Construct the source with correct shape. _ = ArrayImageSource(self.im, angles=self.sim.angles) # Should match this error message. msg = r"Angles should be shape.*" # Construct the source with wrong shape. wrong_width = np.random.randn(self.n, 2) with raises(ValueError, match=msg): _ = ArrayImageSource(self.im, angles=wrong_width) wrong_dim = np.random.randn(self.n, 3, 3) with raises(ValueError, match=msg): _ = ArrayImageSource(self.im, angles=wrong_dim)
def output( self, classes, classes_refl, rot, shifts=None, coefs=None, ): """ Return class averages. :param classes: class indices (refering to src). (n_img, n_nbor) :param classes_refl: Bool representing whether to reflect image in `classes` :param rot: Array of in-plane rotation angles (Radians) of image in `classes` :param shifts: Optional array of shifts for image in `classes`. :coefs: Optional Fourier bessel coefs (avoids recomputing). :return: Stack of Synthetic Class Average images as Image instance. """ logger.info(f"Select {self.n_classes} Classes from Nearest Neighbors") # generate indices for random sample (can do something smart with corr later). # For testing just take the first n_classes so it matches earlier plots for manual comparison # This is assumed to be reasonably random. selection = np.arange(self.n_classes) imgs = self.src.images(0, self.src.n) fb_avgs = np.empty((self.n_classes, self.fb_basis.count), dtype=self.src.dtype) for i in tqdm(range(self.n_classes)): j = selection[i] # Get the neighbors neighbors_ids = classes[j] # Get coefs in Fourier Bessel Basis if not provided as an argument. if coefs is None: neighbors_imgs = Image(imgs[neighbors_ids]) if shifts is not None: neighbors_imgs.shift(shifts[i]) neighbors_coefs = self.fb_basis.evaluate_t(neighbors_imgs) else: neighbors_coefs = coefs[neighbors_ids] if shifts is not None: neighbors_coefs = self.fb_basis.shift( neighbors_coefs, shifts[i]) # Rotate in Fourier Bessel neighbors_coefs = self.fb_basis.rotate(neighbors_coefs, rot[j], classes_refl[j]) # Averaging in FB fb_avgs[i] = np.mean(neighbors_coefs, axis=0) # Now we convert the averaged images from FB to Cartesian. return ArrayImageSource(self.fb_basis.evaluate(fb_avgs))
def setUp(self): with importlib_resources.path(tests.saved_test_data, "sample_data_model.star") as path: self.starfile = StarFile(path) # Independent Image object for testing Image source methods L = 768 self.im = Image(misc.face(gray=True).astype("float64")[:L, :L]) self.img_src = ArrayImageSource(self.im) # We also want to flex the stack logic. self.n = 21 im_stack = np.broadcast_to(self.im.data, (self.n, L, L)) # make each image methodically different im_stack = np.multiply(im_stack, np.arange(self.n)[:, None, None]) self.im_stack = Image(im_stack) self.img_src_stack = ArrayImageSource(self.im_stack) # Create a tmpdir object for this test instance self._tmpdir = tempfile.TemporaryDirectory() # Get the directory from the name attribute of the instance self.tmpdir = self._tmpdir.name
def testAdaptiveSupportBadThreshold(self): """ Method should raise meaningful error when passed unreasonable thresholds. """ discs = np.empty((self.size, self.size)) # Intentional Dummy Data img_src = ArrayImageSource(discs) with pytest.raises(ValueError, match=r"Given energy_threshold.*"): _ = adaptive_support(img_src, -0.5) with pytest.raises(ValueError, match=r"Given energy_threshold.*"): _ = adaptive_support(img_src, 9000)
def testArrayImageSourceRotGetterError(self): """ Test that ArrayImageSource when instantiated without required rotations/angles gives an appropriate error. Here we specifically test `rots`. """ # Construct the source for testing. src = ArrayImageSource(self.im) # Test we raise with expected message from getter. with raises(RuntimeError, match=r"Consumer of ArrayImageSource.*"): _ = src.rots
def testParseval(self): """ Here we construct a source of white noise. Then code tests that the average noise power in the real domain, is equivalent to the sum of the magnitudes squared of all frequency coefficients in the Fourier domain. These are calculated by WhiteNoiseEstimator and AnisotropicNoiseEstimator respectively. See Parseval/Plancherel's Theorem. """ wht_noise = np.random.randn(1024, 128, 128).astype(self.dtype) src = ArrayImageSource(wht_noise) wht_noise_estimator = WhiteNoiseEstimator(src, batchSize=512) wht_noise_variance = wht_noise_estimator.estimate() noise_estimator = AnisotropicNoiseEstimator(src, batchSize=512) noise_variance = noise_estimator.estimate() self.assertTrue(np.allclose(noise_variance, wht_noise_variance))
def testArrayImageSourceAngGetterError(self): """ Test that ArrayImageSource when instantiated without required rotations/angles gives an appropriate error. """ # Construct the source for testing. # Rotations (via angles) are required, # but we intentionally do not pass # to instantiater here. src = ArrayImageSource(self.im) # Test we raise with expected message with raises(RuntimeError, match=r"Consumer of ArrayImageSource.*"): _ = src.angles # We also test that a source consumer generates same error, # by instantiating a volume estimator. estimator = MeanEstimator(src, self.basis, preconditioner="none") # Test we raise with expected message with raises(RuntimeError, match=r"Consumer of ArrayImageSource.*"): _ = estimator.estimate()
def test_adaptive_support_F(self): """ Test Fourier support of Gaussian relates to normal distribution. """ # Generate stack of 2D Gaussian images. imgs = np.tile( gaussian_2d(self.size, sigma_x=self.sigma, sigma_y=self.sigma), (self.n_disc, 1, 1), ) # Setup ImageSource like objects img_src = ArrayImageSource(imgs) for ref, threshold in self.references.items(): c, R = adaptive_support(img_src, threshold) # Assert spatial support is close to normal. R_true = ref * self.sigma # Standard deviation in Fourier space is given by 1/(2 * pi * # sigma). This can be obtained by applying the Poisson summation # formula to the continuous FT which gives that the discrete FT is # well approximated by a Gaussian with that particular standard # deviation. c_true = ref / (2 * np.pi * self.sigma) # Since we're dealing with the square of the Gaussian, this # effectively divides the sigmas by sqrt(2). R_true /= np.sqrt(2) c_true /= np.sqrt(2) # Accuracy is not perfect, but within 5% if sigma is in the right # range (too small, R is inaccurate; too big, c is inaccurate. self.assertTrue(abs(R - R_true) / R_true < 0.05) self.assertTrue(abs(c - c_true) / c_true < 0.05)
# ------------------------------ # # Now we will use an ASPIRE pipeline to whiten the image stack. # Here we will introduce our ``Source`` class and demonstrate applying an ``Xform``. # ``Source`` classes are what we use in processing pipelines. # They provide a consistent interface to a variety of underlying data sources. # In this case, we'll just use our Image in an ArrayImageSource to run a small experiment. # # If you were happy with the experiment design on an array of test data, # the source is easily swapped out to something like RelionSource, # which might point at a stack of images too large to fit in memory at once. # The implementation of batching for memory management # would be managed behind the scenes for you. imgs_src = ArrayImageSource(imgs_with_noise) # We'll copy the orginals for comparison later, before we process them further. noisy_imgs_copy = imgs_src.images(0, n_imgs) # One of the tools we can use is a NoiseEstimator, # which consumes from a Source. noise_estimator = AnisotropicNoiseEstimator(imgs_src) # Once we have the estimator instance, # we can use it in a transform applied to our Source. imgs_src.whiten(noise_estimator.filter) # Peek at two whitened images and their corresponding spectrum. fig, axs = plt.subplots(2, 2)
def testArrayImageSource(self): # An Image can be wrapped in an ArrayImageSource when we need to deal with ImageSource objects. src = ArrayImageSource(self.im) im = src.images(start=0, num=np.inf) self.assertTrue(np.allclose(im.asnumpy(), self.im_np))
classYOvalR = np.zeros((N, L, L)) for i, theta in enumerate(thetas): classRound[i] = np.asarray(PILImage.fromarray(round_disc).rotate(theta)) classOval[i] = np.asarray(PILImage.fromarray(oval_disc).rotate(theta)) classYOvalL[i] = np.asarray(PILImage.fromarray(yoval_discL).rotate(theta)) classYOvalR[i] = np.asarray(PILImage.fromarray(yoval_discR).rotate(theta)) # We'll make an example data set by concatentating then shuffling these. example_array = np.concatenate( (classRound, classOval, classYOvalL, classYOvalR)) np.random.seed(1234567) np.random.shuffle(example_array) # So now that we have cooked up an example dataset, lets create an ASPIRE source src = ArrayImageSource(example_array) # Let's peek at the images to make sure they're shuffled up nicely src.images(0, 10).show() # %% # Class Average # ------------- # # We use the ASPIRE ``RIRClass2D`` class to classify the images via the rotationally invariant representation (RIR) # algorithm. We then yield class averages by performing ``classify``. rir = RIRClass2D( src, fspca_components=400, bispectrum_components=300, # Compressed Features after last PCA stage.