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