def test_trim_and_mask(self): """ Test that regions that only have masks contributions are not present in the angular average """ image = np.ones(shape=(256, 256), dtype=np.float) center = (image.shape[0] / 2, image.shape[1] / 2) xc, yc = center # Create an image with a wide ring extent = np.arange(0, image.shape[0]) xx, yy = np.meshgrid(extent, extent) rr = np.hypot(xx - xc, yy - yc) mask = np.ones_like(image, dtype=np.bool) mask[rr < 20] = False # image[rr < 20] = 0 radius, intensity = azimuthal_average(image, center, mask=mask, trim=False) self.assertEqual(radius.min(), 0) radius_trimmed, intensity_trimmed = azimuthal_average(image, center, mask=mask, trim=True) self.assertEqual(radius_trimmed.min(), 20)
def test_mask_and_nan(self): """ Test that azimuthal_average with masks does not yield NaNs. This can happen for large masks. """ image = np.ones(shape=(256, 256), dtype=np.int16) mask = np.zeros_like(image, dtype=np.bool) mask[100:156, 100:156] = True _, av = azimuthal_average(image, center=(128, 128), mask=mask, trim=False) self.assertFalse(np.any(np.isnan(av)))
def test_trivial_array(self): """ Test azimuthal_average on an array of zeroes """ image = np.zeros(shape=(256, 256), dtype=np.float) center = (image.shape[0] / 2, image.shape[1] / 2) radius, intensity = azimuthal_average(image, center) self.assertTrue(intensity.sum() == 0) self.assertSequenceEqual(intensity.shape, radius.shape)
def test_azimuthal_average_trivial_array(): """ Test azimuthal_average on an array of zeroes """ image = np.zeros(shape=(256, 256), dtype=float) center = (image.shape[0] / 2, image.shape[1] / 2) radius, intensity = azimuthal_average(image, center) assert intensity.sum() == 0 assert intensity.shape == radius.shape
def test_ring(self): """ Test azimuthal_average on an image with a wide ring """ image = np.zeros(shape=(256, 256), dtype=np.float) center = (image.shape[0] / 2, image.shape[1] / 2) xc, yc = center # Create an image with a wide ring extent = np.arange(0, image.shape[0]) xx, yy = np.meshgrid(extent, extent) rr = np.sqrt((xx - xc)**2 + (yy - yc)**2) image[np.logical_and(24 < rr, rr < 26)] = 1 radius, intensity = azimuthal_average(image, center) self.assertEqual(intensity.max(), image.max()) self.assertSequenceEqual(radius.shape, intensity.shape)
def test_angular_bounds(self): """ Test azimuthal_average with a restrictive angular_bounds argument """ image = np.zeros(shape=(256, 256), dtype=np.float) center = (image.shape[0] / 2, image.shape[1] / 2) xc, yc = center # Create an image with a wide ring extent = np.arange(0, image.shape[0]) xx, yy = np.meshgrid(extent, extent) rr = np.sqrt((xx - xc)**2 + (yy - yc)**2) angles = np.rad2deg(np.arctan2(yy - yc, xx - xc)) + 180 image[np.logical_and(0 <= angles, angles <= 60)] = 1 with self.subTest("0 - 360"): radius, intensity = azimuthal_average(image, center, angular_bounds=None) r360, int360 = azimuthal_average(image, center, angular_bounds=(0, 360)) self.assertTrue(np.allclose(intensity, int360)) with self.subTest("Inside angle bounds"): radius, intensity = azimuthal_average(image, center, angular_bounds=(0, 60)) self.assertTrue(np.allclose(intensity, np.ones_like(intensity))) with self.subTest("Overlapping bounds"): radius, intensity = azimuthal_average(image, center, angular_bounds=(15, 75)) self.assertFalse(np.all(intensity < np.ones_like(intensity))) with self.subTest("Outside angle bounds"): radius, intensity = azimuthal_average(image, center, angular_bounds=(60, 360)) self.assertTrue(np.allclose(intensity, np.zeros_like(intensity))) with self.subTest("Inside angle bounds with 360deg rollover"): radius, intensity = azimuthal_average(image, center, angular_bounds=(60 + 360, 360 + 360)) self.assertTrue(np.allclose(intensity, np.zeros_like(intensity)))
def test_ring_with_mask(self): """ Test azimuthal_average on an image with a wide ring """ image = np.zeros(shape=(256, 256), dtype=np.float) center = (image.shape[0] / 2, image.shape[1] / 2) xc, yc = center mask = np.ones_like(image, dtype=np.bool) mask[120:140, 0:140] = False # Create an image with a wide ring extent = np.arange(0, image.shape[0]) xx, yy = np.meshgrid(extent, extent) rr = np.sqrt((xx - xc) ** 2 + (yy - yc) ** 2) image[np.logical_and(24 < rr, rr < 26)] = 1 # Add some ridiculously high value under the mask # to see if it will be taken intou account image[128, 128] = 10000 radius, intensity = azimuthal_average(image, center, mask=mask, trim=False) # The maximum value outside of the mask area was set to 1 self.assertEqual(intensity.max(), 1) self.assertSequenceEqual(radius.shape, intensity.shape)
def test_azimuthal_average_angular_bounds(): """ Test azimuthal_average with a restrictive angular_bounds argument """ image = np.zeros(shape=(256, 256), dtype=float) center = (image.shape[0] / 2, image.shape[1] / 2) xc, yc = center # Create an image with a wide ring extent = np.arange(0, image.shape[0]) xx, yy = np.meshgrid(extent, extent) rr = np.sqrt((xx - xc)**2 + (yy - yc)**2) angles = np.rad2deg(np.arctan2(yy - yc, xx - xc)) + 180 image[np.logical_and(0 <= angles, angles <= 60)] = 1 radius, intensity = azimuthal_average(image, center, angular_bounds=None) r360, int360 = azimuthal_average(image, center, angular_bounds=(0, 360)) assert np.allclose(intensity, int360) radius, intensity = azimuthal_average(image, center, angular_bounds=(0, 60)) assert np.allclose(intensity, np.ones_like(intensity)) radius, intensity = azimuthal_average(image, center, angular_bounds=(15, 75)) assert not np.all(intensity < np.ones_like(intensity)) radius, intensity = azimuthal_average(image, center, angular_bounds=(60, 360)) assert np.allclose(intensity, np.zeros_like(intensity)) radius, intensity = azimuthal_average(image, center, angular_bounds=(60 + 360, 360 + 360)) assert np.allclose(intensity, np.zeros_like(intensity))
def compute_angular_averages( self, center=None, normalized=False, angular_bounds=None, trim=True, callback=None, ): """ Compute the angular averages. Parameters ---------- center : 2-tuple or None, optional Center of the diffraction patterns. If None (default), the dataset attribute will be used instead. normalized : bool, optional If True, each pattern is normalized to its integral. angular_bounds : 2-tuple of float or None, optional Angle bounds are specified in degrees. 0 degrees is defined as the positive x-axis. Angle bounds outside [0, 360) are mapped back to [0, 360). trim : bool, optional If True, leading/trailing zeros - possibly due to masks - are trimmed. callback : callable or None, optional Callable of a single argument, to which the calculation progress will be passed as an integer between 0 and 100. """ # TODO: allow to cut away regions if not any([self.center, center]): raise RuntimeError( "Center attribute must be either saved in the dataset \ as an attribute or be provided.") if callback is None: callback = lambda i: None if center is not None: self.center = center # Because it is difficult to know the angular averaged data's shape in advance, # we calculate it first and store it next callback(0) results = list() for index, timedelay in enumerate(self.time_points): px_radius, avg = azimuthal_average( self.diff_data(timedelay), center=self.center, mask=self.valid_mask, angular_bounds=angular_bounds, trim=False, ) # px_radius is not stored but used once results.append(avg) callback(int(100 * index / len(self.time_points))) # Concatenate arrays for intensity and error # If trimming is enabled, there might be a problem where # different averages are trimmed to different length # therefore, we trim to the most restrictive bounds if trim: bounds = [_trim_bounds(I) for I in results] min_bound = max(min(bound) for bound in bounds) max_bound = min(max(bound) for bound in bounds) results = [I[min_bound:max_bound] for I in results] px_radius = px_radius[min_bound:max_bound] rintensity = np.stack(results, axis=0) if normalized: rintensity /= np.sum(rintensity, axis=1, keepdims=True) # We allow resizing. In theory, an angular averave could never be # longer than the diagonal of resolution self.powder_group["intensity"].resize(rintensity.shape) self.powder_group["intensity"].write_direct(rintensity) self.powder_group["px_radius"].resize(px_radius.shape) self.powder_group["px_radius"].write_direct(px_radius) # Use px_radius as placeholder for scattering_vector until calibration self.powder_group["scattering_vector"].resize(px_radius.shape) self.powder_group["scattering_vector"].write_direct(px_radius) self.powder_group["baseline"].resize(rintensity.shape) self.powder_group["baseline"].write_direct(np.zeros_like(rintensity)) self.powder_eq.cache_clear() callback(100)