def set_phase_parameters(
        self,
        number: int = 1,
        atom_coordinates: Optional[dict] = None,
        formula: Optional[str] = None,
        info: Optional[str] = None,
        lattice_constants: Union[None, np.ndarray, List[float],
                                 List[int]] = None,
        laue_group: Optional[str] = None,
        material_name: Optional[str] = None,
        point_group: Optional[str] = None,
        setting: Optional[int] = None,
        source: Optional[str] = None,
        space_group: Optional[int] = None,
        symmetry: Optional[int] = None,
    ):
        """Set parameters for one phase in signal metadata.

        A phase node with default values is created if none is present
        in the metadata when this method is called.

        Parameters
        ----------
        number
            Phase number.
        atom_coordinates
            Dictionary of dictionaries with one or more of the atoms in
            the unit cell, on the form `{'1': {'atom': 'Ni',
            'coordinates': [0, 0, 0], 'site_occupation': 1,
            'debye_waller_factor': 0}, '2': {'atom': 'O',... etc.`
            `debye_waller_factor` in units of nm^2, and
            `site_occupation` in range [0, 1].
        formula
            Phase formula, e.g. 'Fe2' or 'Ni'.
        info
            Whatever phase info the user finds relevant.
        lattice_constants
            Six lattice constants a, b, c, alpha, beta, gamma.
        laue_group
            Phase Laue group.
        material_name
            Name of material.
        point_group
            Phase point group.
        setting
            Space group's origin setting.
        source
            Literature reference for phase data.
        space_group
            Number between 1 and 230.
        symmetry
            Phase symmetry.

        See Also
        --------
        set_simulation_parameters

        Examples
        --------
        >>> s.metadata.Sample.Phases.Number_1.atom_coordinates.Number_1
        ├── atom =
        ├── coordinates = array([0., 0., 0.])
        ├── debye_waller_factor = 0.0
        └── site_occupation = 0.0
        >>> s.set_phase_parameters(
        ...     number=1, atom_coordinates={
        ...         '1': {'atom': 'Ni', 'coordinates': [0, 0, 0],
        ...         'site_occupation': 1,
        ...         'debye_waller_factor': 0.0035}})
        >>> s.metadata.Sample.Phases.Number_1.atom_coordinates.Number_1
        ├── atom = Ni
        ├── coordinates = array([0., 0., 0.])
        ├── debye_waller_factor = 0.0035
        └── site_occupation = 1
        """
        # Ensure atom coordinates are numpy arrays
        if atom_coordinates is not None:
            for phase, val in atom_coordinates.items():
                atom_coordinates[phase]["coordinates"] = np.array(
                    atom_coordinates[phase]["coordinates"])

        inputs = {
            "atom_coordinates": atom_coordinates,
            "formula": formula,
            "info": info,
            "lattice_constants": lattice_constants,
            "laue_group": laue_group,
            "material_name": material_name,
            "point_group": point_group,
            "setting": setting,
            "source": source,
            "space_group": space_group,
            "symmetry": symmetry,
        }

        # Remove None values
        phase = {k: v for k, v in inputs.items() if v is not None}
        _update_phase_info(self.metadata, phase, number)
