def initialize_pixels_with_beam(self, beam=None): """ Calculate the pixel position in the reciprocal space and several corrections. :param beam: The beam object :return: None """ if beam is None: return self._has_beam = True self._beam = beam wavevector = beam.get_wavevector() polar = beam.Polarization intensity = beam.get_photons_per_pulse() / beam.get_focus_area() # Get the reciprocal positions and the corrections (self.pixel_position_reciprocal, self.pixel_distance_reciprocal, self.polarization_correction, self.solid_angle_per_pixel ) = pg.get_reciprocal_position_and_correction( pixel_position=self.pixel_position, polarization=polar, wave_vector=wavevector, pixel_area=self.pixel_area, orientation=self.orientation) # Put all the corrections together self.linear_correction = intensity * self.Thomson_factor * xp.multiply( self.polarization_correction, self.solid_angle_per_pixel)
def solid_angle(pixel_center, pixel_area, orientation): """ Calculate the solid angle for each pixel. :param pixel_center: The position of each pixel in real space. In pixel stack format. :param orientation: The orientation of the detector. :param pixel_area: The pixel area for each pixel. In pixel stack format. :return: Solid angle of each pixel. """ orientation = xp.asarray(orientation) # Use 1D format pixel_center_1d = reshape_pixels_position_arrays_to_1d(pixel_center) pixel_center_norm_1d = xp.sqrt(xp.sum(xp.square(pixel_center_1d), axis=-1)) pixel_area_1d = xp.reshape(pixel_area, np.prod(pixel_area.shape)) # Calculate the direction of each pixel. pixel_center_direction_1d = pixel_center_1d / pixel_center_norm_1d[:, xp.newaxis] # Normalize the orientation vector orientation_norm = xp.sqrt(xp.sum(xp.square(orientation))) orientation_normalized = orientation / orientation_norm # The correction induced by projection which is a factor of cosine. cosine_1d = xp.abs(xp.dot(pixel_center_direction_1d, orientation_normalized)) # Calculate the solid angle ignoring the projection solid_angle_1d = xp.divide(pixel_area_1d, xp.square(pixel_center_norm_1d)) solid_angle_1d = xp.multiply(cosine_1d, solid_angle_1d) # Restore the pixel stack format solid_angle_stack = xp.reshape(solid_angle_1d, pixel_area.shape) return solid_angle_stack
def add_correction_batch(self, pattern_batch): """ Add corrections to a batch of image stack :param pattern_batch [image stack index,image stack shape] :return: """ self.ensure_beam() return xp.multiply(pattern_batch, self.linear_correction[xp.newaxis])
def add_correction_and_quantization(self, pattern): """ Add corrections to image stack and apply quantization to the image stack :param pattern: The image stack. :return: images with linear correction applied and quantized """ self.ensure_beam() return xp.random.poisson(xp.multiply(pattern, self.linear_correction))
def add_correction(self, pattern): """ Add linear correction to the image stack :param pattern: The image stack :return: image stack with linear correction applied """ self.ensure_beam() return xp.multiply(pattern, self.linear_correction)
def add_polarization_correction(self, pattern): """ Add polarization correction to the image stack :param pattern: image stack :return: image stack with polarization correction applied """ self.ensure_beam() return xp.multiply(pattern, self.polarization_correction)
def add_solid_angle_correction(self, pattern): """ Add solid angle corrections to the image stack. :param pattern: Pattern stack :return: Pattern stack with solid angle correction """ self.ensure_beam() return xp.multiply(pattern, self.solid_angle_per_pixel)
def add_correction_and_quantization_batch(self, pattern_batch): """ Add corrections to a batch of image stack and apply quantization to the batch :param pattern_batch: [image stack index, image stack shape] :return: """ self.ensure_beam() return xp.random.poisson( xp.multiply(pattern_batch, self.linear_correction[xp.newaxis]))
def get_intensity_field(self, particle, device=None): """ Generate a single diffraction pattern without any correction from the particle object. :param particle: The particle object. :return: A diffraction pattern. """ self.ensure_beam() if device: deprecation_message("Device option is deprecated. " "Everything now runs on the GPU.") import skopi.gpu.diffraction as pgd # Only import GPU if needed diffraction_pattern = pgd.calculate_diffraction_pattern_gpu( self.pixel_position_reciprocal, particle, "intensity") return xp.multiply(diffraction_pattern, self.linear_correction)
def extract_slice(local_index, local_weight, volume): """ Take one slice from the volume given the index and weight map. :param local_index: The index containing values to take. :param local_weight: The weight for each index. :param volume: The volume to slice from. :return: The slice or an array of zeros is if dimensions are incompatible. """ local_index = xp.asarray(local_index) local_weight = xp.asarray(local_weight) volume = xp.asarray(volume) # Convert the index of the 3D diffraction volume to 1D pattern_shape = local_index.shape[:3] pixel_num = int(np.prod(pattern_shape)) volume_num_1d = volume.shape[0] convertion_factor = xp.array( [volume_num_1d * volume_num_1d, volume_num_1d, 1], dtype=np.int64) index_2d = xp.reshape(local_index, [pixel_num, 8, 3]) index_2d = xp.matmul(index_2d, convertion_factor) volume_1d = xp.reshape(volume, volume_num_1d ** 3) weight_2d = xp.reshape(local_weight, [pixel_num, 8]) # Expand the data to merge try: data_to_merge = volume_1d[index_2d] except IndexError: print("Requested slice and diffraction volume have incompatible dimensions") return np.zeros(pattern_shape) # Merge the data data_merged = xp.sum(xp.multiply(weight_2d, data_to_merge), axis=-1) data = xp.reshape(data_merged, pattern_shape) return data
def __init__(self, N_pixel, det_size, det_distance, beam=None): """ Initialize the detector :param N_pixel: Number of pixels per dimension. :param det_size: Length of detector sides (m). :param det_distance: Sample-Detector distance (m). """ super(SimpleSquareDetector, self).__init__() ar = xp.arange(N_pixel) sep = float(det_size) / N_pixel x = -det_size / 2 + sep / 2 + ar * sep y = -det_size / 2 + sep / 2 + ar * sep X, Y = xp.meshgrid(x, y, indexing='xy') Xar, Yar = xp.meshgrid(ar, ar, indexing='xy') self.panel_num = 1 self.panel_pixel_num_x = N_pixel self.panel_pixel_num_y = N_pixel self._shape = (1, N_pixel, N_pixel) # Define all properties the detector should have self._distance = det_distance self.center_z = self._distance * xp.ones(self._shape, dtype=xp.float64) p_center_x = xp.stack((X, )) p_center_y = xp.stack((Y, )) self.pixel_width = sep * xp.ones(self._shape, dtype=xp.float64) self.pixel_height = sep * xp.ones(self._shape, dtype=xp.float64) self.center_x = p_center_x self.center_y = p_center_y # construct the the pixel position array self.pixel_position = xp.zeros(self._shape + (3, )) self.pixel_position[..., 0] = self.center_x self.pixel_position[..., 1] = self.center_y self.pixel_position[..., 2] = self.center_z # Pixel map p_map_x = xp.stack((Xar, )) p_map_y = xp.stack((Yar, )) # [panel number, pixel num x, pixel num y] self.pixel_index_map = xp.stack((p_map_x, p_map_y), axis=-1) # Detector pixel number info self.detector_pixel_num_x = asnumpy( xp.max(self.pixel_index_map[..., 0]) + 1) self.detector_pixel_num_y = asnumpy( xp.max(self.pixel_index_map[..., 1]) + 1) # Panel pixel number info # number of pixels in each panel in x/y direction self.panel_pixel_num_x = self.pixel_index_map.shape[1] self.panel_pixel_num_y = self.pixel_index_map.shape[2] # total number of pixels (px*py) self.pixel_num_total = np.prod(self._shape) # Calculate the pixel area self.pixel_area = xp.multiply(self.pixel_height, self.pixel_width) # Get reciprocal space configurations and corrections. self.initialize_pixels_with_beam(beam=beam)
def initialize(self, geom, beam): """ Initialize the detector with user defined parameters :param geom: The dictionary containing all the necessary information to initialized the detector. :param beam: The beam object :return: None """ """ Doc: To use this class, the user has to provide the necessary information to initialize the detector. All the necessary entries are listed in the example notebook. """ # 'detector distance': detector distance in (m) ########################################################################################## # Extract necessary information ########################################################################################## # Define the hierarchy system. For simplicity, we only use two-layer structure. for key in {'panel number', 'panel pixel num x', 'panel pixel num y'}: if key not in geom: raise KeyError("Missing required '{}' key.".format(key)) self.panel_num = int(geom['panel number']) self.panel_pixel_num_x = int(geom['panel pixel num x']) self.panel_pixel_num_y = int(geom['panel pixel num y']) self._shape = (self.panel_num, self.panel_pixel_num_x, self.panel_pixel_num_y) # Define all properties the detector should have self._distance = None if 'pixel center z' in geom: if 'detector distance' in geom: raise ValueError("Please provide one of " "'pixel center z' or 'detector distance'.") self.center_z = xp.asarray(geom['pixel center z'], dtype=xp.float64) self._distance = float(self.center_z.mean()) else: if 'detector distance' not in geom: KeyError("Missing required 'detector distance' key.") self._distance = float(geom['detector distance']) self.center_z = self._distance * xp.ones(self._shape, dtype=xp.float64) # Below: [panel number, pixel num x, pixel num y] in (m) # Change dtype and make numpy/cupy array self.pixel_width = xp.asarray(geom['pixel width'], dtype=xp.float64) self.pixel_height = xp.asarray(geom['pixel height'], dtype=xp.float64) self.center_x = xp.asarray(geom['pixel center x'], dtype=xp.float64) self.center_y = xp.asarray(geom['pixel center y'], dtype=xp.float64) self.orientation = np.array([0, 0, 1]) # construct the the pixel position array self.pixel_position = xp.zeros(self._shape + (3, )) self.pixel_position[..., 0] = self.center_x self.pixel_position[..., 1] = self.center_y self.pixel_position[..., 2] = self.center_z # Pixel map if 'pixel map' in geom: # [panel number, pixel num x, pixel num y] self.pixel_index_map = xp.asarray(geom['pixel map'], dtype=xp.int64) # Detector pixel number info self.detector_pixel_num_x = asnumpy( xp.max(self.pixel_index_map[..., 0]) + 1) self.detector_pixel_num_y = asnumpy( xp.max(self.pixel_index_map[..., 1]) + 1) # Panel pixel number info # number of pixels in each panel in x/y direction self.panel_pixel_num_x = self.pixel_index_map.shape[1] self.panel_pixel_num_y = self.pixel_index_map.shape[2] # total number of pixels (px*py) self.pixel_num_total = np.prod(self._shape) ########################################################################################### # Do necessary calculation to finishes the initialization ########################################################################################### # self.geometry currently only work for the pre-defined detectors self.geometry = geom # Calculate the pixel area self.pixel_area = xp.multiply(self.pixel_height, self.pixel_width) # Get reciprocal space configurations and corrections. self.initialize_pixels_with_beam(beam=beam) ########################################################################################## # Do necessary calculation to finishes the initialization ########################################################################################## # Detector effects if 'pedestal' in geom: self._pedestal = xp.asarray(geom['pedestal'], dtype=xp.float64) else: self._pedestal = xp.zeros((self.panel_num, self.panel_pixel_num_x, self.panel_pixel_num_y)) if 'pixel rms' in geom: self._pixel_rms = xp.asarray(geom['pixel rms'], dtype=xp.float64) else: self._pixel_rms = xp.zeros((self.panel_num, self.panel_pixel_num_x, self.panel_pixel_num_y)) if 'pixel bkgd' in geom: self._pixel_bkgd = xp.asarray(geom['pixel bkgd'], dtype=xp.float64) else: self._pixel_bkgd = xp.zeros( (self.panel_num, self.panel_pixel_num_x, self.panel_pixel_num_y)) if 'pixel status' in geom: self._pixel_status = xp.asarray(geom['pixel status'], dtype=xp.float64) else: self._pixel_status = xp.zeros( (self.panel_num, self.panel_pixel_num_x, self.panel_pixel_num_y)) if 'pixel mask' in geom: self._pixel_mask = xp.asarray(geom['pixel mask'], dtype=xp.float64) else: self._pixel_mask = xp.zeros( (self.panel_num, self.panel_pixel_num_x, self.panel_pixel_num_y)) if 'pixel gain' in geom: self._pixel_gain = xp.asarray(geom['pixel gain'], dtype=xp.float64) else: self._pixel_gain = xp.ones((self.panel_num, self.panel_pixel_num_x, self.panel_pixel_num_y))
def initialize(self, geom, run_num=0, cframe=0): """ Initialize the detector :param geom: The *-end.data file which characterizes the geometry profile. :param run_num: The run_num containing the background, rms and gain and the other pixel pixel properties. :param cframe: The desired coordinate frame, 0 for psana and 1 for lab conventions. :return: None """ # Redirect the output stream old_stdout = sys.stdout f = six.StringIO() # f = open('Detector_initialization.log', 'w') sys.stdout = f ########################################################################################### # Initialize the geometry configuration ############################################################################################ self.geometry = GeometryAccess(geom, cframe=cframe) self.run_num = run_num # Set coordinate in real space (convert to m) temp = [xp.asarray(t) * 1e-6 for t in self.geometry.get_pixel_coords(cframe=cframe)] temp_index = [xp.asarray(t) for t in self.geometry.get_pixel_coord_indexes(cframe=cframe)] self.panel_num = np.prod(temp[0].shape[:-2]) self._distance = float(temp[2].mean()) self._shape = (self.panel_num, temp[0].shape[-2], temp[0].shape[-1]) self.pixel_position = xp.zeros(self._shape + (3,)) self.pixel_index_map = xp.zeros(self._shape + (2,)) for n in range(3): self.pixel_position[..., n] = temp[n].reshape(self._shape) for n in range(2): self.pixel_index_map[..., n] = temp_index[n].reshape(self._shape) self.pixel_index_map = self.pixel_index_map.astype(xp.int64) # Get the range of the pixel index self.detector_pixel_num_x = asnumpy( xp.max(self.pixel_index_map[..., 0]) + 1) self.detector_pixel_num_y = asnumpy( xp.max(self.pixel_index_map[..., 1]) + 1) self.panel_pixel_num_x = np.array([self.pixel_index_map.shape[1], ] * self.panel_num) self.panel_pixel_num_y = np.array([self.pixel_index_map.shape[2], ] * self.panel_num) self.pixel_num_total = np.sum(np.multiply(self.panel_pixel_num_x, self.panel_pixel_num_y)) tmp = float(self.geometry.get_pixel_scale_size() * 1e-6) # Convert to m self.pixel_width = xp.ones( (self.panel_num, self.panel_pixel_num_x[0], self.panel_pixel_num_y[0])) * tmp self.pixel_height = xp.ones( (self.panel_num, self.panel_pixel_num_x[0], self.panel_pixel_num_y[0])) * tmp # Calculate the pixel area self.pixel_area = xp.multiply(self.pixel_height, self.pixel_width) ########################################################################################### # Initialize the pixel effects ########################################################################################### # first we should parse the path parsed_path = geom.split('/') self.exp = parsed_path[-5] if self.exp == 'calib': self.exp = parsed_path[-6] self.group = parsed_path[-4] self.source = parsed_path[-3] self._pedestals = None self._pixel_rms = None self._pixel_mask = None self._pixel_bkgd = None self._pixel_status = None self._pixel_gain = None if psana_version==1: try: cbase = self._get_cbase() self.calibdir = '/'.join(parsed_path[:-4]) pbits = 255 gcp = GenericCalibPars(cbase, self.calibdir, self.group, self.source, run_num, pbits) self._pedestals = gcp.pedestals() self._pixel_rms = gcp.pixel_rms() self._pixel_mask = gcp.pixel_mask() self._pixel_bkgd = gcp.pixel_bkgd() self._pixel_status = gcp.pixel_status() self._pixel_gain = gcp.pixel_gain() except NotImplementedError: # No GenericCalibPars information. pass else: try: self.det = self._get_det_id(self.group) except NotImplementedError: # No GenericCalibPars information. self.det = None # Redirect the output stream sys.stdout = old_stdout