Example #1
0
def _get_main_header_from_signal(signal,
                                 version=2,
                                 frame_header_extra_bytes=0):
    dt = np.dtype(TVIPS_RECORDER_GENERAL_HEADER)
    header = np.zeros((1, ), dtype=dt)
    header['size'] = dt.itemsize
    header['version'] = version
    # original pixel scale
    mode = _guess_image_mode(signal)
    scale = signal.axes_manager[-1].scale
    offsetx = signal.axes_manager[-2].offset
    offsety = signal.axes_manager[-1].offset
    unit = signal.axes_manager[-1].units
    if mode == 1:
        to_unit = "nm"
    elif mode == 2:
        to_unit = "1/nm"
    else:
        to_unit = ""
    if to_unit:
        scale = round((scale * _ureg(unit)).to(to_unit).magnitude)
        offsetx = round((offsetx * _ureg(unit)).to(to_unit).magnitude)
        offsety = round((offsety * _ureg(unit)).to(to_unit).magnitude)
    else:
        warnings.warn(
            "Image scale units could not be converted, "
            "saving axes scales as is.", UserWarning)
    header['dimx'] = signal.axes_manager[-2].size
    header['dimy'] = signal.axes_manager[-1].size
    header['offsetx'] = offsetx
    header['offsety'] = offsety
    header['pixelsize'] = scale
    header['bitsperpixel'] = signal.data.dtype.itemsize * 8
    header['binx'] = 1
    header['biny'] = 1
    dtf = np.dtype(TVIPS_RECORDER_FRAME_HEADER)
    header['frameheaderbytes'] = dtf.itemsize + frame_header_extra_bytes
    header['dummy'] = "HYPERSPY " * 22 + "HYPERS"
    header['ht'] = signal.metadata.get_item(
        "Acquisition_instrument.TEM.beam_energy", 0)
    cl = signal.metadata.get_item("Acquisition_instrument.TEM.camera_length",
                                  0)
    mag = signal.metadata.get_item("Acquisition_instrument.TEM.magnification",
                                   0)
    if (cl != 0 and mag != 0):
        header['magtotal'] = 0
    elif (cl != 0 and mode == 2):
        header['magtotal'] = cl
    elif (mag != 0 and mode == 1):
        header['magtotal'] = mag
    else:
        header['magtotal'] = 0
    return header
Example #2
0
def _convert_scale_units(value, units, factor=1):
    v = float(value) * _ureg(units)
    converted_v = (factor * v).to_compact()
    converted_value = converted_v.magnitude / factor
    converted_units = '{:~}'.format(converted_v.units)

    return converted_value, converted_units
Example #3
0
def _guess_image_mode(signal):
    """
    Guess whether the dataset contains images (1) or diffraction patterns (2).
    If no decent guess can be made, None is returned.
    """
    # original pixel scale
    scale = signal.axes_manager[-1].scale
    unit = signal.axes_manager[-1].units
    mode = None
    try:
        pixel_size = scale * _ureg(unit)
    except (AttributeError, pint.UndefinedUnitError):
        pass
    else:
        if pixel_size.is_compatible_with("m"):
            mode = 1
        elif pixel_size.is_compatible_with("1/m"):
            mode = 2
        else:
            pass
    return mode