Esempio n. 2
0
def file_writer(
    filename: str,
    signal,
    add_scan: Optional[bool] = None,
    scan_number: int = 1,
    **kwargs,
):
    """Write an :class:`~kikuchipy.signals.EBSD` or
    :class:`~kikuchipy.signals.LazyEBSD` signal to an existing,
    but not open, or new h5ebsd file.

    Only writing to kikuchipy's h5ebsd format is supported.

    Parameters
    ----------
    filename
        Full path of HDF file.
    signal : kikuchipy.signals.EBSD or kikuchipy.signals.LazyEBSD
        Signal instance.
    add_scan
        Add signal to an existing, but not open, h5ebsd file. If it does
        not exist it is created and the signal is written to it.
    scan_number
        Scan number in name of HDF dataset when writing to an existing,
        but not open, h5ebsd file.
    kwargs
        Keyword arguments passed to :meth:`h5py:Group.require_dataset`.
    """
    # Set manufacturer and version to use in file
    from kikuchipy.release import version as ver_signal

    man_ver_dict = {"manufacturer": "kikuchipy", "version": ver_signal}

    # Open file in correct mode
    mode = "w"
    if os.path.isfile(filename) and add_scan:
        mode = "r+"
    try:
        f = h5py.File(filename, mode=mode)
    except OSError:
        raise OSError("Cannot write to an already open file.")

    if os.path.isfile(filename) and add_scan:
        check_h5ebsd(f)
        man_file, ver_file = manufacturer_version(f)
        if man_ver_dict["manufacturer"].lower() != man_file.lower():
            f.close()
            raise IOError(
                f"Only writing to kikuchipy's (and not {man_file}'s) h5ebsd "
                "format is supported."
            )
        man_ver_dict["version"] = ver_file

        # Get valid scan number
        scans_file = [f[k] for k in f["/"].keys() if "Scan" in k]
        scan_nos = [int(i.name.split()[-1]) for i in scans_file]
        for i in scan_nos:
            if i == scan_number:
                q = f"Scan {i} already in file, enter another scan number:\n"
                scan_number = _get_input_variable(q, int)
                if scan_number is None:
                    raise IOError("Invalid scan number.")
    else:  # File did not exist
        dict2h5ebsdgroup(man_ver_dict, f["/"], **kwargs)

    scan_group = f.create_group("Scan " + str(scan_number))

    # Create scan dictionary with EBSD and SEM metadata
    # Add scan size, image size and detector pixel size to dictionary to write
    data_shape = [1] * 4  # (ny, nx, sy, sx)
    data_scales = [1] * 4  # (y, x, dy, dx)
    nav_extent = [0, 1, 0, 1]  # (x0, x1, y0, y1)
    am = signal.axes_manager
    nav_axes = am.navigation_axes
    nav_dim = am.navigation_dimension
    if nav_dim == 1:
        nav_axis = nav_axes[0]
        if nav_axis.name == "y":
            data_shape[0] = nav_axis.size
            data_scales[0] = nav_axis.scale
            nav_extent[2:] = am.navigation_extent
        else:  # nav_axis.name == "x" or something else
            data_shape[1] = nav_axis.size
            data_scales[1] = nav_axis.scale
            nav_extent[:2] = am.navigation_extent
    elif nav_dim == 2:
        data_shape[:2] = [i.size for i in nav_axes][::-1]
        data_scales[:2] = [i.scale for i in nav_axes][::-1]
        nav_extent = am.navigation_extent
    data_shape[2:] = am.signal_shape
    data_scales[2:] = [i.scale for i in am.signal_axes]
    ny, nx, sy, sx = data_shape
    scale_ny, scale_nx, scale_sy, _ = data_scales
    md = signal.metadata.deepcopy()
    sem_node, ebsd_node = metadata_nodes(["sem", "ebsd"])
    md.set_item(ebsd_node + ".pattern_width", sx)
    md.set_item(ebsd_node + ".pattern_height", sy)
    md.set_item(ebsd_node + ".n_columns", nx)
    md.set_item(ebsd_node + ".n_rows", ny)
    md.set_item(ebsd_node + ".step_x", scale_nx)
    md.set_item(ebsd_node + ".step_y", scale_ny)
    md.set_item(ebsd_node + ".detector_pixel_size", scale_sy)
    # Separate EBSD and SEM metadata
    det_str, ebsd_str = ebsd_node.split(".")[-2:]  # Detector and EBSD nodes
    md_sem = md.get_item(sem_node).copy().as_dictionary()  # SEM node as dict
    md_det = md_sem.pop(det_str)  # Remove/assign detector node from SEM node
    md_ebsd = md_det.pop(ebsd_str)
    # Phases
    if md.get_item("Sample.Phases") is None:
        md = _update_phase_info(md, _phase_metadata())  # Add default phase
    md_ebsd["Phases"] = md.Sample.Phases.as_dictionary()
    for phase in md_ebsd["Phases"].keys():  # Ensure coordinates are arrays
        atom_coordinates = md_ebsd["Phases"][phase]["atom_coordinates"]
        for atom in atom_coordinates.keys():
            atom_coordinates[atom]["coordinates"] = np.array(
                atom_coordinates[atom]["coordinates"]
            )
    scan = {"EBSD": {"Header": md_ebsd}, "SEM": {"Header": md_sem}}

    # Write scan dictionary to HDF groups
    dict2h5ebsdgroup(scan, scan_group)

    # Write signal to file
    man_pats = manufacturer_pattern_names()
    dset_pattern_name = man_pats["kikuchipy"]
    overwrite_dataset(
        scan_group.create_group("EBSD/Data"),
        signal.data.reshape(nx * ny, sy, sx),
        dset_pattern_name,
        signal_axes=(2, 1),
        **kwargs,
    )
    nx_start, nx_stop, ny_start, ny_stop = nav_extent
    sample_pos = {
        "y_sample": np.tile(np.linspace(ny_start, ny_stop, ny), nx),
        "x_sample": np.tile(np.linspace(nx_start, nx_stop, nx), ny),
    }
    dict2h5ebsdgroup(sample_pos, scan_group["EBSD/Data"])

    f.close()
