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