def _make_centre_array_from_signal(signal, x=None, y=None): a_m = signal.axes_manager shape = a_m.navigation_shape[::-1] if x is None: centre_x_array = np.ones(shape) * a_m.signal_axes[0].value2index(0) else: centre_x_array = np.ones(shape) * x if y is None: centre_y_array = np.ones(shape) * a_m.signal_axes[1].value2index(0) else: centre_y_array = np.ones(shape) * y if not isiterable(centre_x_array): centre_x_array = np.array([centre_x_array]) if not isiterable(centre_y_array): centre_y_array = np.array([centre_y_array]) return (centre_x_array, centre_y_array)
def _filter_peak_array_radius(peak_array, xc, yc, r_min=None, r_max=None): """Remove peaks from a peak_array, based on distance from a point. Parameters ---------- peak_array : NumPy array In the form [[[[y0, x0], [y1, x1]]]] xc, yc : scalars, NumPy array Centre position r_min, r_max : scalar Remove peaks which are within r_min and r_max distance from the centre. One of them must be specified. Returns ------- peak_array_filtered : NumPy array Similar to peak_array input, but with the too-close peaks removed. See Also -------- _filter_peak_list _filter_4D_peak_array _filter_peak_list_radius """ if not isiterable(xc): xc = np.ones(peak_array.shape[:2]) * xc if not isiterable(yc): yc = np.ones(peak_array.shape[:2]) * yc peak_array_filtered = np.empty(shape=peak_array.shape[:2], dtype=object) for iy, ix in np.ndindex(peak_array.shape[:2]): temp_xc, temp_yc = xc[iy, ix], yc[iy, ix] peak_list_filtered = _filter_peak_list_radius(peak_array[iy, ix], xc=temp_xc, yc=temp_yc, r_min=r_min, r_max=r_max) peak_array_filtered[iy, ix] = np.array(peak_list_filtered) return peak_array_filtered
def add_elements(self, elements, include_pre_edges=False): """Declare the elemental composition of the sample. The ionisation edges of the elements present in the current energy range will be added automatically. Parameters ---------- elements : tuple of strings The symbol of the elements. Note this input must always be in the form of a tuple. Meaning: add_elements(('C',)) will work, while add_elements(('C')) will NOT work. include_pre_edges : bool If True, the ionization edges with an onset below the lower energy limit of the SI will be incluided Examples -------- >>> s = signals.EELSSpectrum(np.arange(1024)) >>> s.add_elements(('C', 'O')) Adding C_K subshell Adding O_K subshell Raises ------ ValueError """ if not isiterable(elements) or isinstance(elements, basestring): raise ValueError( "Input must be in the form of a tuple. For example, " "if `s` is the variable containing this EELS spectrum:\n " ">>> s.add_elements(('C',))\n" "See the docstring for more information.") for element in elements: if element in elements_db: self.elements.add(element) else: raise ValueError( "%s is not a valid symbol of a chemical element" % element) if not hasattr(self.metadata, 'Sample'): self.metadata.add_node('Sample') self.metadata.Sample.elements = list(self.elements) if self.elements: self.generate_subshells(include_pre_edges)
def add_elements(self, elements): """Add elements and the corresponding X-ray lines. The list of elements is stored in `metadata.Sample.elements` Parameters ---------- elements : list of strings The symbol of the elements. Examples -------- >>> s = hs.datasets.example_signals.EDS_SEM_Spectrum() >>> print(s.metadata.Sample.elements) >>> s.add_elements(['Ar']) >>> print(s.metadata.Sample.elements) ['Al' 'C' 'Cu' 'Mn' 'Zr'] ['Al', 'Ar', 'C', 'Cu', 'Mn', 'Zr'] See also -------- set_elements, add_lines, set_lines """ if not isiterable(elements) or isinstance(elements, str): raise ValueError( "Input must be in the form of a list. For example, " "if `s` is the variable containing this EDS spectrum:\n " ">>> s.add_elements(('C',))\n" "See the docstring for more information.") if "Sample.elements" in self.metadata: elements_ = set(self.metadata.Sample.elements) else: elements_ = set() for element in elements: if element in elements_db: elements_.add(element) else: raise ValueError( "%s is not a valid chemical element symbol." % element) if not hasattr(self.metadata, 'Sample'): self.metadata.add_node('Sample') self.metadata.Sample.elements = sorted(list(elements_))
def add_elements(self, elements): """Add elements and the corresponding X-ray lines. The list of elements is stored in `metadata.Sample.elements` Parameters ---------- elements : list of strings The symbol of the elements. Examples -------- >>> s = hs.datasets.example_signals.EDS_SEM_Spectrum() >>> print(s.metadata.Sample.elements) >>> s.add_elements(['Ar']) >>> print(s.metadata.Sample.elements) ['Al' 'C' 'Cu' 'Mn' 'Zr'] ['Al', 'Ar', 'C', 'Cu', 'Mn', 'Zr'] See also -------- set_elements, add_lines, set_lines """ if not isiterable(elements) or isinstance(elements, str): raise ValueError( "Input must be in the form of a list. For example, " "if `s` is the variable containing this EDS spectrum:\n " ">>> s.add_elements(('C',))\n" "See the docstring for more information.") if "Sample.elements" in self.metadata: elements_ = set(self.metadata.Sample.elements) else: elements_ = set() for element in elements: if element in elements_db: elements_.add(element) else: raise ValueError("%s is not a valid chemical element symbol." % element) if not hasattr(self.metadata, 'Sample'): self.metadata.add_node('Sample') self.metadata.Sample.elements = sorted(list(elements_))
def _set_axis_attribute_values(self, attr, values): """Set the given attribute of all the axes to the given value(s) Parameters ---------- attr : string The DataAxis attribute to set. values: any If iterable, it must have the same number of items as axes are in this AxesManager instance. If not iterable, the attribute of all the axes are set to the given value. """ if not isiterable(values): values = [values] * len(self._axes) elif len(values) != len(self._axes): raise ValueError("Values must have the same number" "of items are axes are in this AxesManager") for axis, value in zip(self._axes, values): setattr(axis, attr, value)
def _set_axis_attribute_values(self, attr, values): """Set the given attribute of all the axes to the given value(s) Parameters ---------- attr : string The DataAxis attribute to set. values: any If iterable, it must have the same number of items as axes are in this AxesManager instance. If not iterable, the attribute of all the axes are set to the given value. """ if not isiterable(values): values = [values, ] * len(self._axes) elif len(values) != len(self._axes): raise ValueError("Values must have the same number" "of items are axes are in this AxesManager") for axis, value in zip(self._axes, values): setattr(axis, attr, value)
def add_elements(self, elements): """Add elements and the corresponding X-ray lines. The list of elements is stored in `mapped_parameters.Sample.elements` Parameters ---------- elements : list of strings The symbol of the elements. See also -------- set_elements, add_lines, set_lines. """ if not isiterable(elements) or isinstance(elements, basestring): raise ValueError( "Input must be in the form of a list. For example, " "if `s` is the variable containing this EELS spectrum:\n " ">>> s.add_elements(('C',))\n" "See the docstring for more information.") if "Sample.elements" in self.mapped_parameters: elements_ = set(self.mapped_parameters.Sample.elements) else: elements_ = set() for element in elements: if element in elements_db: elements_.add(element) else: raise ValueError( "%s is not a valid chemical element symbol." % element) if not hasattr(self.mapped_parameters, 'Sample'): self.mapped_parameters.add_node('Sample') self.mapped_parameters.Sample.elements = sorted(list(elements_))
def add_elements(self, elements): """Add elements and the corresponding X-ray lines. The list of elements is stored in `mapped_parameters.Sample.elements` Parameters ---------- elements : list of strings The symbol of the elements. See also -------- set_elements, add_lines, set_lines. """ if not isiterable(elements) or isinstance(elements, basestring): raise ValueError( "Input must be in the form of a list. For example, " "if `s` is the variable containing this EELS spectrum:\n " ">>> s.add_elements(('C',))\n" "See the docstring for more information.") if "Sample.elements" in self.mapped_parameters: elements_ = set(self.mapped_parameters.Sample.elements) else: elements_ = set() for element in elements: if element in elements_db: elements_.add(element) else: raise ValueError("%s is not a valid chemical element symbol." % element) if not hasattr(self.mapped_parameters, 'Sample'): self.mapped_parameters.add_node('Sample') self.mapped_parameters.Sample.elements = sorted(list(elements_))
def get_gui(self, toolkey, display=True, toolkit=None, **kwargs): if not TOOLKIT_REGISTRY: raise ImportError( "No toolkit registered. Install hyperspy_gui_ipywidgets or " "hyperspy_gui_traitsui GUI elements. If hyperspy_gui_traits" "is installed, initialize a toolkit supported by traitsui " "before importing HyperSpy." ) from hyperspy.defaults_parser import preferences if isinstance(toolkit, str): toolkit = (toolkit,) if isiterable(toolkit): toolkits = set() for tk in toolkit: if tk in TOOLKIT_REGISTRY: toolkits.add(tk) else: raise ValueError( "{} is not a registered toolkit.".format(tk) ) elif toolkit is None: toolkits = set() available_disabled_toolkits = set() if "ipywidgets" in TOOLKIT_REGISTRY: if preferences.GUIs.enable_ipywidgets_gui: toolkits.add("ipywidgets") else: available_disabled_toolkits.add("ipywidgets") if "traitsui" in TOOLKIT_REGISTRY: if preferences.GUIs.enable_traitsui_gui: toolkits.add("traitsui") else: available_disabled_toolkits.add("traitsui") if not toolkits and available_disabled_toolkits: is_or_are = "is" if len( available_disabled_toolkits) == 1 else "are" them_or_it = ("it" if len(available_disabled_toolkits) == 1 else "them") raise ValueError( "No toolkit available. The {} {} installed but " "disabled in `preferences`. Enable {} in `preferences` or " "manually select a toolkit with the `toolkit` argument.".format( _toolkits_to_string(available_disabled_toolkits), is_or_are, them_or_it) ) else: raise ValueError( "`toolkit` must be a string, an iterable of strings or None.") if toolkey not in UI_REGISTRY or not UI_REGISTRY[toolkey]: propose = KNOWN_TOOLKITS - TOOLKIT_REGISTRY if propose: propose = ["hyperspy_gui_{}".format(tk) for tk in propose] if len(propose) > 1: propose_ = ", ".join(propose[:-1]) propose = propose_ + " and/or {}".format(propose[-1]) else: propose = propose.pop() raise NotImplementedError( "There is no user interface registered for this feature." "Try installing {}.".format(propose)) if not display: widgets = {} available_toolkits = set() used_toolkits = set() for toolkit, f in UI_REGISTRY[toolkey].items(): if toolkit in toolkits: used_toolkits.add(toolkit) thisw = f(obj=self, display=display, **kwargs) if not display: widgets[toolkit] = thisw else: available_toolkits.add(toolkit) if not used_toolkits and available_toolkits: is_or_are = "is" if len(toolkits) == 1 else "are" raise NotImplementedError( "The {} {} not available for this functionality,try with " "the {}.".format( _toolkits_to_string(toolkits), is_or_are, _toolkits_to_string(available_toolkits))) if not display: return widgets
def add_marker(self, marker, plot_on_signal=True, plot_marker=True, permanent=False, plot_signal=True): """ Add a marker to the signal or navigator plot. Plot the signal, if not yet plotted Parameters ---------- marker : marker object or iterable of marker objects The marker or iterable (list, tuple, ...) of markers to add. See `plot.markers`. If you want to add a large number of markers, add them as an iterable, since this will be much faster. plot_on_signal : bool, default True If True, add the marker to the signal If False, add the marker to the navigator plot_marker : bool, default True If True, plot the marker. permanent : bool, default False If False, the marker will only appear in the current plot. If True, the marker will be added to the metadata.Markers list, and be plotted with plot(plot_markers=True). If the signal is saved as a HyperSpy HDF5 file, the markers will be stored in the HDF5 signal and be restored when the file is loaded. Examples -------- >>> import scipy.misc >>> import hyperspy.api as hs >>> im = hs.signals.Signal2D(scipy.misc.ascent()) >>> m = hs.markers.rectangle(x1=150, y1=100, x2=400, y2=400, color='red') >>> im.add_marker(m) Adding to a 1D signal, where the point will change when the navigation index is changed >>> import numpy as np >>> s = hs.signals.Signal1D(np.random.random((3, 100))) >>> marker = hs.markers.point((19, 10, 60), (0.2, 0.5, 0.9)) >>> s.add_marker(marker, permanent=True, plot_marker=True) >>> s.plot(plot_markers=True) Add permanent marker >>> s = hs.signals.Signal2D(np.random.random((100, 100))) >>> marker = hs.markers.point(50, 60) >>> s.add_marker(marker, permanent=True, plot_marker=True) >>> s.plot(plot_markers=True) Add permanent marker which changes with navigation position, and do not add it to a current plot >>> s = hs.signals.Signal2D(np.random.randint(10, size=(3, 100, 100))) >>> marker = hs.markers.point((10, 30, 50), (30, 50, 60), color='red') >>> s.add_marker(marker, permanent=True, plot_marker=False) >>> s.plot(plot_markers=True) Removing a permanent marker >>> s = hs.signals.Signal2D(np.random.randint(10, size=(100, 100))) >>> marker = hs.markers.point(10, 60, color='red') >>> marker.name = "point_marker" >>> s.add_marker(marker, permanent=True) >>> del s.metadata.Markers.point_marker Adding many markers as a list >>> from numpy.random import random >>> s = hs.signals.Signal2D(np.random.randint(10, size=(100, 100))) >>> marker_list = [] >>> for i in range(100): ... marker = hs.markers.point(random()*100, random()*100, color='red') ... marker_list.append(marker) >>> s.add_marker(marker_list, permanent=True) """ if isiterable(marker): marker_list = marker else: marker_list = [marker] markers_dict = {} if permanent: if not self.metadata.has_item('Markers'): self.metadata.add_node('Markers') marker_object_list = [] for marker_tuple in list(self.metadata.Markers): marker_object_list.append(marker_tuple[1]) name_list = self.metadata.Markers.keys() marker_name_suffix = 1 for m in marker_list: marker_data_shape = m._get_data_shape() if (not (len(marker_data_shape) == 0)) and ( marker_data_shape != self.axes_manager.navigation_shape): raise ValueError("Navigation shape of the marker must be 0 or the " "same navigation shape as this signal.") if (m.signal is not None) and (m.signal is not self): raise ValueError("Markers can not be added to several signals") m._plot_on_signal = plot_on_signal if plot_marker: if self._plot is None: self.plot() if m._plot_on_signal: self._plot.signal_plot.add_marker(m) else: if self._plot.navigator_plot is None: self.plot() self._plot.navigator_plot.add_marker(m) m.plot(update_plot=False) if permanent: for marker_object in marker_object_list: if m is marker_object: raise ValueError("Marker already added to signal") name = m.name temp_name = name while temp_name in name_list: temp_name = name + str(marker_name_suffix) marker_name_suffix += 1 m.name = temp_name markers_dict[m.name] = m m.signal = self marker_object_list.append(m) name_list.append(m.name) if permanent: self.metadata.Markers = markers_dict if plot_marker: if self._plot.signal_plot: self._plot.signal_plot.ax.hspy_fig._draw_animated() if self._plot.navigator_plot: self._plot.navigator_plot.ax.hspy_fig._draw_animated()
def add_atom_list( self, x, y, sigma_x=1, sigma_y=1, amplitude=1, rotation=0): """ Add several atoms to the test data. Parameters ---------- x, y : iterable Position of the atoms. Must be iterable, and have the same size. sigma_x, sigma_y : number or iterable, default 1 If number: all the atoms will have the same sigma. Use iterable for setting different sigmas for different atoms. If iterable: must be same length as x and y iterables. amplitude : number or iterable, default 1 If number: all the atoms will have the same amplitude. Use iterable for setting different amplitude for different atoms. If iterable: must be same length as x and y iterables. rotation : number or iterable, default 0 If number: all the atoms will have the same rotation. Use iterable for setting different rotation for different atoms. If iterable: must be same length as x and y iterables. Examples -------- >>> from temul.external.atomap_devel_012.testing_tools import MakeTestData >>> test_data = MakeTestData(200, 200) >>> import numpy as np >>> x, y = np.mgrid[0:200:10j, 0:200:10j] >>> x, y = x.flatten(), y.flatten() >>> test_data.add_atom_list(x, y) >>> test_data.signal.plot() """ if len(x) != len(y): raise ValueError("x and y needs to have the same length") if isiterable(sigma_x): if len(sigma_x) != len(x): raise ValueError("sigma_x and x needs to have the same length") else: sigma_x = [sigma_x] * len(x) if isiterable(sigma_y): if len(sigma_y) != len(y): raise ValueError("sigma_y and x needs to have the same length") else: sigma_y = [sigma_y] * len(x) if isiterable(amplitude): if len(amplitude) != len(x): raise ValueError( "amplitude and x needs to have the same length") else: amplitude = [amplitude] * len(x) if isiterable(rotation): if len(rotation) != len(x): raise ValueError( "rotation and x needs to have the same length") else: rotation = [rotation] * len(x) iterator = zip(x, y, sigma_x, sigma_y, amplitude, rotation) for tx, ty, tsigma_x, tsigma_y, tamplitude, trotation in iterator: self.add_atom(tx, ty, tsigma_x, tsigma_y, tamplitude, trotation)
def get_gui(self, toolkey, display=True, toolkit=None, **kwargs): if not TOOLKIT_REGISTRY: raise ImportError( "No toolkit registered. Install hyperspy_gui_ipywidgets or " "hyperspy_gui_traitsui GUI elements.") from hyperspy.defaults_parser import preferences if isinstance(toolkit, str): toolkit = (toolkit, ) if isiterable(toolkit): toolkits = set() for tk in toolkit: if tk in TOOLKIT_REGISTRY: toolkits.add(tk) else: raise ValueError(f"{tk} is not a registered toolkit.") elif toolkit is None: toolkits = set() available_disabled_toolkits = set() if "ipywidgets" in TOOLKIT_REGISTRY: if preferences.GUIs.enable_ipywidgets_gui: toolkits.add("ipywidgets") else: available_disabled_toolkits.add("ipywidgets") if "traitsui" in TOOLKIT_REGISTRY: if preferences.GUIs.enable_traitsui_gui: toolkits.add("traitsui") else: available_disabled_toolkits.add("traitsui") if not toolkits and available_disabled_toolkits: is_or_are = "is" if len( available_disabled_toolkits) == 1 else "are" them_or_it = ("it" if len(available_disabled_toolkits) == 1 else "them") raise ValueError( "No toolkit available. The " f"{_toolkits_to_string(available_disabled_toolkits)} " f"{is_or_are} installed but disabled in `preferences`. " f"Enable {them_or_it} in `preferences` or " "manually select a toolkit with the `toolkit` argument.") else: raise ValueError( "`toolkit` must be a string, an iterable of strings or None.") if toolkey not in UI_REGISTRY or not UI_REGISTRY[toolkey]: propose = KNOWN_TOOLKITS - TOOLKIT_REGISTRY if propose: propose = [f"hyperspy_gui_{tk}" for tk in propose] if len(propose) > 1: propose_ = ", ".join(propose[:-1]) propose = f"{propose_} and/or {propose[-1]}" else: propose = propose.pop() raise NotImplementedError( "There is no user interface registered for this feature." f"Try installing {propose}.") if not display: widgets = {} available_toolkits = set() used_toolkits = set() for toolkit, specs in UI_REGISTRY[toolkey].items(): f = getattr(importlib.import_module(specs["module"]), specs["function"]) if toolkit in toolkits: used_toolkits.add(toolkit) try: thisw = f(obj=self, display=display, **kwargs) except NotImplementedError as e: # traitsui raises this exception when the backend is # not supported if toolkit == "traitsui": pass else: raise e if not display: widgets[toolkit] = thisw else: available_toolkits.add(toolkit) if not used_toolkits and available_toolkits: is_or_are = "is" if len(toolkits) == 1 else "are" raise NotImplementedError( f"The {_toolkits_to_string(toolkits)} {is_or_are} not available " "for this functionality, try with the " f"{_toolkits_to_string(available_toolkits)}.") if not display: return widgets
def get_probe_area(self, navigation_axes=None): """ Calculates a pixel area which can be approximated to probe area, when the beam is larger than or equal to pixel size. The probe area can be calculated only when the number of navigation dimension are less than 2 and all the units have the dimensions of length. Parameters ---------- navigation_axes : DataAxis, string or integer (or list of) Navigation axes corresponding to the probe area. If string or integer, the provided value is used to index the ``axes_manager``. Returns ------- probe area in nm². Examples -------- >>> s = hs.datasets.example_signals.EDS_TEM_Spectrum() >>> si = hs.stack([s]*3) >>> si.axes_manager.navigation_axes[0].scale = 0.01 >>> si.axes_manager.navigation_axes[0].units = 'μm' >>> si.get_probe_area() 100.0 """ if navigation_axes is None: navigation_axes = self.axes_manager.navigation_axes elif not isiterable(navigation_axes): navigation_axes = [navigation_axes] if len(navigation_axes) == 0: raise ValueError("The navigation dimension is zero, the probe " "area can not be calculated automatically.") elif len(navigation_axes) > 2: raise ValueError("The navigation axes corresponding to the probe " "are ambiguous and the probe area can not be " "calculated automatically.") scales = [] for axis in navigation_axes: try: if not isinstance(navigation_axes, DataAxis): axis = self.axes_manager[axis] scales.append(axis.convert_to_units('nm', inplace=False)[0]) except pint.DimensionalityError: raise ValueError(f"The unit of the axis {axis} has not the " "dimension of length.") if len(scales) == 1: probe_area = scales[0]**2 else: probe_area = scales[0] * scales[1] if probe_area == 1: warnings.warn( "Please note that the probe area has been " "calculated to be 1 nm², meaning that it is highly " "likley that the scale of the navigation axes have not " "been set correctly. Please read the user " "guide for how to set this.") return probe_area
def get_ellipse_model_ransac( data, xf=128, yf=128, rf_lim=30, semi_len_min=70, semi_len_max=90, semi_len_ratio_lim=1.2, min_samples=6, residual_threshold=10, max_trails=500, show_progressbar=True, ): """Pick a random number of data points to fit an ellipse to. The ellipse's constraints can be specified. See skimage.measure.ransac for more information. Parameters ---------- data : NumPy array In the form [[[[x0, y0], [x1, y1], ...]]] xf, yf : scalar, optional Default 128 rf_lim : scalar, optional How far the ellipse centre can be from (xf, yf) semi_len_min, semi_len_max : scalar, optional Limits of the semi lengths semi_len_ratio_lim : scalar, optional Limit of the ratio of the semi length, must be equal or larger than 1. This ratio is calculated by taking the largest semi length divided by the smallest semi length: max(semi0, semi1)/min(semi0, semi1). So for a perfect circle this ratio will be 1. min_samples : scalar, optional Minimum number of data points to fit the ellipse model to. residual_threshold : scalar, optional Maximum distance for a data point to be considered an inlier. max_trails : scalar, optional Maximum number of tries for the ransac algorithm. show_progressbar : bool, optional Default True Returns ------- ellipse_array, inlier_array : NumPy array Model data is accessed in ellipse_array, where each probe position (for two axes) contain a list with the ellipse parameters: [y, x, semi_len0, semi_len1, rotation]. If no ellipse is found this is None. """ if not isiterable(xf): xf = np.ones(data.shape[:2]) * xf if not isiterable(yf): yf = np.ones(data.shape[:2]) * yf ellipse_array = np.zeros(data.shape[:2], dtype=object) inlier_array = np.zeros(data.shape[:2], dtype=object) num_total = data.shape[0] * data.shape[1] t = tqdm(np.ndindex(data.shape[:2]), disable=not show_progressbar, total=num_total) for iy, ix in t: temp_xf, temp_yf = xf[iy, ix], yf[iy, ix] ellipse_model, inliers = get_ellipse_model_ransac_single_frame( data[iy, ix], xf=temp_yf, yf=temp_xf, rf_lim=rf_lim, semi_len_min=semi_len_min, semi_len_max=semi_len_max, semi_len_ratio_lim=semi_len_ratio_lim, min_samples=min_samples, residual_threshold=residual_threshold, max_trails=max_trails, ) if ellipse_model is not None: params = ellipse_model.params else: params = None ellipse_array[iy, ix] = params inlier_array[iy, ix] = inliers return ellipse_array, inlier_array
def generate_4d_data( probe_size_x=10, probe_size_y=10, image_size_x=50, image_size_y=50, disk_x=25, disk_y=25, disk_r=5, disk_I=20, ring_x=25, ring_y=25, ring_r=20, ring_I=6, ring_lw=0, ring_e_x=None, ring_e_y=25, ring_e_semi_len0=15, ring_e_semi_len1=15, ring_e_r=0, ring_e_I=6, ring_e_lw=1, blur=True, blur_sigma=1, downscale=True, add_noise=False, noise_amplitude=1, lazy=False, lazy_chunks=None, show_progressbar=True, ): """Generate a test dataset containing a disk and diffraction ring. Useful for checking that radial average algorithms are working properly. The centre, intensity and radius position of the ring and disk can vary as a function of probe position, through the disk_x, disk_y, disk_r, disk_I, ring_x, ring_y, ring_r and ring_I arguments. In addition, the line width of the ring can be varied with ring_lw. There is also an elliptical ring, which can be added separately to the circular ring. This elliptical ring uses the ring_e_* arguments. It is disabled by default. The ring can be deactivated by setting ring_x=None. The disk can be deactivated by setting disk_x=None. The elliptical ring can be deactivated by setting ring_e_x=None. Parameters ---------- probe_size_x, probe_size_y : int, default 10 Size of the navigation dimension. image_size_x, image_size_y : int, default 50 Size of the signal dimension. disk_x, disk_y : int or NumPy 2D-array, default 20 Centre position of the disk. Either integer or NumPy 2-D array. See examples on how to make them the correct size. To deactivate the disk, set disk_x=None. disk_r : int or NumPy 2D-array, default 5 Radius of the disk. Either integer or NumPy 2-D array. See examples on how to make it the correct size. disk_I : int or NumPy 2D-array, default 20 Intensity of the disk, for each of the pixels. So if I=30, the each pixel in the disk will have a value of 30. Note, this value will change if blur=True or downscale=True. ring_x, ring_y : int or NumPy 2D-array, default 20 Centre position of the ring. Either integer or NumPy 2-D array. See examples on how to make them the correct size. To deactivate the ring, set ring_x=None. ring_r : int or NumPy 2D-array, default 20 Radius of the ring. Either integer or NumPy 2-D array. See examples on how to make it the correct size. ring_I : int or NumPy 2D-array, default 6 Intensity of the ring, for each of the pixels. So if I=5, each pixel in the ring will have a value of 5. Note, this value will change if blur=True or downscale=True. ring_lw : int or NumPy 2D-array, default 0 Line width of the ring. If ring_lw=1, the line will be 3 pixels wide. If ring_lw=2, the line will be 5 pixels wide. ring_e_x, ring_e_y : int or NumPy 2D-array, default 20 Centre position of the elliptical ring. Either integer or NumPy 2-D array. See examples on how to make them the correct size. To deactivate the ring, set ring_x=None (which is the default). ring_e_semi_len0, ring_e_semi_len1 : int or NumPy 2D-array, default 15 Semi lengths of the elliptical ring. Either integer or NumPy 2-D arrays. See examples on how to make it the correct size. ring_e_I : int or NumPy 2D-array, default 6 Intensity of the elliptical ring, for each of the pixels. So if I=5, each pixel in the ring will have a value of 5. Note, this value will change if blur=True or downscale=True. ring_e_r : int or NumPy 2D-array, default 0 Rotation of the elliptical ring, in radians. ring_e_lw : int or NumPy 2D-array, default 0 Line width of the ring. If ring_lw=1, the line will be 3 pixels wide. If ring_lw=2, the line will be 5 pixels wide. blur : bool, default True If True, do a Gaussian blur of the disk. blur_sigma : int, default 1 Sigma of the Gaussian blurring, if blur is True. downscale : bool, default True If True, use upscaling (then downscaling) to anti-alise the disk. add_noise : bool, default False Add Gaussian random noise. noise_amplitude : float, default 1 The amplitude of the noise, if add_noise is True. lazy : bool, default False If True, the signal will be lazy lazy_chunks : tuple, optional Used if lazy is True, default (10, 10, 10, 10). Returns ------- signal : HyperSpy Signal2D Signal with 2 navigation dimensions and 2 signal dimensions. Examples -------- >>> from pyxem.dummy_data import make_diffraction_test_data as mdtd >>> s = mdtd.generate_4d_data(show_progressbar=False) >>> s.plot() Using more arguments >>> s = mdtd.generate_4d_data(probe_size_x=20, probe_size_y=30, ... image_size_x=50, image_size_y=90, ... disk_x=30, disk_y=70, disk_r=9, disk_I=30, ... ring_x=35, ring_y=65, ring_r=20, ring_I=10, ... blur=False, downscale=False, show_progressbar=False) Adding some Gaussian random noise >>> s = mdtd.generate_4d_data(add_noise=True, noise_amplitude=3, ... show_progressbar=False) Different centre positions for each probe position. Note the size=(20, 10), and probe_x=10, probe_y=20: size=(y, x). >>> import numpy as np >>> disk_x = np.random.randint(5, 35, size=(20, 10)) >>> disk_y = np.random.randint(5, 45, size=(20, 10)) >>> disk_I = np.random.randint(50, 100, size=(20, 10)) >>> ring_x = np.random.randint(5, 35, size=(20, 10)) >>> ring_y = np.random.randint(5, 45, size=(20, 10)) >>> ring_r = np.random.randint(10, 15, size=(20, 10)) >>> ring_I = np.random.randint(1, 30, size=(20, 10)) >>> ring_lw = np.random.randint(1, 5, size=(20, 10)) >>> s = mdtd.generate_4d_data(probe_size_x=10, probe_size_y=20, ... image_size_x=40, image_size_y=50, disk_x=disk_x, disk_y=disk_y, ... disk_I=disk_I, ring_x=ring_x, ring_y=ring_y, ring_r=ring_r, ... ring_I=ring_I, ring_lw=ring_lw, show_progressbar=False) Do not plot the disk >>> s = mdtd.generate_4d_data(disk_x=None, show_progressbar=False) Do not plot the ring >>> s = mdtd.generate_4d_data(ring_x=None, show_progressbar=False) Plot only an elliptical ring >>> from numpy.random import randint, random >>> s = mdtd.generate_4d_data( ... probe_size_x=10, probe_size_y=10, ... disk_x=None, ring_x=None, ... ring_e_x=randint(20, 30, (10, 10)), ... ring_e_y=randint(20, 30, (10, 10)), ... ring_e_semi_len0=randint(10, 20, (10, 10)), ... ring_e_semi_len1=randint(10, 20, (10, 10)), ... ring_e_r=random((10, 10))*np.pi, ... ring_e_lw=randint(1, 3, (10, 10))) """ if disk_x is None: plot_disk = False else: plot_disk = True if not isiterable(disk_x): disk_x = np.ones((probe_size_y, probe_size_x)) * disk_x if not isiterable(disk_y): disk_y = np.ones((probe_size_y, probe_size_x)) * disk_y if not isiterable(disk_r): disk_r = np.ones((probe_size_y, probe_size_x)) * disk_r if not isiterable(disk_I): disk_I = np.ones((probe_size_y, probe_size_x)) * disk_I if ring_x is None: plot_ring = False else: plot_ring = True if not isiterable(ring_x): ring_x = np.ones((probe_size_y, probe_size_x)) * ring_x if not isiterable(ring_y): ring_y = np.ones((probe_size_y, probe_size_x)) * ring_y if not isiterable(ring_r): ring_r = np.ones((probe_size_y, probe_size_x)) * ring_r if not isiterable(ring_I): ring_I = np.ones((probe_size_y, probe_size_x)) * ring_I if not isiterable(ring_lw): ring_lw = np.ones((probe_size_y, probe_size_x)) * ring_lw if ring_e_x is None: plot_ring_e = False else: plot_ring_e = True if not isiterable(ring_e_x): ring_e_x = np.ones((probe_size_y, probe_size_x)) * ring_e_x if not isiterable(ring_e_y): ring_e_y = np.ones((probe_size_y, probe_size_x)) * ring_e_y if not isiterable(ring_e_semi_len0): ring_e_semi_len0 = np.ones((probe_size_y, probe_size_x)) * ring_e_semi_len0 if not isiterable(ring_e_semi_len1): ring_e_semi_len1 = np.ones((probe_size_y, probe_size_x)) * ring_e_semi_len1 if not isiterable(ring_e_I): ring_e_I = np.ones((probe_size_y, probe_size_x)) * ring_e_I if not isiterable(ring_e_lw): ring_e_lw = np.ones((probe_size_y, probe_size_x)) * ring_e_lw if not isiterable(ring_e_r): ring_e_r = np.ones((probe_size_y, probe_size_x)) * ring_e_r signal_shape = (probe_size_y, probe_size_x, image_size_y, image_size_x) s = Diffraction2D(np.zeros(shape=signal_shape)) for i in tqdm(s, desc="Make test data", disable=not show_progressbar): index = s.axes_manager.indices[::-1] test_data = MakeTestData( size_x=image_size_x, size_y=image_size_y, default=False, blur=blur, blur_sigma=blur_sigma, downscale=downscale, ) if plot_disk: dx, dy, dr = disk_x[index], disk_y[index], disk_r[index] dI = disk_I[index] test_data.add_disk(dx, dy, dr, intensity=dI) if plot_ring: rx, ry, rr = ring_x[index], ring_y[index], ring_r[index] rI, rLW = ring_I[index], ring_lw[index] test_data.add_ring(rx, ry, rr, intensity=rI, lw_pix=rLW) if plot_ring_e: rex, rey = ring_e_x[index], ring_e_y[index] resl0, resl1 = ring_e_semi_len0[index], ring_e_semi_len1[index] reI, reLW, rer = ring_e_I[index], ring_e_lw[index], ring_e_r[index] test_data.add_ring_ellipse( x0=rex, y0=rey, semi_len0=resl0, semi_len1=resl1, rotation=rer, intensity=reI, lw_r=reLW, ) s.data[index][:] = test_data.signal.data[:] if add_noise: s.data[index][:] += ( np.random.random(size=(image_size_y, image_size_x)) * noise_amplitude ) s.axes_manager.indices = [0] * s.axes_manager.navigation_dimension if lazy: if lazy_chunks is None: lazy_chunks = 10, 10, 10, 10 data_lazy = da.from_array(s.data, lazy_chunks) s = LazyDiffraction2D(data_lazy) return s
def fit_ellipses_to_signal(s, radial_signal_span_list, prepeak_range=None, angleN=20, show_progressbar=True): """ Parameters ---------- s : HyperSpy Signal2D radial_signal_span_list : tuple prepeak_range : tuple, optional angleN : list or int, default 20 show_progressbar : bool, default True Returns ------- signal : HyperSpy 2D signal Fitted ellipse and fitting points are stored as HyperSpy markers in the metadata xC, yC : floats Centre position semi_len0, semi_len1 : floats Length of the two semi-axes. rot : float Angle between semi_len0 and the positive x-axis. Since semi_len0, is not necessarily the longest semi-axis, the rotation will _not_ be between the major semi-axis and the positive x-axis. In radians, between 0 and pi. The rotation is clockwise, so at rot = 0.1 the ellipse will be pointing in the positive x-direction, and negative y-direction. eccen : float Eccentricity of the ellipse Examples -------- >>> import pyxem.dummy_data import make_diffraction_test_data as mdtd >>> s = ps.PixelatedSTEM(np.zeros((200, 220))) >>> s.axes_manager[0].offset, s.axes_manager[1].offset = -100, -110 >>> xx, yy = np.meshgrid(s.axes_manager[0].axis, s.axes_manager[1].axis) >>> ellipse_ring = mdtd._get_elliptical_ring(xx, yy, 0, 0, 50, 70, 0.8) >>> s.data += ellipse_ring >>> from pyxem..utils.radial_tools import fit_ellipses_to_signal >>> output = fit_ellipses_to_signal( ... s, [(40, 80)], angleN=30, show_progressbar=False) """ if not isiterable(angleN): angleN = [angleN] * len(radial_signal_span_list) else: if len(angleN) != len(radial_signal_span_list): raise ValueError( "angleN and radial_signal_span_list needs to have " "the same length") marker_list = [] ellipse_list = [] for i, (radial_signal_span, aN) in enumerate(zip(radial_signal_span_list, angleN)): s_ra = get_radius_vs_angle( s, radial_signal_span, angleN=aN, prepeak_range=prepeak_range, show_progressbar=show_progressbar, ) x, y = _get_xy_points_from_radius_angle_plot(s_ra) ellipse_parameters = _fit_ellipse_to_xy_points(x, y) output = _get_ellipse_parameters(ellipse_parameters) ellipse_list.append(output) marker_list.extend( _get_marker_list(ellipse_parameters, x_list=x, y_list=y, name="circle" + str(i))) s_m = s.deepcopy() s_m.add_marker(marker_list, permanent=True, plot_marker=False) return s_m, ellipse_list
def compute_navigator(self, index=None, chunks_number=None, show_progressbar=None): """ Compute the navigator by taking the sum over a single chunk contained the specified coordinate. Taking the sum over a single chunk is a computationally efficient approach to compute the navigator. The data can be rechunk by specifying the ``chunks_number`` argument. Parameters ---------- index : (int, float, None) or iterable, optional Specified where to take the sum, follows HyperSpy indexing syntax for integer and float. If None, the index is the centre of the signal_space chunks_number : (int, None) or iterable, optional Define the number of chunks in the signal space used for rechunk the when calculating of the navigator. Useful to define the range over which the sum is calculated. If None, the existing chunking will be considered when picking the chunk used in the navigator calculation. %s Returns ------- None. Note ---- The number of chunks will affect where the sum is taken. If the sum needs to be taken in the centre of the signal space (for example, in the case of diffraction pattern), the number of chunk needs to be an odd number, so that the middle is centered. """ signal_shape = self.axes_manager.signal_shape if index is None: index = [round(shape / 2) for shape in signal_shape] else: if not isiterable(index): index = [index] * len(signal_shape) index = [ axis._get_index(_idx) for _idx, axis in zip(index, self.axes_manager.signal_axes) ] _logger.info(f"Using index: {index}") if chunks_number is None: chunks = self.data.chunks else: if not isiterable(chunks_number): chunks_number = [chunks_number] * len(signal_shape) # Determine the chunk size signal_chunks = da.core.normalize_chunks([ int(size / cn) for cn, size in zip(chunks_number, signal_shape) ], shape=signal_shape) # Needs to reverse the chunks list to match dask chunking order signal_chunks = list(signal_chunks)[::-1] navigation_chunks = ['auto'] * len( self.axes_manager.navigation_shape) if LooseVersion(dask.__version__) >= LooseVersion("2.30.0"): kwargs = {'balance': True} else: kwargs = {} chunks = self.data.rechunk([*navigation_chunks, *signal_chunks], **kwargs).chunks # Get the slice of the corresponding chunk signal_size = len(signal_shape) signal_chunks = tuple(chunks[i - signal_size] for i in range(signal_size)) _logger.info(f"Signal chunks: {signal_chunks}") isig_slice = get_signal_chunk_slice(index, chunks) _logger.info(f'Computing sum over signal dimension: {isig_slice}') axes = [axis.index_in_array for axis in self.axes_manager.signal_axes] navigator = self.isig[isig_slice].sum(axes) navigator.compute(show_progressbar=show_progressbar) navigator.original_metadata.set_item('sum_from', isig_slice) self.navigator = navigator.T