Esempio n. 3
0
def edaxheader2dicts(
    scan_group: h5py.Group, md: DictionaryTreeBrowser
) -> Tuple[DictionaryTreeBrowser, DictionaryTreeBrowser, DictionaryTreeBrowser]:
    """Return scan metadata dictionaries from an EDAX TSL h5ebsd file.

    Parameters
    ----------
    scan_group : h5py:Group
        HDF group of scan data and header.
    md
        Dictionary with empty fields from kikuchipy's metadata.

    Returns
    -------
    md
        kikuchipy ``metadata`` elements available in EDAX file.
    omd
        All metadata available in EDAX file.
    scan_size
        Scan, image, step and detector pixel size available in EDAX
        file.
    """
    # Get header group as dictionary
    pattern_dset_names = list(manufacturer_pattern_names().values())
    hd = hdf5group2dict(
        group=scan_group["EBSD/Header"],
        data_dset_names=pattern_dset_names,
        recursive=True,
    )

    # Populate metadata dictionary
    sem_node, ebsd_node = metadata_nodes(["sem", "ebsd"])
    md.set_item(ebsd_node + ".azimuth_angle", hd["Camera Azimuthal Angle"])
    md.set_item(ebsd_node + ".elevation_angle", hd["Camera Elevation Angle"])
    grid_type = hd["Grid Type"]
    if grid_type == "SqrGrid":
        md.set_item(ebsd_node + ".grid_type", "square")
    else:
        raise IOError(
            f"Only square grids are supported, however a {grid_type} grid was "
            "passed."
        )
    md.set_item(ebsd_node + ".sample_tilt", hd["Sample Tilt"])
    md.set_item("General.authors", hd["Operator"])
    md.set_item("General.notes", hd["Notes"])
    md.set_item(ebsd_node + ".xpc", hd["Pattern Center Calibration"]["x-star"])
    md.set_item(ebsd_node + ".ypc", hd["Pattern Center Calibration"]["y-star"])
    md.set_item(ebsd_node + ".zpc", hd["Pattern Center Calibration"]["z-star"])
    md.set_item(sem_node + ".working_distance", hd["Working Distance"])
    if "SEM-PRIAS Images" in scan_group.keys():
        md.set_item(
            sem_node + ".magnification",
            scan_group["SEM-PRIAS Images/Header/Mag"][0],
        )
    # Loop over phases in group and add to metadata
    for phase_no, phase in hd["Phase"].items():
        phase["material_name"] = phase["MaterialName"]
        phase["lattice_constants"] = [
            phase["Lattice Constant a"],
            phase["Lattice Constant b"],
            phase["Lattice Constant c"],
            phase["Lattice Constant alpha"],
            phase["Lattice Constant beta"],
            phase["Lattice Constant gamma"],
        ]
        md = _update_phase_info(md, phase, phase_no)

    # Populate original metadata dictionary
    omd = DictionaryTreeBrowser({"edax_header": hd})

    # Populate scan size dictionary
    scan_size = DictionaryTreeBrowser()
    scan_size.set_item("sx", hd["Pattern Width"])
    scan_size.set_item("sy", hd["Pattern Height"])
    scan_size.set_item("nx", hd["nColumns"])
    scan_size.set_item("ny", hd["nRows"])
    scan_size.set_item("step_x", hd["Step X"])
    scan_size.set_item("step_y", hd["Step Y"])
    scan_size.set_item("delta", 1.0)

    return md, omd, scan_size
