class RegionSeg(QGroupBox): """ Region segmentation method panel """ def __init__( self, parent, calculate_volumes_default=CALCULATE_VOLUMES_DEFAULT, summarise_volumes_default=SUMMARIZE_VOLUMES_DEFAULT, brush_size=BRUSH_SIZE, image_file_extension=IMAGE_FILE_EXT, num_colors=NUM_COLORS, ): super(RegionSeg, self).__init__() self.parent = parent self.calculate_volumes_default = calculate_volumes_default self.summarise_volumes_default = summarise_volumes_default # Brushes / ... self.brush_size_default = BRUSH_SIZE # Keep track of default self.brush_size = brush_size # Initialise self.num_colors = num_colors # File formats self.image_file_extension = image_file_extension def add_region_panel(self, row): self.region_panel = QGroupBox("Region analysis") region_layout = QGridLayout() add_button( "Add region", region_layout, self.add_region, 2, 0, ) add_button( "Analyse regions", region_layout, self.run_region_analysis, 2, 1, ) self.calculate_volumes_checkbox = add_checkbox( region_layout, self.calculate_volumes_default, "Calculate volumes", 0, ) self.summarise_volumes_checkbox = add_checkbox( region_layout, self.summarise_volumes_default, "Summarise volumes", 1, ) region_layout.setColumnMinimumWidth(1, COLUMN_WIDTH) self.region_panel.setLayout(region_layout) self.parent.layout.addWidget(self.region_panel, row, 0, 1, 2) self.region_panel.setVisible(False) def toggle_region_panel(self): # TODO: Change color scheme directly when theme is switched # TODO: "text-align" property should follow constant SEGM_METHODS_PANEL_ALIGN if self.region_panel.isVisible(): self.region_panel.setVisible(False) if self.parent.viewer.theme == "dark": self.parent.show_regionseg_button.setStyleSheet( f"QPushButton {{ background-color: #414851; text-align:{SEGM_METHODS_PANEL_ALIGN};}}" f"QPushButton:pressed {{ background-color: #414851; text-align:{SEGM_METHODS_PANEL_ALIGN};}}" ) else: self.parent.show_regionseg_button.setStyleSheet( f"QPushButton {{ background-color: #d6d0ce; text-align:{SEGM_METHODS_PANEL_ALIGN};}}" f"QPushButton:pressed {{ background-color: #d6d0ce; text-align:{SEGM_METHODS_PANEL_ALIGN};}}" ) else: self.region_panel.setVisible(True) if self.parent.viewer.theme == "dark": self.parent.show_regionseg_button.setStyleSheet( f"QPushButton {{ background-color: #7e868f; text-align:{SEGM_METHODS_PANEL_ALIGN};}}" f"QPushButton:pressed {{ background-color: #7e868f; text-align:{SEGM_METHODS_PANEL_ALIGN};}}" ) else: self.parent.show_regionseg_button.setStyleSheet( f"QPushButton {{ background-color: #fdf194; text-align:{SEGM_METHODS_PANEL_ALIGN};}}" f"QPushButton:pressed {{ background-color: #fdf194; text-align:{SEGM_METHODS_PANEL_ALIGN};}}" ) def check_saved_region(self): add_existing_region_segmentation( self.parent.paths.regions_directory, self.parent.viewer, self.parent.label_layers, self.image_file_extension, ) def add_region(self): print("Adding a new region\n") self.region_panel.setVisible(True) # Should be visible by default! add_new_region_layer( self.parent.viewer, self.parent.label_layers, self.parent.base_layer.data, self.brush_size, self.num_colors, ) def run_region_analysis(self): if self.parent.label_layers: print("Running region analysis") worker = region_analysis( self.parent.label_layers, self.parent.atlas_layer.data, self.parent.atlas, self.parent.paths.regions_directory, output_csv_file=self.parent.paths.region_summary_csv, volumes=self.calculate_volumes_checkbox.isChecked(), summarise=self.summarise_volumes_checkbox.isChecked(), ) worker.start() else: print("No regions found")
class AnimationWindow(PyDialog): """ +-------------------+ | Animation | +-------------------------+ | icase ______ | | scale ______ Default | | time ______ Default | | | | nframes ______ Default | | resolu. ______ Default | | Dir ______ Browse | | iFrame ______ | | | | Animations: | | o Scale, Phase, Time | | | | x delete images | | x repeat | # TODO: change to an integer | x make gif | | | | Step, RunAll | | Close | +-------------------------+ TODO: add key-frame support """ def __init__(self, data, win_parent=None): PyDialog.__init__(self, data, win_parent) self.set_font_size(data['font_size']) self.istep = 0 self._animate_type = 'time' self._updated_animation = False self._active_deformation = 0. self._icase_fringe = data['icase_fringe'] self._icase_disp = data['icase_disp'] self._icase_vector = data['icase_vector'] self._default_name = data['name'] self._default_time = data['time'] self._default_fps = data['frames/sec'] self._default_resolution = data['resolution'] self._scale = data['scale'] self._default_scale = data['default_scale'] self._default_is_scale = data['is_scale'] self._arrow_scale = data['arrow_scale'] self._default_arrow_scale = data['default_arrow_scale'] self._phase = data['phase'] self._default_phase = data['default_phase'] self._default_dirname = data['dirname'] self._default_gif_name = os.path.join(self._default_dirname, data['name'] + '.gif') self.animation_types = [ 'Animate Scale', ] #'Animate Phase', #'Animate Time', #'Animate Frequency Sweep' #] self.setWindowTitle('Animate Model') self.create_widgets() self.create_layout() self.set_connections() self.is_gui = False if hasattr(self.win_parent, '_updated_legend'): self.win_parent.is_animate_open = True self.is_gui = True self.on_update_min_max_defaults() def create_widgets(self): """creates the menu objects""" self.box_scale = QGroupBox('Animate Scale') self.box_time = QGroupBox('Animate Time') icase_max = 1000 # TODO: update 1000 self.checkbox_fringe = QCheckBox('Animate') self.checkbox_fringe.setToolTip( 'Animate the fringe in addition to the deflection') #self.checkbox_disp = QCheckBox('Animate') self.checkbox_fringe.setEnabled(False) self.icase_fringe_label = QLabel("iCase (Fringe):") self.icase_fringe_edit = QSpinBox(self) self.icase_fringe_edit.setRange(0, icase_max) self.icase_fringe_edit.setSingleStep(1) if self._icase_fringe is not None: self.icase_fringe_edit.setValue(self._icase_fringe) self.icase_fringe_edit.setToolTip( 'Case Number for the Scale/Phase Animation Type.\n' 'Defaults to the result you had shown when you clicked "Create Animation".\n' 'iCase can be seen by clicking "Apply" on a result.') self.icase_disp_label = QLabel("iCase (Disp):") self.icase_disp_edit = QSpinBox(self) self.icase_disp_edit.setRange(1, icase_max) self.icase_disp_edit.setSingleStep(1) if self._icase_disp is not None: self.icase_disp_edit.setValue(self._icase_disp) self.checkbox_vector = QCheckBox('Animate') self.checkbox_vector.setToolTip( 'Animate the vector in addition to the deflection') self.checkbox_vector.hide() #self.checkbox_disp = QCheckBox('Animate') self.icase_vector_label = QLabel("iCase (Vector):") self.icase_vector_edit = QSpinBox(self) self.icase_vector_edit.setRange(1, icase_max) self.icase_vector_edit.setSingleStep(1) if self._icase_vector is not None: self.icase_vector_edit.setValue(self._icase_vector) self.scale_label = QLabel("True Scale:") self.scale_edit = QLineEdit(str(self._scale)) self.scale_button = QPushButton("Default") self.scale_edit.setToolTip('Scale factor of the "deflection"') self.scale_button.setToolTip('Sets the scale factor of the gif to %s' % self._scale) self.arrow_scale_label = QLabel("Arrow Scale:") self.arrow_scale_edit = QLineEdit(str(self._scale)) self.arrow_scale_button = QPushButton("Default") self.arrow_scale_edit.setToolTip('Scale factor of the "arrows"') self.arrow_scale_button.setToolTip( 'Sets the arrow scale factor of the gif to %s' % self._arrow_scale) self.arrow_scale_label.setVisible(False) self.arrow_scale_edit.setVisible(False) self.arrow_scale_button.setVisible(False) self.time_label = QLabel("Total Time (sec):") self.time_edit = QDoubleSpinBox(self) self.time_edit.setValue(self._default_time) self.time_edit.setRange(0.1, 5. * 60.) self.time_edit.setDecimals(2) self.time_edit.setSingleStep(0.1) self.time_button = QPushButton("Default") self.time_edit.setToolTip("Total time of the gif") self.time_button.setToolTip('Sets the total time of the gif to %.2f' % self._default_time) self.fps_label = QLabel("Frames/Second:") self.fps_edit = QSpinBox(self) self.fps_edit.setRange(1, 60) self.fps_edit.setSingleStep(1) self.fps_edit.setValue(self._default_fps) self.fps_button = QPushButton("Default") self.fps_edit.setToolTip( "A higher FPS is smoother, but may not play well for large gifs") self.fps_button.setToolTip('Sets the FPS to %s' % self._default_fps) self.resolution_label = QLabel("Resolution Scale:") self.resolution_edit = QSpinBox(self) self.resolution_edit.setRange(1, 5) self.resolution_edit.setSingleStep(1) self.resolution_edit.setValue(self._default_resolution) self.resolution_button = QPushButton("Default") self.resolution_edit.setToolTip( 'Scales the window resolution by an integer factor') self.resolution_button.setToolTip('Sets the resolution to %s' % self._default_resolution) #----------------- # Time plot self.fringe_label = QLabel("Fringe") self.icase_fringe_start_edit = QSpinBox(self) self.icase_fringe_start_edit.setRange(0, icase_max) self.icase_fringe_start_edit.setSingleStep(1) self.icase_fringe_start_edit.setValue(self._icase_fringe) self.icase_fringe_start_button = QPushButton("Default") self.icase_fringe_end_edit = QSpinBox(self) self.icase_fringe_end_edit.setRange(0, icase_max) self.icase_fringe_end_edit.setSingleStep(1) self.icase_fringe_end_edit.setValue(self._icase_fringe) self.icase_fringe_end_button = QPushButton("Default") self.icase_fringe_delta_edit = QSpinBox(self) self.icase_fringe_delta_edit.setRange(1, icase_max) self.icase_fringe_delta_edit.setSingleStep(1) self.icase_fringe_delta_edit.setValue(1) self.icase_fringe_delta_button = QPushButton("Default") self.displacement_label = QLabel("Displacement") self.icase_start = QLabel("iCase Start:") self.icase_disp_start_edit = QSpinBox(self) self.icase_disp_start_edit.setRange(0, icase_max) self.icase_disp_start_edit.setSingleStep(1) self.icase_disp_start_edit.setValue(self._icase_fringe) self.icase_disp_start_button = QPushButton("Default") self.icase_end_label = QLabel("iCase End:") self.icase_disp_end_edit = QSpinBox(self) self.icase_disp_end_edit.setRange(0, icase_max) self.icase_disp_end_edit.setSingleStep(1) self.icase_disp_end_edit.setValue(self._icase_fringe) self.icase_disp_end_button = QPushButton("Default") self.icase_delta_label = QLabel("iCase Delta:") self.icase_disp_delta_edit = QSpinBox(self) self.icase_disp_delta_edit.setRange(1, icase_max) self.icase_disp_delta_edit.setSingleStep(1) self.icase_disp_delta_edit.setValue(1) self.icase_disp_delta_button = QPushButton("Default") self.min_value_enable = QCheckBox() self.min_value_label = QLabel("Min Value:") self.min_value_edit = QLineEdit('') #self.min_value_edit.setRange(1, 1000) #self.min_value_edit.setSingleStep(1) #self.min_value_edit.setValue(1) self.min_value_button = QPushButton("Default") self.max_value_enable = QCheckBox() self.max_value_label = QLabel("Max Value:") self.max_value_edit = QLineEdit('') #self.min_value_edit.setRange(1, 1000) # TODO: update 1000 #self.min_value_edit.setSingleStep(1) #self.min_value_edit.setValue(1) self.max_value_button = QPushButton("Default") # TODO: enable this (uncomment) ------------------------------------------ #self.min_value_enable.hide() #self.min_value.hide() #self.min_value_edit.hide() #self.min_value_button.hide() #self.max_value_enable.hide() #self.max_value.hide() #self.max_value_edit.hide() #self.max_value_button.hide() # TODO: enable this (uncomment) ------------------------------------------ self.icase_disp_start_edit.setToolTip( 'The first frame of the animation') self.icase_disp_end_edit.setToolTip( 'The last frame of the animation\n' 'Assumes icase_start + nframes * icase_delta = icase_end') self.icase_disp_delta_edit.setToolTip( 'The frame step size (to skip non-consecutive results).\n' 'Frame skipping can be used to:\n' " - skip across results that you don't want to plot\n" ' - adjust the FPS') self.min_value_edit.setToolTip( 'Min value of the legend (not supported)') self.max_value_edit.setToolTip( 'Max value of the legend (not supported)') #'time' : 0., #'default_time' : 0, #'icase_start' : 10, #'icase_delta' : 3, #'min_value' : 0., #'max_value' : 1000., self.browse_folder_label = QLabel('Output Directory:') self.browse_folder_edit = QLineEdit(str(self._default_dirname)) self.browse_folder_button = QPushButton('Browse') self.browse_folder_edit.setToolTip( 'Location to save the png/gif files') self.gif_label = QLabel("Gif Filename:") self.gif_edit = QLineEdit(str(self._default_name + '.gif')) self.gif_button = QPushButton('Default') self.gif_edit.setToolTip('Name of the gif') self.gif_button.setToolTip('Sets the name of the gif to %s.gif' % self._default_name) # scale / phase if 1: # pragma: no cover self.animate_scale_radio = QRadioButton("Animate Scale") self.animate_phase_radio = QRadioButton("Animate Phase") self.animate_time_radio = QRadioButton("Animate Time") self.animate_freq_sweeep_radio = QRadioButton( "Animate Frequency Sweep") self.animate_scale_radio.setToolTip( 'Animates the scale factor based on the "Animation Type"') self.animate_time_radio.setToolTip( 'Animates the time/load/mode step') self.animate_scale_radio.setChecked(self._default_is_scale) self.animate_phase_radio.setChecked(not self._default_is_scale) self.animate_time_radio.setChecked(False) msg = 'Scale : Animates the scale factor based on the "Animation Profile"\n' if self._default_phase is None: self.animate_phase_radio.setDisabled(True) self.animate_phase_radio.setToolTip( 'Animates the phase angle ' '(only for complex results)') msg += 'Phase : Animates the phase angle (only for complex results)\n' else: self.animate_phase_radio.setToolTip("Animates the phase angle") msg += 'Phase : Animates the phase angle\n' msg += ( 'Time : Animates the time/load/mode step\n' 'Freq Sweep : Animates a complex result across a range of frequencies ' '(not supported)\n') self.animate_freq_sweeep_radio.setDisabled(True) self.animate_freq_sweeep_radio.setToolTip( 'Animates a complex result across a range of frequencies (not supported)' ) else: msg = 'Scale : Animates the scale factor based on the "Animation Profile"\n' if self._default_phase is None: #self.animate_phase_radio.setDisabled(True) #self.animate_phase_radio.setToolTip('Animates the phase angle ' #'(only for complex results)') msg += 'Phase : Animates the phase angle (only for complex results)\n' else: #self.animate_phase_radio.setToolTip("Animates the phase angle") msg += 'Phase : Animates the phase angle\n' msg += ( 'Time : Animates the time/load/mode step\n' 'Freq Sweep : Animates a complex result across a range of frequencies ' '(not supported)\n') self.animation_type = QLabel("Animation Type:") animation_type = OrderedDict() #scale_msg = 'Scale\n' #phase_msg = 'Phase\n' #time_msg = 'Time\n' #animation_types = [ #('Animate Scale', scale_msg), #('Animate Phase', phase_msg), #('Animate Time', time_msg), ##'Animate Frequency Sweep' #] if self._phase is not None: self.animation_types.append('Animate Phase') self.animation_types.append('Animate Time') self.animation_profile_label = QLabel("Animation Profile:") self.animation_profile_edit = QComboBox() for animation_profile in ANIMATION_PROFILES: self.animation_profile_edit.addItem(animation_profile) self.animation_profile_edit.setToolTip('The profile for a scaled GIF') self.animation_type_edit = QComboBox() # TODO: add a tooltip for each item for animation_type in self.animation_types: self.animation_type_edit.addItem(animation_type) #self.animation_type_edit.setToolTip('The profile for a scaled GIF') self.animation_type_edit.setToolTip(msg.rstrip()) self.csv_profile_label = QLabel("CSV profile:") self.csv_profile_edit = QLineEdit() self.csv_profile_browse_button = QPushButton('Browse') self.csv_profile_edit.setToolTip( 'The path to the CSV file of (Scale1, Scale2, Scale3, ...)') #widget = QWidget(self) #horizontal_vertical_group = QButtonGroup(widget) #horizontal_vertical_group.addButton(self.animate_scale_radio) #horizontal_vertical_group.addButton(self.animate_phase_radio) #horizontal_vertical_group.addButton(self.animate_time_radio) #horizontal_vertical_group.addButton(self.animate_freq_sweeep_radio) # animate in gui self.animate_in_gui_checkbox = QCheckBox("Animate In GUI?") self.animate_in_gui_checkbox.setChecked(True) # make images self.make_images_checkbox = QCheckBox("Make images?") self.make_images_checkbox.setChecked(True) # make images self.overwrite_images_checkbox = QCheckBox("Overwrite images?") self.overwrite_images_checkbox.setChecked(True) # delete images when finished self.delete_images_checkbox = QCheckBox("Delete images when finished?") self.delete_images_checkbox.setChecked(True) # endless loop self.repeat_checkbox = QCheckBox("Repeat?") self.repeat_checkbox.setChecked(True) self.repeat_checkbox.setToolTip( "Repeating creates an infinitely looping gif") # endless loop self.make_gif_checkbox = QCheckBox("Make Gif?") if IS_IMAGEIO: self.make_gif_checkbox.setChecked(True) else: self.make_gif_checkbox.setChecked(False) self.make_gif_checkbox.setEnabled(False) self.make_gif_checkbox.setToolTip( 'imageio is not available; install it') # bottom buttons self.step_button = QPushButton("Step") self.wipe_button = QPushButton("Wipe Deformed Shape") self.stop_button = QPushButton("Stop") self.run_button = QPushButton("Run") self.step_button.setToolTip( 'Steps through the animation (for testing)') self.wipe_button.setToolTip( 'Removes the existing "deflecton" from the animation') self.stop_button.setToolTip('Stops the animation') self.run_button.setToolTip('Creates the animation') self.step_button.hide() self.wipe_button.hide() self.wipe_button.setEnabled(False) #self.wipe_button.hide() self.stop_button.setEnabled(False) self.cancel_button = QPushButton("Close") #self.set_grid_time(enabled=False) #self.set_grid_scale(enabled=self._default_is_scale) if self._default_phase: self.on_animate_phase(force=True) set_combo_box_text(self.animation_type_edit, 'Animate Phase') else: self.on_animate_scale(force=True) def set_connections(self): """creates button actions""" self.checkbox_vector.clicked.connect(self.on_checkbox_vector) self.scale_button.clicked.connect(self.on_default_scale) self.arrow_scale_button.clicked.connect(self.on_default_arrow_scale) self.time_button.clicked.connect(self.on_default_time) self.fps_button.clicked.connect(self.on_default_fps) self.resolution_button.clicked.connect(self.on_default_resolution) self.browse_folder_button.clicked.connect(self.on_browse_folder) self.csv_profile_browse_button.clicked.connect(self.on_browse_csv) self.gif_button.clicked.connect(self.on_default_name) self.step_button.clicked.connect(self.on_step) self.wipe_button.clicked.connect(self.on_wipe) self.stop_button.clicked.connect(self.on_stop) self.run_button.clicked.connect(self.on_run) self.min_value_enable.clicked.connect(self.on_min_value_enable) self.max_value_enable.clicked.connect(self.on_max_value_enable) self.min_value_button.clicked.connect(self.on_min_value_default) self.max_value_button.clicked.connect(self.on_max_value_default) self.icase_disp_start_button.clicked.connect( self.on_update_min_max_defaults) #self.animate_scale_radio.clicked.connect(self.on_animate_scale) #self.animate_phase_radio.clicked.connect(self.on_animate_phase) #self.animate_time_radio.clicked.connect(self.on_animate_time) self.animation_type_edit.currentIndexChanged.connect(self.on_animate) #self.animate_freq_sweeep_radio self.cancel_button.clicked.connect(self.on_cancel) self.animate_in_gui_checkbox.clicked.connect(self.on_animate_in_gui) self.animate_in_gui_checkbox.setChecked(True) self.on_animate_in_gui() def on_checkbox_vector(self): is_enabled = self.checkbox_vector.isEnabled() is_checked = self.checkbox_vector.isChecked() enable_edit = is_enabled and is_checked self.icase_vector_label.setEnabled(is_checked) self.icase_vector_edit.setEnabled(enable_edit) def on_animate_in_gui(self): animate_in_gui = self.animate_in_gui_checkbox.isChecked() enable = not animate_in_gui if HIDE_WHEN_INACTIVE: self.make_images_checkbox.setVisible(enable) self.delete_images_checkbox.setVisible(enable) self.make_gif_checkbox.setVisible(enable) self.repeat_checkbox.setVisible(enable) self.resolution_button.setVisible(enable) self.resolution_label.setVisible(enable) self.resolution_edit.setVisible(enable) self.gif_label.setVisible(enable) self.gif_edit.setVisible(enable) self.gif_button.setVisible(enable) self.browse_folder_label.setVisible(enable) self.browse_folder_button.setVisible(enable) self.browse_folder_edit.setVisible(enable) self.step_button.setEnabled(enable) self.make_images_checkbox.setEnabled(enable) self.delete_images_checkbox.setEnabled(enable) self.make_gif_checkbox.setEnabled(enable) self.repeat_checkbox.setEnabled(enable) self.resolution_button.setEnabled(enable) self.resolution_edit.setEnabled(enable) self.gif_edit.setEnabled(enable) self.gif_button.setEnabled(enable) self.browse_folder_button.setEnabled(enable) self.browse_folder_edit.setEnabled(enable) self.step_button.setEnabled(enable) #wipe_button def on_animate(self, value): """ animate pulldown Parameters ---------- value : int index in animation_types """ #animation_types = ['Animate Scale', 'Animate Phase', 'Animate Time', #'Animate Frequency Sweep'] animation_type = self.animation_types[value] if animation_type == 'Animate Scale': self.on_animate_scale() elif animation_type == 'Animate Phase': self.on_animate_phase() elif animation_type == 'Animate Time': self.on_animate_time() else: raise NotImplementedError('value = ', value) def on_animate_time(self, force=False): """enables the secondary input""" #print('on_animate_time') if self._animate_type == 'scale' or force: self.set_grid_scale(False, 'time') self.set_grid_time(True, 'time') self._animate_type = 'time' def on_animate_scale(self, force=False): """enables the secondary input""" #print('on_animate_scale') self.set_grid_scale(True, 'scale') if self._animate_type == 'time' or force: self.set_grid_time(False, 'scale') self._animate_type = 'scale' def on_animate_phase(self, force=False): """enables the secondary input""" #print('on_animate_phase') if self._animate_type == 'scale' or force: self.set_grid_scale(False, 'phase') if self._animate_type == 'time' or force: self.set_grid_time(False, 'phase') self._animate_type = 'phase' def set_grid_scale(self, enabled=True, word=''): """enables/disables the secondary input""" #print('%s-set_grid_scale; enabled = %r' % (word, enabled)) if HIDE_WHEN_INACTIVE: self.box_scale.setVisible(enabled) self.animation_profile_label.setVisible(enabled) self.animation_profile_edit.setVisible(enabled) #self.min_value_enable.setVisible(enabled) #self.max_value_enable.setVisible(enabled) self.animation_profile_label.setEnabled(enabled) self.animation_profile_edit.setEnabled(enabled) # TODO: doesn't work... #self.csv_profile.setEnabled(enabled) #self.csv_profile_edit.setEnabled(enabled) #self.csv_profile_button.setEnabled(enabled) self.min_value_enable.setEnabled(enabled) self.max_value_enable.setEnabled(enabled) self.on_min_value_enable() self.on_max_value_enable() def set_grid_time(self, enabled=True, word=''): """enables/disables the secondary input""" #print('%s-set_grid_time; enabled = %r' % (word, enabled)) if HIDE_WHEN_INACTIVE: self.box_time.setVisible(enabled) self.checkbox_fringe.setVisible(not enabled) self.icase_fringe_label.setVisible(not enabled) self.icase_fringe_edit.setVisible(not enabled) self.icase_disp_label.setVisible(not enabled) self.icase_disp_edit.setVisible(not enabled) self.icase_vector_label.setVisible(not enabled) self.icase_vector_edit.setVisible(not enabled) #self.icase_fringe_delta_edit.setVisible(enabled) self.icase_disp_delta_edit.setVisible(enabled) self.icase_disp_delta_edit.setVisible(enabled) self.fps_label.setVisible(enabled) self.fps_edit.setVisible(enabled) self.fps_button.setVisible(enabled) self.displacement_label.setEnabled(enabled) self.fringe_label.setEnabled(enabled) self.icase_start.setEnabled(enabled) self.icase_disp_start_edit.setEnabled(enabled) self.icase_disp_start_button.setEnabled(enabled) self.icase_fringe_start_edit.setEnabled(enabled) self.icase_fringe_start_button.setEnabled(enabled) self.icase_end_label.setEnabled(enabled) self.icase_disp_end_edit.setEnabled(enabled) self.icase_disp_end_button.setEnabled(enabled) self.icase_fringe_end_edit.setEnabled(enabled) self.icase_fringe_end_button.setEnabled(enabled) self.icase_delta_label.setEnabled(enabled) self.icase_disp_delta_edit.setEnabled(enabled) self.icase_disp_delta_button.setEnabled(enabled) self.icase_fringe_delta_edit.setEnabled(enabled) self.icase_fringe_delta_button.setEnabled(enabled) #----------------------------------------------------------------------- is_min_enabled = self.min_value_enable.isChecked() self.min_value_label.setEnabled(is_min_enabled) self.min_value_edit.setEnabled(is_min_enabled) self.min_value_button.setEnabled(is_min_enabled) is_max_enabled = self.max_value_enable.isChecked() self.max_value_label.setEnabled(is_max_enabled) self.max_value_edit.setEnabled(is_max_enabled) self.max_value_button.setEnabled(is_max_enabled) self.min_value_enable.setEnabled(enabled) self.on_min_value_enable() #self.min_value.setEnabled(enabled) #self.min_value_edit.setEnabled(enabled) #self.min_value_button.setEnabled(enabled) self.max_value_enable.setEnabled(enabled) self.on_max_value_enable() #self.max_value.setEnabled(enabled) #self.max_value_edit.setEnabled(enabled) #self.max_value_button.setEnabled(enabled) self.icase_fringe_label.setEnabled(not enabled) self.icase_fringe_edit.setEnabled(not enabled) self.checkbox_fringe.setEnabled(not enabled) self.icase_disp_label.setEnabled(not enabled) self.icase_disp_edit.setEnabled(not enabled) self.icase_vector_label.setEnabled(not enabled) self.icase_vector_edit.setEnabled(not enabled) self.checkbox_vector.setEnabled(not enabled) self.on_checkbox_vector() self.fps_label.setEnabled(not enabled) self.fps_edit.setEnabled(not enabled) self.fps_button.setEnabled(not enabled) def on_min_value_enable(self): """ The min edit value box is enabled when we switch to time and the box is checked """ is_min_enabled = self.min_value_enable.isChecked( ) and self.min_value_enable.isEnabled() self.min_value_label.setEnabled(is_min_enabled) self.min_value_edit.setEnabled(is_min_enabled) self.min_value_button.setEnabled(is_min_enabled) def on_max_value_enable(self): """ The max edit value box is enabled when we switch to time and the box is checked """ is_max_enabled = self.max_value_enable.isChecked( ) and self.max_value_enable.isEnabled() self.max_value_label.setEnabled(is_max_enabled) self.max_value_edit.setEnabled(is_max_enabled) self.max_value_button.setEnabled(is_max_enabled) def on_update_min_max_defaults(self): """ When the icase is changed, the min/max value default message is changed """ icase = self.icase_disp_start_edit.value() min_value, max_value = self.get_min_max(icase) self.min_value_button.setToolTip('Sets the min value to %g' % min_value) self.max_value_button.setToolTip('Sets the max value to %g' % max_value) def on_min_value_default(self): """When min default icase is pressued, update the value""" icase = self.icase_disp_start_edit.value() min_value = self.get_min_max(icase)[0] self.min_value_edit.setText(str(min_value)) self.min_value_edit.setStyleSheet("QLineEdit{background: white;}") def on_max_value_default(self): """When max default icase is pressued, update the value""" icase = self.icase_disp_start_edit.value() max_value = self.get_min_max(icase)[1] self.max_value_edit.setText(str(max_value)) self.max_value_edit.setStyleSheet("QLineEdit{background: white;}") def on_browse_folder(self): """opens a folder dialog""" dirname = getexistingdirectory(parent=self, caption='Select a Directory', basedir='', options=QFileDialog.ShowDirsOnly) if not dirname: return self.browse_folder_edit.setText(dirname) def on_browse_csv(self): """opens a file dialog""" default_filename = '' file_types = 'Delimited Text (*.txt; *.dat; *.csv)' dirname = open_file_dialog(self, 'Select a CSV File', default_filename, file_types) if not dirname: return self.csv_profile_browse_button.setText(dirname) def on_default_name(self): """sets the default gif name""" self.gif_edit.setText(self._default_name + '.gif') def on_default_scale(self): """sets the default displacement scale factor""" self.scale_edit.setText(str(self._default_scale)) self.scale_edit.setStyleSheet("QLineEdit{background: white;}") def on_default_arrow_scale(self): """sets the default arrow scale factor""" self.arrow_scale_edit.setText(str(self._default_arrow_scale)) self.arrow_scale_edit.setStyleSheet("QLineEdit{background: white;}") def on_default_time(self): """sets the default gif time""" self.time_edit.setValue(self._default_time) def on_default_fps(self): """sets the default FPS""" self.fps_edit.setValue(self._default_fps) def on_default_resolution(self): """sets the default image resolution scale factor""" self.resolution_edit.setValue(self._default_resolution) def create_layout(self): """displays the menu objects""" grid = QGridLayout() irow = 0 grid.addWidget(self.icase_fringe_label, irow, 0) grid.addWidget(self.icase_fringe_edit, irow, 1) grid.addWidget(self.checkbox_fringe, irow, 2) irow += 1 grid.addWidget(self.icase_disp_label, irow, 0) grid.addWidget(self.icase_disp_edit, irow, 1) #grid.addWidget(self.checkbox_disp, irow, 2) irow += 1 grid.addWidget(self.icase_vector_label, irow, 0) grid.addWidget(self.icase_vector_edit, irow, 1) grid.addWidget(self.checkbox_vector, irow, 2) irow += 1 grid.addWidget(self.scale_label, irow, 0) grid.addWidget(self.scale_edit, irow, 1) grid.addWidget(self.scale_button, irow, 2) irow += 1 grid.addWidget(self.arrow_scale_label, irow, 0) grid.addWidget(self.arrow_scale_edit, irow, 1) grid.addWidget(self.arrow_scale_button, irow, 2) irow += 1 grid.addWidget(self.time_label, irow, 0) grid.addWidget(self.time_edit, irow, 1) grid.addWidget(self.time_button, irow, 2) irow += 1 # spacer spacer = QLabel('') grid.addWidget(self.fps_label, irow, 0) grid.addWidget(self.fps_edit, irow, 1) grid.addWidget(self.fps_button, irow, 2) irow += 1 grid.addWidget(self.animation_type, irow, 0) grid.addWidget(self.animation_type_edit, irow, 1) irow += 1 grid.addWidget(spacer, irow, 0) irow += 1 #---------- #Time grid_time = QGridLayout() jrow = 0 self.fringe_label.setAlignment(Qt.AlignCenter) self.displacement_label.setAlignment(Qt.AlignCenter) if not IS_TIME_FRINGE: self.fringe_label.hide() self.icase_fringe_delta_edit.hide() self.icase_fringe_start_edit.hide() self.icase_fringe_end_edit.hide() self.icase_fringe_delta_button.hide() grid_time.addWidget(self.displacement_label, jrow, 1) grid_time.addWidget(self.fringe_label, jrow, 2) jrow += 1 grid_time.addWidget(self.icase_start, jrow, 0) grid_time.addWidget(self.icase_disp_start_edit, jrow, 1) grid_time.addWidget(self.icase_fringe_start_edit, jrow, 2) #grid_time.addWidget(self.icase_disp_start_button, jrow, 2) jrow += 1 grid_time.addWidget(self.icase_end_label, jrow, 0) grid_time.addWidget(self.icase_disp_end_edit, jrow, 1) grid_time.addWidget(self.icase_fringe_end_edit, jrow, 2) #grid_time.addWidget(self.icase_end_button, jrow, 2) jrow += 1 grid_time.addWidget(self.icase_delta_label, jrow, 0) grid_time.addWidget(self.icase_disp_delta_edit, jrow, 1) grid_time.addWidget(self.icase_fringe_delta_edit, jrow, 2) #grid_time.addWidget(self.icase_delta_button, jrow, 2) jrow += 1 hbox_min = QHBoxLayout() hbox_min.addWidget(self.min_value_enable) hbox_min.addWidget(self.min_value_label) grid_time.addLayout(hbox_min, jrow, 0) grid_time.addWidget(self.min_value_edit, jrow, 1) grid_time.addWidget(self.min_value_button, jrow, 2) jrow += 1 hbox_max = QHBoxLayout() hbox_max.addWidget(self.max_value_enable) hbox_max.addWidget(self.max_value_label) grid_time.addLayout(hbox_max, jrow, 0) grid_time.addWidget(self.max_value_edit, jrow, 1) grid_time.addWidget(self.max_value_button, jrow, 2) jrow += 1 grid_time.addWidget(spacer, jrow, 0) jrow += 1 #-------------- grid_scale = QGridLayout() grid_scale.addWidget(self.animation_profile_label, 0, 0) grid_scale.addWidget(self.animation_profile_edit, 0, 1) #grid_scale.addWidget(self.csv_profile, 1, 0) #grid_scale.addWidget(self.csv_profile_edit, 1, 1) #grid_scale.addWidget(self.csv_profile_browse_button, 1, 2) self.csv_profile = QLabel("CSV profile:") self.csv_profile_edit = QLineEdit() self.csv_profile_button = QPushButton('Browse') #box_time = QVBoxLayout() # TODO: It's super annoying that the animate time box doesn't # line up with the previous box self.box_scale.setLayout(grid_scale) self.box_time.setLayout(grid_time) #---------- grid2 = QGridLayout() irow = 0 #grid2.addWidget(self.animate_scale_radio, 8, 0) #grid2.addWidget(self.animate_phase_radio, 8, 1) #grid2.addWidget(self.animate_time_radio, 8, 2) #grid2.addWidget(self.animate_freq_sweeep_radio, 8, 3) grid2.addWidget(self.animate_in_gui_checkbox, irow, 0) irow += 1 grid2.addWidget(self.resolution_label, irow, 0) grid2.addWidget(self.resolution_edit, irow, 1) grid2.addWidget(self.resolution_button, irow, 2) irow += 1 grid2.addWidget(self.browse_folder_label, irow, 0) grid2.addWidget(self.browse_folder_edit, irow, 1) grid2.addWidget(self.browse_folder_button, irow, 2) irow += 1 grid2.addWidget(self.gif_label, irow, 0) grid2.addWidget(self.gif_edit, irow, 1) grid2.addWidget(self.gif_button, irow, 2) irow += 1 grid2.addWidget(self.make_images_checkbox, irow, 0) #grid2.addWidget(self.overwrite_images_checkbox, irow, 0) grid2.addWidget(self.delete_images_checkbox, irow, 1) grid2.addWidget(self.make_gif_checkbox, irow, 2) irow += 1 grid2.addWidget(self.repeat_checkbox, irow, 0) irow += 1 grid2.addWidget(spacer, irow, 0) grid_hbox = QHBoxLayout() grid_hbox.addWidget(spacer) grid_hbox.addLayout(grid2) grid_hbox.addWidget(spacer) # bottom buttons step_run_box = QHBoxLayout() step_run_box.addWidget(self.step_button) step_run_box.addWidget(self.wipe_button) step_run_box.addWidget(self.stop_button) step_run_box.addWidget(self.run_button) ok_cancel_box = QHBoxLayout() ok_cancel_box.addWidget(self.cancel_button) vbox = QVBoxLayout() vbox.addLayout(grid) vbox.addWidget(self.box_scale) vbox.addWidget(self.box_time) #vbox.addLayout(checkboxes) vbox.addLayout(grid_hbox) vbox.addStretch() vbox.addLayout(step_run_box) vbox.addLayout(ok_cancel_box) self.setLayout(vbox) def on_step(self): """click the Step button""" passed, validate_out = self.on_validate() if passed: try: self._make_gif(validate_out, istep=self.istep) self.istep += 1 except IndexError: self._make_gif(validate_out, istep=0) self.istep += 1 self.wipe_button.setEnabled(True) def on_wipe(self): """click the Wipe button""" passed, validate_out = self.on_validate(wipe=True) if passed: self.istep = 0 self._make_gif(validate_out, istep=self.istep) self.wipe_button.setEnabled(False) self.stop_button.setEnabled(False) def on_stop(self): """click the Stop button""" #passed, validate_out = self.on_validate() #if passed: #self._make_gif(validate_out, stop_animation=True) if self.is_gui: self.win_parent.win_parent.stop_animation() self.wipe_button.setEnabled(True) self.stop_button.setEnabled(False) def on_run(self): """click the Run button""" self.istep = 0 self.wipe_button.setEnabled(False) self.stop_button.setEnabled(True) passed, validate_out = self.on_validate() if passed: self._make_gif(validate_out, istep=None) return passed def _make_gif(self, validate_out, istep=None, stop_animation=False): """interface for making the gif""" (icase_fringe, icase_disp, icase_vector, scale, time, fps, animate_in_gui, magnify, output_dir, gifbase, min_value, max_value) = validate_out fps = int(fps) gif_filename = None if not stop_animation and not animate_in_gui and gifbase is not None: if gifbase.lower().endswith('.gif'): gifbase = gifbase[:-4] gif_filename = os.path.join(output_dir, gifbase + '.gif') animate_fringe = self.checkbox_fringe.isChecked() animate_vector = self.checkbox_vector.isChecked() animate_scale = self.animate_scale_radio.isChecked() animate_phase = self.animate_phase_radio.isChecked() animate_time = self.animate_time_radio.isChecked() if not self.checkbox_vector.isEnabled(): icase_vector = None animate_scale = False animate_phase = False animate_time = False if self._animate_type == 'scale': animate_scale = True elif self._animate_type == 'phase': animate_phase = True elif self._animate_type == 'time': animate_time = True else: raise NotImplementedError(self._animate_type) make_images = self.make_images_checkbox.isChecked() delete_images = self.delete_images_checkbox.isChecked() make_gif = self.make_gif_checkbox.isChecked() animation_profile = str(self.animation_profile_edit.currentText()) icase_disp_start = self.icase_disp_start_edit.value() icase_disp_end = self.icase_disp_end_edit.value() icase_disp_delta = self.icase_disp_delta_edit.value() bool_repeat = self.repeat_checkbox.isChecked( ) # TODO: change this to an integer if bool_repeat: nrepeat = 0 else: nrepeat = 1 #self.out_data['is_shown'] = self.show_radio.isChecked() #icase = self._icase if self.is_gui: self.win_parent.win_parent.make_gif( gif_filename, scale, istep=istep, animate_scale=animate_scale, animate_phase=animate_phase, animate_time=animate_time, icase_fringe=icase_fringe, icase_disp=icase_disp, icase_vector=icase_vector, animate_fringe=animate_fringe, animate_vector=animate_vector, icase_start=icase_disp_start, icase_end=icase_disp_end, icase_delta=icase_disp_delta, time=time, animation_profile=animation_profile, nrepeat=nrepeat, fps=fps, magnify=magnify, make_images=make_images, delete_images=delete_images, make_gif=make_gif, stop_animation=stop_animation, animate_in_gui=animate_in_gui, min_value=min_value, max_value=max_value, ) self.out_data['clicked_ok'] = True self.out_data['close'] = True def get_min_max(self, icase): if self.is_gui: (obj, (i, name)) = self.win_parent.win_parent.result_cases[icase] min_value, max_value = obj.get_min_max(i, name) else: return 0., 1.0 return min_value, max_value def on_validate(self, wipe=False): """checks to see if the input is valid""" # requires no special validation icase_fringe, flag0 = check_int(self.icase_fringe_edit) icase_disp, unused_flaga = check_int(self.icase_disp_edit) icase_vector, unused_flagb = check_int(self.icase_vector_edit) #icase_disp = self._icase_disp #icase_vector = self._icase_vector scale, flag1 = check_float(self.scale_edit) time, flag2 = check_float(self.time_edit) fps, flag3 = check_float(self.fps_edit) min_value = max_value = None flag4 = flag5 = True if self.min_value_edit.isEnabled(): min_value, flag4 = check_float(self.min_value_edit) if self.max_value_edit.isEnabled(): max_value, flag5 = check_float(self.max_value_edit) if wipe: animate_in_gui = False scale = 0. flag1 = True else: animate_in_gui = self.animate_in_gui_checkbox.isChecked() if animate_in_gui or wipe: passed = all([flag0, flag1, flag2, flag3, flag4, flag5]) magnify, output_dir, gifbase = None, None, None else: magnify, flag6 = check_int(self.resolution_edit) output_dir, flag7 = self.check_path(self.browse_folder_edit) gifbase, flag8 = self.check_name(self.gif_edit) passed = all([ flag0, flag1, flag2, flag3, flag4, flag5, flag6, flag7, flag8 ]) return passed, (icase_fringe, icase_disp, icase_vector, scale, time, fps, animate_in_gui, magnify, output_dir, gifbase, min_value, max_value) @staticmethod def check_name(cell): """verifies that the data is string-able""" cell_value = cell.text() try: text = str(cell_value).strip() except UnicodeEncodeError: cell.setStyleSheet("QLineEdit{background: red;}") return None, False if len(text): cell.setStyleSheet("QLineEdit{background: white;}") return text, True else: cell.setStyleSheet("QLineEdit{background: red;}") return None, False def check_path(self, cell): """verifies that the path exists""" text, passed = self.check_name(cell) if not passed: return None, False if os.path.exists(text): cell.setStyleSheet("QLineEdit{background: white;}") return text, True else: cell.setStyleSheet("QLineEdit{background: red;}") return None, False #def on_ok(self): #"""click the OK button""" #passed = self.on_apply() #if passed: #self.win_parent._animation_window_shown = False #self.close() ##self.destroy() def on_cancel(self): """click the Cancel button""" self.on_stop() self.out_data['close'] = True self.close()
class SegmentationWidget(QWidget): def __init__( self, viewer, boundaries_string=BOUNDARIES_STRING, ): super(SegmentationWidget, self).__init__() # general variables self.viewer = viewer # Disable / overwrite napari viewer functions # that either do not make sense or should be avoided by the user disable_napari_btns(self.viewer) disable_napari_key_bindings() # overwrite_napari_roll(self.viewer) # Main layers self.base_layer = [] # Contains registered brain / reference brain self.atlas_layer = [] # Contains annotations / region information # Track variables self.track_layers = [] # Region variables self.label_layers = [] # Atlas variables self.current_atlas_name = "" self.atlas = None self.boundaries_string = boundaries_string self.directory = "" # Set up segmentation methods self.region_seg = RegionSeg(self) self.track_seg = TrackSeg(self) # Generate main layout self.setup_main_layout() if DISPLAY_REGION_INFO: @self.viewer.mouse_move_callbacks.append def display_region_info(v, event): """ Show brain region info on mouse over in status bar on the right """ assert self.viewer == v if len(v.layers) and self.atlas_layer and self.atlas: _, _, _, region_info = structure_from_viewer( self.viewer.status, self.atlas_layer, self.atlas) self.viewer.help = region_info def setup_main_layout(self): """ Construct main layout of widget """ self.layout = QGridLayout() self.layout.setContentsMargins(10, 10, 10, 10) self.layout.setAlignment(QtCore.Qt.AlignTop) self.layout.setSpacing(4) # 3 Steps: # - Loading panel # - Segmentation methods panel # -> Individual segmentation methods (which are invisible at first) # - Saving panel self.add_loading_panel(1) self.add_segmentation_methods_panel(1) self.track_seg.add_track_panel(2) # Track segmentation subpanel self.region_seg.add_region_panel(3) # Region segmentation subpanel self.add_saving_panel(4) # Take care of status label self.status_label = QLabel() self.status_label.setText("Ready") self.layout.addWidget(self.status_label, 5, 0) self.setLayout(self.layout) # PANELS ############################################################### def add_segmentation_methods_panel(self, row, column=1): """ Segmentation methods chooser panel: Toggle visibility of segmentation methods """ self.toggle_methods_panel = QGroupBox("Segmentation") self.toggle_methods_layout = QGridLayout() self.toggle_methods_layout.setContentsMargins(10, 10, 10, 10) self.toggle_methods_layout.setSpacing(5) self.toggle_methods_layout.setAlignment(QtCore.Qt.AlignBottom) self.show_trackseg_button = add_button( "Track tracing", self.toggle_methods_layout, self.track_seg.toggle_track_panel, 0, 1, minimum_width=COLUMN_WIDTH, alignment=SEGM_METHODS_PANEL_ALIGN, ) self.show_trackseg_button.setEnabled(False) self.show_regionseg_button = add_button( "Region segmentation", self.toggle_methods_layout, self.region_seg.toggle_region_panel, 1, 1, minimum_width=COLUMN_WIDTH, alignment=SEGM_METHODS_PANEL_ALIGN, ) self.show_regionseg_button.setEnabled(False) self.toggle_methods_layout.setColumnMinimumWidth(1, COLUMN_WIDTH) self.toggle_methods_panel.setLayout(self.toggle_methods_layout) self.toggle_methods_panel.setVisible(True) self.layout.addWidget(self.toggle_methods_panel, row, column, 1, 1) def add_loading_panel(self, row, column=0): """ Loading panel: - Load project (sample space) - Load project (atlas space) - Atlas chooser """ self.load_data_panel = QGroupBox("Load data") self.load_data_layout = QGridLayout() self.load_data_layout.setSpacing(15) self.load_data_layout.setContentsMargins(10, 10, 10, 10) self.load_data_layout.setAlignment(QtCore.Qt.AlignBottom) self.load_button = add_button( "Load project (sample space)", self.load_data_layout, self.load_brainreg_directory_sample, 0, 0, minimum_width=COLUMN_WIDTH, alignment=LOADING_PANEL_ALIGN, ) self.load_button_standard = add_button( "Load project (atlas space)", self.load_data_layout, self.load_brainreg_directory_standard, 1, 0, minimum_width=COLUMN_WIDTH, alignment=LOADING_PANEL_ALIGN, ) self.add_atlas_menu(self.load_data_layout) self.load_data_layout.setColumnMinimumWidth(0, COLUMN_WIDTH) self.load_data_panel.setLayout(self.load_data_layout) self.load_data_panel.setVisible(True) self.layout.addWidget(self.load_data_panel, row, column, 1, 1) def add_saving_panel(self, row): """ Saving/Export panel """ self.save_data_panel = QGroupBox() self.save_data_layout = QGridLayout() self.export_button = add_button( "To brainrender", self.save_data_layout, self.export_to_brainrender, 0, 0, visibility=False, ) self.save_button = add_button("Save", self.save_data_layout, self.save, 0, 1, visibility=False) self.save_data_layout.setColumnMinimumWidth(1, COLUMN_WIDTH) self.save_data_panel.setLayout(self.save_data_layout) self.layout.addWidget(self.save_data_panel, row, 0, 1, 2) self.save_data_panel.setVisible(False) # ATLAS INTERACTION #################################################### def add_atlas_menu(self, layout): list_of_atlasses = ["Load atlas"] available_atlases = get_available_atlases() for atlas in available_atlases.keys(): atlas_desc = f"{atlas} v{available_atlases[atlas]}" list_of_atlasses.append(atlas_desc) atlas_menu, _ = add_combobox( layout, None, list_of_atlasses, 2, 0, label_stack=True, callback=self.initialise_atlas, width=COLUMN_WIDTH, ) self.atlas_menu = atlas_menu def initialise_atlas(self): atlas_string = self.atlas_menu.currentText() atlas_name = atlas_string.split(" ")[0].strip() if atlas_name != self.current_atlas_name: status = self.remove_layers() if not status: # Something prevented deletion self.reset_atlas_menu() return else: print(f"{atlas_string} already selected for segmentation.") self.reset_atlas_menu() return # Get / set output directory self.set_output_directory() if not self.directory: self.reset_atlas_menu() return self.current_atlas_name = atlas_name # Instantiate atlas layers self.load_atlas() self.directory = self.directory / atlas_name self.paths = Paths(self.directory, atlas_space=True) self.status_label.setText("Ready") # Set window title self.viewer.title = f"Atlas: {self.current_atlas_name}" self.initialise_segmentation_interface() # Check / load previous regions and tracks self.region_seg.check_saved_region() self.track_seg.check_saved_track() self.reset_atlas_menu() def set_output_directory(self): self.status_label.setText("Loading...") options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog self.directory = QFileDialog.getExistingDirectory( self, "Select output directory", options=options, ) if self.directory != "": self.directory = Path(self.directory) def load_atlas(self): atlas = BrainGlobeAtlas(self.current_atlas_name) self.atlas = atlas self.base_layer = self.viewer.add_image( self.atlas.reference, name="Reference", ) self.atlas_layer = self.viewer.add_labels( self.atlas.annotation, name=self.atlas.atlas_name, blending="additive", opacity=0.3, visible=False, ) self.standard_space = True def reset_atlas_menu(self): # Reset menu for atlas - show initial description self.atlas_menu.blockSignals(True) self.atlas_menu.setCurrentIndex(0) self.atlas_menu.blockSignals(False) # BRAINREG INTERACTION ################################################# def load_brainreg_directory_sample(self): self.get_brainreg_directory(standard_space=False) def load_brainreg_directory_standard(self): self.get_brainreg_directory(standard_space=True) def get_brainreg_directory(self, standard_space): """ Shows file dialog to choose output directory and sets global directory info """ if standard_space: self.plugin = "brainreg_standard" self.standard_space = True else: self.plugin = "brainreg" self.standard_space = False self.status_label.setText("Loading...") options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog brainreg_directory = QFileDialog.getExistingDirectory( self, "Select brainreg directory", options=options, ) if not brainreg_directory: return if self.directory != brainreg_directory: status = self.remove_layers() if not status: return # Something prevented deletion self.directory = Path(brainreg_directory) else: print(f"{str(brainreg_directory)} already loaded.") return # Otherwise, proceed loading brainreg dir self.load_brainreg_directory() def load_brainreg_directory(self): """ Opens brainreg folder in napari. Calls initialise_loaded_data to set up layers / info. Then checks for previously loaded data. """ try: self.viewer.open(str(self.directory), plugin=self.plugin) self.paths = Paths( self.directory, standard_space=self.standard_space, ) self.initialise_loaded_data() except ValueError: print(f"The directory ({self.directory}) does not appear to be " f"a brainreg directory, please try again.") return # Check / load previous regions and tracks self.region_seg.check_saved_region() self.track_seg.check_saved_track() def initialise_loaded_data(self): """ Set up brainreg layers in napari / fill with new data and info """ try: self.viewer.layers.remove(self.boundaries_string) except KeyError: pass self.base_layer = self.viewer.layers["Registered image"] self.metadata = self.base_layer.metadata self.atlas = self.metadata["atlas_class"] self.atlas_layer = self.viewer.layers[self.metadata["atlas"]] self.initialise_segmentation_interface() # Set window title self.viewer.title = ( f"Brainreg: {self.metadata['atlas']} ({self.plugin})") self.status_label.setText("Ready") # MORE LAYOUT COMPONENTS ########################################### def initialise_segmentation_interface(self): self.reset_variables() self.initialise_image_view() self.save_data_panel.setVisible(True) self.save_button.setVisible(True) self.export_button.setVisible(self.standard_space) self.show_regionseg_button.setEnabled(True) self.show_trackseg_button.setEnabled(True) self.status_label.setText("Ready") def initialise_image_view(self): self.set_z_position() def set_z_position(self): midpoint = int(round(len(self.base_layer.data) / 2)) self.viewer.dims.set_point(0, midpoint) def reset_variables(self): """ Reset atlas scale dependent variables - point_size (Track segmentation) - spline_size (Track segmentation) - brush_size (Region segmentation) """ self.mean_voxel_size = int( np.sum(self.atlas.resolution) / len(self.atlas.resolution)) self.track_seg.point_size = (self.track_seg.point_size_default / self.mean_voxel_size) self.track_seg.spline_size = (self.track_seg.spline_size_default / self.mean_voxel_size) self.region_seg.brush_size = (self.region_seg.brush_size_default / self.mean_voxel_size) return def display_delete_warning(self): """ Display a warning in a pop up that informs about deletion of all annotation layers """ message_reply = QMessageBox.question( self, "About to remove layers", "All layers are about to be deleted. Proceed?", QMessageBox.Yes | QMessageBox.Cancel, ) if message_reply == QMessageBox.Yes: return True else: return False def remove_layers(self): """ TODO: This needs work. Runs into an error currently when switching from a annotated project to another one """ if len(self.viewer.layers) != 0: # Check with user if that is really what is wanted if self.track_layers or self.label_layers: choice = self.display_delete_warning() if not choice: print('Preventing deletion because user chose "Cancel"') return False # Remove old layers for layer in list(self.viewer.layers): try: self.viewer.layers.remove(layer) except IndexError: # no idea why this happens pass # There seems to be a napari bug trying to access previously used slider # values. Trying to circument for now self.viewer.window.qt_viewer.dims._last_used = None self.track_layers = [] self.label_layers = [] return True def save(self): if self.label_layers or self.track_layers: print("Saving") worker = save_all( self.paths.regions_directory, self.paths.tracks_directory, self.label_layers, self.track_layers, track_file_extension=TRACK_FILE_EXT, ) worker.start() def export_to_brainrender(self): print("Exporting") max_axis_2 = self.base_layer.shape[2] worker = export_all( self.paths.regions_directory, self.paths.tracks_directory, self.label_layers, self.track_seg.splines, self.track_seg.spline_names, self.atlas.resolution[0], max_axis_2, ) worker.start()
class TrackSeg(QGroupBox): """ Track segmentation method panel """ def __init__( self, parent, point_size=POINT_SIZE, spline_size=SPLINE_SIZE, track_file_extension=TRACK_FILE_EXT, spline_points_default=SPLINE_POINTS_DEFAULT, spline_smoothing_default=SPLINE_SMOOTHING_DEFAULT, fit_degree_default=FIT_DEGREE_DEFAULT, summarise_track_default=SUMMARISE_TRACK_DEFAULT, ): super(TrackSeg, self).__init__() self.parent = parent self.tree = None self.summarise_track_default = summarise_track_default # Point / Spline fitting settings self.point_size_default = POINT_SIZE # Keep track of default self.point_size = point_size # Initialise self.spline_points_default = spline_points_default self.spline_size_default = SPLINE_SIZE # Keep track of default self.spline_size = spline_size # Initialise self.spline_smoothing_default = spline_smoothing_default self.fit_degree_default = fit_degree_default # File formats self.track_file_extension = track_file_extension # Initialise spline and spline names self.splines = None self.spline_names = None def add_track_panel(self, row): self.track_panel = QGroupBox("Track tracing") track_layout = QGridLayout() add_button( "Add surface points", track_layout, self.add_surface_points, 5, 1, ) add_button( "Add track", track_layout, self.add_track, 6, 0, ) add_button( "Trace tracks", track_layout, self.run_track_analysis, 6, 1, ) self.summarise_track_checkbox = add_checkbox( track_layout, self.summarise_track_default, "Summarise", 0, ) self.fit_degree = add_int_box( track_layout, self.fit_degree_default, 1, 5, "Fit degree", 1, ) self.spline_smoothing = add_float_box( track_layout, self.spline_smoothing_default, 0, 1, "Spline smoothing", 0.1, 2, ) self.spline_points = add_int_box( track_layout, self.spline_points_default, 1, 10000, "Spline points", 3, ) track_layout.setColumnMinimumWidth(1, COLUMN_WIDTH) self.track_panel.setLayout(track_layout) self.parent.layout.addWidget(self.track_panel, row, 0, 1, 2) self.track_panel.setVisible(False) def toggle_track_panel(self): # TODO: Change color scheme directly when theme is switched # TODO: "text-align" property should follow constant SEGM_METHODS_PANEL_ALIGN if self.track_panel.isVisible(): self.track_panel.setVisible(False) if self.parent.viewer.theme == "dark": self.parent.show_trackseg_button.setStyleSheet( f"QPushButton {{ background-color: #414851; text-align:{SEGM_METHODS_PANEL_ALIGN};}}" f"QPushButton:pressed {{ background-color: #414851; text-align:{SEGM_METHODS_PANEL_ALIGN};}}" ) else: self.parent.show_trackseg_button.setStyleSheet( f"QPushButton {{ background-color: #d6d0ce; text-align:{SEGM_METHODS_PANEL_ALIGN};}}" f"QPushButton:pressed {{ background-color: #d6d0ce; text-align:{SEGM_METHODS_PANEL_ALIGN};}}" ) else: self.track_panel.setVisible(True) if self.parent.viewer.theme == "dark": self.parent.show_trackseg_button.setStyleSheet( f"QPushButton {{ background-color: #7e868f; text-align:{SEGM_METHODS_PANEL_ALIGN};}}" f"QPushButton:pressed {{ background-color: #7e868f; text-align:{SEGM_METHODS_PANEL_ALIGN};}}" ) else: self.parent.show_trackseg_button.setStyleSheet( f"QPushButton {{ background-color: #fdf194; text-align:{SEGM_METHODS_PANEL_ALIGN};}}" f"QPushButton:pressed {{ background-color: #fdf194; text-align:{SEGM_METHODS_PANEL_ALIGN};}}" ) def check_saved_track(self): track_files = glob( str(self.parent.paths.tracks_directory) + "/*" + self.track_file_extension) if self.parent.paths.tracks_directory.exists() and track_files != []: for track_file in track_files: self.parent.track_layers.append( add_existing_track_layers( self.parent.viewer, track_file, self.point_size, )) def add_track(self): print("Adding a new track\n") self.splines = None self.spline_names = None self.track_panel.setVisible(True) # Should be visible by default! add_new_track_layer( self.parent.viewer, self.parent.track_layers, self.point_size, ) def add_surface_points(self): if self.parent.track_layers: print("Adding surface points (this may take a while)") if self.tree is None: self.create_brain_surface_tree() for track_layer in self.parent.track_layers: try: _, index = self.tree.query(track_layer.data[0]) except IndexError: print( f"{track_layer.name} does not appear to hold any data") continue surface_point = self.tree.data[index] track_layer.data = np.vstack((surface_point, track_layer.data)) print("Finished!\n") else: print("No tracks found.") def create_brain_surface_tree(self): self.tree = create_KDTree_from_image(self.parent.atlas_layer.data) def run_track_analysis(self, override=False): if self.parent.track_layers: if not override: choice = display_warning( self.parent, "About to analyse tracks", "Existing files will be will be deleted. Proceed?", ) else: choice = True # for debugging if choice: print("Running track analysis") self.splines, self.spline_names = track_analysis( self.parent.viewer, self.parent.atlas, self.parent.paths.tracks_directory, self.parent.track_layers, self.spline_size, spline_points=self.spline_points.value(), fit_degree=self.fit_degree.value(), spline_smoothing=self.spline_smoothing.value(), summarise_track=self.summarise_track_checkbox.isChecked(), ) print("Finished!\n") else: print("Preventing analysis as user chose 'Cancel'") else: print("No tracks found.")
class MainLauncher(SiriusMainWindow): """Main Launcher.""" showMonitor = Signal() showStatus = Signal() showEgun = Signal() def __init__(self, parent=None, prefix=VACA_PREFIX): """Init.""" super().__init__(parent) self._prefix = prefix # window settings self.setObjectName('ASApp') self.setWindowTitle('Sirius Launcher') self.setWindowIcon( qta.icon('mdi.rocket', color=get_appropriate_color('AS'))) screens = QApplication.screens() screen_idx = 3 if len(screens) == 8 else 0 topleft = screens[screen_idx].geometry().topLeft() self.move(topleft.x(), topleft.y() + 20) self.setWindowFlags(Qt.WindowStaysOnTopHint) # menubar menubar = get_object(ismenubar=True) menubar.setNativeMenuBar(False) self.setMenuBar(menubar) self._setupUi() # connect window signals connect_newprocess(self, 'sirius-hla-as-ap-monitor.py', parent=self, signal=self.showMonitor) connect_newprocess(self, 'sirius-hla-si-ap-genstatus.py', parent=self, signal=self.showStatus) connect_newprocess(self, 'sirius-hla-li-eg-control.py', parent=self, signal=self.showEgun) # set focus policy self.setFocus(True) self.setFocusPolicy(Qt.StrongFocus) def _setupUi(self): # Machine Shift self.wid_shift = QGroupBox('Machine Shift') machshift_pvname = SiriusPVName( 'AS-Glob:AP-MachShift:Mode-Sel').substitute(prefix=self._prefix) cbox_shift_mode = SiriusEnumComboBox(self, machshift_pvname) label_shift_mode = MachShiftLabel(self, self._prefix) label_shift_mode.label.setStyleSheet( 'QLabel{max-height: 2em; min-width: 7em;}') lay_shift = QGridLayout(self.wid_shift) lay_shift.setVerticalSpacing(5) lay_shift.setAlignment(Qt.AlignCenter) lay_shift.addWidget(cbox_shift_mode, 1, 0) lay_shift.addWidget(label_shift_mode, 2, 0) # Injection System self.wid_injsys = QGroupBox('Inj.System') wid_injsys = InjSysStbyControlWidget(self, self._prefix, is_summary=True) lay_injsys = QGridLayout(self.wid_injsys) lay_injsys.setAlignment(Qt.AlignCenter) lay_injsys.setContentsMargins(0, 0, 0, 0) lay_injsys.addWidget(wid_injsys) # Egun triggers self.wid_egun = QGroupBox('Egun Trig.') self.wid_egun.setStyleSheet('min-width: 5em;') egun_dev = SiriusPVName('LI-01:EG-TriggerPS').substitute( prefix=self._prefix) but_egun_trigger = PyDMStateButton( self, egun_dev.substitute(propty_name='enable')) led_egun_trigger = SiriusLedState( self, egun_dev.substitute(propty_name='enablereal')) lay_egun = QGridLayout(self.wid_egun) lay_egun.setVerticalSpacing(5) lay_egun.addWidget(but_egun_trigger, 1, 0) lay_egun.addWidget(led_egun_trigger, 2, 0) # injection control injctrl_dev = SiriusPVName('AS-Glob:AP-InjCtrl') injctrl_dev = injctrl_dev.substitute(prefix=self._prefix) self.wid_inject = QGroupBox('Injection') self.ch_injmode = SiriusConnectionSignal( injctrl_dev.substitute(propty='Mode-Sts')) self.ch_injmode.new_value_signal[int].connect( self._handle_injmode_settings_vis) # # Settings label_sett = QLabel('<h4>Sett.</h4>', self, alignment=Qt.AlignCenter) led_sett = InjDiagLed(self) self.wid_injsett = QWidget() lay_injsett = QGridLayout(self.wid_injsett) lay_injsett.setContentsMargins(0, 0, 0, 0) lay_injsett.setAlignment(Qt.AlignTop) lay_injsett.addWidget(label_sett, 0, 0) lay_injsett.addWidget(led_sett, 1, 0) # # Auto Stop self.label_injauto = QLabel('<h4>AutoStop</h4>', self, alignment=Qt.AlignCenter) self.but_injauto = PyDMStateButton( self, injctrl_dev.substitute(propty='AutoStop-Sel')) self.led_injauto = SiriusLedState( self, injctrl_dev.substitute(propty='AutoStop-Sts')) self.wid_injauto = QWidget() self.wid_injauto.setObjectName('wid') self.wid_injauto.setStyleSheet("#wid{min-width: 8em; max-width: 8em;}") lay_injauto = QGridLayout(self.wid_injauto) lay_injauto.setContentsMargins(0, 0, 0, 0) lay_injauto.setAlignment(Qt.AlignTop) lay_injauto.addWidget(self.label_injauto, 0, 0) lay_injauto.addWidget(self.but_injauto, 1, 0) lay_injauto.addWidget(self.led_injauto, 2, 0) # # Top-up status label_tusts = QLabel('<h4>Status</h4>', self, alignment=Qt.AlignCenter) label_tunow = ClockLabel(self) label_tunow.setStyleSheet('max-height:2em;') label_tunxt = SiriusLabel( self, injctrl_dev.substitute(propty='TopUpNextInj-Mon')) label_tunxt.displayFormat = SiriusLabel.DisplayFormat.Time label_tunxt.setAlignment(Qt.AlignCenter) label_tunxt.setStyleSheet('max-height:2em;') but_round = PyDMPushButton( self, '', qta.icon('mdi.tilde'), 1, False, injctrl_dev.substitute(propty='TopUpNextInjRound-Cmd')) but_round.setObjectName('but') but_round.setStyleSheet( '#but{min-width:18px; max-width:18px; icon-size:16px;}') self.wid_tusts = QWidget() self.wid_tusts.setObjectName('wid') self.wid_tusts.setStyleSheet("#wid{min-width: 8em; max-width: 8em;}") lay_tusts = QGridLayout(self.wid_tusts) lay_tusts.setContentsMargins(0, 0, 0, 0) lay_tusts.setAlignment(Qt.AlignTop) lay_tusts.addWidget(label_tusts, 0, 0, 1, 2) lay_tusts.addWidget(QLabel('Now:', self), 1, 0) lay_tusts.addWidget(label_tunow, 1, 1) lay_tusts.addWidget(QLabel('Next:', self), 2, 0) lay_tusts.addWidget(label_tunxt, 2, 1) lay_tusts.addWidget(but_round, 2, 2) self.wid_tusts.setVisible(False) # # Control label_inj = QLabel('<h4>Control</h4>', self, alignment=Qt.AlignCenter) self.but_tiinj = EVGInjectionButton(self, self._prefix) self.but_topup = PyDMStateButton( self, injctrl_dev.substitute(propty='TopUpState-Sel')) self.but_topup.setVisible(False) lay_inject_sel = QGridLayout() lay_inject_sel.setAlignment(Qt.AlignCenter) lay_inject_sel.addWidget(self.but_tiinj, 0, 0) lay_inject_sel.addWidget(self.but_topup, 0, 0) led_injsts = EVGInjectionLed(self, self._prefix) label_injcnt = PyDMLabel(self) label_injcnt.setToolTip( 'Count injection pulses when Egun Trigger is enabled.') label_injcnt.channel = SiriusPVName( 'AS-Glob:AP-CurrInfo:InjCount-Mon').substitute(prefix=self._prefix) label_injcnt.setStyleSheet('QLabel{max-width: 3.5em;}') lay_inject_sts = QHBoxLayout() lay_inject_sts.setContentsMargins(0, 0, 0, 0) lay_inject_sts.setAlignment(Qt.AlignTop) lay_inject_sts.addWidget(led_injsts) lay_inject_sts.addWidget(label_injcnt) self.wid_injctrl = QWidget() lay_injctrl = QGridLayout(self.wid_injctrl) lay_injctrl.setContentsMargins(0, 0, 0, 0) lay_injctrl.setAlignment(Qt.AlignTop) lay_injctrl.addWidget(label_inj, 0, 2, alignment=Qt.AlignCenter) lay_injctrl.addLayout(lay_inject_sel, 1, 2, alignment=Qt.AlignCenter) lay_injctrl.addLayout(lay_inject_sts, 2, 2, alignment=Qt.AlignCenter) # # Injection Auxiliary section self.wid_injlog = QGroupBox('Injection Log') label_injlog = PyDMLogLabel(self, injctrl_dev.substitute(propty='Log-Mon'), replace=[ 'Remaining time', ]) lay_injlog = QGridLayout(self.wid_injlog) lay_injlog.addWidget(label_injlog, 0, 0) self.wid_injlog.setVisible(False) self.wid_mon = MonitorSummaryWidget(self, self._prefix) self.wid_mon.setVisible(False) # # Target Current self._ld_currtgt = QLabel('<h4>Target Curr.</h4>', self, alignment=Qt.AlignCenter) self._sb_currtgt = SiriusSpinbox( self, injctrl_dev.substitute(propty='TargetCurrent-SP')) self._sb_currtgt.showStepExponent = False self._lb_currtgt = PyDMLabel( self, injctrl_dev.substitute(propty='TargetCurrent-RB')) self._lb_currtgt.showUnits = True self._wid_tcurr = QWidget() lay_tcurr = QGridLayout(self._wid_tcurr) lay_tcurr.setContentsMargins(0, 0, 0, 0) lay_tcurr.setAlignment(Qt.AlignTop) lay_tcurr.addWidget(self._ld_currtgt, 0, 0) lay_tcurr.addWidget(self._sb_currtgt, 1, 0) lay_tcurr.addWidget(self._lb_currtgt, 2, 0) # # Bucket List self._bucket_list = BucketList(self, prefix=self._prefix, min_size=15) self.wid_fill = QWidget() lay_fill = QGridLayout(self.wid_fill) lay_fill.setContentsMargins(0, 0, 0, 0) lay_fill.setHorizontalSpacing(9) lay_fill.addWidget(self._wid_tcurr, 0, 0) lay_fill.addWidget(self._bucket_list, 0, 1) self.wid_fill.setVisible(False) pbt_aux = QPushButton('v', self) pbt_aux.setToolTip('Show Injection Auxiliary section.') pbt_aux.clicked.connect(self._toggle_show_injaux) pbt_aux.setStyleSheet('QPushButton{max-width: 0.8em;}') pbt_bl = QPushButton('>', self) pbt_bl.setToolTip('Show bucket list and target current controls.') pbt_bl.clicked.connect(self._toggle_show_bucketlist) pbt_bl.setStyleSheet('QPushButton{max-width: 0.8em;}') lay_inj = QGridLayout(self.wid_inject) lay_inj.setAlignment(Qt.AlignTop) lay_inj.setVerticalSpacing(5) lay_inj.setHorizontalSpacing(12) lay_inj.addWidget(self.wid_injsett, 0, 0, 2, 1) lay_inj.addWidget(self.wid_injauto, 0, 1, 2, 1) lay_inj.addWidget(self.wid_tusts, 0, 1, 2, 1) lay_inj.addWidget(self.wid_injctrl, 0, 2, 2, 1) lay_inj.addWidget(pbt_bl, 0, 3, alignment=Qt.AlignTop) lay_inj.addWidget(pbt_aux, 1, 3, alignment=Qt.AlignBottom) lay_inj.addWidget(self.wid_fill, 0, 4, 2, 1) # Current curr_pvname = SiriusPVName( 'SI-Glob:AP-CurrInfo:Current-Mon').substitute(prefix=self._prefix) self.label_curr = PyDMLabel(self, curr_pvname) self.label_curr.showUnits = True self.label_curr.setStyleSheet(""" QLabel{ font-size: 18pt; qproperty-alignment: AlignCenter; min-width: 6em; max-width: 6em; }""") self.wid_curr = QGroupBox('Current') lay_curr = QHBoxLayout(self.wid_curr) lay_curr.addWidget(self.label_curr) # RF Kill Beam self.wid_rfkill = QGroupBox('RF Kill Beam') self.wid_rfkill.setObjectName('RFKillBeam') rfkill_bt = RFKillBeamButton(self, self._prefix) rfkill_lay = QGridLayout(self.wid_rfkill) rfkill_lay.addWidget(rfkill_bt) # menu buttons self.wid_pbt = QPushButton('v', self) self.wid_pbt.clicked.connect(self._toggle_show_menubutton) self.wid_pbt.setStyleSheet('QPushButton{max-width: 0.8em;}') self._menubutton = get_object(ismenubar=False, parent=self) self._menubutton.layout().setContentsMargins(0, 0, 0, 0) self._menubutton.layout().setSpacing(4) self._menubutton.setVisible(False) hlay1 = QHBoxLayout() hlay1.setContentsMargins(0, 0, 0, 0) hlay1.addWidget(self.wid_shift) hlay1.addWidget(self.wid_injsys) hlay1.addWidget(self.wid_egun) hlay1.addWidget(self.wid_inject) hlay1.addWidget(self.wid_curr) hlay1.addWidget(self.wid_rfkill) hlay2 = QHBoxLayout() hlay2.setContentsMargins(0, 0, 0, 0) hlay2.addWidget(self.wid_injlog) hlay2.addWidget(self.wid_mon) cwid = QWidget(self) lay = QGridLayout(cwid) lay.addLayout(hlay1, 0, 0) lay.addLayout(hlay2, 1, 0) lay.addWidget(self._menubutton, 2, 0, 1, 2) lay.addWidget(self.wid_pbt, 0, 1, 2, 1, alignment=Qt.AlignRight | Qt.AlignBottom) self.setCentralWidget(cwid) def _toggle_show_menubutton(self): self._menubutton.setVisible(self._menubutton.isHidden()) text = 'v' if self._menubutton.isHidden() else '^' self.sender().setText(text) self.centralWidget().adjustSize() self.adjustSize() def _toggle_show_bucketlist(self): show = self.wid_fill.isHidden() self.wid_fill.setVisible(show) text = '<' if show else '>' tooltip = ('Hide' if show else 'Show') + ' bucket list controls.' self.sender().setText(text) self.sender().setToolTip(tooltip) self.sender().parent().adjustSize() self.centralWidget().adjustSize() self.adjustSize() def _toggle_show_injaux(self): show = self.wid_mon.isHidden() self.wid_mon.setVisible(show) self.wid_injlog.setVisible(show) text = '^' if show else 'v' tooltip = ('Hide' if show else 'Show') + ' Injection Auxiliary section.' self.sender().setText(text) self.sender().setToolTip(tooltip) self.sender().parent().adjustSize() self.centralWidget().adjustSize() self.adjustSize() @Slot(int) def _handle_injmode_settings_vis(self, new_mode): is_topup = new_mode == _InjConst.InjMode.TopUp self.label_injauto.setVisible(not is_topup) self.but_injauto.setVisible(not is_topup) self.led_injauto.setVisible(not is_topup) self.wid_tusts.setVisible(is_topup) self.but_tiinj.setVisible(not is_topup) self.but_topup.setVisible(is_topup) def mouseDoubleClickEvent(self, event): """Implement mouseDoubleClickEvent.""" if event.button() == Qt.LeftButton: if self.wid_curr.underMouse(): self.showStatus.emit() elif self.wid_shift.underMouse(): self.showStatus.emit() elif self.wid_egun.underMouse(): self.showEgun.emit() elif self.wid_mon.underMouse(): self.showMonitor.emit() super().mouseDoubleClickEvent(event) def changeEvent(self, event): """Implement changeEvent.""" if event.type() == QEvent.FontChange: fontsize = self.app.font().pointSize() self.label_curr.setStyleSheet( 'QLabel{' ' font-size: ' + str(fontsize + 8) + 'pt;' ' qproperty-alignment: AlignCenter;' ' min-width: 6em; max-width: 6em;' '}') self.ensurePolished()
def _setupMeasSettingsWidget(self, mode): if mode == 'Normal': prefix = self.dcct_prefix visible = True elif mode == 'Fast': prefix = self.dcct_prefix.substitute(propty_name=mode) visible = False gbox_modesettings = QGroupBox(mode + ' Measurement Mode Settings', self) l_smpcnt = QLabel('Sample Count: ', self) spinbox_SampleCnt = PyDMSpinbox( self, prefix.substitute(propty=prefix.propty_name + 'SampleCnt-SP')) spinbox_SampleCnt.showStepExponent = False label_SampleCnt = PyDMLabel( self, prefix.substitute(propty=prefix.propty_name + 'SampleCnt-RB')) hlay_smpcnt = QHBoxLayout() hlay_smpcnt.addWidget(spinbox_SampleCnt) hlay_smpcnt.addWidget(label_SampleCnt) l_measperiod = QLabel('Period [s]: ', self) spinbox_MeasPeriod = PyDMSpinbox( self, prefix.substitute(propty=prefix.propty_name + 'MeasPeriod-SP')) spinbox_MeasPeriod.showStepExponent = False label_MeasPeriod = PyDMLabel( self, prefix.substitute(propty=prefix.propty_name + 'MeasPeriod-RB')) hlay_measperiod = QHBoxLayout() hlay_measperiod.addWidget(spinbox_MeasPeriod) hlay_measperiod.addWidget(label_MeasPeriod) l_measupdateperiod = QLabel('Measured Period [s]: ', self) label_MeasUpdatePeriod = PyDMLabel( self, self.dcct_prefix.substitute(propty='MeasUpdatePeriod-Mon')) l_imped = QLabel('Impedance: ', self) enumcombobox_Imped = PyDMEnumComboBox( self, prefix.substitute(propty=prefix.propty_name + 'Imped-Sel')) label_Imped = PyDMLabel( self, prefix.substitute(propty=prefix.propty_name + 'Imped-Sts')) hlay_imped = QHBoxLayout() hlay_imped.addWidget(enumcombobox_Imped) hlay_imped.addWidget(label_Imped) l_offset = QLabel('Relative Offset Enable: ', self) statebutton_RelEnbl = PyDMStateButton( self, prefix.substitute(propty=prefix.propty_name + 'RelEnbl-Sel')) statebutton_RelEnbl.shape = 1 statebutton_RelEnbl.setStyleSheet('min-width:6em; max-width:6em;') label_RelEnbl = PyDMLabel( self, prefix.substitute(propty=prefix.propty_name + 'RelEnbl-Sts')) hlay_offset = QHBoxLayout() hlay_offset.addWidget(statebutton_RelEnbl) hlay_offset.addWidget(label_RelEnbl) l_rellvl = QLabel('Relative Offset Level [V]: ', self) spinbox_RelLvl = PyDMSpinbox( self, prefix.substitute(propty=prefix.propty_name + 'RelLvl-SP')) spinbox_RelLvl.showStepExponent = False label_RelLvl = PyDMLabel( self, prefix.substitute(propty=prefix.propty_name + 'RelLvl-RB')) label_RelLvl.precisionFromPV = False label_RelLvl.precision = 9 pushbutton_RelAcq = PyDMPushButton( parent=self, label='Acquire Offset', pressValue=1, init_channel=prefix.substitute(propty=prefix.propty_name + 'RelAcq-Cmd')) pushbutton_RelAcq.setAutoDefault(False) pushbutton_RelAcq.setDefault(False) hlay_rellvl = QHBoxLayout() hlay_rellvl.addWidget(spinbox_RelLvl) hlay_rellvl.addWidget(label_RelLvl) hlay_rellvl.addWidget(pushbutton_RelAcq) flay_modesettings = QFormLayout() flay_modesettings.setLabelAlignment(Qt.AlignRight) flay_modesettings.setFormAlignment(Qt.AlignHCenter) flay_modesettings.addRow(l_smpcnt, hlay_smpcnt) flay_modesettings.addRow(l_measperiod, hlay_measperiod) flay_modesettings.addRow(l_measupdateperiod, label_MeasUpdatePeriod) flay_modesettings.addRow(l_imped, hlay_imped) flay_modesettings.addRow(l_offset, hlay_offset) flay_modesettings.addRow(l_rellvl, hlay_rellvl) if mode == 'Normal': l_linesync = QLabel('Line Synchronization: ', self) statebutton_LineSync = PyDMStateButton( self, prefix.substitute(propty=prefix.propty_name + 'LineSync-Sel')) statebutton_LineSync.shape = 1 statebutton_LineSync.setStyleSheet('min-width:6em; max-width:6em;') label_LineSync = PyDMLabel( self, prefix.substitute(propty=prefix.propty_name + 'LineSync-Sts')) hlay_linesync = QHBoxLayout() hlay_linesync.addWidget(statebutton_LineSync) hlay_linesync.addWidget(label_LineSync) label_avg = QLabel('<h4>Average Filter</h4>', self) l_avgenbl = QLabel('Enable: ', self) statebutton_AvgFilterEnbl = PyDMStateButton( self, prefix.substitute(propty=prefix.propty_name + 'AvgFilterEnbl-Sel')) statebutton_AvgFilterEnbl.shape = 1 label_AvgFilterEnbl = PyDMLabel( self, prefix.substitute(propty=prefix.propty_name + 'AvgFilterEnbl-Sts')) hlay_avgenbl = QHBoxLayout() hlay_avgenbl.addWidget(statebutton_AvgFilterEnbl) hlay_avgenbl.addWidget(label_AvgFilterEnbl) l_avgcnt = QLabel('Samples: ', self) spinbox_AvgFilterCount = PyDMSpinbox( self, prefix.substitute(propty=prefix.propty_name + 'AvgFilterCnt-SP')) spinbox_AvgFilterCount.showStepExponent = False label_AvgFilterCount = PyDMLabel( self, prefix.substitute(propty=prefix.propty_name + 'AvgFilterCnt-RB')) hlay_avgcnt = QHBoxLayout() hlay_avgcnt.addWidget(spinbox_AvgFilterCount) hlay_avgcnt.addWidget(label_AvgFilterCount) l_avgtyp = QLabel('Type: ', self) enumcombobox_AvgFilterTyp = PyDMEnumComboBox( self, self.dcct_prefix.substitute(propty='AvgFilterTyp-Sel')) label_AvgFilterTyp = PyDMLabel( self, self.dcct_prefix.substitute(propty='AvgFilterTyp-Sts')) hlay_avgtyp = QHBoxLayout() hlay_avgtyp.addWidget(enumcombobox_AvgFilterTyp) hlay_avgtyp.addWidget(label_AvgFilterTyp) l_avgwin = QLabel('Noise window size [%]: ', self) spinbox_AvgFilterWind = PyDMSpinbox( self, prefix.substitute(propty=prefix.propty_name + 'AvgFilterWind-SP')) spinbox_AvgFilterWind.showStepExponent = False label_AvgFilterWind = PyDMLabel( self, prefix.substitute(propty=prefix.propty_name + 'AvgFilterWind-RB')) hlay_avgwin = QHBoxLayout() hlay_avgwin.addWidget(spinbox_AvgFilterWind) hlay_avgwin.addWidget(label_AvgFilterWind) flay_modesettings.addRow(l_linesync, hlay_linesync) flay_modesettings.addRow(QLabel('')) flay_modesettings.addRow(label_avg) flay_modesettings.addRow(l_avgenbl, hlay_avgenbl) flay_modesettings.addRow(l_avgcnt, hlay_avgcnt) flay_modesettings.addRow(l_avgtyp, hlay_avgtyp) flay_modesettings.addRow(l_avgwin, hlay_avgwin) gbox_modesettings.setLayout(flay_modesettings) gbox_modesettings.setVisible(visible) gbox_modesettings.setStyleSheet(""" PyDMSpinbox, PyDMLabel{ min-width:6em; max-width:6em; qproperty-alignment: AlignCenter;} PyDMLedMultiChannel, PyDMStateButton, PyDMEnumComboBox{ min-width:6em; max-width:6em;}""") return gbox_modesettings
class General(QWidget): def __init__( self, viewer, point_size=30, spline_size=10, track_file_extension=".h5", image_file_extension=".nii", x_scaling=10, y_scaling=10, z_scaling=10, num_colors=10, brush_size=30, spline_points_default=1000, spline_smoothing_default=0.1, fit_degree_default=3, summarise_track_default=True, add_surface_point_default=False, calculate_volumes_default=True, summarise_volumes_default=True, region_alpha_default=0.8, structure_alpha_default=0.8, shading_default="flat", region_to_add_default="", vtkplotter_shading_types=["flat", "giroud", "phong"], ): super(General, self).__init__() self.point_size = point_size self.spline_size = spline_size # general variables self.viewer = viewer self.x_scaling = x_scaling self.y_scaling = y_scaling self.z_scaling = z_scaling # track variables self.track_layers = [] self.track_file_extension = track_file_extension self.spline_points_default = spline_points_default self.spline_smoothing_default = spline_smoothing_default self.summarise_track_default = summarise_track_default self.add_surface_point_default = add_surface_point_default self.fit_degree_default = fit_degree_default self.napari_point_size = int(BRAINRENDER_TO_NAPARI_SCALE * self.point_size) self.napari_spline_size = int(BRAINRENDER_TO_NAPARI_SCALE * self.spline_size) # region variables self.label_layers = [] self.image_file_extension = image_file_extension self.brush_size = brush_size self.num_colors = num_colors self.calculate_volumes_default = calculate_volumes_default self.summarise_volumes_default = summarise_volumes_default # atlas variables self.region_labels = [] # brainrender variables self.region_alpha_default = region_alpha_default self.structure_alpha_default = structure_alpha_default self.shading_default = shading_default self.region_to_add_default = region_to_add_default self.vtkplotter_shading_types = vtkplotter_shading_types self.setup_layout() def setup_layout(self): self.instantiated = False layout = QGridLayout() self.load_button = add_button( "Load project", layout, self.load_amap_directory, 0, 0, minimum_width=200, ) self.load_atlas_button = add_button("Load atlas", layout, self.load_atlas, 0, 1, visibility=False) self.save_button = add_button("Save", layout, self.save, 7, 1, visibility=False) self.status_label = QLabel() self.status_label.setText("Ready") layout.addWidget(self.status_label, 8, 0) layout.setAlignment(QtCore.Qt.AlignTop) layout.setSpacing(4) self.setLayout(layout) self.add_track_panel(layout) self.add_region_panel(layout) self.add_brainrender_panel(layout) self.setLayout(layout) def add_track_panel(self, layout): self.track_panel = QGroupBox("Track tracing") track_layout = QGridLayout() add_button( "Add track", track_layout, self.add_track, 5, 0, ) add_button( "Trace tracks", track_layout, self.run_track_analysis, 5, 1, ) self.summarise_track_checkbox = add_checkbox( track_layout, self.summarise_track_default, "Summarise", 0, ) self.add_surface_point_checkbox = add_checkbox( track_layout, self.add_surface_point_default, "Add surface point", 1, ) self.fit_degree = add_int_box( track_layout, self.fit_degree_default, 1, 5, "Fit degree", 2, ) self.spline_smoothing = add_float_box( track_layout, self.spline_smoothing_default, 0, 1, "Spline smoothing", 0.1, 3, ) self.spline_points = add_int_box( track_layout, self.spline_points_default, 1, 10000, "Spline points", 4, ) track_layout.setColumnMinimumWidth(1, 150) self.track_panel.setLayout(track_layout) layout.addWidget(self.track_panel, 3, 0, 1, 2) layout.setAlignment(QtCore.Qt.AlignTop) layout.setSpacing(4) self.track_panel.setVisible(False) def add_region_panel(self, layout): self.region_panel = QGroupBox("Region analysis") region_layout = QGridLayout() add_button( "Add region", region_layout, self.add_new_region, 2, 0, ) add_button( "Analyse regions", region_layout, self.run_region_analysis, 2, 1, ) self.calculate_volumes_checkbox = add_checkbox( region_layout, self.calculate_volumes_default, "Calculate volumes", 0, ) self.summarise_volumes_checkbox = add_checkbox( region_layout, self.summarise_volumes_default, "Summarise volumes", 1, ) region_layout.setColumnMinimumWidth(1, 150) self.region_panel.setLayout(region_layout) layout.addWidget(self.region_panel, 5, 0, 1, 2) layout.setAlignment(QtCore.Qt.AlignTop) layout.setSpacing(4) self.region_panel.setVisible(False) def add_brainrender_panel(self, layout): self.initialise_brainrender() self.brainrender_panel = QGroupBox("brainrender") brainrender_layout = QGridLayout() add_button( "View in Brainrender", brainrender_layout, self.to_brainrender, 4, 1, ) self.region_alpha = add_float_box( brainrender_layout, self.region_alpha_default, 0, 1, "Segmented region alpha", 0.1, 0, ) self.structure_alpha = add_float_box( brainrender_layout, self.structure_alpha_default, 0, 1, "Atlas region alpha", 0.1, 1, ) self.shading = add_combobox( brainrender_layout, "Segmented region shading", self.vtkplotter_shading_types, 2, ) self.region_to_render = add_combobox( brainrender_layout, "Region to render", self.available_meshes, 3, ) brainrender_layout.setColumnMinimumWidth(1, 150) self.brainrender_panel.setLayout(brainrender_layout) layout.addWidget(self.brainrender_panel, 6, 0, 1, 2) layout.setAlignment(QtCore.Qt.AlignTop) layout.setSpacing(4) self.brainrender_panel.setVisible(False) def load_atlas(self): if not self.region_labels: self.status_label.setText(f"Loading ...") self.region_labels = self.viewer.add_labels( prepare_load_nii(self.paths.annotations, memory=memory), name="Region labels", opacity=0.2, ) self.region_labels.editable = False @self.region_labels.mouse_move_callbacks.append def display_region_name(layer, event): display_brain_region_name(layer, self.structures_df) self.status_label.setText(f"Ready") def initialise_image_view(self): self.set_z_position() def set_z_position(self): midpoint = int(round(len(self.base_layer.data) / 2)) self.viewer.dims.set_point(0, midpoint) def load_amap_directory(self): self.select_nii_file() self.registration_directory = self.downsampled_file.parent self.status_label.setText(f"Loading ...") self.paths = Paths(self.registration_directory, self.downsampled_file) if not self.paths.tmp__inverse_transformed_image.exists(): print( f"The image: '{self.downsampled_file}' has not been transformed into standard " f"space, and so must be transformed before segmentation.\n") transform_image_to_standard_space( self.registration_directory, image_to_transform_fname=self.downsampled_file, output_fname=self.paths.tmp__inverse_transformed_image, log_file_path=self.paths.tmp__inverse_transform_log_path, error_file_path=self.paths.tmp__inverse_transform_error_path, ) else: print("Registered image exists, skipping registration\n") self.registered_image = prepare_load_nii( self.paths.tmp__inverse_transformed_image, memory=memory) self.base_layer = display_channel( self.viewer, self.registration_directory, self.paths.tmp__inverse_transformed_image, memory=memory, name="Image in standard space", ) self.initialise_image_view() self.structures_df = load_structures_as_df(get_structures_path()) self.load_button.setMinimumWidth(0) self.load_atlas_button.setVisible(True) self.save_button.setVisible(True) self.initialise_region_segmentation() self.initialise_track_tracing() self.status_label.setText(f"Ready") def select_nii_file(self): options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog file, _ = QFileDialog.getOpenFileName( self, "Select the downsampled image you wish to segment", "", "Nifti files (*.nii)", options=options, ) self.downsampled_file = Path(file) def initialise_track_tracing(self): track_files = glob( str(self.paths.tracks_directory) + "/*" + self.track_file_extension) if self.paths.tracks_directory.exists() and track_files != []: for track_file in track_files: self.track_layers.append( add_existing_track_layers( self.viewer, track_file, self.napari_point_size, self.x_scaling, self.y_scaling, self.z_scaling, )) self.track_panel.setVisible(True) self.region_panel.setVisible(True) self.brainrender_panel.setVisible(True) def add_track(self): print("Adding a new track\n") add_new_track_layer(self.viewer, self.track_layers, self.napari_point_size) def run_track_analysis(self): print("Running track analysis") self.scene, self.splines = track_analysis( self.viewer, self.base_layer, self.scene, self.paths.tracks_directory, self.x_scaling, self.y_scaling, self.z_scaling, self.napari_spline_size, add_surface_to_points=self.add_surface_point_checkbox.isChecked(), spline_points=self.spline_points.value(), fit_degree=self.fit_degree.value(), spline_smoothing=self.spline_smoothing.value(), point_size=self.point_size, spline_size=self.spline_size, summarise_track=self.summarise_track_checkbox.isChecked(), track_file_extension=self.track_file_extension, ) print("Finished!\n") def initialise_region_segmentation(self): add_existing_region_segmentation( self.paths.regions_directory, self.viewer, self.label_layers, self.image_file_extension, memory=memory, ) def add_new_region(self): print("Adding a new region\n") add_new_region_layer( self.viewer, self.label_layers, self.registered_image, self.brush_size, self.num_colors, ) def run_region_analysis(self): print("Running region analysis") worker = region_analysis( self.label_layers, self.structures_df, self.paths.regions_directory, self.paths.annotations, self.paths.hemispheres, output_csv_file=self.paths.region_summary_csv, volumes=self.calculate_volumes_checkbox.isChecked(), summarise=self.summarise_volumes_checkbox.isChecked(), ) worker.start() def initialise_brainrender(self): self.scene = Scene(add_root=True) self.splines = None self.available_meshes = [""] + self.scene.atlas.all_avaliable_meshes def to_brainrender(self): print("Closing viewer and viewing in brainrender.") QApplication.closeAllWindows() view_in_brainrender( self.scene, self.splines, self.paths.regions_directory, alpha=self.region_alpha.value(), shading=str(self.shading.currentText()), region_to_add=str(self.region_to_render.currentText()), region_alpha=self.structure_alpha.value(), ) def save(self): print("Saving") worker = save_all( self.viewer, self.paths.regions_directory, self.paths.tracks_directory, self.label_layers, self.track_layers, self.paths.downsampled_image, self.x_scaling, self.y_scaling, self.z_scaling, track_file_extension=self.track_file_extension, ) worker.start()
class CurationWidget(QWidget): def __init__( self, viewer, ): super(CurationWidget, self).__init__() self.viewer = viewer self.background_layer = [] self.background_path = "" self.signal_layer = [] self.signal_path = "" self.directory = "" self.output_directory = None self.setup_main_layout() def setup_main_layout(self): """ Construct main layout of widget """ self.layout = QGridLayout() self.layout.setContentsMargins(10, 10, 10, 10) self.layout.setAlignment(QtCore.Qt.AlignTop) self.layout.setSpacing(4) self.add_loading_panel(1) self.status_label = QLabel() self.status_label.setText("Ready") self.layout.addWidget(self.status_label, 5, 0) self.setLayout(self.layout) def add_loading_panel(self, row, column=0): """ Loading panel: - Load project (sample space) - Load project (atlas space) - Atlas chooser """ self.load_data_panel = QGroupBox("Load data") self.load_data_layout = QGridLayout() self.load_data_layout.setSpacing(15) self.load_data_layout.setContentsMargins(10, 10, 10, 10) self.load_data_layout.setAlignment(QtCore.Qt.AlignBottom) # self.load_cellfinder_dir_button = add_button( # "Load cellfinder project", # self.load_data_layout, # self.get_cellfinder_directory, # 0, # 0, # minimum_width=COLUMN_WIDTH, # ) # self.load_background_button = add_button( # "Load background", # self.load_data_layout, # self.get_background, # 1, # 0, # minimum_width=COLUMN_WIDTH, # ) self.load_signal_button = add_button( "Load signal", self.load_data_layout, self.get_signal, 2, 0, minimum_width=COLUMN_WIDTH, ) self.add_cells_button = add_button( "Add cell count", self.load_data_layout, self.add_cell_count, 3, 0, minimum_width=COLUMN_WIDTH, ) self.add_cells_button = add_button( "Load cell count", self.load_data_layout, self.load_cells, 4, 0, minimum_width=COLUMN_WIDTH, ) self.save_cells_button = add_button( "Save cells", self.load_data_layout, self.save_cell_count, 5, 0, minimum_width=COLUMN_WIDTH, ) self.analyse_cells_button = add_button( "Analyse cells", self.load_data_layout, self.analyse_cells, 6, 0, minimum_width=COLUMN_WIDTH, ) self.load_data_layout.setColumnMinimumWidth(0, COLUMN_WIDTH) self.load_data_panel.setLayout(self.load_data_layout) self.load_data_panel.setVisible(True) self.layout.addWidget(self.load_data_panel, row, column, 1, 1) def get_cellfinder_directory(self): self.status_label.setText("Loading...") options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog cellfinder_directory = QFileDialog.getExistingDirectory( self, "Select cellfinder directory", options=options, ) if not cellfinder_directory: return if self.directory != cellfinder_directory: self.directory = Path(cellfinder_directory) else: print(f"{str(cellfinder_directory)} already loaded.") return # Otherwise, proceed loading brainreg dir self.load_cellfinder_directory() self.status_label.setText("Ready...") def load_cellfinder_directory(self): try: self.viewer.open(str(self.directory), plugin="cellfinder") except ValueError: print(f"The directory ({self.directory}) does not appear to be " f"a cellfinder directory, please try again.") return def get_background(self): self.background_layer, self.background_path = self.get_data( name="background", visible=False) def get_signal(self): self.signal_layer, self.signal_path = self.get_data(name="signal") self.status_label.setText("Ready") def get_data(self, name="", visible=True): self.status_label.setText("Loading...") options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog directory = QFileDialog.getExistingDirectory( self, f"Select {name} channel", options=options, ) if not directory: return if self.directory != directory: self.directory = Path(directory) else: print(f"{str(directory)} already loaded.") return return self.load_data(name=name, visible=visible), directory def load_data(self, name="", visible=True): try: img_paths = get_sorted_file_paths(self.directory, file_extension=".tif") images = magic_imread(img_paths, use_dask=True, stack=True) return self.viewer.add_image(images) # return self.viewer.open( # str(self.directory), name=name, visible=visible # ) except ValueError: print(f"The directory ({self.directory}) cannot be " f"loaded, please try again.") return def add_cell_count(self): self.cell_layer = self.viewer.add_points( np.empty((0, 3)), symbol="ring", n_dimensional=True, size=10, opacity=0.6, face_color="lightgoldenrodyellow", name="cells", ) def load_cells(self): options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog filename = QFileDialog.getOpenFileName( self, "Select cell file...", filter="cellfinder xml files (*.xml)", options=options, ) cells, _ = get_cell_arrays(filename[0]) self.cell_layer = self.viewer.add_points( cells, symbol="ring", n_dimensional=True, size=10, opacity=0.6, face_color="lightgoldenrodyellow", name="cells", ) def save_cell_count(self): self.status_label.setText("Saving cells") print("Saving cells") self.get_output_directory() filename = self.output_directory / "cells.xml" cells_to_save = [] for idx, point in enumerate(self.cell_layer.data): cell = Cell([point[2], point[1], point[0]], Cell.CELL) cells_to_save.append(cell) save_cells(cells_to_save, str(filename)) self.status_label.setText("Ready") print("Done!") def get_output_directory(self): if self.output_directory is None: options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog self.output_directory = QFileDialog.getExistingDirectory( self, "Select output directory", options=options, ) self.output_directory = Path(self.output_directory) def analyse_cells(self): self.status_label.setText("Analysing cells") print("Analysing cells") self.get_output_directory() self.get_brainreg_directory() analyse_cell_positions( self.cell_layer.data, self.brainreg_directory, self.signal_path, self.output_directory, ) self.status_label.setText("Ready") def get_brainreg_directory(self): options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog self.brainreg_directory = QFileDialog.getExistingDirectory( self, "Select brainreg directory", options=options, )
class InjCtrlWindow(SiriusMainWindow): """InjCtrl Main Window.""" showMonitor = Signal() showStatus = Signal() showEgun = Signal() def __init__(self, parent=None, prefix=''): """Init.""" super().__init__(parent) self._prefix = prefix self._inj_dev = SiriusPVName('AS-Glob:AP-InjCtrl') self._inj_prefix = self._inj_dev.substitute(prefix=prefix) self.setWindowTitle('Injection Controls') self.setObjectName('ASApp') self.setWindowIcon( qta.icon('fa5s.syringe', color=get_appropriate_color('AS'))) self._setupUi() self.setFocus(True) self.setFocusPolicy(Qt.StrongFocus) def _setupUi(self): self.title = QLabel('<h3>Injection Control<h3>', self, alignment=Qt.AlignCenter) self.title.setStyleSheet('QLabel{max-height:1.6em;}') self.wid_comm = self._setupMainBarWidget() self.wid_comm.setSizePolicy(QSzPlcy.Preferred, QSzPlcy.Fixed) self.wid_sett = self._setupSettingsWidget() self.wid_sett.setSizePolicy(QSzPlcy.Preferred, QSzPlcy.MinimumExpanding) self.wid_mon = self._setupMonitorWidget() self.wid_log = self._setupLogWidget() wid_row = QWidget() wid_row.setSizePolicy(QSzPlcy.Preferred, QSzPlcy.Fixed) hbox_row = QHBoxLayout(wid_row) hbox_row.setContentsMargins(0, 0, 0, 0) hbox_row.setStretch(0, 3) hbox_row.setStretch(1, 5) hbox_row.addWidget(self.wid_mon) hbox_row.addWidget(self.wid_log) wid = QWidget(self) lay = QVBoxLayout(wid) lay.addWidget(self.title) lay.addWidget(self.wid_comm) lay.addWidget(self.wid_comm) lay.addWidget(self.wid_sett) lay.addWidget(wid_row) lay.setStretch(1, 1) lay.setStretch(2, 3) lay.setStretch(3, 2) self.setCentralWidget(wid) self._ch_injmode = SiriusConnectionSignal( self._inj_prefix.substitute(propty='Mode-Sel')) self._ch_injmode.new_value_signal[int].connect( self._handle_injmode_settings_vis) # connect window signals connect_newprocess(self, 'sirius-hla-as-ap-monitor.py', parent=self, signal=self.showMonitor) connect_newprocess(self, 'sirius-hla-si-ap-genstatus.py', parent=self, signal=self.showStatus) connect_newprocess(self, 'sirius-hla-li-eg-control.py', parent=self, signal=self.showEgun) def _setupMainBarWidget(self): # Shift machshift_pvname = SiriusPVName( 'AS-Glob:AP-MachShift:Mode-Sel').substitute(prefix=self._prefix) self._cb_shift = SiriusEnumComboBox(self, machshift_pvname) self._lb_shift = MachShiftLabel(self, prefix=self._prefix) self._lb_shift.setStyleSheet( 'QLabel{max-height: 2em; min-width: 7em;}') self.wid_shift = QGroupBox('Mach.Shift', self) lay_shift = QVBoxLayout(self.wid_shift) lay_shift.addWidget(self._cb_shift) lay_shift.addWidget(self._lb_shift) # Injection System self.wid_is_summ = InjSysStbyControlWidget(self, is_summary=True) self.wid_injsys = QGroupBox('Inj.System', self) lay_injsys = QGridLayout(self.wid_injsys) lay_injsys.setContentsMargins(0, 0, 0, 0) lay_injsys.addWidget(self.wid_is_summ, 0, 0) # self.wid_is_full = InjSysStbyControlWidget(self, is_summary=False) # self.wid_is_full.setVisible(False) # lay_injsys.addWidget(self.wid_is_full, 0, 0) # self._icon_expd = qta.icon('fa5s.plus') # self._icon_comp = qta.icon('fa5s.minus') # self.bt_is_tgl = QPushButton(self._icon_expd, '', self) # self.bt_is_tgl.clicked.connect(self._handle_injsys_details_vis) # self.bt_is_tgl.setObjectName('bt') # self.bt_is_tgl.setStyleSheet(""" # #bt{ # min-width: 0.8em; max-width: 0.8em; # min-height: 0.8em; max-height: 0.8em; # icon-size:12px;} # """) # lay_injsys.addWidget( # self.bt_is_tgl, 0, 1, alignment=Qt.AlignRight | Qt.AlignBottom) # EGun egun_dev = SiriusPVName('LI-01:EG-TriggerPS').substitute( prefix=self._prefix) self._sb_eguntrg = PyDMStateButton( self, egun_dev.substitute(propty_name='enable')) self._led_eguntrg = SiriusLedState( self, egun_dev.substitute(propty_name='enablereal')) self._led_eguntrg.setStyleSheet( 'QLed{min-width: 1.29em; max-width: 1.29em;}') self.wid_egun = self._create_groupwidget('EGun Trig.', [ self._sb_eguntrg, ], [ self._led_eguntrg, ]) # Injection self._pb_tiinj = EVGInjectionButton(self, self._prefix) self._pb_topup = PyDMStateButton( self, init_channel=self._inj_prefix.substitute(propty='TopUpState-Sel')) self._pb_topup.setVisible(False) self._led_injti = EVGInjectionLed(self, self._prefix) self._lb_injcnt = PyDMLabel(self) self._lb_injcnt.setToolTip( 'Count injection pulses when Egun Trigger is enabled.') ch_injcnt = SiriusPVName( 'AS-Glob:AP-CurrInfo:InjCount-Mon').substitute(prefix=self._prefix) self._lb_injcnt.channel = ch_injcnt self._lb_injcnt.setStyleSheet('QLabel{max-width: 3.5em;}') hbox_injsts = QHBoxLayout() hbox_injsts.setContentsMargins(0, 0, 0, 0) hbox_injsts.addWidget(self._led_injti) hbox_injsts.addWidget(self._lb_injcnt) self.wid_inj = QGroupBox('Injection', self) lay_inj = QGridLayout(self.wid_inj) lay_inj.setAlignment(Qt.AlignCenter) lay_inj.addWidget(self._pb_tiinj, 0, 0) lay_inj.addWidget(self._pb_topup, 0, 0) lay_inj.addLayout(hbox_injsts, 1, 0) # Current curr_pvname = SiriusPVName( 'SI-Glob:AP-CurrInfo:Current-Mon').substitute(prefix=self._prefix) self._lb_curr = PyDMLabel(self, curr_pvname) self._lb_curr.showUnits = True self._lb_curr.setStyleSheet(""" QLabel{ font-size: 18pt; qproperty-alignment: AlignCenter; min-width: 5.5em; max-width: 5.5em; }""") self.wid_curr = QGroupBox('Current', self) lay_curr = QHBoxLayout(self.wid_curr) lay_curr.addWidget(self._lb_curr) # TopUp status self._lb_tusts = PyDMLabel( self, self._inj_prefix.substitute(propty='TopUpState-Sts')) self._lb_tusts.setAlignment(Qt.AlignCenter) self._lb_tusts.setStyleSheet('QLabel{max-height:2em;}') self._ld_tunow = QLabel('Now:', self, alignment=Qt.AlignRight | Qt.AlignVCenter) self._lb_tunow = ClockLabel(self) self._lb_tunow.setStyleSheet('QLabel{max-height:2em;}') self._ld_tunxt = QLabel('Next:', self, alignment=Qt.AlignRight | Qt.AlignVCenter) self._lb_tunxt = SiriusLabel( self, self._inj_prefix.substitute(propty='TopUpNextInj-Mon')) self._lb_tunxt.displayFormat = SiriusLabel.DisplayFormat.Time self._lb_tunxt.setAlignment(Qt.AlignCenter) self._lb_tunxt.setStyleSheet('QLabel{max-height:2em;}') self._pb_round = PyDMPushButton( self, label='', icon=qta.icon('mdi.tilde'), pressValue=1, init_channel=self._inj_prefix.substitute( propty='TopUpNextInjRound-Cmd')) self._pb_round.setObjectName('but') self._pb_round.setStyleSheet( '#but{min-width:18px; max-width:18px; icon-size:16px;}') self.wid_tusts = QGroupBox('Top-up status') self.wid_tusts.setVisible(False) lay_tusts = QGridLayout(self.wid_tusts) lay_tusts.addWidget(self._lb_tusts, 0, 0, 1, 2) lay_tusts.addWidget(self._ld_tunow, 1, 0) lay_tusts.addWidget(self._lb_tunow, 1, 1) lay_tusts.addWidget(self._ld_tunxt, 2, 0) lay_tusts.addWidget(self._lb_tunxt, 2, 1) lay_tusts.addWidget(self._pb_round, 2, 2) wid = QWidget() lay = QGridLayout(wid) lay.setContentsMargins(0, 0, 0, 0) lay.addWidget(self.wid_shift, 0, 0) lay.addWidget(self.wid_injsys, 0, 1) lay.addWidget(self.wid_egun, 0, 2) lay.addWidget(self.wid_inj, 0, 3) lay.addWidget(self.wid_tusts, 0, 4) lay.addWidget(self.wid_curr, 0, 5) lay.setColumnStretch(0, 3) lay.setColumnStretch(1, 2) lay.setColumnStretch(2, 2) lay.setColumnStretch(3, 2) lay.setColumnStretch(4, 4) lay.setColumnStretch(5, 3) wid.setStyleSheet('.QLabel{min-height: 1em; max-height: 1em;}') return wid def _setupSettingsWidget(self): # group of labels to set the same stylesheet labelsdesc, labelsmon = list(), list() # Mode self._ld_injmode = QLabel('Mode', self) labelsdesc.append(self._ld_injmode) self._cb_injmode = SiriusEnumComboBox( self, self._inj_prefix.substitute(propty='Mode-Sel')) self._lb_injmode = PyDMLabel( self, self._inj_prefix.substitute(propty='Mode-Sts')) self._lb_injmode.showUnits = True labelsmon.append(self._lb_injmode) # Target current self._ld_currtgt = QLabel('Target Curr.', self) labelsdesc.append(self._ld_currtgt) self._sb_currtgt = SiriusSpinbox( self, self._inj_prefix.substitute(propty='TargetCurrent-SP')) self._sb_currtgt.showStepExponent = False self._lb_currtgt = PyDMLabel( self, self._inj_prefix.substitute(propty='TargetCurrent-RB')) self._lb_currtgt.showUnits = True labelsmon.append(self._lb_currtgt) # mode specific configurations self.wid_dcdtls = self._setupDecayModeWidget() self.wid_tudtls = self._setupTopUpModeWidget() self.wid_tudtls.setVisible(False) # Mon self._ld_injset = QLabel('Setup ok', self) labelsdesc.append(self._ld_injset) self._led_injset = InjDiagLed(self) # Type self._ld_injtype = QLabel('Type', self) labelsdesc.append(self._ld_injtype) self._cb_injtype = SiriusEnumComboBox( self, self._inj_prefix.substitute(propty='Type-Sel')) self._lb_injtype = PyDMLabel( self, self._inj_prefix.substitute(propty='Type-Sts')) labelsmon.append(self._lb_injtype) self._lb_injtype_mon = PyDMLabel( self, self._inj_prefix.substitute(propty='Type-Mon')) labelsmon.append(self._lb_injtype_mon) self._ch_injtype = SiriusConnectionSignal( self._inj_prefix.substitute(propty='Type-Sel')) self._ch_injtype.new_value_signal[int].connect( self._handle_injtype_settings_vis) # Single bunch bias voltage self._ld_sbbias = QLabel('SB Bias Voltage', self) labelsdesc.append(self._ld_sbbias) self._sb_sbbias = SiriusSpinbox( self, self._inj_prefix.substitute(propty='SglBunBiasVolt-SP')) self._sb_sbbias.showStepExponent = False self._lb_sbbias = PyDMLabel( self, self._inj_prefix.substitute(propty='SglBunBiasVolt-RB')) self._lb_sbbias.showUnits = True labelsmon.append(self._lb_sbbias) self._ld_sbbias.setVisible(False) self._sb_sbbias.setVisible(False) self._lb_sbbias.setVisible(False) # Multi bunch bias voltage self._ld_mbbias = QLabel('MB Bias Volt.', self) labelsdesc.append(self._ld_mbbias) self._sb_mbbias = SiriusSpinbox( self, self._inj_prefix.substitute(propty='MultBunBiasVolt-SP')) self._sb_mbbias.showStepExponent = False self._lb_mbbias = PyDMLabel( self, self._inj_prefix.substitute(propty='MultBunBiasVolt-RB')) self._lb_mbbias.showUnits = True labelsmon.append(self._lb_mbbias) # bias voltage mon ch_bias_mon = SiriusPVName('LI-01:EG-BiasPS').substitute( prefix=self._prefix, propty_name='voltinsoft') self._lb_bias_mon = PyDMLabel(self, ch_bias_mon) self._lb_bias_mon.showUnits = True labelsmon.append(self._lb_bias_mon) # Filament current op value self._ld_filaopcurr = QLabel('Fila.Op. Curr.', self) labelsdesc.append(self._ld_filaopcurr) self._sb_filaopcurr = SiriusSpinbox( self, self._inj_prefix.substitute(propty='FilaOpCurr-SP')) self._sb_filaopcurr.showStepExponent = False self._lb_filaopcurr = PyDMLabel( self, self._inj_prefix.substitute(propty='FilaOpCurr-RB')) self._lb_filaopcurr.showUnits = True labelsmon.append(self._lb_filaopcurr) ch_filacurr_mon = SiriusPVName('LI-01:EG-FilaPS').substitute( prefix=self._prefix, propty_name='currentinsoft') self._lb_filaopcurr_mon = PyDMLabel(self, ch_filacurr_mon) self._lb_filaopcurr_mon.showUnits = True labelsmon.append(self._lb_filaopcurr_mon) # High voltage op value self._ld_hvopvolt = QLabel('HV.Op. Volt.', self) labelsdesc.append(self._ld_hvopvolt) self._sb_hvopvolt = SiriusSpinbox( self, self._inj_prefix.substitute(propty='HVOpVolt-SP')) self._sb_hvopvolt.showStepExponent = False self._lb_hvopvolt = PyDMLabel( self, self._inj_prefix.substitute(propty='HVOpVolt-RB')) self._lb_hvopvolt.showUnits = True labelsmon.append(self._lb_hvopvolt) ch_hvvolt_mon = SiriusPVName('LI-01:EG-HVPS').substitute( prefix=self._prefix, propty_name='voltinsoft') self._lb_hvopvolt_mon = PyDMLabel(self, ch_hvvolt_mon) self._lb_hvopvolt_mon.showUnits = True labelsmon.append(self._lb_hvopvolt_mon) # header ld_sp = QLabel('<h4>SP</h4>', self, alignment=Qt.AlignCenter) ld_rb = QLabel('<h4>RB</h4>', self, alignment=Qt.AlignCenter) ld_mon = QLabel('<h4>Mon</h4>', self, alignment=Qt.AlignCenter) # Bucket list self._wid_bl = BucketList(self, prefix=self._prefix, min_size=15, show_graph=True) self._wid_bl.setSizePolicy(QSzPlcy.Preferred, QSzPlcy.MinimumExpanding) wid1 = QWidget() wid1.setSizePolicy(QSzPlcy.Preferred, QSzPlcy.Fixed) glay1 = QGridLayout(wid1) glay1.setAlignment(Qt.AlignTop) glay1.addWidget(self._ld_injset, 0, 0) glay1.addWidget(self._led_injset, 0, 1) glay1.addWidget(self._ld_injmode, 1, 0) glay1.addWidget(self._cb_injmode, 1, 1) glay1.addWidget(self._lb_injmode, 1, 2) glay1.addWidget(self._ld_currtgt, 2, 0) glay1.addWidget(self._sb_currtgt, 2, 1) glay1.addWidget(self._lb_currtgt, 2, 2) glay1.addWidget(self.wid_tudtls, 3, 0, 2, 3) glay1.addWidget(self.wid_dcdtls, 3, 0, 2, 3) glay1.setColumnStretch(0, 3) glay1.setColumnStretch(1, 2) glay1.setColumnStretch(2, 2) wid2 = QWidget() wid2.setSizePolicy(QSzPlcy.Preferred, QSzPlcy.Fixed) glay2 = QGridLayout(wid2) glay2.setAlignment(Qt.AlignTop) glay2.addWidget(ld_sp, 0, 1) glay2.addWidget(ld_rb, 0, 2) glay2.addWidget(ld_mon, 0, 3) glay2.addWidget(self._ld_injtype, 1, 0) glay2.addWidget(self._cb_injtype, 1, 1) glay2.addWidget(self._lb_injtype, 1, 2) glay2.addWidget(self._lb_injtype_mon, 1, 3) glay2.addWidget(self._ld_sbbias, 2, 0) glay2.addWidget(self._sb_sbbias, 2, 1) glay2.addWidget(self._lb_sbbias, 2, 2) glay2.addWidget(self._ld_mbbias, 2, 0) glay2.addWidget(self._sb_mbbias, 2, 1) glay2.addWidget(self._lb_mbbias, 2, 2) glay2.addWidget(self._lb_bias_mon, 2, 3) glay2.addWidget(self._ld_filaopcurr, 4, 0) glay2.addWidget(self._sb_filaopcurr, 4, 1) glay2.addWidget(self._lb_filaopcurr, 4, 2) glay2.addWidget(self._lb_filaopcurr_mon, 4, 3) glay2.addWidget(self._ld_hvopvolt, 5, 0) glay2.addWidget(self._sb_hvopvolt, 5, 1) glay2.addWidget(self._lb_hvopvolt, 5, 2) glay2.addWidget(self._lb_hvopvolt_mon, 5, 3) glay2.setColumnStretch(0, 3) glay2.setColumnStretch(1, 2) glay2.setColumnStretch(2, 2) glay2.setColumnStretch(3, 2) wid = QGroupBox('Settings') lay = QGridLayout(wid) lay.addWidget(wid1, 0, 0, alignment=Qt.AlignTop) lay.addWidget(wid2, 0, 1, alignment=Qt.AlignTop) lay.addWidget(self._wid_bl, 1, 0, 1, 2) lay.setColumnStretch(0, 3) lay.setColumnStretch(1, 4) for lbl in labelsdesc: lbl.setStyleSheet(""" QLabel{ min-width: 6.5em; max-width: 6.5em; min-height: 1.5em; qproperty-alignment: 'AlignRight | AlignVCenter'; }""") for lbl in labelsmon: lbl.setStyleSheet("PyDMLabel{qproperty-alignment: AlignCenter;}") return wid def _setupTopUpModeWidget(self): self._ld_tuperd = QLabel('Period', self) self._sb_tuperd = SiriusSpinbox( self, self._inj_prefix.substitute(propty='TopUpPeriod-SP')) self._sb_tuperd.showStepExponent = False self._lb_tuperd = PyDMLabel( self, self._inj_prefix.substitute(propty='TopUpPeriod-RB')) self._lb_tuperd.showUnits = True self._ld_tumaxpu = QLabel('Max.Nr.Pulses', self) self._sb_tumaxpu = SiriusSpinbox( self, self._inj_prefix.substitute(propty='TopUpMaxNrPulses-SP')) self._sb_tumaxpu.showStepExponent = False self._lb_tumaxpu = PyDMLabel( self, self._inj_prefix.substitute(propty='TopUpMaxNrPulses-RB')) self._lb_tumaxpu.showUnits = True wid = QWidget() lay = QGridLayout(wid) lay.setContentsMargins(0, 6, 0, 0) lay.setAlignment(Qt.AlignTop) lay.addWidget(self._ld_tuperd, 0, 0) lay.addWidget(self._sb_tuperd, 0, 1) lay.addWidget(self._lb_tuperd, 0, 2) lay.addWidget(self._ld_tumaxpu, 1, 0) lay.addWidget(self._sb_tumaxpu, 1, 1) lay.addWidget(self._lb_tumaxpu, 1, 2) lay.setColumnStretch(0, 3) lay.setColumnStretch(1, 2) lay.setColumnStretch(2, 2) wid.setStyleSheet(""" .QLabel{ min-width: 6.5em; max-width: 6.5em; min-height: 1.5em; qproperty-alignment: 'AlignRight | AlignVCenter'; } PyDMLabel{ qproperty-alignment: AlignCenter; }""") return wid def _setupDecayModeWidget(self): self._ld_autostop = QLabel('Auto Stop', self) self._cb_autostop = PyDMStateButton( self, self._inj_prefix.substitute(propty='AutoStop-Sel')) self._cb_autostop.shape = 1 self._led_autostop = SiriusLedState( self, self._inj_prefix.substitute(propty='AutoStop-Sts')) wid = QWidget() lay = QGridLayout(wid) lay.setAlignment(Qt.AlignTop) lay.setContentsMargins(0, 6, 0, 0) lay.addWidget(self._ld_autostop, 0, 0) lay.addWidget(self._cb_autostop, 0, 1) lay.addWidget(self._led_autostop, 0, 2) lay.setColumnStretch(0, 3) lay.setColumnStretch(1, 2) lay.setColumnStretch(2, 2) wid.setStyleSheet(""" .QLabel{ min-width: 6.5em; max-width: 6.5em; min-height: 1.5em; qproperty-alignment: 'AlignRight | AlignVCenter'; } PyDMLabel{ qproperty-alignment: AlignCenter; }""") return wid def _setupLogWidget(self): self._log = PyDMLogLabel(self, self._inj_prefix.substitute(propty='Log-Mon'), [ 'Remaining time', ]) wid = QGroupBox('Log') lay = QHBoxLayout(wid) lay.addWidget(self._log) return wid def _setupMonitorWidget(self): self.wid_mon = MonitorSummaryWidget(self) wid = QWidget(self) lay = QGridLayout(wid) lay.setContentsMargins(0, 0, 0, 0) lay.addWidget(self.wid_mon, 0, 0) return wid # ---- auxiliary commands ---- @Slot(int) def _handle_injtype_settings_vis(self, new_type): is_sb = new_type == _Const.InjType.SingleBunch self._ld_sbbias.setVisible(is_sb) self._sb_sbbias.setVisible(is_sb) self._lb_sbbias.setVisible(is_sb) self._ld_mbbias.setVisible(not is_sb) self._sb_mbbias.setVisible(not is_sb) self._lb_mbbias.setVisible(not is_sb) self.centralWidget().adjustSize() self.adjustSize() @Slot(int) def _handle_injmode_settings_vis(self, new_mode): is_topoup = new_mode == _Const.InjMode.TopUp self.wid_tudtls.setVisible(is_topoup) self._pb_topup.setVisible(is_topoup) self.wid_dcdtls.setVisible(not is_topoup) self._pb_tiinj.setVisible(not is_topoup) self.wid_tusts.setVisible(is_topoup) def _handle_injsys_details_vis(self): exp = self.wid_is_summ.isVisible() icon = self._icon_comp if exp else self._icon_expd self.sender().setIcon(icon) self.wid_is_summ.setVisible(not exp) self.wid_is_full.setVisible(exp) self.centralWidget().adjustSize() self.adjustSize() def _create_groupwidget(self, title, sp_wids, rb_wids, aux_wids=None): hbox_sp = QHBoxLayout() hbox_sp.setAlignment(Qt.AlignCenter) hbox_sp.setContentsMargins(0, 0, 0, 0) for wid in sp_wids: hbox_sp.addWidget(wid) hbox_rb = QHBoxLayout() hbox_rb.setAlignment(Qt.AlignCenter) hbox_rb.setContentsMargins(0, 0, 0, 0) for wid in rb_wids: hbox_rb.addWidget(wid) box = QGroupBox(title, self) if title else QWidget(self) lay = QVBoxLayout(box) lay.setAlignment(Qt.AlignCenter) if not isinstance(box, QGroupBox): lay.setContentsMargins(0, 0, 0, 0) lay.addLayout(hbox_sp) lay.addLayout(hbox_rb) if aux_wids: hbox_aux = QHBoxLayout() hbox_aux.setAlignment(Qt.AlignCenter) hbox_aux.setContentsMargins(0, 0, 0, 0) for wid in aux_wids: hbox_aux.addWidget(wid) lay.addLayout(hbox_aux) return box # ---- events ---- def mouseDoubleClickEvent(self, event): """Implement mouseDoubleClickEvent.""" if event.button() == Qt.LeftButton: if self.wid_curr.underMouse(): self.showStatus.emit() elif self.wid_shift.underMouse(): self.showStatus.emit() elif self.wid_egun.underMouse(): self.showEgun.emit() elif self.wid_mon.underMouse(): self.showMonitor.emit() super().mouseDoubleClickEvent(event) def changeEvent(self, event): """Implement changeEvent.""" if event.type() == QEvent.FontChange: fontsize = self.app.font().pointSize() self._lb_curr.setStyleSheet('QLabel{' ' font-size: ' + str(fontsize + 8) + 'pt;' ' qproperty-alignment: AlignCenter;' ' min-width: 6em; max-width: 6em;' '}') self.ensurePolished()