def test_set_item_nd(self): map_size = 10 d = {"iq": np.arange(map_size)} props = CrystalMapProperties(d, id=np.arange(map_size)) # 2D prop_2d = np.arange(map_size * 2).reshape((10, 2)) props["prop_2d"] = prop_2d assert np.allclose(props["prop_2d"], prop_2d) # 3D prop_3d = np.arange(map_size * 4).reshape((10, 2, 2)) props["prop_3d"] = prop_3d assert np.allclose(props["prop_3d"], prop_3d) with pytest.raises(IndexError, match="boolean index did not match indexed"): props["prop_3d_wrong"] = np.random.random(40).reshape((2, 10, 2)) # Update 2D array, accounting for in data values is_in_data = np.ones(map_size, dtype=bool) is_in_data[5] = False props.is_in_data = is_in_data new_prop_2d = np.arange(map_size * 2).reshape((map_size, 2)) props["prop_2d"] = new_prop_2d[is_in_data] np.allclose(props["prop_2d"], new_prop_2d[is_in_data])
def test_set_item_error(self): map_size = 10 is_in_data = np.ones(map_size, dtype=bool) id_to_change = 3 is_in_data[id_to_change] = False d = {"iq": np.arange(map_size)} props = CrystalMapProperties(d, id=np.arange(map_size), is_in_data=is_in_data) # Set array with an array with pytest.raises(ValueError, match="NumPy boolean array indexing assignment"): props["iq"] = np.arange(map_size) + 1 # Set new 2D array props.is_in_data[id_to_change] = True with pytest.raises(TypeError, match="NumPy boolean array indexing assignment"): new_shape = (10 // 2, 10 // 5) props["dp"] = np.arange(map_size).reshape(new_shape)
def test_set_item(self): map_size = 10 is_in_data = np.ones(map_size, dtype=bool) is_in_data[5] = False d = {"iq": np.arange(map_size)} props = CrystalMapProperties(d, id=np.arange(map_size), is_in_data=is_in_data) # Set array with an array n_in_data = map_size - len(np.where(~is_in_data)[0]) props["iq"] = np.arange(n_in_data) + 1 expected_array = np.array([1, 2, 3, 4, 5, 5, 6, 7, 8, 9]) assert np.allclose(props.get("iq"), expected_array) # Set array with one value props["iq"] = 2 expected_array2 = np.ones(map_size) * 2 expected_array2[5] = expected_array[5] assert np.allclose(props.get("iq"), expected_array2)
def test_init_properties(self, dictionary, id, is_in_data): props = CrystalMapProperties(dictionary=dictionary, id=id, is_in_data=is_in_data) assert props == dictionary assert np.allclose(props.id, id) if is_in_data is None: is_in_data = np.ones(id.size, dtype=bool) assert np.allclose(props.is_in_data, is_in_data)
def test_set_item_error(self): map_size = 10 is_in_data = np.ones(map_size, dtype=bool) id_to_change = 3 is_in_data[id_to_change] = False d = {"iq": np.arange(map_size)} props = CrystalMapProperties(d, id=np.arange(map_size), is_in_data=is_in_data) # Set array with an array with pytest.raises(ValueError, match="shape mismatch: value array of shape"): props["iq"] = np.arange(map_size) + 1 # Set new 2D array props.is_in_data[id_to_change] = True with pytest.raises(IndexError, match="boolean index did not match indexed"): new_shape = (10 // 2, 10 // 5) props["dp"] = np.arange(map_size).reshape(new_shape)
def __init__( self, rotations, phase_id=None, x=None, y=None, z=None, phase_list=None, prop=None, scan_unit=None, is_in_data=None, ): """ Parameters ---------- rotations : orix.quaternion.rotation.Rotation Rotation of each data point. Must be passed with all spatial dimensions in the first array axis (flattened). May contain multiple rotations per point, included in the second array axes. Crystal map data size is set equal to the first array axis' size. phase_id : numpy.ndarray, optional Phase ID of each pixel. IDs equal to -1 are considered not indexed. If None is passed (default), all points are considered to belong to one phase with ID 0. x : numpy.ndarray, optional Map x coordinate of each data point. If None is passed, the map is assumed to be 1D, and it is set to an array of increasing integers from 0 to the length of the `phase_id` array. y : numpy.ndarray, optional Map y coordinate of each data point. If None is passed, the map is assumed to be 1D, and it is set to None. z : numpy.ndarray, optional Map z coordinate of each data point. If None is passed, the map is assumed to be 2D or 1D, and it is set to None. phase_list : PhaseList, optional A list of phases in the data with their with names, space groups, point groups, and structures. The order in which the phases appear in the list is important, as it is this, and not the phases' IDs, that is used to link the phases to the input `phase_id` if the IDs aren't exactly the same as in `phase_id`. If None (default), a phase list with as many phases as there are unique phase IDs in `phase_id` is created. prop : dict of numpy.ndarray, optional Dictionary of properties of each data point. scan_unit : str, optional Length unit of the data. If None (default), "px" is used. is_in_data : np.ndarray, optional Array of booleans signifying whether a point is in the data. Examples -------- >>> from diffpy.structure import Atom, Lattice, Structure >>> import numpy as np >>> from orix.crystal_map import CrystalMap >>> from orix.quaternion.rotation import Rotation >>> euler1, euler2, euler3, x, y, iq, dp, phase_id = np.loadtxt( ... "/some/file.ang", unpack=True) >>> euler_angles = np.column_stack((euler1, euler2, euler3)) >>> rotations = Rotation.from_euler(euler_angles) >>> properties = {"iq": iq, "dp": dp} >>> structures = [ ... Structure( ... title="austenite", ... atoms=[Atom("fe", [0] * 3)], ... lattice=Lattice(0.360, 0.360, 0.360, 90, 90, 90) ... ), ... Structure( ... title="ferrite", ... atoms=[Atom("fe", [0] * 3)], ... lattice=Lattice(0.287, 0.287, 0.287, 90, 90, 90) ... ) ... ] >>> pl = PhaseList(space_groups=[225, 229], structures=structures) >>> cm = CrystalMap( ... rotations=rotations, ... phase_id=phase_id, ... x=x, ... y=y, ... phase_list=pl, ... prop=properties, ... ) """ # Set rotations if not isinstance(rotations, Rotation): raise ValueError( f"rotations must be of type {Rotation}, not {type(rotations)}." ) self._rotations = rotations # Set data size data_size = rotations.shape[0] # Set phase IDs if phase_id is None: # Assume single phase data phase_id = np.zeros(data_size) phase_id = phase_id.astype(int) self._phase_id = phase_id # Set data point IDs point_id = np.arange(data_size) self._id = point_id # Set spatial coordinates if x is None and y is None and z is None: x = np.arange(data_size) self._x = x self._y = y self._z = z # Create phase list # Sorted in ascending order unique_phase_ids = np.unique(phase_id) include_not_indexed = False if unique_phase_ids[0] == -1: include_not_indexed = True unique_phase_ids = unique_phase_ids[1:] # Also sorted in ascending order if phase_list is None: self.phases = PhaseList(ids=unique_phase_ids) else: phase_list = copy.deepcopy(phase_list) phase_ids = phase_list.ids n_different = len(phase_ids) - len(unique_phase_ids) if n_different > 0: # Remove superfluous phases by removing the phases whose # ID is not in the ID array, in descending list order for i in phase_ids[::-1]: if i not in unique_phase_ids: del phase_list[i] n_different -= 1 if n_different == 0: break elif n_different < 0: # Create new phase list adding the missing phases with # default initial values phase_list = PhaseList( names=phase_list.names, space_groups=phase_list.space_groups, point_groups=phase_list.point_groups, colors=phase_list.colors, structures=phase_list.structures, ids=unique_phase_ids, ) # Ensure phase list IDs correspond to IDs in phase_id array new_ids = list(unique_phase_ids.astype(int)) phase_list._dict = dict(zip(new_ids, phase_list._dict.values())) self.phases = phase_list # Set whether measurements are indexed is_indexed = np.ones(data_size, dtype=bool) is_indexed[np.where(phase_id == -1)] = False # Add "not_indexed" to phase list and ensure not indexed points # have correct phase ID if include_not_indexed: self.phases.add_not_indexed() self._phase_id[~is_indexed] = -1 # Set array with True for points in data if is_in_data is None: is_in_data = np.ones(data_size, dtype=bool) self.is_in_data = is_in_data # Set scan unit if scan_unit is None: scan_unit = "px" self.scan_unit = scan_unit # Set properties if prop is None: prop = {} self._prop = CrystalMapProperties(prop, id=point_id) # Set original data shape (needed if data shape changes in # __getitem__()) self._original_shape = self._data_shape_from_coordinates()