def nfold(im, mod, center=None, mask=None, fill_value=0.0): """ Returns an images averaged according to n-fold rotational symmetry. This can be used to boost the signal-to-noise ratio on an image with known symmetry, e.g. a diffraction pattern. Parameters ---------- im : array_like, ndim 2 Image to be azimuthally-symmetrized. center : array_like, shape (2,) or None, optional Coordinates of the center (in pixels). If ``center=None``, the image is rotated around its center, i.e. ``center=(rows / 2 - 0.5, cols / 2 - 0.5)``. mod : int Fold symmetry number. Valid numbers must be a divisor of 360. mask : `~numpy.ndarray` or None, optional Mask of `image`. The mask should evaluate to `True` (or 1) on invalid pixels. If None (default), no mask is used. fill_value : float, optional In the case of a mask that overlaps with itself when rotationally averaged, the overlapping regions will be filled with this value. Returns ------- out : `~numpy.ndarray`, dtype float Symmetrized image. Raises ------ ValueError : If `mod` is not a divisor of 360 deg. """ if (360 % mod): raise ValueError( '{}-fold rotational symmetry is not valid (not a divisor of 360).'. format(mod)) angles = range(0, 360, int(360 / mod)) # Data-type must be float because of use of NaN im = np.array(im, dtype=np.float, copy=True) if mask is not None: im[mask] = np.nan kwargs = { 'center': center, 'mode': 'constant', 'cval': 0, 'preserve_range': True } # Use weights because edges of the pictures, which might be cropped by the rotation # should not count in the average wt = np.ones_like(im, dtype=np.uint8) weights = (rotate(wt, angle, **kwargs) for angle in angles) rotated = (rotate(im, angle, **kwargs) for angle in angles) avg = average(rotated, weights=weights, ignore_nan=True) return nan_to_num(avg, fill_value, copy=False)
def test_vs_numpy(self): """ Test average vs. numpy.average """ stream = [np.random.random(size=(64, 64)) for _ in range(5)] stack = np.dstack(stream) for axis in (0, 1, 2, None): with self.subTest("axis = {}".format(axis)): from_stream = average(stream, axis=axis) from_numpy = np.average(stack, axis=axis) self.assertTrue(np.allclose(from_numpy, from_stream))
def test_weighted_average(self): """ Test results of weighted average against numpy.average """ stream = [np.random.random(size=(16, 16)) for _ in range(5)] with self.subTest("float weights"): weights = [random() for _ in stream] from_average = average(stream, weights=weights) from_numpy = np.average( np.dstack(stream), axis=2, weights=np.array(weights) ) self.assertTrue(np.allclose(from_average, from_numpy)) with self.subTest("array weights"): weights = [np.random.random(size=stream[0].shape) for _ in stream] from_average = average(stream, weights=weights) from_numpy = np.average( np.dstack(stream), axis=2, weights=np.dstack(weights) ) self.assertTrue(np.allclose(from_average, from_numpy))
def reflection(im, angle, center=None, mask=None, fill_value=0.0): """ Symmetrize an image according to a reflection plane. Parameters ---------- im : array_like, ndim 2 Image to be symmetrized. angle : float Angle (in degrees) of the line that defines the reflection plane. This angle increases counter-clockwise from the positive x-axis. Angles larger that 360 are mapped back to [0, 360). Note that ``angle`` and ``angle + 180`` are equivalent. center : array_like, shape (2,) or None, optional Coordinates of the center (in pixels). If ``center=None``, the image is rotated around its center, i.e. ``center=(rows / 2 - 0.5, cols / 2 - 0.5)``. mask : `~numpy.ndarray` or None, optional Mask of `image`. The mask should evaluate to `True` (or 1) on valid pixels. If None (default), no mask is used. fill_value : float, optional In the case of a mask that overlaps with itself when rotationally averaged, the overlapping regions will be filled with this value. Returns ------- out : `~numpy.ndarray`, dtype float Symmetrized image. """ angle = float(angle) % 360 # Data-type must be float because of use of NaN im = np.array(im, dtype=np.float, copy=True) reflected = np.array(im, copy=True) # reflected image if mask is not None: invalid_pixels = np.logical_not(mask) im[invalid_pixels] = np.nan reflected[invalid_pixels] = np.nan kwargs = { "center": center, "mode": "constant", "cval": 0, "preserve_range": True } # Rotate the 'reflected' image so that the reflection line is the x-axis # Flip the image along the y-axis # Rotate back to original orientation # FIXME: this will not work properly for images that are offcenter reflected = rotate(reflected, -angle, **kwargs) reflected = mirror(reflected, axes=0) reflected = rotate(reflected, angle, **kwargs) return nan_to_num(average([im, reflected]), fill_value, copy=False)
def test_ignore_nan(self): """ Test that NaNs are handled correctly """ stream = [np.random.random(size=(16, 12)) for _ in range(5)] for s in stream: s[randint(0, 15), randint(0, 11)] = np.nan with catch_warnings(): simplefilter("ignore") from_average = average(stream, ignore_nan=True) from_numpy = np.nanmean(np.dstack(stream), axis=2) self.assertTrue(np.allclose(from_average, from_numpy))
def _raw_combine(timedelay, raw, exclude_scans, normalize, align, valid_mask, dtype): images = raw.itertime(timedelay, exclude_scans=exclude_scans) if align: # Note : the fast = False fixes issue #11, where single crystal images were not successfully aligned. images = ialign(images, mask=valid_mask) # Set up normalization if normalize: images, images2 = itercopy(images, copies=2) # Compute the total intensity of first image # This will be the reference point first2, images2 = peek(images2) initial_weight = np.sum(first2[valid_mask]) weights = (initial_weight / np.sum(image[valid_mask]) for image in images2) else: weights = None return average(images, weights=weights).astype(dtype)
def test_trivial(self): """ Test average() on a stream of zeroes """ stream = repeat(np.zeros((64, 64), dtype=np.float), times=5) for av in average(stream): self.assertTrue(np.allclose(av, np.zeros_like(av)))
def background(self): """ Laser background """ backgrounds = map(diffread, iglob(join(self.source, "background.*.pumpoff.tif"))) return average(backgrounds)
def nfold(im, mod, center=None, mask=None, fill_value=0.0): """ Returns an images averaged according to n-fold rotational symmetry. This can be used to boost the signal-to-noise ratio on an image with known symmetry, e.g. a diffraction pattern. Parameters ---------- im : array_like, ndim 2 Image to be azimuthally-symmetrized. center : array_like, shape (2,) or None, optional Coordinates of the center (in pixels) in the format ``center=[col, row]``. If ``center=None``, the image is rotated around the center of the array, i.e. ``center=(cols / 2 - 0.5, rows / 2 - 0.5)``. mod : int Fold symmetry number. Valid numbers must be a divisor of 360. mask : `~numpy.ndarray` or None, optional Mask of `image`. The mask should evaluate to `True` (or 1) on valid pixels. If None (default), no mask is used. fill_value : float, optional In the case of a mask that overlaps with itself when rotationally averaged, the overlapping regions will be filled with this value. Returns ------- out : `~numpy.ndarray`, dtype float Symmetrized image. Raises ------ ValueError : If `mod` is not a divisor of 360 deg. """ if 360 % mod: raise ValueError( f"{mod}-fold rotational symmetry is not valid (not a divisor of 360)." ) angles = range(0, 360, int(360 / mod)) im = np.array(im, copy=True) kwargs = { "center": center, "mode": "constant", "cval": 0, "preserve_range": True } if mask is None: return ns.average(rotate(im, angle, **kwargs) for angle in angles) # Use weights because edges of the pictures, which might be cropped by the rotation # should not count in the average wt = np.ones_like(mask, dtype=im.dtype) wt[np.logical_not(mask)] = 0 weights = (rotate(wt, angle, **kwargs) for angle in angles) imgs = (rotate(im, angle, **kwargs) for angle in angles) avg = ns.average(imgs, weights=weights) # Mask may overlap with itself during symmetrization. At those points, the average # will be zero (because the weights are 0 there) # However, because users may want to change that value to `fill_value != 0`, we need # to know where is the overlap if fill_value != 0.0: invalid_pixels = np.logical_not(mask) masks = (rotate(invalid_pixels, angle, **kwargs) for angle in angles) overlap = ns.prod(masks).astype(bool) # equivalent to logical_and avg[overlap] = fill_value return ns.nan_to_num(avg, fill_value=fill_value)