diff --git a/hexrdgui/calibration/panel_buffer_dialog.py b/hexrdgui/calibration/panel_buffer_dialog.py index a22e25aa6..47556e889 100644 --- a/hexrdgui/calibration/panel_buffer_dialog.py +++ b/hexrdgui/calibration/panel_buffer_dialog.py @@ -7,6 +7,12 @@ import matplotlib.pyplot as plt import numpy as np +from hexrd.utils.panel_buffer import ( + panel_buffer_from_str, + valid_panel_buffer_names, +) + +from hexrdgui.create_hedm_instrument import create_hedm_instrument from hexrdgui.hexrd_config import HexrdConfig from hexrdgui.ui_loader import UiLoader from hexrdgui.utils import block_signals @@ -14,6 +20,7 @@ CONFIG_MODE_BORDER = 'border' CONFIG_MODE_NUMPY = 'numpy' +CONFIG_MODE_NAME = 'name' class PanelBufferDialog(QObject): @@ -41,6 +48,7 @@ def __init__(self, detector, parent=None): # Hide the tab bar. It gets selected by changes to the combo box. self.ui.tab_widget.tabBar().hide() self.setup_combo_box_data() + self.setup_valid_names() self.update_gui() @@ -58,11 +66,17 @@ def setup_connections(self): def setup_combo_box_data(self): item_data = [ CONFIG_MODE_BORDER, - CONFIG_MODE_NUMPY + CONFIG_MODE_NUMPY, + CONFIG_MODE_NAME, ] for i, data in enumerate(item_data): self.ui.config_mode.setItemData(i, data) + def setup_valid_names(self): + w = self.ui.selected_name + w.clear() + w.addItems(valid_panel_buffer_names()) + def show(self): self.ui.show() @@ -114,13 +128,16 @@ def widgets(self): return [ self.ui.file_name, self.ui.border_x_spinbox, - self.ui.border_y_spinbox + self.ui.border_y_spinbox, + self.ui.selected_name, ] @property def current_editing_buffer_value(self): if self.mode == CONFIG_MODE_BORDER: return [self.x_border, self.y_border] + elif self.mode == CONFIG_MODE_NAME: + return self.ui.selected_name.currentText() elif self.file_name == '': # Just return the currently saved buffer return copy.deepcopy(self.current_saved_buffer_value) @@ -165,25 +182,49 @@ def update_config(self): self.detector_config['buffer'] = value + if isinstance(value, str): + # Check if this has ROIs and a group + # If so, ask if the user wants to apply this setting + # to all detectors in this group. + group = HexrdConfig().detector_group(self.detector) + if HexrdConfig().instrument_has_roi and group: + det_keys = HexrdConfig().detectors_in_group(group) + if len(det_keys) > 1: + title = 'Apply to Other Detectors?' + msg = ( + f'Set panel buffer "{value}" to all ' + f'detectors in the group "{group}"?' + ) + response = QMessageBox.question(self.ui, title, msg) + if response == QMessageBox.Yes: + for det_key in det_keys: + config = HexrdConfig().detector(det_key) + config['buffer'] = value + return True def update_gui(self): with block_signals(*self.widgets): if 'buffer' in self.detector_config: - buffer = np.asarray(self.detector_config['buffer']) + buffer = self.detector_config['buffer'] + if isinstance(buffer, str): + self.mode = CONFIG_MODE_NAME + self.ui.selected_name.setCurrentText(buffer) + else: + buffer = np.asarray(buffer) - if buffer.size in (1, 2): - self.mode = CONFIG_MODE_BORDER - if np.array_equal(buffer, None): - buffer = np.asarray([0]) + if buffer.size in (1, 2): + self.mode = CONFIG_MODE_BORDER + if np.array_equal(buffer, None): + buffer = np.asarray([0]) - if buffer.size == 1: - buffer = [buffer.item()] * 2 + if buffer.size == 1: + buffer = [buffer.item()] * 2 - self.ui.border_x_spinbox.setValue(buffer[0]) - self.ui.border_y_spinbox.setValue(buffer[1]) - else: - self.mode = CONFIG_MODE_NUMPY + self.ui.border_x_spinbox.setValue(buffer[0]) + self.ui.border_y_spinbox.setValue(buffer[1]) + else: + self.mode = CONFIG_MODE_NUMPY self.update_mode_tab() @@ -209,8 +250,11 @@ def update_mode_tab(self): self.update_enable_states() def update_enable_states(self): - buffer = np.asarray(self.current_editing_buffer_value) - has_numpy_array = buffer.size > 2 + has_numpy_array = False + if not isinstance(self.current_editing_buffer_value, str): + buffer = np.asarray(self.current_editing_buffer_value) + has_numpy_array = buffer.size > 2 + self.ui.show_panel_buffer.setEnabled(has_numpy_array) def clear_panel_buffer(self): @@ -224,10 +268,16 @@ def default_buffer(self): return [0., 0.] def show_panel_buffer(self): - buffer = np.asarray(self.current_editing_buffer_value) - if buffer.size <= 2: - # We only support showing numpy array buffers currently - return + buffer = self.current_editing_buffer_value + if isinstance(buffer, str): + instr = create_hedm_instrument() + panel = instr.detectors[self.detector] + buffer = panel_buffer_from_str(buffer, panel) + else: + buffer = np.asarray(buffer) + if buffer.size <= 2: + # We only support showing numpy array buffers currently + return fig, ax = plt.subplots() fig.canvas.manager.set_window_title(f'{self.detector}') diff --git a/hexrdgui/hexrd_config.py b/hexrdgui/hexrd_config.py index 5edef8b80..6e884dab1 100644 --- a/hexrdgui/hexrd_config.py +++ b/hexrdgui/hexrd_config.py @@ -20,6 +20,7 @@ from hexrd.material import load_materials_hdf5, save_materials_hdf5, Material from hexrd.rotations import angleAxisOfRotMat, RotMatEuler, rotMatOfExpMap from hexrd.utils.decorators import memoize +from hexrd.utils.panel_buffer import panel_buffer_from_str from hexrd.utils.yaml import NumpyToNativeDumper from hexrd.valunits import valWUnit @@ -1699,6 +1700,15 @@ def detector_group(self, detector_name): det = self.detector(detector_name) return det.get('group', {}) + def detectors_in_group(self, group: str) -> list[str]: + names = [] + for det_key in self.detectors: + this_group = self.detector_group(det_key) + if this_group == group: + names.append(det_key) + + return names + def detector_pixel_size(self, detector_name): detector = self.detector(detector_name) return detector.get('pixels', {}).get('size', [0.1, 0.1]) @@ -3061,11 +3071,23 @@ def recent_images(self, images): def clean_panel_buffers(self): # Ensure that the panel buffer sizes match the pixel sizes. # If not, clear the panel buffer and print a warning. + instr = None for name, det_info in self.detectors.items(): buffer = det_info.get('buffer') if buffer is None: continue + if isinstance(buffer, str): + if instr is None: + # This instrument is not fully set up properly like + # the one from `create_hedm_instrument()` is, but it + # has the shape and roi set up properly, which are + # needed for the function. + iconfig = self.instrument_config_none_euler_convention + instr = HEDMInstrument(instrument_config=iconfig) + + buffer = panel_buffer_from_str(buffer, instr.detectors[name]) + buffer = np.asarray(buffer) if buffer.ndim == 1: continue diff --git a/hexrdgui/image_file_manager.py b/hexrdgui/image_file_manager.py index 32e3949c9..3ff380470 100644 --- a/hexrdgui/image_file_manager.py +++ b/hexrdgui/image_file_manager.py @@ -147,12 +147,17 @@ def open_file(self, f, options=None): elif ext in self.HDF5_FILE_EXTS: regular_hdf5 = True with h5py.File(f, 'r') as data: + eiger_stream_format = None if data.attrs.get('version') == 'CHESS_EIGER_STREAM_V1': - ims_type = 'eiger-stream-v1' + eiger_stream_format = 'eiger-stream-v1' + elif data.attrs.get('version') == 'CHESS_EIGER_STREAM_V2': + eiger_stream_format = 'eiger-stream-v2' + + if eiger_stream_format is not None: registry = ( imageseries.load.registry.Registry.adapter_registry ) - if ims_type not in registry: + if eiger_stream_format not in registry: msg = ( '"dectris-compression" must be installed to load ' 'eiger stream files.\n\n' @@ -160,7 +165,7 @@ def open_file(self, f, options=None): ) raise Exception(msg) - ims = imageseries.open(f, 'eiger-stream-v1') + ims = imageseries.open(f, eiger_stream_format) regular_hdf5 = False else: dset = data['/'.join(self.path)] @@ -251,7 +256,7 @@ def hdf_path_exists(self, f): def hdf5_path_exists(self, f): # If it is a special HDF5 file, just return True with h5py.File(f, 'r') as rf: - if rf.attrs.get('version') == 'CHESS_EIGER_STREAM_V1': + if rf.attrs.get('version', '').startswith('CHESS_EIGER_STREAM'): return True all_paths = [] diff --git a/hexrdgui/image_mode_widget.py b/hexrdgui/image_mode_widget.py index 581c40dc0..b6175712c 100644 --- a/hexrdgui/image_mode_widget.py +++ b/hexrdgui/image_mode_widget.py @@ -5,6 +5,8 @@ from PySide6.QtCore import QEvent, QObject, QTimer, Signal from PySide6.QtWidgets import QApplication +from hexrd.imageseries import ImageSeries + from hexrdgui.azimuthal_overlay_manager import AzimuthalOverlayManager from hexrdgui.constants import PolarXAxisType, ViewType from hexrdgui.create_hedm_instrument import create_hedm_instrument @@ -55,6 +57,7 @@ def __init__(self, parent=None): # FIXME: why is projecting from raw different? self.ui.stereo_project_from_polar.setVisible(False) + self.setup_eiger_stream_v2_options() self.setup_connections() self.update_gui_from_config() @@ -67,6 +70,10 @@ def setup_connections(self): HexrdConfig().set_stitch_raw_roi_images) self.ui.raw_show_zoom_dialog.clicked.connect( self.raw_show_zoom_dialog) + self.ui.eiger_stream_v2_setting.currentIndexChanged.connect( + self.on_eiger_stream_v2_settings_modified) + self.ui.eiger_stream_v2_multiplier.valueChanged.connect( + self.on_eiger_stream_v2_settings_modified) self.ui.cartesian_pixel_size.valueChanged.connect( HexrdConfig()._set_cartesian_pixel_size) self.ui.cartesian_virtual_plane_distance.valueChanged.connect( @@ -153,7 +160,80 @@ def setup_connections(self): HexrdConfig().set_stereo_project_from_polar) ImageLoadManager().new_images_loaded.connect( - self.update_visibility_states) + self.on_new_images_loaded) + + def setup_eiger_stream_v2_options(self): + combo = self.ui.eiger_stream_v2_setting + combo.clear() + + options = { + 'Threshold 1': 'threshold_1', + 'Threshold 2': 'threshold_2', + 'Difference': 'man_diff', + } + for k, v in options.items(): + combo.addItem(k, v) + + def on_new_images_loaded(self): + self.update_visibility_states() + self.update_eiger_stream_v2_settings() + + def update_eiger_stream_v2_settings(self): + ims_dict = HexrdConfig().imageseries_dict + + # We assume that all imageseries have the same options set + visible = False + if ims_dict: + ims = next(iter(ims_dict.values())) + visible = _is_eiger_stream_v2(ims) + + self.ui.eiger_stream_v2_group.setVisible(visible) + + if not visible: + # Nothing else to do + return + + settings = ims.option_values() + w = self.ui.eiger_stream_v2_setting + idx = w.findData(settings['threshold_setting']) + if idx != -1: + with block_signals(w): + w.setCurrentIndex(idx) + + mult_enabled = w.currentData() == 'man_diff' + mult_widgets = [ + self.ui.eiger_stream_v2_multiplier_label, + self.ui.eiger_stream_v2_multiplier, + ] + for w in mult_widgets: + w.setEnabled(mult_enabled) + + w = self.ui.eiger_stream_v2_multiplier + with block_signals(w): + w.setValue(settings['multiplier']) + + def on_eiger_stream_v2_settings_modified(self): + ims_dict = HexrdConfig().imageseries_dict + + if ( + not ims_dict or + not _is_eiger_stream_v2(next(iter(ims_dict.values()))) + ): + # This shouldn't have been triggered. Let's ignore it. + self.update_eiger_stream_v2_settings() + return + + settings = { + 'threshold_setting': self.ui.eiger_stream_v2_setting.currentData(), + 'multiplier': self.ui.eiger_stream_v2_multiplier.value(), + } + + for ims in ims_dict.values(): + for k, v in settings.items(): + ims.set_option(k, v) + + # Trigger all the same logic as if we loaded new images + ImageLoadManager().new_images_loaded.emit() def eventFilter(self, target, event): if target is self.ui and event.type() == QEvent.Resize: @@ -269,6 +349,7 @@ def update_gui_from_config(self): self.update_enable_states() self.update_visibility_states() + self.update_eiger_stream_v2_settings() def update_enable_states(self): apply_snip1d = self.ui.polar_apply_snip1d.isChecked() @@ -370,7 +451,6 @@ def auto_generate_polar_params(self): # Get the GUI to update with the new values self.update_gui_from_config() - @property def polar_apply_tth_distortion(self): return self.ui.polar_apply_tth_distortion.isChecked() @@ -589,3 +669,23 @@ def compute_polar_params(panel, max_tth_ps, max_eta_ps, min_tth, max_tth): ptth, peta = panel.pixel_angles() min_tth.append(np.degrees(np.min(ptth))) max_tth.append(np.degrees(np.max(ptth))) + + +def _get_ims_format(ims: ImageSeries) -> str | None: + # If "None" is returned, the format could not be determined + + # We have to recursively "dig" into the imageseries and adapters + # in order to find the original adapter. + adapter = ims + while hasattr(adapter, '_adapter') or hasattr(adapter, '_imser'): + if hasattr(adapter, '_adapter'): + adapter = adapter._adapter + else: + # ProcessedImageSeries have an '_imser' on them + adapter = adapter._imser + + return getattr(adapter, 'format', None) + + +def _is_eiger_stream_v2(ims: ImageSeries) -> bool: + return _get_ims_format(ims) == 'eiger-stream-v2' diff --git a/hexrdgui/masking/mask_manager.py b/hexrdgui/masking/mask_manager.py index 2b4675082..aa4c3e078 100644 --- a/hexrdgui/masking/mask_manager.py +++ b/hexrdgui/masking/mask_manager.py @@ -5,6 +5,7 @@ from hexrdgui import utils from hexrdgui.constants import ViewType +from hexrdgui.create_hedm_instrument import create_hedm_instrument from hexrdgui.masking.constants import CURRENT_MASK_VERSION, MaskType from hexrdgui.masking.create_polar_mask import ( create_polar_mask_from_raw, rebuild_polar_masks @@ -18,6 +19,7 @@ from hexrdgui.utils import unique_name from hexrd.instrument import unwrap_dict_to_h5 +from hexrd.utils.panel_buffer import panel_buffer_from_str from abc import ABC, abstractmethod @@ -506,9 +508,18 @@ def update_name(self, old_name, new_name): def masks_to_panel_buffer(self, selection): # Set the visible masks as the panel buffer(s) # We must ensure that we are using raw masks + instr = None for det, mask in HexrdConfig().raw_masks_dict.items(): detector_config = HexrdConfig().detector(det) buffer_value = detector_config.get('buffer', None) + if isinstance(buffer_value, str): + # Convert to an array + if instr is None: + instr = create_hedm_instrument() + + panel = instr.detectors[det] + buffer_value = panel_buffer_from_str(buffer_value, panel) + if isinstance(buffer_value, np.ndarray) and buffer_value.ndim == 2: # NOTE: The `logical_and` and `logical_or` here are being # applied to the *masks*, not the un-masked regions. This is diff --git a/hexrdgui/masking/mask_manager_dialog.py b/hexrdgui/masking/mask_manager_dialog.py index 73ae8cd7a..1325edc8b 100644 --- a/hexrdgui/masking/mask_manager_dialog.py +++ b/hexrdgui/masking/mask_manager_dialog.py @@ -308,7 +308,12 @@ def masks_to_panel_buffer(self): selection = 'Replace buffer' for det in HexrdConfig().detectors.values(): buff_val = det.get('buffer', None) - if isinstance(buff_val, np.ndarray) and buff_val.ndim == 2: + if ( + isinstance(buff_val, str) or ( + isinstance(buff_val, np.ndarray) and + buff_val.ndim == 2 + ) + ): show_dialog = True break diff --git a/hexrdgui/pinhole_correction_editor.py b/hexrdgui/pinhole_correction_editor.py index 7af47305f..c39754f5e 100644 --- a/hexrdgui/pinhole_correction_editor.py +++ b/hexrdgui/pinhole_correction_editor.py @@ -8,6 +8,7 @@ import hexrd.resources from hexrd.material import _angstroms, _kev, Material +from hexrd.utils.panel_buffer import panel_buffer_as_2d_array from hexrd.xrdutil.phutil import ( JHEPinholeDistortion, RyggPinholeDistortion, LayerDistortion, ) @@ -342,31 +343,15 @@ def apply_panel_buffers(self): # merge with any existing panel buffer for det_key, det in instr.detectors.items(): # "True" means keep, "False" means ignore - pb = det.panel_buffer - if pb is not None: - if pb.ndim == 2: - new_buff = np.logical_and(pb, ph_buffer[det_key]) - elif pb.ndim == 1 and not np.allclose(pb, 0): - # have edge buffer - ebuff = np.ones(det.shape, dtype=bool) - npix_row = int(np.ceil(pb[0]/det.pixel_size_row)) - npix_col = int(np.ceil(pb[1]/det.pixel_size_col)) - ebuff[:npix_row, :] = False - ebuff[-npix_row:, :] = False - ebuff[:, :npix_col] = False - ebuff[:, -npix_col:] = False - new_buff = np.logical_and(ebuff, ph_buffer[det_key]) - else: - new_buff = ph_buffer[det_key] - - det.panel_buffer = new_buff - else: - det.panel_buffer = ph_buffer[det_key] + pb = panel_buffer_as_2d_array(det.panel_buffer) + det.panel_buffer = np.logical_and(pb, ph_buffer[det_key]) # Now set them in the hexrdgui config iconfig = HexrdConfig().config['instrument'] for det_key, det in instr.detectors.items(): det_config = iconfig['detectors'][det_key] + # We know the panel buffer here is a 2D array since we set it + # earlier det_config['buffer'] = det.panel_buffer msg = 'Pinhole dimensions were applied to the panel buffers' diff --git a/hexrdgui/resources/ui/image_mode_widget.ui b/hexrdgui/resources/ui/image_mode_widget.ui index 805dd3441..ac0342eb9 100644 --- a/hexrdgui/resources/ui/image_mode_widget.ui +++ b/hexrdgui/resources/ui/image_mode_widget.ui @@ -38,7 +38,7 @@ QTabWidget::Rounded - 2 + 0 @@ -88,14 +88,7 @@ - - - - Show Zoom Dialog - - - - + Qt::Vertical @@ -108,6 +101,71 @@ + + + + Show Zoom Dialog + + + + + + + Eiger Stream V2 Settings + + + + + + <html><head/><body><p>Whether to use energy threshold 1, energy threshold 2, or the following expression:</p><p><br/></p><p><span style=" font-weight:600;">threshold_1 - multiplier * threshold_2<br/></span></p><p>The multiplier is only used if &quot;Difference&quot; is selected.</p></body></html> + + + Threshold Setting: + + + + + + + <html><head/><body><p>Whether to use energy threshold 1, energy threshold 2, or the following expression:</p><p><br/></p><p><span style=" font-weight:600;">threshold_1 - multiplier * threshold_2<br/></span></p><p>The multiplier is only used if &quot;Difference&quot; is selected.</p></body></html> + + + + + + + <html><head/><body><p>The multiplier to use when the &quot;Threshold Setting&quot; is &quot;Difference&quot;. The multiplier is used in the following expression:</p><p><br/></p><p><span style=" font-weight:600;">threshold_1 - multiplier * threshold_2</span></p></body></html> + + + Multiplier: + + + + + + + <html><head/><body><p>The multiplier to use when the &quot;Threshold Setting&quot; is &quot;Difference&quot;. The multiplier is used in the following expression:</p><p><br/></p><p><span style=" font-weight:600;">threshold_1 - multiplier * threshold_2</span></p></body></html> + + + false + + + 8 + + + -10000000.000000000000000 + + + 100000000.000000000000000 + + + 1.000000000000000 + + + + + + @@ -352,8 +410,8 @@ 0 0 - 522 - 700 + 524 + 691 @@ -1106,10 +1164,13 @@ raw_show_saturation raw_stitch_roi_images raw_show_zoom_dialog + eiger_stream_v2_setting + eiger_stream_v2_multiplier cartesian_pixel_size cartesian_plane_normal_rotate_x cartesian_virtual_plane_distance cartesian_plane_normal_rotate_y + polar_scroll_area polar_active_beam polar_pixel_size_tth polar_pixel_size_eta diff --git a/hexrdgui/resources/ui/panel_buffer_dialog.ui b/hexrdgui/resources/ui/panel_buffer_dialog.ui index 3eab6ce15..c8a040f14 100644 --- a/hexrdgui/resources/ui/panel_buffer_dialog.ui +++ b/hexrdgui/resources/ui/panel_buffer_dialog.ui @@ -7,7 +7,7 @@ 0 0 478 - 361 + 398 @@ -30,7 +30,7 @@ - The panel buffer can be set in two ways. Either by provided the border in mm or through a NumPy array, interpretation is boolean with true marking valid pixels. + The panel buffer can be set in three ways. By providing the border in mm, through a NumPy array the shape of the detector (interpretation is boolean with true marking valid pixels), or by selecting a known panel buffer by name. true @@ -61,6 +61,11 @@ NumPy + + + Name + + @@ -182,6 +187,30 @@ + + + Name + + + + + + <html><head/><body><p>Select a known panel buffer by name.</p><p><br/></p><p>Either the shape of the known panel buffer must match the shape of the current detector, or, in the case of subpanels (where each panel has an ROI and a group defined), the known panel buffer for the monolith can still be selected, in which case the relevant subregion of the panel buffer is automatically extracted using the ROI information.</p></body></html> + + + Name: + + + + + + + <html><head/><body><p>Select a known panel buffer by name.</p><p><br/></p><p>Either the shape of the known panel buffer must match the shape of the current detector, or, in the case of subpanels (where each panel has an ROI and a group defined), the known panel buffer for the monolith can still be selected, in which case the relevant subregion of the panel buffer is automatically extracted using the ROI information.</p></body></html> + + + + + @@ -244,6 +273,8 @@ file_name select_file_button show_panel_buffer + selected_name + clear_panel_buffer diff --git a/hexrdgui/utils/__init__.py b/hexrdgui/utils/__init__.py index 40e55fad6..50aedacf3 100644 --- a/hexrdgui/utils/__init__.py +++ b/hexrdgui/utils/__init__.py @@ -21,6 +21,7 @@ from hexrd.transforms.xfcapi import makeRotMatOfExpMap from hexrd.utils.decorators import memoize from hexrd.utils.hkl import str_to_hkl +from hexrd.utils.panel_buffer import panel_buffer_as_2d_array class SnipAlgorithmType(IntEnum): @@ -477,28 +478,7 @@ def add_sample_points(points, min_output_length): def convert_panel_buffer_to_2d_array(panel): - # Take whatever the panel buffer is and convert it to a 2D array - if panel.panel_buffer is None: - # Just make a panel buffer with all True values - panel.panel_buffer = np.ones(panel.shape, dtype=bool) - elif panel.panel_buffer.shape == (2,): - # The two floats are specifying the borders in mm for x and y. - # Convert to pixel borders. Swap x and y so we have i, j in pixels. - borders = np.round([ - panel.panel_buffer[1] / panel.pixel_size_row, - panel.panel_buffer[0] / panel.pixel_size_col, - ]).astype(int) - - # Convert to array - panel_buffer = np.zeros(panel.shape, dtype=bool) - - # We can't do `-borders[i]` since that doesn't work for 0, - # so we must do `panel.shape[i] - borders[i]` instead. - panel_buffer[borders[0]:panel.shape[0] - borders[0], - borders[1]:panel.shape[1] - borders[1]] = True - panel.panel_buffer = panel_buffer - elif panel.panel_buffer.ndim != 2: - raise NotImplementedError(panel.panel_buffer.ndim) + panel.panel_buffer = panel_buffer_as_2d_array(panel) @contextmanager