Exemple #1
0
def isnr(images, fill_value=0.0):
    """
    Streaming, pixelwise signal-to-noise ratio (SNR).

    Parameters
    ----------
    images : iterable of ndarray
        These images should represent identical measurements. ``images`` can also be a generator.
    fill_value : float, optional
        Division-by-zero results will be filled with this value.

    Yields
    ------
    snr : `~numpy.ndarray`
        Pixelwise signal-to-noise ratio

    See Also
    --------
    snr_from_collection : pixelwise signal-to-noise ratio from a collection of measurements
    """
    first, images = peek(images)
    snr = np.empty_like(first)

    images1, images2 = itercopy(images, 2)
    for mean, std in zip(imean(images1), istd(images2)):
        valid = std != 0
        snr[valid] = mean[valid] / std[valid]
        snr[np.logical_not(valid)] = fill_value
        yield snr
Exemple #2
0
def mask_from_collection(images, px_thresh=(0, 3e4), std_thresh=None):
    """
    Determine binary mask from a set of images. These images should represent identical measurements, e.g. a set
    of diffraction patterns before photoexcitation. Pixels are rejected on the following two criteria:

        * Pixels with a value above a certain threshold or below zero, for any image in the set, are considered dead;
        * Pixels with a cumulative standard deviation above a certain threshold are considered uncertain.

    This function operates in constant-memory; it is therefore safe to use on a large collection
    of images (>10GB).

    Parameters
    ----------
    images : iterable of ndarray
        These images should represent identical measurements. ``images`` can also be a generator.
    px_thresh : float or iterable, optional
        Pixels with a value outside of [``min(px_thresh)``, ``max(px_thresh)``] in any of the images in ``images``
        are rejected. If ``px_thresh`` is a single float, it is assumed to be the maximal intensity, and no lower
        bound is enforced.
    std_thresh : int or float or None, optional
        Standard-deviation threshold. If the standard deviation of a pixel exceeds ``std_thresh``,
        it is rejected. If None (default), a threshold is not enforced.

    Returns
    -------
    mask : `~numpy.ndarray`, dtype bool
        Pixel mask. Pixels where ``mask`` is True are invalid.

    Notes
    -----
    ``numpy.inf`` can be used to have a lower pixel value bound but no upper bound. For example, to
    reject all negative pixels only, set ``px_thresh = (0, numpy.inf)``.
    """
    if isinstance(px_thresh, Iterable):
        min_int, max_int = min(px_thresh), max(px_thresh)
    else:
        min_int, max_int = None, px_thresh

    first, images = peek(images)
    mask = np.zeros_like(first, dtype=bool)  # 0 = False

    if std_thresh is not None:
        images, images_for_std = itercopy(images)
        std_calc = istd(images_for_std)
    else:
        std_calc = repeat(np.inf)

    for image, std in zip(images, std_calc):

        mask[image > max_int] = True

        if std_thresh is not None:
            mask[std > std_thresh] = True

        if min_int is not None:
            mask[image < min_int] = True

    return mask
Exemple #3
0
def register_time_shifts(traces, reference=None, method="auto"):
    """
    Measure the time shifts between time traces and a reference by cross-correlation.

    .. versionadded:: 1.0.1.1

    Parameters
    ----------
    traces : iterable of ndarrays
        Time traces. These time-traces should be physically equivalent. Generators of time traces
        are also supported. All traces and ``reference`` must have the same shape.
    reference : `~numpy.ndarray` or None, optional
        If provided, the time-zero shift between the traces in ``traces`` will be measured
        with respect to ``reference``. Otherwise, the first trace in ``traces`` will be used
        as a reference.
    method : str {'auto', 'fft', 'direct'}, optional
        A string indicating which method to use to calculate the correlation.

    Returns
    -------
    shifts : `~numpy.ndarray`, ndim 1, dtype float
        Time shifts as indices (possibly fractional). The length of ``shifts`` is always
        equal to the number of time-traces; in the case where ``reference = None``, the first
        shifts will always be identically zero.

    Raises
    ------
    ValueError : if not all traces have the same shape.
    ValueError : if traces are not 1D arrays

    See Also
    --------
    register_time_shift : measure time-shift between a single trace and a reference.
    """
    # fromiter can preallocate the full array if the number of traces
    # is known in advance
    try:
        count = len(traces)
    except TypeError:
        count = -1

    traces = iter(traces)

    if reference is None:
        reference, traces = peek(traces)
    reference = np.atleast_1d(reference)

    kwargs = {"reference": reference, "method": method}

    shifts = map(partial(register_time_shift, **kwargs), traces)
    return np.fromiter(shifts, dtype=float, count=count)