Example #4
0
def file_writer(filename, signal, **kwds):
    """
    Write signal to TVIPS file.

    Parameters
    ----------
    file: str
        Filename of the file to write to. If not supplied, a _000 suffix will
        automatically be appended before the extension.
    signal : instance of hyperspy Signal2D
        The signal to save.
    max_file_size: int, optional
        Maximum size of individual files in bytes. By default there is no
        maximum and everything is stored in a single file. Otherwise, files
        are split into sequential parts denoted by a suffix _xxx starting
        from _000.
    version: int, optional
        TVIPS file format version (1 or 2), defaults to 2
    frame_header_extra_bytes: int, optional
        Number of bytes to pad the frame headers with, defaults to 0
    mode: int, optional
        Imaging mode. 1 is imaging, 2 is diffraction. By default the mode is
        guessed from signal type and signal units.
    """
    # only signal2d is allowed
    from hyperspy._signals.signal2d import Signal2D
    if not isinstance(signal, Signal2D):
        raise ValueError("Only Signal2D supported for writing to TVIPS file.")
    fnb, ext = os.path.splitext(filename)
    if fnb.endswith("_000"):
        fnb = fnb[:-4]
    version = kwds.pop("version", 2)
    fheb = kwds.pop("frame_header_extra_bytes", 0)
    main_header = _get_main_header_from_signal(signal, version, fheb)
    # frame header + frame dtype
    record_dtype = _get_frame_record_dtype_from_signal(signal, fheb)
    num_frames = signal.axes_manager.navigation_size
    total_file_size = main_header.itemsize + num_frames * record_dtype.itemsize
    max_file_size = kwds.pop("max_file_size", None)
    if max_file_size is None:
        max_file_size = total_file_size
    minimum_file_size = main_header.itemsize + record_dtype.itemsize
    if max_file_size < minimum_file_size:
        warnings.warn(
            f"The minimum file size for this dataset is {minimum_file_size} bytes"
        )
        max_file_size = minimum_file_size
    # frame metadata
    start_date_str = signal.metadata.get_item("General.date", "1970-01-01")
    start_time_str = signal.metadata.get_item("General.time", "00:00:00")
    tz = signal.metadata.get_item("General.time_zone", "UTC")
    datetime_str = f"{start_date_str} {start_time_str} {tz}"
    time_dt = dtparse(datetime_str)
    time_dt_utc = time_dt.astimezone(timezone.utc)
    # workaround for timestamp not working on Windows, see https://bugs.python.org/issue37527
    BEGIN = datetime(1970, 1, 1, 0).replace(tzinfo=timezone.utc)
    timestamp = (time_dt_utc - BEGIN).total_seconds()
    nav_units = signal.axes_manager[0].units
    nav_increment = signal.axes_manager[0].scale
    try:
        time_increment = (nav_increment * _ureg(nav_units)).to("ms").magnitude
    except (AttributeError, pint.UndefinedUnitError, pint.DimensionalityError):
        time_increment = 1
    # imaging or diffraction
    mode = kwds.pop("mode", None)
    if mode is None:
        mode = _guess_image_mode(signal)
    mode = 2 if mode is None else mode
    stagex = signal.metadata.get_item("Acquisition_instrument.TEM.Stage.x", 0)
    stagey = signal.metadata.get_item("Acquisition_instrument.TEM.Stage.y", 0)
    stagez = signal.metadata.get_item("Acquisition_instrument.TEM.Stage.z", 0)
    stagea = signal.metadata.get_item("Acquisition_instrument.TEM.tilt_alpha",
                                      0)
    stageb = signal.metadata.get_item("Acquisition_instrument.TEM.tilt_beta",
                                      0)
    # TODO: is fcurrent actually beam current??
    fcurrent = signal.metadata.get_item(
        "Acquisition_instrument.TEM.beam_current", 0)
    frames_to_save = num_frames
    current_frame = 0
    file_index = 0
    with signal.unfolded(unfold_navigation=True, unfold_signal=False):
        while (frames_to_save != 0):
            suffix = "_" + (f"{file_index}".zfill(3))
            filename = fnb + suffix + ext
            if file_index == 0:
                with open(filename, "wb") as f:
                    main_header.tofile(f)
                    file_location = f.tell()
                    open_mode = "r+"
            else:
                file_location = 0
                open_mode = "w+"
            frames_saved = (max_file_size -
                            file_location) // record_dtype.itemsize
            # last file can contain fewer images
            if frames_to_save < frames_saved:
                frames_saved = frames_to_save
            file_memmap = np.memmap(
                filename,
                dtype=record_dtype,
                mode=open_mode,
                offset=file_location,
                shape=frames_saved,
            )
            # fill in the metadata
            file_memmap["mode"] = mode
            file_memmap["stagex"] = stagex
            file_memmap["stagey"] = stagey
            file_memmap["stagez"] = stagez
            file_memmap["stagea"] = stagea
            file_memmap["stageb"] = stageb
            file_memmap['fcurrent'] = fcurrent
            rotator = np.arange(current_frame, current_frame + frames_saved)
            milliseconds = rotator * time_increment
            timestamps = (timestamp + milliseconds / 1000).astype(int)
            milliseconds = milliseconds % 1000
            file_memmap["timestamp"] = timestamps
            file_memmap["ms"] = milliseconds
            file_memmap["rotidx"] = rotator + 1
            data = signal.data[current_frame:current_frame + frames_saved]
            if signal._lazy:
                show_progressbar = kwds.get(
                    "show_progressbar", preferences.General.show_progressbar)
                cm = ProgressBar if show_progressbar else dummy_context_manager
                with cm():
                    data.store(file_memmap["data"])
            else:
                file_memmap["data"] = data
            file_memmap.flush()
            file_index += 1
            frames_to_save -= frames_saved
            current_frame += frames_saved
