def percentile_range(self, low=69.0, high=99.9): d = HexrdConfig().current_images_dict() l = min([np.percentile(d[key], low) for key in d.keys()]) h = min([np.percentile(d[key], high) for key in d.keys()]) if h - l < 5: h = l + 5 # This debug is useful for now, keeping it in for sanity... print("Range to be used: ", l, " -> ", h) return l, h
def cartesian_viewer(): images_dict = HexrdConfig().current_images_dict() plane_data = HexrdConfig().active_material.planeData pixel_size = HexrdConfig().cartesian_pixel_size # HEDMInstrument expects None Euler angle convention for the # config. Let's get it as such. iconfig = HexrdConfig().instrument_config_none_euler_convention rme = HexrdConfig().rotation_matrix_euler() instr = instrument.HEDMInstrument(instrument_config=iconfig, tilt_calibration_mapping=rme) # Make sure each key in the image dict is in the panel_ids if images_dict.keys() != instr._detectors.keys(): msg = ('Images do not match the panel ids!\n' + 'Images: ' + str(list(images_dict.keys())) + '\n' + 'PanelIds: ' + str(list(instr._detectors.keys()))) raise Exception(msg) return InstrumentViewer(instr, images_dict, plane_data, pixel_size)
class InstrumentViewer: def __init__(self): self.type = ViewType.cartesian self.instr = create_hedm_instrument() self.images_dict = HexrdConfig().current_images_dict() # Perform some checks before proceeding self.check_keys_match() self.check_angles_feasible() self.pixel_size = HexrdConfig().cartesian_pixel_size self.warp_dict = {} self.detector_corners = {} dist = HexrdConfig().cartesian_virtual_plane_distance dplane_tvec = np.array([0., 0., -dist]) rotate_x = HexrdConfig().cartesian_plane_normal_rotate_x rotate_y = HexrdConfig().cartesian_plane_normal_rotate_y dplane_tilt = np.radians(np.array(([rotate_x, rotate_y, 0.]))) self.dplane = DisplayPlane(tvec=dplane_tvec, tilt=dplane_tilt) self.make_dpanel() # Check that the image size won't be too big... self.check_size_feasible() self.plot_dplane() def check_keys_match(self): # Make sure each key in the image dict is in the panel_ids if self.images_dict.keys() != self.instr._detectors.keys(): msg = ('Images do not match the panel ids!\n' f'Images: {str(list(self.images_dict.keys()))}\n' f'PanelIds: {str(list(self.instr._detectors.keys()))}') raise Exception(msg) def check_angles_feasible(self): max_angle = 120.0 # Check all combinations of detectors. If any have an angle # between them that is greater than the max, raise an exception. combos = itertools.combinations(self.instr._detectors.items(), 2) bad_combos = [] acos = np.arccos norm = np.linalg.norm for x, y in combos: n1, n2 = x[1].normal, y[1].normal angle = np.degrees(acos(np.dot(n1, n2) / norm(n1) / norm(n2))) if angle > max_angle: bad_combos.append(((x[0], y[0]), angle)) if bad_combos: msg = 'Cartesian plot not feasible\n' msg += 'Angle between detectors is too large\n' msg += '\n'.join([f'{x[0]}: {x[1]}' for x in bad_combos]) raise Exception(msg) def check_size_feasible(self): available_mem = psutil.virtual_memory().available img_dtype = np.float64 dtype_size = np.dtype(img_dtype).itemsize mem_usage = self.dpanel.rows * self.dpanel.cols * dtype_size # Extra memory we probably need for other things... mem_extra_buffer = 1e7 mem_usage += mem_extra_buffer if mem_usage > available_mem: def format_size(size): sizes = [ ('TB', 1e12), ('GB', 1e9), ('MB', 1e6), ('KB', 1e3), ('B', 1) ] for s in sizes: if size > s[1]: return f'{round(size / s[1], 2)} {s[0]}' msg = 'Not enough memory for Cartesian plot\n' msg += f'Memory available: {format_size(available_mem)}\n' msg += f'Memory required: {format_size(mem_usage)}' raise Exception(msg) def make_dpanel(self): self.dpanel_sizes = self.dplane.panel_size(self.instr) self.dpanel = self.dplane.display_panel(self.dpanel_sizes, self.pixel_size, self.instr.beam_vector) @property def extent(self): # We might want to use self.dpanel.col_edge_vec and # self.dpanel.row_edge_vec here instead. # !!! recall that extents are (left, right, bottom, top) x_lim = self.dpanel.col_dim / 2 y_lim = self.dpanel.row_dim / 2 return -x_lim, x_lim, -y_lim, y_lim def update_overlay_data(self): if not HexrdConfig().show_overlays: # Nothing to do return # must update self.dpanel from HexrdConfig self.pixel_size = HexrdConfig().cartesian_pixel_size self.make_dpanel() # The overlays for the Cartesian view are made via a fake # instrument with a single detector. # Make a copy of the instrument and modify. temp_instr = copy.deepcopy(self.instr) temp_instr._detectors.clear() temp_instr._detectors['dpanel'] = self.dpanel update_overlay_data(temp_instr, self.type) def plot_dplane(self): # Cache the image max and min for later use images = self.images_dict.values() self.min = min([x.min() for x in images]) self.max = max([x.max() for x in images]) # Create the warped image for each detector for detector_id in self.images_dict.keys(): self.create_warped_image(detector_id) # Generate the final image self.generate_image() def detector_borders(self, det): corners = self.detector_corners.get(det, []) # These corners are in pixel coordinates. Convert to Cartesian. # Swap x and y first. corners = [[y, x] for x, y in corners] corners = self.dpanel.pixelToCart(corners) x_vals = [x[0] for x in corners] y_vals = [x[1] for x in corners] if x_vals and y_vals: # Double each set of points. # This is so that if a point is moved, it won't affect the # other line sharing this point. x_vals = [x for x in x_vals for _ in (0, 1)] y_vals = [y for y in y_vals for _ in (0, 1)] # Move the first point to the back to complete the line x_vals += [x_vals.pop(0)] y_vals += [y_vals.pop(0)] # Make sure all points are inside the frame. # If there are points outside the frame, move them inside. extent = self.extent x_range = (extent[0], extent[1]) y_range = (extent[2], extent[3]) def out_of_frame(p): # Check if point p is out of the frame return (not x_range[0] <= p[0] <= x_range[1] or not y_range[0] <= p[1] <= y_range[1]) def move_point_into_frame(p, p2): # Make sure we don't divide by zero if p2[0] == p[0]: return # Move point p into the frame via its line equation # y = mx + b m = (p2[1] - p[1]) / (p2[0] - p[0]) b = p[1] - m * p[0] if p[0] < x_range[0]: p[0] = x_range[0] p[1] = m * p[0] + b elif p[0] > x_range[1]: p[0] = x_range[1] p[1] = m * p[0] + b if p[1] < y_range[0]: p[1] = y_range[0] p[0] = (p[1] - b) / m elif p[1] > y_range[1]: p[1] = y_range[1] p[0] = (p[1] - b) / m i = 0 while i < len(x_vals) - 1: # We look at pairs of points at a time p1 = [x_vals[i], y_vals[i]] p2 = [x_vals[i + 1], y_vals[i + 1]] if out_of_frame(p1): # Move the point into the frame via its line equation move_point_into_frame(p1, p2) x_vals[i], y_vals[i] = p1[0], p1[1] # Insert a pair of Nones to disconnect the drawing x_vals.insert(i, None) y_vals.insert(i, None) i += 1 if out_of_frame(p2): # Move the point into the frame via its line equation move_point_into_frame(p2, p1) x_vals[i + 1], y_vals[i + 1] = p2[0], p2[1] i += 2 # The border drawer expects a list of lines. # We only have one line here. return [(x_vals, y_vals)] @property def all_detector_borders(self): borders = {} for det in self.images_dict.keys(): borders[det] = self.detector_borders(det) return borders def create_warped_image(self, detector_id): img = self.images_dict[detector_id] panel = self.instr._detectors[detector_id] # map corners corners = np.vstack( [panel.corner_ll, panel.corner_lr, panel.corner_ur, panel.corner_ul, ] ) mp = panel.map_to_plane(corners, self.dplane.rmat, self.dplane.tvec) col_edges = self.dpanel.col_edge_vec row_edges = self.dpanel.row_edge_vec j_col = cellIndices(col_edges, mp[:, 0]) i_row = cellIndices(row_edges, mp[:, 1]) src = np.vstack([j_col, i_row]).T # Save detector corners in pixel coordinates self.detector_corners[detector_id] = src dst = panel.cartToPixel(corners, pixels=True) dst = dst[:, ::-1] tform3 = tf.ProjectiveTransform() tform3.estimate(src, dst) res = tf.warp(img, tform3, output_shape=(self.dpanel.rows, self.dpanel.cols)) self.warp_dict[detector_id] = res return res def generate_image(self): img = np.zeros((self.dpanel.rows, self.dpanel.cols)) for key in self.images_dict.keys(): img += self.warp_dict[key] # Rescale the data to match the scale of the original dataset # TODO: try to get create_calibration_image to not rescale the # result to be between 0 and 1 in the first place so this will # not be necessary. self.img = np.interp(img, (img.min(), img.max()), (self.min, self.max)) def update_detector(self, det): # First, convert to the "None" angle convention iconfig = HexrdConfig().instrument_config_none_euler_convention t_conf = iconfig['detectors'][det]['transform'] self.instr.detectors[det].tvec = t_conf['translation'] self.instr.detectors[det].tilt = t_conf['tilt'] # Update the individual detector image self.create_warped_image(det) # Generate the final image self.generate_image()
class PolarView: """Create (two-theta, eta) plot of detectors """ def __init__(self, instrument): self.instr = instrument self.images_dict = HexrdConfig().images_dict self.warp_dict = {} self.snip1d_background = None self.update_angular_grid() @property def detectors(self): return self.instr.detectors @property def chi(self): return self.instr.chi @property def tvec_s(self): return self.instr.tvec @property def tth_min(self): return np.radians(HexrdConfig().polar_res_tth_min) @property def tth_max(self): return np.radians(HexrdConfig().polar_res_tth_max) @property def tth_range(self): return self.tth_max - self.tth_min @property def tth_pixel_size(self): return HexrdConfig().polar_pixel_size_tth def tth_to_pixel(self, tth): """ convert two-theta value to pixel value (float) along two-theta axis """ return np.degrees(tth - self.tth_min) / self.tth_pixel_size @property def eta_min(self): return np.radians(HexrdConfig().polar_res_eta_min) @property def eta_max(self): return np.radians(HexrdConfig().polar_res_eta_max) @property def eta_range(self): return self.eta_max - self.eta_min @property def eta_pixel_size(self): return HexrdConfig().polar_pixel_size_eta def eta_to_pixel(self, eta): """ convert eta value to pixel value (float) along eta axis """ return np.degrees(eta - self.eta_min) / self.eta_pixel_size @property def ntth(self): return int(np.degrees(self.tth_range) / self.tth_pixel_size) @property def neta(self): return int(np.degrees(self.eta_range) / self.eta_pixel_size) @property def shape(self): return (self.neta, self.ntth) def update_angular_grid(self): tth_vec = np.radians(self.tth_pixel_size * (np.arange(self.ntth)))\ + self.tth_min + 0.5 * np.radians(self.tth_pixel_size) eta_vec = np.radians(self.eta_pixel_size * (np.arange(self.neta)))\ + self.eta_min + 0.5 * np.radians(self.eta_pixel_size) self._angular_grid = np.meshgrid(eta_vec, tth_vec, indexing='ij') @property def angular_grid(self): return self._angular_grid @property def eta_period(self): return HexrdConfig().polar_res_eta_period @property def images_dict(self): return self._images_dict @images_dict.setter def images_dict(self, v): self._images_dict = v # Cache the image min and max for later use self.min = min(x.min() for x in v.values()) self.max = max(x.max() for x in v.values()) def detector_borders(self, det): panel = self.detectors[det] row_vec, col_vec = panel.row_pixel_vec, panel.col_pixel_vec x_start, x_stop = col_vec[0], col_vec[-1] y_start, y_stop = row_vec[0], row_vec[-1] # Create the borders in Cartesian borders = [[[x, y_start] for x in col_vec], [[x, y_stop] for x in col_vec], [[x_start, y] for y in row_vec], [[x_stop, y] for y in row_vec]] # Convert each border to angles for i, border in enumerate(borders): angles, _ = detectorXYToGvec(border, panel.rmat, ct.identity_3x3, panel.tvec, ct.zeros_3, ct.zeros_3, beamVec=panel.bvec, etaVec=panel.evec) angles = np.array(angles) angles[1:, :] = mapAngle(angles[1:, :], np.radians(self.eta_period), units='radians') # Convert to degrees, and keep them as lists for # easier modification later borders[i] = np.degrees(angles).tolist() # Here, we are going to remove points that are out-of-bounds, # and we are going to insert None in between points that are far # apart (in the y component), so that they are not connected in the # plot. This happens for detectors that are wrapped in the image. x_range = np.degrees((self.tth_min, self.tth_max)) y_range = np.degrees((self.eta_min, self.eta_max)) # "Far apart" is currently defined as half of the y range max_y_distance = abs(y_range[1] - y_range[0]) / 2.0 for j in range(4): border_x, border_y = borders[j][0], borders[j][1] i = 0 # These should be the same length, but just in case... while i < len(border_x) and i < len(border_y): x, y = border_x[i], border_y[i] if (not x_range[0] <= x <= x_range[1] or not y_range[0] <= y <= y_range[1]): # The point is out of bounds, remove it del border_x[i], border_y[i] continue if i != 0 and abs(y - border_y[i - 1]) > max_y_distance: # Points are too far apart. Insert a None border_x.insert(i, None) border_y.insert(i, None) i += 1 i += 1 return borders @property def all_detector_borders(self): borders = {} for key in self.images_dict.keys(): borders[key] = self.detector_borders(key) return borders def create_warp_image(self, det): angpts = self.angular_grid dummy_ome = np.zeros((self.ntth * self.neta)) # lcount = 0 panel = self.detectors[det] img = self.images_dict[det] gvec_angs = np.vstack( [angpts[1].flatten(), angpts[0].flatten(), dummy_ome]).T xypts = np.nan * np.ones((len(gvec_angs), 2)) valid_xys, rmats_s, on_plane = _project_on_detector_plane( gvec_angs, panel.rmat, np.eye(3), self.chi, panel.tvec, tvec_c, self.tvec_s, panel.distortion, beamVec=panel.bvec) xypts[on_plane] = valid_xys wimg = panel.interpolate_bilinear( xypts, img, pad_with_nans=True, ).reshape(self.shape) nan_mask = np.isnan(wimg) # Store as masked array self.warp_dict[det] = np.ma.masked_array(data=wimg, mask=nan_mask, fill_value=0.) return self.warp_dict[det] def generate_image(self): # sum masked images in self.warp_dict using np.ma ufuncs # # !!! checked that np.ma.sum is applying logical OR across masks (JVB) # !!! assignment to img fills NaNs with zeros # !!! NOTE: cannot set `data` attribute on masked_array, # but can manipulate mask maimg = np.ma.sum(np.ma.stack(self.warp_dict.values()), axis=0) img = maimg.data # Rescale the data to match the scale of the original dataset kwargs = { 'image': img, 'in_range': (np.nanmin(img), np.nanmax(img)), 'out_range': (self.min, self.max), } img = rescale_intensity(**kwargs) # do SNIP if requested # !!! Fast snip1d (ndimage) cannot handle nans # from hexrdgui.hexrd.ui.utils: # Fast_SNIP_1D = 0 # SNIP_1D = 1 # SNIP_2D = 2 if HexrdConfig().polar_apply_snip1d: if HexrdConfig().polar_snip1d_algorithm in [1, 2]: img[maimg.mask] = np.nan self.snip1d_background = run_snip1d(img) # Perform the background subtraction img -= self.snip1d_background else: self.snip1d_background = None # Apply user-specified masks if they are present for mask in HexrdConfig().visible_polar_masks: maimg.mask = np.logical_or(maimg.mask, ~mask) img[maimg.mask] = np.nan self.img = img # just a normal ndarry with NaNs def warp_all_images(self): # Create the warped image for each detector for det in self.images_dict.keys(): self.create_warp_image(det) # Generate the final image self.generate_image() def update_images_dict(self): if HexrdConfig().any_intensity_corrections: self.images_dict = HexrdConfig().images_dict def update_detector(self, det): # If there are intensity corrections and the detector transform # has been modified, we need to update the images dict. self.update_images_dict() # First, convert to the "None" angle convention iconfig = HexrdConfig().instrument_config_none_euler_convention t_conf = iconfig['detectors'][det]['transform'] self.instr.detectors[det].tvec = t_conf['translation'] self.instr.detectors[det].tilt = t_conf['tilt'] # Update the individual detector image self.create_warp_image(det) # Generate the final image self.generate_image()
class PolarView: """Create (two-theta, eta) plot of detectors """ def __init__(self, instrument): self.instr = instrument self.images_dict = HexrdConfig().current_images_dict() self.warp_dict = {} self.snip1d_background = None self.update_angular_grid() @property def detectors(self): return self.instr.detectors @property def chi(self): return self.instr.chi @property def tvec_s(self): return self.instr.tvec @property def tth_min(self): return np.radians(HexrdConfig().polar_res_tth_min) @property def tth_max(self): return np.radians(HexrdConfig().polar_res_tth_max) @property def tth_range(self): return self.tth_max - self.tth_min @property def tth_pixel_size(self): return HexrdConfig().polar_pixel_size_tth def tth_to_pixel(self, tth): """ convert two-theta value to pixel value (float) along two-theta axis """ return np.degrees(tth - self.tth_min) / self.tth_pixel_size @property def eta_min(self): return np.radians(HexrdConfig().polar_res_eta_min) @property def eta_max(self): return np.radians(HexrdConfig().polar_res_eta_max) @property def eta_range(self): return self.eta_max - self.eta_min @property def eta_pixel_size(self): return HexrdConfig().polar_pixel_size_eta def eta_to_pixel(self, eta): """ convert eta value to pixel value (float) along eta axis """ return np.degrees(eta - self.eta_min) / self.eta_pixel_size @property def ntth(self): return int(np.degrees(self.tth_range) / self.tth_pixel_size) @property def neta(self): return int(np.degrees(self.eta_range) / self.eta_pixel_size) @property def shape(self): return (self.neta, self.ntth) def update_angular_grid(self): tth_vec = np.radians(self.tth_pixel_size * (np.arange(self.ntth)))\ + self.tth_min + 0.5 * np.radians(self.tth_pixel_size) eta_vec = np.radians(self.eta_pixel_size * (np.arange(self.neta)))\ + self.eta_min + 0.5 * np.radians(self.eta_pixel_size) self._angular_grid = np.meshgrid(eta_vec, tth_vec, indexing='ij') @property def angular_grid(self): return self._angular_grid def detector_borders(self, det): panel = self.detectors[det] row_vec, col_vec = panel.row_pixel_vec, panel.col_pixel_vec x_start, x_stop = col_vec[0], col_vec[-1] y_start, y_stop = row_vec[0], row_vec[-1] # Create the borders in Cartesian borders = [[[x, y_start] for x in col_vec], [[x, y_stop] for x in col_vec], [[x_start, y] for y in row_vec], [[x_stop, y] for y in row_vec]] # Convert each border to angles for i, border in enumerate(borders): angles, _ = detectorXYToGvec(border, panel.rmat, ct.identity_3x3, panel.tvec, ct.zeros_3, ct.zeros_3, beamVec=panel.bvec, etaVec=panel.evec) # Convert to degrees, and keep them as lists for # easier modification later borders[i] = np.degrees(angles).tolist() # Here, we are going to remove points that are out-of-bounds, # and we are going to insert None in between points that are far # apart (in the y component), so that they are not connected in the # plot. This happens for detectors that are wrapped in the image. x_range = np.degrees((self.tth_min, self.tth_max)) y_range = np.degrees((self.eta_min, self.eta_max)) # "Far apart" is currently defined as half of the y range max_y_distance = abs(y_range[1] - y_range[0]) / 2.0 for j in range(4): border_x, border_y = borders[j][0], borders[j][1] i = 0 # These should be the same length, but just in case... while i < len(border_x) and i < len(border_y): x, y = border_x[i], border_y[i] if (not x_range[0] <= x <= x_range[1] or not y_range[0] <= y <= y_range[1]): # The point is out of bounds, remove it del border_x[i], border_y[i] continue if i != 0 and abs(y - border_y[i - 1]) > max_y_distance: # Points are too far apart. Insert a None border_x.insert(i, None) border_y.insert(i, None) i += 1 i += 1 return borders @property def all_detector_borders(self): borders = {} for key in self.images_dict.keys(): borders[key] = self.detector_borders(key) return borders def create_warp_image(self, det): angpts = self.angular_grid dummy_ome = np.zeros((self.ntth * self.neta)) # lcount = 0 panel = self.detectors[det] img = self.images_dict[det] gvec_angs = np.vstack( [angpts[1].flatten(), angpts[0].flatten(), dummy_ome]).T xypts = np.nan * np.ones((len(gvec_angs), 2)) valid_xys, rmats_s, on_plane = _project_on_detector_plane( gvec_angs, panel.rmat, np.eye(3), self.chi, panel.tvec, tvec_c, self.tvec_s, panel.distortion, beamVec=panel.bvec) xypts[on_plane] = valid_xys self.warp_dict[det] = panel.interpolate_bilinear( xypts, img, pad_with_nans=False).reshape(self.shape) return self.warp_dict[det] def generate_image(self): img = np.zeros(self.shape) for key in self.images_dict.keys(): img += self.warp_dict[key] # ??? do log scaling here # img = log_scale_img(log_scale_img(sqrt_scale_img(img))) # Rescale the data to match the scale of the original dataset img = rescale_intensity(img, out_range=(self.min, self.max)) if HexrdConfig().polar_apply_snip1d: self.snip1d_background = run_snip1d(img) # Perform the background subtraction img -= self.snip1d_background else: self.snip1d_background = None # Apply masks if they are present masks = HexrdConfig().polar_masks for mask in masks: img[~mask] = 0 self.img = img def warp_all_images(self): # Cache the image max and min for later use images = self.images_dict.values() self.min = min([x.min() for x in images]) self.max = max([x.max() for x in images]) # Create the warped image for each detector for det in self.images_dict.keys(): self.create_warp_image(det) # Generate the final image self.generate_image() def update_detector(self, det): # First, convert to the "None" angle convention iconfig = HexrdConfig().instrument_config_none_euler_convention t_conf = iconfig['detectors'][det]['transform'] self.instr.detectors[det].tvec = t_conf['translation'] self.instr.detectors[det].tilt = t_conf['tilt'] # Update the individual detector image self.create_warp_image(det) # Generate the final image self.generate_image()
class InstrumentViewer: def __init__(self): self.type = ViewType.cartesian self.instr = create_hedm_instrument() self.images_dict = HexrdConfig().current_images_dict() # Make sure each key in the image dict is in the panel_ids if self.images_dict.keys() != self.instr._detectors.keys(): msg = ('Images do not match the panel ids!\n' + 'Images: ' + str(list(self.images_dict.keys())) + '\n' + 'PanelIds: ' + str(list(self.instr._detectors.keys()))) raise Exception(msg) self.pixel_size = HexrdConfig().cartesian_pixel_size self.warp_dict = {} self.detector_corners = {} dist = HexrdConfig().cartesian_virtual_plane_distance dplane_tvec = np.array([0., 0., -dist]) rotate_x = HexrdConfig().cartesian_plane_normal_rotate_x rotate_y = HexrdConfig().cartesian_plane_normal_rotate_y dplane_tilt = np.radians(np.array(([rotate_x, rotate_y, 0.]))) self.dplane = DisplayPlane(tvec=dplane_tvec, tilt=dplane_tilt) self.make_dpanel() self.plot_dplane() def make_dpanel(self): self.dpanel_sizes = self.dplane.panel_size(self.instr) self.dpanel = self.dplane.display_panel(self.dpanel_sizes, self.pixel_size, self.instr.beam_vector) @property def extent(self): # We might want to use self.dpanel.col_edge_vec and # self.dpanel.row_edge_vec here instead. # !!! recall that extents are (left, right, bottom, top) x_lim = self.dpanel.col_dim / 2 y_lim = self.dpanel.row_dim / 2 return -x_lim, x_lim, -y_lim, y_lim def update_overlay_data(self): if not HexrdConfig().show_overlays: # Nothing to do return # must update self.dpanel from HexrdConfig self.pixel_size = HexrdConfig().cartesian_pixel_size self.make_dpanel() # The overlays for the Cartesian view are made via a fake # instrument with a single detector. # Make a copy of the instrument and modify. temp_instr = copy.deepcopy(self.instr) temp_instr._detectors.clear() temp_instr._detectors['dpanel'] = self.dpanel update_overlay_data(temp_instr, self.type) def plot_dplane(self): # Cache the image max and min for later use images = self.images_dict.values() self.min = min([x.min() for x in images]) self.max = max([x.max() for x in images]) # Create the warped image for each detector for detector_id in self.images_dict.keys(): self.create_warped_image(detector_id) # Generate the final image self.generate_image() def detector_borders(self, det): corners = self.detector_corners.get(det, []) # These corners are in pixel coordinates. Convert to Cartesian. # Swap x and y first. corners = [[y, x] for x, y in corners] corners = self.dpanel.pixelToCart(corners) x_vals = [x[0] for x in corners] y_vals = [x[1] for x in corners] if x_vals and y_vals: # Double each set of points. # This is so that if a point is moved, it won't affect the # other line sharing this point. x_vals = [x for x in x_vals for _ in (0, 1)] y_vals = [y for y in y_vals for _ in (0, 1)] # Move the first point to the back to complete the line x_vals += [x_vals.pop(0)] y_vals += [y_vals.pop(0)] # Make sure all points are inside the frame. # If there are points outside the frame, move them inside. extent = self.extent x_range = (extent[0], extent[1]) y_range = (extent[2], extent[3]) def out_of_frame(p): # Check if point p is out of the frame return (not x_range[0] <= p[0] <= x_range[1] or not y_range[0] <= p[1] <= y_range[1]) def move_point_into_frame(p, p2): # Make sure we don't divide by zero if p2[0] == p[0]: return # Move point p into the frame via its line equation # y = mx + b m = (p2[1] - p[1]) / (p2[0] - p[0]) b = p[1] - m * p[0] if p[0] < x_range[0]: p[0] = x_range[0] p[1] = m * p[0] + b elif p[0] > x_range[1]: p[0] = x_range[1] p[1] = m * p[0] + b if p[1] < y_range[0]: p[1] = y_range[0] p[0] = (p[1] - b) / m elif p[1] > y_range[1]: p[1] = y_range[1] p[0] = (p[1] - b) / m i = 0 while i < len(x_vals) - 1: # We look at pairs of points at a time p1 = [x_vals[i], y_vals[i]] p2 = [x_vals[i + 1], y_vals[i + 1]] if out_of_frame(p1): # Move the point into the frame via its line equation move_point_into_frame(p1, p2) x_vals[i], y_vals[i] = p1[0], p1[1] # Insert a pair of Nones to disconnect the drawing x_vals.insert(i, None) y_vals.insert(i, None) i += 1 if out_of_frame(p2): # Move the point into the frame via its line equation move_point_into_frame(p2, p1) x_vals[i + 1], y_vals[i + 1] = p2[0], p2[1] i += 2 # The border drawer expects a list of lines. # We only have one line here. return [(x_vals, y_vals)] @property def all_detector_borders(self): borders = {} for det in self.images_dict.keys(): borders[det] = self.detector_borders(det) return borders def create_warped_image(self, detector_id): img = self.images_dict[detector_id] panel = self.instr._detectors[detector_id] # map corners corners = np.vstack( [panel.corner_ll, panel.corner_lr, panel.corner_ur, panel.corner_ul, ] ) mp = panel.map_to_plane(corners, self.dplane.rmat, self.dplane.tvec) col_edges = self.dpanel.col_edge_vec row_edges = self.dpanel.row_edge_vec j_col = cellIndices(col_edges, mp[:, 0]) i_row = cellIndices(row_edges, mp[:, 1]) src = np.vstack([j_col, i_row]).T # Save detector corners in pixel coordinates self.detector_corners[detector_id] = src dst = panel.cartToPixel(corners, pixels=True) dst = dst[:, ::-1] tform3 = tf.ProjectiveTransform() tform3.estimate(src, dst) res = tf.warp(img, tform3, output_shape=(self.dpanel.rows, self.dpanel.cols)) self.warp_dict[detector_id] = res return res def generate_image(self): img = np.zeros((self.dpanel.rows, self.dpanel.cols)) for key in self.images_dict.keys(): img += self.warp_dict[key] # Rescale the data to match the scale of the original dataset # TODO: try to get create_calibration_image to not rescale the # result to be between 0 and 1 in the first place so this will # not be necessary. self.img = np.interp(img, (img.min(), img.max()), (self.min, self.max)) def update_detector(self, det): # First, convert to the "None" angle convention iconfig = HexrdConfig().instrument_config_none_euler_convention t_conf = iconfig['detectors'][det]['transform'] self.instr.detectors[det].tvec = t_conf['translation'] self.instr.detectors[det].tilt = t_conf['tilt'] # Update the individual detector image self.create_warped_image(det) # Generate the final image self.generate_image()