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
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
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)
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 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)