Esempio n. 4
0
def brukerheader2dicts(
    scan_group: h5py.Group, md: DictionaryTreeBrowser
) -> Tuple[DictionaryTreeBrowser, DictionaryTreeBrowser, DictionaryTreeBrowser]:
    """Return scan metadata dictionaries from a Bruker h5ebsd file.

    Parameters
    ----------
    scan_group : h5py:Group
        HDF group of scan data and header.
    md : hyperspy.misc.utils.DictionaryTreeBrowser
        Dictionary with empty fields from kikuchipy's metadata.

    Returns
    -------
    md
        kikuchipy ``metadata`` elements available in Bruker file.
    omd
        All metadata available in Bruker file.
    scan_size
        Scan, image, step and detector pixel size available in Bruker
        file.
    """
    # Get header group and data group as dictionaries
    pattern_dset_names = list(manufacturer_pattern_names().values())
    hd = hdf5group2dict(
        group=scan_group["EBSD/Header"],
        data_dset_names=pattern_dset_names,
        recursive=True,
    )
    dd = hdf5group2dict(
        group=scan_group["EBSD/Data"], data_dset_names=pattern_dset_names,
    )

    # Populate metadata dictionary
    sem_node, ebsd_node = metadata_nodes(["sem", "ebsd"])
    md.set_item(ebsd_node + ".elevation_angle", hd["CameraTilt"])
    grid_type = hd["Grid Type"]
    if grid_type == "isometric":
        md.set_item(ebsd_node + ".grid_type", "square")
    else:
        raise IOError(
            f"Only square grids are supported, however a {grid_type} grid was "
            "passed."
        )
    md.set_item(ebsd_node + ".sample_tilt", hd["SampleTilt"])
    md.set_item(ebsd_node + ".xpc", np.mean(dd["PCX"]))
    md.set_item(ebsd_node + ".ypc", np.mean(dd["PCY"]))
    md.set_item(ebsd_node + ".zpc", np.mean(dd["DD"]))
    md.set_item(ebsd_node + ".static_background", hd["StaticBackground"])
    md.set_item(sem_node + ".working_distance", hd["WD"])
    md.set_item(sem_node + ".beam_energy", hd["KV"])
    md.set_item(sem_node + ".magnification", hd["Magnification"])
    # Loop over phases
    for phase_no, phase in hd["Phases"].items():
        phase["material_name"] = phase["Name"]
        phase["space_group"] = phase["IT"]
        phase["atom_coordinates"] = {}
        for key, val in phase["AtomPositions"].items():
            atom = val.split(",")
            atom[1:] = list(map(float, atom[1:]))
            phase["atom_coordinates"][key] = {
                "atom": atom[0],
                "coordinates": atom[1:4],
                "site_occupation": atom[4],
                "debye_waller_factor": atom[5],
            }
        md = _update_phase_info(md, phase, phase_no)

    # Populate original metadata dictionary
    omd = DictionaryTreeBrowser({"bruker_header": hd})

    # Populate scan size dictionary
    scan_size = DictionaryTreeBrowser()
    scan_size.set_item("sx", hd["PatternWidth"])
    scan_size.set_item("sy", hd["PatternHeight"])
    scan_size.set_item("nx", hd["NCOLS"])
    scan_size.set_item("ny", hd["NROWS"])
    scan_size.set_item("step_x", hd["XSTEP"])
    scan_size.set_item("step_y", hd["YSTEP"])
    scan_size.set_item(
        "delta", hd["DetectorFullHeightMicrons"] / hd["UnClippedPatternHeight"]
    )

    return md, omd, scan_size