Exemple #4
0
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)
Exemple #5
0
    def from_collection(cls,
                        patterns,
                        filename,
                        time_points,
                        metadata,
                        valid_mask=None,
                        dtype=None,
                        ckwargs=None,
                        callback=None,
                        **kwargs):
        """
        Create a DiffractionDataset from a collection of diffraction patterns and metadata.

        Parameters
        ----------
        patterns : iterable of ndarray or ndarray
            Diffraction patterns. These should be in the same order as ``time_points``. Note that
            the iterable can be a generator, in which case it will be consumed. 
        filename : str or path-like
            Path to the assembled DiffractionDataset.
        time_points : array_like, shape (N,)
            Time-points of the diffraction patterns, in picoseconds.
        metadata : dict
            Valid keys are contained in ``DiffractionDataset.valid_metadata``.
        valid_mask : ndarray or None, optional
            Boolean array that evaluates to True on valid pixels. This information is useful in
            cases where a beamblock is used.
        dtype : dtype or None, optional
            Patterns will be cast to ``dtype``. If None (default), ``dtype`` will be set to the same
            data-type as the first pattern in ``patterns``.
        ckwargs : dict, optional
            HDF5 compression keyword arguments. Refer to ``h5py``'s documentation for details.
            Default is to use the `lzf` compression pipeline.
        callback : callable or None, optional
            Callable that takes an int between 0 and 99. This can be used for progress update when
            ``patterns`` is a generator and involves large computations.
        kwargs
            Keywords are passed to ``h5py.File`` constructor. 
            Default is file-mode 'x', which raises error if file already exists.
            Default libver is 'latest'.
        
        Returns
        -------
        dataset : DiffractionDataset
        """
        if "mode" not in kwargs:
            kwargs["mode"] = "x"

        if "libver" not in kwargs:
            kwargs["libver"] = "latest"

        # H5py will raise an exception if arrays are not contiguous
        # patterns = map(np.ascontiguousarray, iter(patterns))

        if callback is None:
            callback = lambda _: None

        time_points = np.array(time_points).reshape(-1)

        if ckwargs is None:
            ckwargs = {
                "compression": "lzf",
                "shuffle": True,
                "fletcher32": True
            }
        ckwargs[
            "chunks"] = True  # For some reason, if no chunking, writing to disk is SLOW

        first, patterns = peek(patterns)
        if dtype is None:
            dtype = first.dtype
        resolution = first.shape

        if valid_mask is None:
            valid_mask = np.ones(first.shape, dtype=np.bool)

        callback(0)
        with cls(filename, **kwargs) as file:

            # Note that keys not associated with an ExperimentalParameter
            # descriptor will not be recorded in the file.
            metadata.pop("time_points", None)
            for key, val in metadata.items():
                if key not in cls.valid_metadata:
                    continue
                setattr(file, key, val)

            # Record time-points as a dataset; then, changes to it will be reflected
            # in other dimension scales
            gp = file.experimental_parameters_group
            times = gp.create_dataset("time_points",
                                      data=time_points,
                                      dtype=np.float)
            mask = gp.create_dataset("valid_mask",
                                     data=valid_mask,
                                     dtype=np.bool)

            pgp = file.diffraction_group
            dset = pgp.create_dataset(name="intensity",
                                      shape=resolution + (len(time_points), ),
                                      dtype=dtype,
                                      **ckwargs)

            # Making use of the H5DS dimension scales
            # http://docs.h5py.org/en/latest/high/dims.html
            dset.dims.create_scale(times, "time-delay")
            dset.dims[2].attach_scale(times)

            # At each iteration, we flush the changes to file
            # If this is not done, data can be accumulated in memory (>5GB)
            # until this loop is done.
            for index, pattern in enumerate(patterns):
                dset.write_direct(pattern,
                                  source_sel=np.s_[:, :],
                                  dest_sel=np.s_[:, :, index])
                file.flush()
                callback(round(100 * index / np.size(time_points)))

        callback(100)

        # Now that the file exists, we can switch to read/write mode
        kwargs["mode"] = "r+"
        return cls(filename, **kwargs)