def linearity_func(self, value): def poly4(array_1d, coeffs): """ Define a 4th order polynomial and apply it on a 1D array. :param array_1d: a numpy 1D array :param coeffs: sequence of 5 Real numbers, the coefficients [a, b, c, d, e] of the polynomial ax^4 + bx^3 + cx^2 + dx + e :return: the updated 1D array """ return (coeffs[0] * array_1d**4 + coeffs[1] * array_1d**3 + coeffs[2] * array_1d**2 + coeffs[3] * array_1d + coeffs[4]) if value is None: self._linearity_func = None return valid.valid_container( value, container_types=(tuple, list, np.ndarray), length=5, item_types=Real, name="linearity_func", ) self._linearity_func = partial(poly4, coeffs=value)
def rotation_matrix(self, stage_name, angles): """ Calculate a 3D rotation matrix given rotation axes and angles. :param stage_name: supported stage name, 'sample' or 'detector' :param angles: list of angular values in degrees for the stage circles during the measurement :return: the rotation matrix as a numpy ndarray of shape (3, 3) """ self.valid_name(stage_name) nb_circles = len(self.__getattribute__(self.valid_names[stage_name])) if isinstance(angles, Number): angles = (angles, ) valid.valid_container( angles, container_types=(list, tuple, np.ndarray), length=nb_circles, item_types=Real, name="angles", ) # create a list of rotation matrices corresponding to the circles, # index 0 corresponds to the most outer circle rotation_matrices = [ RotationMatrix(circle, angles[idx]).get_matrix() for idx, circle in enumerate( self.__getattribute__(self.valid_names[stage_name])) ] # calculate the total tranformation matrix by rotating back # from outer circles to inner circles return np.array(reduce(np.matmul, rotation_matrices))
def in_range(point, extent): """ Return a boolean depending on whether point is in the indices range defined by extent or not. :param point: tuple of two real numbers (2D case) (y, x) or three real numbers (3D case) (z, y, x) representing the voxel indices to be tested :param extent: tuple of four integers (2D case) (y_start, y_stop, x_tart, x_stop) or six integers (3D case) (z_start, z_stop, y_start, y_stop, x_tart, x_stop) representing the range of valid indices :return: True if point belongs to extent, False otherwise """ # check parameters valid.valid_container(point, container_types=(list, tuple, np.ndarray), item_types=Real, name='utilities.in_range') ndim = len(point) if ndim not in {2, 3}: raise ValueError('point should be 2D or 3D') valid.valid_container(extent, container_types=(list, tuple, np.ndarray), length=2 * ndim, item_types=int, name='utilities.in_range') # check the appartenance to the defined extent if ndim == 2: if (extent[0] <= point[0] <= extent[1]) and\ (extent[2] <= point[1] <= extent[3]): return True else: if (extent[0] <= point[0] <= extent[1]) and\ (extent[2] <= point[1] <= extent[3]) and\ (extent[4] <= point[2] <= extent[5]): return True return False
def rotation_matrix_3d(axis_to_align, reference_axis): """ Calculate the rotation matrix which aligns axis_to_align onto reference_axis in 3D. :param axis_to_align: the 3D vector to be aligned (e.g. vector q), expressed in an orthonormal frame x y z :param reference_axis: will align axis_to_align onto this 3D vector, expressed in an orthonormal frame x y z :return: the rotation matrix as a np.array of shape (3, 3) """ # check parameters valid_name = 'utilities.rotation_matrix_3d' valid.valid_container(axis_to_align, container_types=(list, tuple, np.ndarray), length=3, item_types=Real, name=valid_name) valid.valid_container(reference_axis, container_types=(list, tuple, np.ndarray), length=3, item_types=Real, name=valid_name) # normalize the vectors axis_to_align = axis_to_align / np.linalg.norm(axis_to_align) reference_axis = reference_axis / np.linalg.norm(reference_axis) # calculate the skew-symmetric matrix v = np.cross(axis_to_align, reference_axis) skew_sym_matrix = np.array([[0, -v[2], v[1]], [v[2], 0, -v[0]], [-v[1], v[0], 0]]) rotation_matrix = np.identity(3) + skew_sym_matrix +\ np.dot(skew_sym_matrix, skew_sym_matrix) / (1+np.dot(axis_to_align, reference_axis)) return rotation_matrix.transpose()
def get_rocking_circle(self, rocking_angle, stage_name, angles): """ Find the index of the circle which corresponds to the rocking angle. :param rocking_angle: angle which is tilted during the rocking curve in {'outofplane', 'inplane'} :param stage_name: supported stage name, 'sample' or 'detector' :param angles: tuple of angular values in degrees, one for each circle of the sample stage :return: the index of the rocking circles in the list of angles """ # check parameters if rocking_angle not in {"outofplane", "inplane"}: raise ValueError( f"Invalid value {rocking_angle} for rocking_angle," f' should be either "inplane" or "outofplane"') self.valid_name(stage_name) valid.valid_container(angles, container_types=(tuple, list), name="angles") nb_circles = len(angles) # find which angles were scanned candidate_circles = set() for idx in range(nb_circles): if not isinstance(angles[idx], Real) and len(angles[idx]) > 1: # not a number, hence a tuple/list/ndarray (cannot be None) candidate_circles.add(idx) # exclude arrays with identical values wrong_motors = [] for idx in candidate_circles: if (angles[idx][1:] - angles[idx][:-1]).mean( ) < 0.0001: # motor not scanned, noise in the position readings wrong_motors.append(idx) candidate_circles.difference_update(wrong_motors) # check that there is only one candidate remaining if len(candidate_circles) > 1: raise ValueError( "Several circles were identified as scanned motors") if len(candidate_circles) == 0: raise ValueError("No circle was identified as scanned motor") index_circle = next(iter(candidate_circles)) # check that the rotation axis corresponds to the one definec by rocking_angle circles = self.__getattribute__(self.valid_names[stage_name]) if rocking_angle == "inplane": if circles[index_circle][0] != "y": raise ValueError( f"The identified circle '{circles[index_circle]}' is incompatible " f"with the parameter '{rocking_angle}'") else: # 'outofplane' if circles[index_circle][0] != "x": raise ValueError( f"The identified circle '{circles[index_circle]}' is incompatible " f"with the parameter '{rocking_angle}'") return index_circle
def sample_name(self, value): valid.valid_container( value, container_types=str, min_length=1, allow_none=True, name="Detector.sample_name", ) self._sample_name = value
def template_imagefile(self, value): valid.valid_container( value, container_types=str, min_length=0, allow_none=True, name="template_imagefile", ) self._template_imagefile = value
def file_path(self, value): valid.valid_container(value, container_types=str, min_length=1, name="file_path") if pathlib.Path(value).suffix != ".yml": raise ValueError("Expecting a YAML config file") if not os.path.isfile(value): raise ValueError(f"The file {value} does not exist") self._file_path = value
def preprocessing_binning(self, value): valid.valid_container( value, container_types=(tuple, list), length=3, item_types=int, min_excluded=0, name="Detector.preprocessing_binning", ) self._preprocessing_binning = value
def savedir(self, value): valid.valid_container( value, container_types=str, min_length=1, allow_none=True, name="Detector.savedir", ) if value is not None: pathlib.Path(value).mkdir(parents=True, exist_ok=True) self._savedir = value
def rootdir(self, value): valid.valid_container( value, container_types=str, min_length=1, allow_none=True, name="Detector.rootdir", ) if value is not None and not os.path.isdir(value): raise ValueError(f"The directory {value} does not exist") self._rootdir = value
def sample_offsets(self, value): nb_circles = len(self.__getattribute__(self.valid_names["sample"])) if value is None: value = (0, ) * nb_circles valid.valid_container( value, container_types=(tuple, list, np.ndarray), length=nb_circles, item_types=Real, name="Diffractometer.sample_offsets", ) self._sample_offsets = value
def roi(self, value): if not value: # None or empty list/tuple value = [0, self.nb_pixel_y, 0, self.nb_pixel_x] valid.valid_container( value, container_types=(tuple, list, np.ndarray), length=4, item_types=int, name="Detector.roi", ) if value[1] <= value[0] or value[3] <= value[2]: raise ValueError("roi coordinates should be increasing in x and y") self._roi = value
def sample_circles(self, value): valid.valid_container( value, container_types=(tuple, list), min_length=0, item_types=str, name="Diffractometer.sample_circles", ) if any(val not in self.valid_circles for val in value): raise ValueError( "Invalid circle value encountered in sample_circles," f" valid are {self.valid_circles}") self._sample_circles = list(value)
def sum_roi(self, value): if not value: # None or empty list/tuple value = self.roi valid.valid_container( value, container_types=(tuple, list), length=4, item_types=int, name="Detector.sum_roi", ) if value[1] <= value[0] or value[3] <= value[2]: raise ValueError("roi coordinates should be increasing in x and y") self._sum_roi = value
def create_roi(dic: Dict[str, Any]) -> Any: """ Load "roi_detector" from the dictionary of parameters and update it. If the keys "center_roi_x" or "center_roi_y" are defined, it will consider that the current values in roi_detector define a window around the Bragg peak position and the final output will be: [center_roi_y - roi_detector[0], center_roi_y + roi_detector[1], center_roi_x - roi_detector[2], center_roi_x + roi_detector[3]]. If a key is not defined, it will consider that the values of roi_detector are absolute pixels positions, e.g. if only "center_roi_y" is defined, the output will be: [center_roi_y - roi_detector[0], center_roi_y + roi_detector[1], roi_detector[2], roi_detector[3]]. Accordingly, if none of the keys are defined, the output will be: [roi_detector[0], roi_detector[1], roi_detector[2], roi_detector[3]]. :param dic: a dictionary of parameters :return: the calculated region of interest [Vstart, Vstop, Hstart, Hstop] or None """ valid.valid_container(dic, container_types=dict, name="dic") roi = dic.get("roi_detector") valid.valid_container( roi, container_types=(tuple, list, np.ndarray), length=4, item_types=int, allow_none=True, name="roi_detector", ) # update the ROI if roi is not None: center_roi_y = dic.get("center_roi_y") if center_roi_y is not None: valid.valid_item(center_roi_y, allowed_types=int, name="center_roi_y") roi[0] = center_roi_y - roi[0] roi[1] = center_roi_y + roi[1] center_roi_x = dic.get("center_roi_x") if center_roi_x is not None: valid.valid_item(center_roi_x, allowed_types=int, name="center_roi_x") roi[2] = center_roi_x - roi[2] roi[3] = center_roi_x + roi[3] return roi
def test_validcontainer_container_itemtype_ndarray(self): self.assertTrue( valid.valid_container( obj=(np.ones(3), np.zeros(4)), container_types=(tuple, list), item_types=np.ndarray, ))
def test_validcontainer_container_dict_itemtype_int(self): self.assertTrue( valid.valid_container(obj={0: { "x": 1 }}, container_types=dict, item_types=int))
def __init__(self, name, **kwargs): self.custom_pixelsize = kwargs.get("custom_pixelsize") valid.valid_item( self.custom_pixelsize, allowed_types=Real, min_excluded=0, allow_none=True, name="custom_pixelsize", ) self.custom_pixelnumber = kwargs.get("custom_pixelnumber") if isinstance(self.custom_pixelnumber, np.ndarray): self.custom_pixelnumber = list(self.custom_pixelnumber) valid.valid_container( self.custom_pixelnumber, container_types=(list, tuple), length=2, item_types=Integral, min_excluded=0, allow_none=True, name="custom_pixelnumber", ) super().__init__(name=name, **kwargs)
cmap='gray', debug=False) else: obj, _ = util.load_file(file_path) ndim = obj.ndim ######################### # check some parameters # ######################### valid_name = 'bcdi_line_profile' if ndim not in {2, 3}: raise ValueError(f'Number of dimensions = {ndim}, expected 2 or 3') valid.valid_container(direction, container_types=(list, tuple, np.ndarray), length=ndim, item_types=Real, name=valid_name) valid.valid_container(points, container_types=(list, tuple, set), min_length=1, name=valid_name) for point in points: valid.valid_container(point, container_types=(list, tuple, np.ndarray), length=ndim, item_types=Real, min_included=0, name=valid_name)
############################# # define default parameters # ############################# colors = ("b", "g", "r", "c", "m", "y", "k") # for plots markers = (".", "v", "^", "<", ">") # for plots validation_name = "angular_profile" mpl.rcParams["axes.linewidth"] = tick_width # set the linewidth globally ######################### # check some parameters # ######################### valid.valid_item(value=upsampling_factor, allowed_types=int, min_included=1, name=validation_name) valid.valid_container(comment, container_types=str, name=validation_name) if comment.startswith("_"): comment = comment[1:] ################################################## # create the list of directions for the linecuts # ################################################## angles = np.arange(0, 180, angular_step) nb_dir = len(angles) directions = [] for idx in range(nb_dir): directions.append( (np.sin(angles[idx] * np.pi / 180), np.cos(angles[idx] * np.pi / 180))) ################# # load the data #
# end of user-defined parameters # ################################## ######################### # check some parameters # ######################### valid_name = "diffpattern_from_reconstruction" savedir = savedir or datadir pathlib.Path(savedir).mkdir(parents=True, exist_ok=True) if isinstance(voxel_sizes, Real): voxel_sizes = (voxel_sizes,) * 3 valid.valid_container( voxel_sizes, container_types=(list, tuple, np.ndarray), length=3, item_types=Real, min_excluded=0, name=valid_name, ) valid.valid_container( padding_shape, container_types=(tuple, list, np.ndarray), item_types=int, min_excluded=0, length=3, name=valid_name, ) valid.valid_item( peak_value, allowed_types=Real, min_excluded=0, allow_none=True, name=valid_name
num_ticks = 5 # number of ticks to use in axes when tick_spacing is not defined ################################## # end of user-defined parameters # ################################## #################### # Check parameters # #################### valid_name = "bcdi_plot_diffpattern_2D" valid.valid_item(save_sum, allowed_types=bool, name=valid_name) if save_sum: comment = comment + "_sum" valid.valid_container( colorbar_range, container_types=(tuple, list, np.ndarray), item_types=Real, length=2, allow_none=True, name=valid_name, ) if isinstance(tick_spacing, Real) or tick_spacing is None: tick_spacing = (tick_spacing,) * 3 valid.valid_container( tick_spacing, container_types=(tuple, list, np.ndarray), allow_none=True, item_types=Real, min_excluded=0, name=valid_name, ) valid.valid_item(num_ticks, allowed_types=int, min_excluded=0, name=valid_name)
except AttributeError: # mouse pointer out of axes pass ######################### # check some parameters # ######################### if (vmin and vmax) and (vmax <= vmin): raise ValueError("vmax should be larger than vmin") savedir = savedir or datadir valid.valid_container( obj=starting_point, container_types=list, allow_none=True, item_types=int, name="linecut_diffpattern.py", ) valid.valid_container( obj=endpoint, container_types=list, allow_none=True, item_types=int, name="linecut_diffpattern.py", ) ################### # define colormap # ################### my_cmap = ColormapFactory().generate_cmap()
# check and initialize some parameters # ######################################## print(f"\n{len(scans)} scans: {scans}") print(f"\n {len(x_axis)} x_axis values provided:") if len(x_axis) == 0: x_axis = np.arange(len(scans)) if len(x_axis) != len(scans): raise ValueError( "the length of x_axis should be equal to the number of scans") if isinstance(sample_name, str): sample_name = [sample_name for idx in range(len(scans))] valid.valid_container( sample_name, container_types=(tuple, list), length=len(scans), item_types=str, name="preprocess_bcdi", ) if peak_method not in [ "max", "com", "max_com", ]: raise ValueError('invalid value for "peak_method" parameter') int_sum = [] # integrated intensity in the detector ROI int_max = [] # maximum intensity in the detector ROI zcom = [] # center of mass for the first data axis ycom = [] # center of mass for the second data axis xcom = [] # center of mass for the third data axis
def command_line_args(self, value): valid.valid_container(value, container_types=dict, allow_none=True, name="command_line_args") self._command_line_args = value
# check some parameters # ######################### if not datadir.endswith('/'): datadir += '/' savedir = savedir or datadir if not savedir.endswith('/'): savedir += '/' pathlib.Path(savedir).mkdir(parents=True, exist_ok=True) valid.valid_item(isosurface_threshold, allowed_types=Real, min_included=0, max_excluded=1, name=validation_name) valid.valid_container(phasing_shape, container_types=(tuple, list, np.ndarray), allow_none=True, item_types=int, min_excluded=0, name=validation_name) valid.valid_item(value=upsampling_factor, allowed_types=int, min_included=1, name=validation_name) valid.valid_container(comment, container_types=str, name=validation_name) if len(comment) != 0 and not comment.startswith('_'): comment = '_' + comment valid.valid_item(tick_length, allowed_types=int, min_excluded=0, name=validation_name) valid.valid_item(tick_width, allowed_types=int,
filetypes=[("NPZ", "*.npz"), ("NPY", "*.npy")], ) try: mask, _ = util.load_file(file_path) except ValueError: mask = np.zeros(diff_pattern.shape, dtype=int) ########################################################### # crop the diffraction pattern and the mask to compensate # # the "auto_center_resize" option used in PyNX # ########################################################### # The shape will be equal to 'roi_final' parameter of the .cxi file valid.valid_container( obj=crop_roi, container_types=(list, tuple), length=6, item_types=int, allow_none=True, name="prtf_bcdi.py", ) if crop_roi is not None: diff_pattern = diff_pattern[crop_roi[0]:crop_roi[1], crop_roi[2]:crop_roi[3], crop_roi[4]:crop_roi[5]] mask = mask[crop_roi[0]:crop_roi[1], crop_roi[2]:crop_roi[3], crop_roi[4]:crop_roi[5]] ############################################### # bin the diffraction pattern and the mask to # # compensate the "rebin" option used in PyNX # ############################################### # update also the detector pixel sizes to take into account the binning
if ext in {".png", ".jpg", ".tif"}: obj = util.image_to_ndarray(filename=file_path, convert_grey=True) else: obj, _ = util.load_file(file_path) ndim = obj.ndim ######################### # check some parameters # ######################### if ndim != 2: raise ValueError(f"Number of dimensions = {ndim}, expected 2") valid.valid_container( direction, container_types=(list, tuple, np.ndarray), length=ndim, item_types=Real, name=validation_name, ) valid.valid_container(points, container_types=(list, tuple), min_length=1, name=validation_name) for point in points: valid.valid_container( point, container_types=(list, tuple, np.ndarray), length=ndim, item_types=Real, min_included=0,
elif data_frame == 'detector': is_orthogonal = False else: is_orthogonal = True if ref_axis_q not in {'x', 'y', 'z'}: raise ValueError("ref_axis_q should be either 'x', 'y', 'z'") if ref_axis not in {'x', 'y', 'z'}: raise ValueError("ref_axis should be either 'x', 'y', 'z'") if isinstance(output_size, Real): output_size = (output_size, ) * 3 valid.valid_container(output_size, container_types=(tuple, list, np.ndarray), length=3, allow_none=True, item_types=int, name=valid_name) axis_to_array_xyz = { 'x': np.array([1, 0, 0]), 'y': np.array([0, 1, 0]), 'z': np.array([0, 0, 1]) } # in xyz order comment = comment + '_' + str(isosurface_strain) ################### # define colormap # ################### if grey_background: bad_color = '0.7'