Esempio n. 5
0
def brukerheader2dicts(
    scan_group: h5py.Group, md: DictionaryTreeBrowser
) -> Tuple[DictionaryTreeBrowser, DictionaryTreeBrowser,
           DictionaryTreeBrowser]:
    """Return scan metadata dictionaries from a Bruker h5ebsd file.

    Parameters
    ----------
    scan_group : h5py:Group
        HDF group of scan data and header.
    md : hyperspy.misc.utils.DictionaryTreeBrowser
        Dictionary with empty fields from kikuchipy's metadata.

    Returns
    -------
    md
        kikuchipy ``metadata`` elements available in Bruker file.
    omd
        All metadata available in Bruker file.
    scan_size
        Scan, image, step and detector pixel size available in Bruker
        file.
    """
    # Get header group and data group as dictionaries
    pattern_dset_names = list(manufacturer_pattern_names().values())
    hd = hdf5group2dict(
        group=scan_group["EBSD/Header"],
        data_dset_names=pattern_dset_names,
        recursive=True,
    )
    dd = hdf5group2dict(group=scan_group["EBSD/Data"],
                        data_dset_names=pattern_dset_names)

    # Populate metadata dictionary
    sem_node, ebsd_node = metadata_nodes(["sem", "ebsd"])
    md.set_item(ebsd_node + ".elevation_angle", hd["CameraTilt"])
    grid_type = hd["Grid Type"]
    if grid_type == "isometric":
        md.set_item(ebsd_node + ".grid_type", "square")
    else:
        raise IOError(
            f"Only square grids are supported, however a {grid_type} grid was passed"
        )
    # Values: data set name, data group, function to apply, node
    dset_mapping = {
        "sample_tilt": ["SampleTilt", hd, None, ebsd_node],
        "xpz": ["PCX", dd, np.nanmean, ebsd_node],
        "ypc": ["PCY", dd, np.nanmean, ebsd_node],
        "zpc": ["DD", dd, np.nanmean, ebsd_node],
        "static_background": ["StaticBackground", hd, None, ebsd_node],
        "working_distance": ["WD", hd, None, sem_node],
        "beam_energy": ["KV", hd, None, sem_node],
        "magnification": ["Magnification", hd, None, sem_node],
    }
    for k, v in dset_mapping.items():
        dset_name, dset_group, func, node = v
        try:
            dset = dset_group[dset_name]
            if func:
                dset = func(dset)
        except KeyError:
            dset = None
        md.set_item(node + f".{k}", dset)
    # Loop over phases
    for phase_no, phase in hd["Phases"].items():
        phase["material_name"] = phase["Name"]
        phase["space_group"] = phase["IT"]
        phase["atom_coordinates"] = {}
        for key, val in phase["AtomPositions"].items():
            atom = val.split(",")
            atom[1:] = list(map(float, atom[1:]))
            phase["atom_coordinates"][key] = {
                "atom": atom[0],
                "coordinates": atom[1:4],
                "site_occupation": atom[4],
                "debye_waller_factor": atom[5],
            }
        md = _update_phase_info(md, phase, phase_no)

    # Populate original metadata dictionary
    omd = DictionaryTreeBrowser({"bruker_header": hd})

    # Initialize scan size dictionary
    scan_size = DictionaryTreeBrowser()

    # Get region of interest (ROI, only rectangular shape supported)
    try:
        sd = hdf5group2dict(group=scan_group["EBSD/SEM"])
        ir = sd["IY"][()]
        ic = sd["IX"][()]
        roi = True
    except KeyError:
        roi = False
        nr = hd["NROWS"]
        nc = hd["NCOLS"]
    if roi:
        nr_roi, nc_roi, is_rectangular = _bruker_roi_is_rectangular(ir, ic)
        if is_rectangular:
            nr = nr_roi
            nc = nc_roi
            # Get indices of patterns in the 2D map
            idx = np.array([ir - np.min(ir), ic - np.min(ic)])
            scan_size["indices"] = np.ravel_multi_index(idx,
                                                        (nr, nc)).argsort()
        else:
            raise ValueError(
                "Only a rectangular region of interest is supported")

    # Populate scan size dictionary
    scan_size.set_item("sx", hd["PatternWidth"])
    scan_size.set_item("sy", hd["PatternHeight"])
    scan_size.set_item("nx", nc)
    scan_size.set_item("ny", nr)
    scan_size.set_item("step_x", hd["XSTEP"])
    scan_size.set_item("step_y", hd["YSTEP"])
    scan_size.set_item(
        "delta",
        hd["DetectorFullHeightMicrons"] / hd["UnClippedPatternHeight"])

    return md, omd, scan_size