Example #5
0
    def statistics(
        self,
        sb_position=None,
        sb='lower',
        high_cf=False,
        fringe_contrast_algorithm='statistical',
        apodization='hanning',
        single_values=True,
        show_progressbar=False,
        parallel=None,
        max_workers=None,
    ):
        """
        Calculates following statistics for off-axis electron holograms:

        1. Fringe contrast using either statistical definition or
        Fourier space approach (see description of `fringe_contrast_algorithm` parameter)
        2. Fringe sampling (in pixels)
        3. Fringe spacing (in calibrated units)
        4. Carrier frequency (in calibrated units, radians and 1/px)

        Parameters
        ----------
        sb_position : tuple, Signal1D, None
            The sideband position (y, x), referred to the non-shifted FFT.
            It has to be tuple or to have the same dimensionality as the hologram.
            If None, sideband is determined automatically from FFT.
        sb : str, None
            Select which sideband is selected. 'upper', 'lower', 'left' or 'right'.
        high_cf : bool, optional
            If False, the highest carrier frequency allowed for the sideband location is equal to
            half of the Nyquist frequency (Default: False).
        fringe_contrast_algorithm : str
            Select fringe contrast algorithm between:

            * 'fourier': fringe contrast is estimated as 2 * <I(k_0)> / <I(0)>,
              where I(k_0) is intensity of sideband and I(0) is the intensity of central band (FFT origin).
              This method delivers also reasonable estimation if the
              interference pattern do not cover full field of view.
            * 'statistical': fringe contrast is estimated by dividing the
              standard deviation by the mean of the hologram intensity in real
              space. This algorithm relies on regularly spaced fringes and
              covering the entire field of view.

            (Default: 'statistical')
        apodization: str or None, optional
            Used with `fringe_contrast_algorithm='fourier'`. If 'hanning' or 'hamming' apodization window
            will be applied in real space before FFT for estimation of fringe contrast.
            Apodization is typically needed to suppress striking  due to sharp edges of the image,
            which often results in underestimation of the fringe contrast. (Default: 'hanning')
        single_values : bool, optional
            If True calculates statistics only for the first navigation pixels and
            returns the values as single floats (Default: True)
        %s
        %s
        %s

        Returns
        -------
        statistics_dict :
            Dictionary with the statistics

        Raises
        ------
        NotImplementedError
            If the signal axes are non-uniform axes.

        Examples
        --------
        >>> import hyperspy.api as hs
        >>> s = hs.datasets.example_signals.reference_hologram()
        >>> sb_position = s.estimate_sideband_position(high_cf=True)
        >>> s.statistics(sb_position=sb_position)
        {'Fringe spacing (nm)': 3.4860442674236256,
        'Carrier frequency (1/px)': 0.26383819985575441,
        'Carrier frequency (mrad)': 0.56475154609203482,
        'Fringe contrast': 0.071298357213623778,
        'Fringe sampling (px)': 3.7902017241882331,
        'Carrier frequency (1 / nm)': 0.28685808994016415}
        """

        for axis in self.axes_manager.signal_axes:
            if not axis.is_uniform:
                raise NotImplementedError(
                    "This operation is not yet implemented for non-uniform energy axes."
                )
        # Testing match of navigation axes of reference and self
        # (exception: reference nav_dim=1):

        # Parsing sideband position:
        (sb_position,
         sb_position_temp) = _parse_sb_position(self, None, sb_position, sb,
                                                high_cf, parallel)

        # Calculate carrier frequency in 1/px and fringe sampling:
        fourier_sampling = 1. / np.array(self.axes_manager.signal_shape)
        if single_values:
            carrier_freq_px = calculate_carrier_frequency(
                _first_nav_pixel_data(self),
                sb_position=_first_nav_pixel_data(sb_position),
                scale=fourier_sampling)
        else:
            carrier_freq_px = self.map(calculate_carrier_frequency,
                                       sb_position=sb_position,
                                       scale=fourier_sampling,
                                       inplace=False,
                                       ragged=False,
                                       show_progressbar=show_progressbar,
                                       parallel=parallel,
                                       max_workers=max_workers)
        fringe_sampling = np.divide(1., carrier_freq_px)

        try:
            units = _ureg.parse_expression(
                str(self.axes_manager.signal_axes[0].units))
        except UndefinedUnitError:
            raise ValueError('Signal axes units should be defined.')

        # Calculate carrier frequency in 1/units and fringe spacing in units:
        f_sampling_units = np.divide(1., [
            a * b for a, b in zip(self.axes_manager.signal_shape, (
                self.axes_manager.signal_axes[0].scale,
                self.axes_manager.signal_axes[1].scale))
        ])
        if single_values:
            carrier_freq_units = calculate_carrier_frequency(
                _first_nav_pixel_data(self),
                sb_position=_first_nav_pixel_data(sb_position),
                scale=f_sampling_units)
        else:
            carrier_freq_units = self.map(calculate_carrier_frequency,
                                          sb_position=sb_position,
                                          scale=f_sampling_units,
                                          inplace=False,
                                          ragged=False,
                                          show_progressbar=show_progressbar,
                                          parallel=parallel,
                                          max_workers=max_workers)
        fringe_spacing = np.divide(1., carrier_freq_units)

        # Calculate carrier frequency in mrad:
        try:
            ht = self.metadata.Acquisition_instrument.TEM.beam_energy
        except BaseException:
            raise AttributeError("Please define the beam energy."
                                 "You can do this e.g. by using the "
                                 "set_microscope_parameters method.")

        momentum = 2 * constants.m_e * constants.elementary_charge * ht * \
            1000 * (1 + constants.elementary_charge * ht *
                    1000 / (2 * constants.m_e * constants.c ** 2))
        wavelength = constants.h / np.sqrt(momentum) * 1e9  # in nm
        carrier_freq_quantity = wavelength * \
            _ureg('nm') * carrier_freq_units / units * _ureg('rad')
        carrier_freq_mrad = carrier_freq_quantity.to('mrad').magnitude

        # Calculate fringe contrast:
        if fringe_contrast_algorithm == 'fourier':
            if single_values:
                fringe_contrast = estimate_fringe_contrast_fourier(
                    _first_nav_pixel_data(self),
                    sb_position=_first_nav_pixel_data(sb_position),
                    apodization=apodization)
            else:
                fringe_contrast = self.map(estimate_fringe_contrast_fourier,
                                           sb_position=sb_position,
                                           apodization=apodization,
                                           inplace=False,
                                           ragged=False,
                                           show_progressbar=show_progressbar,
                                           parallel=parallel,
                                           max_workers=max_workers)
        elif fringe_contrast_algorithm == 'statistical':
            if single_values:
                fringe_contrast = _first_nav_pixel_data(
                    self).std() / _first_nav_pixel_data(self).mean()
            else:
                fringe_contrast = _estimate_fringe_contrast_statistical(self)
        else:
            raise ValueError(
                "fringe_contrast_algorithm can only be set to fourier or statistical."
            )

        return {
            'Fringe contrast': fringe_contrast,
            'Fringe sampling (px)': fringe_sampling,
            'Fringe spacing ({:~})'.format(units.units): fringe_spacing,
            'Carrier frequency (1/px)': carrier_freq_px,
            'Carrier frequency ({:~})'.format((1. / units).units):
            carrier_freq_units,
            'Carrier frequency (mrad)': carrier_freq_mrad
        }
Example #6
0
def _parse_tuple_Zeiss_with_units(tup, to_units=None):
    (value, parse_units) = tup[1:]
    if to_units is not None:
        v = value * _ureg(parse_units)
        value = float("%.6e" % v.to(to_units).magnitude)
    return value