def save_mantid_mask(mask_vec, h5_name: str, two_theta: float, note: Optional[str] = None) -> None: """ Save a mask vector to :param mask_vec: :param h5_name: :param two_theta: :param note: :return: """ checkdatatypes.check_numpy_arrays('Mask vector', [mask_vec], dimension=1, check_same_shape=False) checkdatatypes.check_file_name(h5_name, False, True, False, 'PyRS masking file to export to') if two_theta is not None: two_theta = to_float('2-theta', two_theta, -360., 360) if note is not None: checkdatatypes.check_string_variable('Mask note', note, None) # create file mask_file = h5py.File(h5_name, 'w') # add data set mask_data_set = mask_file.create_dataset('mask', data=mask_vec) # add attributes if two_theta: mask_data_set.attrs['2theta'] = two_theta # '{}'.format(two_theta) if note: mask_data_set.attrs['note'] = note # close file mask_file.close()
def write_wavelength(self, wave_length: float): """ Set the calibrated wave length Location: .../instrument/monochromator setting/ ... .../ Note: - same wave length to all sub runs - only calibrated wave length in project file - raw wave length comes from a table with setting :param wave_length: wave length in A :return: None """ wave_length = to_float('Wave length', wave_length, min_value=0, max_value=1000) # Create 'monochromator setting' node if it does not exist if HidraConstants.MONO not in list( self._project_h5[HidraConstants.INSTRUMENT].keys()): self._project_h5[HidraConstants.INSTRUMENT].create_group( HidraConstants.MONO) # Get node and write value wl_entry = self._project_h5[HidraConstants.INSTRUMENT][ HidraConstants.MONO] # delete the dataset if it does exist to replace if HidraConstants.WAVELENGTH in list(wl_entry.keys()): del wl_entry[HidraConstants.WAVELENGTH] wl_entry.create_dataset(HidraConstants.WAVELENGTH, data=numpy.array([wave_length]))
def get_pole_figure_vectors(self, det_id, max_cost, min_cost=1.): """ return Pole figure in a numpy 2D array :param det_id: :param max_cost: :param min_cost: :return: 2-tuple: (1) an integer list (2) numpy array with shape (n, 3). n is the number of data points """ # check input if max_cost is None: max_cost = 1.E20 else: max_cost = to_float('Maximum peak fitting cost value', max_cost, min_value=0.) # get raw parameters' fitted value log_index_vec, pole_figure_vec = self._pole_figure_dict[det_id] # get costs and filter out isnan() cost_vec = self.get_peak_fit_parameter_vec('cost', det_id) taken_index_list = list() for idx in range(len(cost_vec)): if min_cost < cost_vec[idx] < max_cost: taken_index_list.append(idx) # END-IF selected_log_index_vec = np.take(log_index_vec, taken_index_list, axis=0) selected_pole_figure_vec = np.take(pole_figure_vec, taken_index_list, axis=0) return selected_log_index_vec, selected_pole_figure_vec
def set_experimental_data(self, two_theta: float, l2: Optional[float], raw_count_vec): """ Set experimental data (for a sub-run) :param two_theta: detector position :param l2: detector distance from center of rotation :param raw_count_vec: detector raw counts :return: """ self._detector_2theta = to_float('2-theta', two_theta, -180, 180) if l2 is not None: l2 = to_float('L2', l2, 1.E-2) self._detector_l2 = l2 checkdatatypes.check_numpy_arrays('Detector (raw) counts', [raw_count_vec], None, False) self._detector_counts = raw_count_vec
def rotate_project_q(self, theta: float, omega: float, chi: float, phi: float, eta: float) -> Tuple[float, float]: """ Projection of angular dependent data onto pole sphere. Analytical solution taken from Chapter 8.3 in Bob He Two-Dimensional X-ray Diffraction _______________________ :param two_theta: :param omega: :param chi: :param phi: :return: 2-tuple as the projection (alpha, beta) """ theta = to_float('theta', theta) omega = to_float('Omega', omega) chi = to_float('chi', chi) phi = to_float('phi', phi) eta = to_float('eta', eta) sp = np.sin(np.deg2rad(phi)) sw = np.sin(np.deg2rad(omega)) sc = np.sin(np.deg2rad(chi)) sg = np.sin(np.deg2rad(eta)) st = np.sin(np.deg2rad(theta)) cp = np.cos(np.deg2rad(phi)) cw = np.cos(np.deg2rad(omega)) cc = np.cos(np.deg2rad(chi)) cg = np.cos(np.deg2rad(eta)) ct = np.cos(np.deg2rad(theta)) h1 = st * (sp * sc * sw + cp * cw) + ct * cg * sp * cc - ct * sg * ( sp * sc * cw - cp * sw) h2 = -st * (cp * sc * sw - sp * cw) - ct * cg * cp * cc + ct * sg * ( cp * sc * cw + sp * sw) # h3 = st*cc*sw - ct*sg*cc*cw - ct*cg*sc h_length = np.sqrt(np.square(h1) + np.square(h2)) alpha = np.arccos(h_length) beta = np.rad2deg(np.arccos(h1 / h_length)) if h2 < 0: beta *= -1. return alpha, beta
def __init__(self, num_rows: int, num_columns: int, pixel_size_x: float, pixel_size_y: float, arm_length: float, calibrated: bool) -> None: """ Initialization of instrument geometry setup for 1 denex detector :param num_rows: number of rows of pixels in detector (number of pixels per column) :param num_columns: number of columns of pixels in detector (number of pixels per row) :param pixel_size_x: pixel size at X direction (along a row) :param pixel_size_y: pixel size at Y direction (along a column) :param arm_length: arm length :param calibrated: flag whether these values are calibrated """ # check inputs self._arm_length = to_float('Arm length', arm_length, min_value=1E-5) self._pixel_size_x = to_float('Pixel size (x)', pixel_size_x, min_value=1E-7) self._pixel_size_y = to_float('Pixel size (y)', pixel_size_y, min_value=1E-7) self._detector_rows = to_int('Number of rows in detector', num_rows, min_value=1) self._detector_columns = to_int('Number of columns in detector', num_columns, min_value=1) # TODO this isn't used - BUG? checkdatatypes.check_bool_variable('Flag indicating instrument setup been calibrated', calibrated)
def two_theta_0(self, value: float) -> None: self._two_theta_0 = to_float('offset in two_theta arm for ideal 0', value, -360, 360)
def rotation_z(self, value: float) -> None: self._rotation_z = to_float('Rotation along Z direction', value, -360, 360)
def center_shift_z(self, value: float) -> None: self._center_shift_z = to_float('Center shift along Z direction', value)
def build_instrument(self, two_theta: float, l2: Optional[float] = None, instrument_calibration=None): """ build instrument considering calibration step 1: rotate instrument according to the calibration step 2: rotate instrument about 2theta :param two_theta :param l2 :param instrument_calibration: DENEXDetectorShift or None (no calibration) :return: """ # Check input two_theta = to_float('2theta', two_theta) # Check or set L2 if l2 is None: l2 = self._instrument_geom_params.arm_length else: l2 = to_float('L2', l2, 1E-2) # print('[DB...L101] Build instrument: 2theta = {}, arm = {} (diff to default = {})' # ''.format(two_theta, l2, l2 - self._instrument_geom_params.arm_length)) # make a copy from raw (constant position) self._pixel_matrix = self._raw_pixel_matrix.copy() # Check and set instrument calibration if instrument_calibration is not None: # check type checkdatatypes.check_type('Instrument calibration', instrument_calibration, instrument_geometry.DENEXDetectorShift) # shift center self._pixel_matrix[:, :, 0] += instrument_calibration.center_shift_x self._pixel_matrix[:, :, 1] += instrument_calibration.center_shift_y # rotation around instrument center # get rotation matrix at origin (for flip, spin and vertical): all data from calibration value rot_x_flip = instrument_calibration.rotation_x * np.pi / 180. rot_y_flip = instrument_calibration.rotation_y * np.pi / 180. rot_z_spin = instrument_calibration.rotation_z * np.pi / 180. calib_matrix = self.generate_rotation_matrix( rot_x_flip, rot_y_flip, rot_z_spin) # print ('[DB...BAT] Calibration rotation matrix:\n{}'.format(calib_matrix)) # and rotate at origin self._pixel_matrix = self._rotate_detector(self._pixel_matrix, calib_matrix) # shift two_theta by offset two_theta += instrument_calibration.two_theta_0 # END-IF-ELSE # push to +Z at length of detector arm arm_l2 = l2 if instrument_calibration is not None: # Apply the shift on Z (arm length) arm_l2 += instrument_calibration.center_shift_z # END-IF self._pixel_matrix[:, :, 2] += arm_l2 # rotate detector (2theta) if it is not zero self.rotate_detector_2theta(two_theta) return self._pixel_matrix
def generate_2theta_histogram_vector(min_2theta: Optional[float], num_bins: int, max_2theta: Optional[float], pixel_2theta_array, mask_array, step_2theta=None): """Generate a 1-D array for histogram 2theta bins Parameters ---------- min_2theta : float or None minimum 2theta or None num_bins : int number of bins max_2theta : float or None maximum 2theta and must be integer pixel_2theta_array : numpy.ndarray 2theta of each detector pixel mask_array : numpy.ndarray or None array of mask Returns ------- numpy.ndarray 2theta values serving as bin boundaries, such its size is 1 larger than num_bins """ # If default value is required: set the default if min_2theta is None or max_2theta is None: # check inputs if mask_array is None: checkdatatypes.check_numpy_arrays('Pixel 2theta angles', [pixel_2theta_array], 1, False) else: checkdatatypes.check_numpy_arrays( 'Pixel 2theta position and mask array', [pixel_2theta_array, mask_array], 1, True) # mask pixel_2theta_array = pixel_2theta_array[np.where( mask_array == 1)] if min_2theta is None: # lower boundary of 2theta for bins is the minimum 2theta angle of all the pixels min_2theta = np.min(pixel_2theta_array) if max_2theta is None: # upper boundary of 2theta for bins is the maximum 2theta angle of all the pixels max_2theta = np.max(pixel_2theta_array) if step_2theta is None: step_2theta = (max_2theta - min_2theta) * 1. / num_bins else: num_bins = np.ceil((max_2theta - min_2theta) / step_2theta) + 1 # Check inputs min_2theta = to_float('Minimum 2theta', min_2theta, 20, 140) max_2theta = to_float('Maximum 2theta', max_2theta, 21, 180) step_2theta = to_float('2theta bin size', step_2theta, 0, 180) if min_2theta >= max_2theta: raise RuntimeError( '2theta range ({}, {}) is invalid for generating histogram' ''.format(min_2theta, max_2theta)) # Create 2theta: these are bin edges from (min - 1/2) to (max + 1/2) with num_bins bins # and (num_bins + 1) data points vec_2theta = np.arange(num_bins + 1).astype(float) * step_2theta + ( min_2theta - step_2theta) if vec_2theta.shape[0] != num_bins + 1: raise RuntimeError( 'Expected = {} vs {}\n2theta min max = {}, {}\n2thetas: {}' ''.format(num_bins, vec_2theta.shape, min_2theta, max_2theta, vec_2theta)) # Sanity check assert vec_2theta.shape[0] == num_bins + 1, '2theta bins (boundary)\'size ({}) shall be exactly ' \ '1 larger than specified num_bins ({})' \ ''.format(vec_2theta.shape, num_bins) return vec_2theta
def test_convert_to_float(): GOOD = 'good' BAD = 'bad' # simple checks assert to_float(GOOD, 42.) == 42. assert to_float(GOOD, 42) == 42. assert to_float(GOOD, '42') == 42. assert to_float(GOOD, 42., 41, 43) == 42. assert to_float(BAD, 42., 42, 43, min_inclusive=True, max_inclusive=False) == 42. assert to_float(BAD, 42., 41, 42, min_inclusive=False, max_inclusive=True) == 42. # check invalid range with pytest.raises(ValueError) as err: to_float(BAD, 42., 43, 41) assert BAD in str(err.value) # check value outside of range with pytest.raises(ValueError) as err: assert not to_float(BAD, 42., 42, 43, min_inclusive=False, max_inclusive=True) assert BAD in str(err.value) with pytest.raises(ValueError) as err: assert not to_float(BAD, 42., 41, 42, min_inclusive=True, max_inclusive=False) assert BAD in str(err.value) # check with non-convertable values for value in [None, 'strings cannot be converted', (1, 2)]: with pytest.raises(TypeError) as err: assert not to_float(BAD, value) assert BAD in str(err.value)