class PlaneWidget(QWidget): def __init__(self): super().__init__() master_layout = QVBoxLayout(self) self.position_slider_box = QGroupBox('plane position (axis 1)') self.position_slider = QLabeledDoubleSlider(Qt.Horizontal, self) self.position_slider.setMinimum(0.05) self.position_slider.setMaximum(64) self.position_slider.setValue(32) position_layout = QHBoxLayout(self.position_slider_box) position_layout.addWidget(self.position_slider) self.thickness_box = QGroupBox('plane thickness') self.thickness_spinbox = QLabeledDoubleSlider(Qt.Horizontal, self) self.thickness_spinbox.setMinimum(1.0) self.thickness_spinbox.setMaximum(64) self.thickness_spinbox.setValue(10) thickness_layout = QHBoxLayout(self.thickness_box) thickness_layout.addWidget(self.thickness_spinbox) master_layout.addWidget(self.position_slider_box) master_layout.addWidget(self.thickness_box)
def _stretch_range_to_contain(wdg: QLabeledDoubleSlider, val: T) -> T: """Set range of `wdg` to include `val`.""" if val > wdg.maximum(): wdg.setMaximum(val) if val < wdg.minimum(): wdg.setMinimum(val) return val
def __init__(self, layer: Image): super().__init__(layer) self.layer.events.colormap.connect(self._on_colormap_change) self.layer.events.gamma.connect(self._on_gamma_change) self.layer.events.contrast_limits.connect( self._on_contrast_limits_change) comboBox = QtColormapComboBox(self) comboBox.setObjectName("colormapComboBox") comboBox._allitems = set(self.layer.colormaps) for name, cm in AVAILABLE_COLORMAPS.items(): if name in self.layer.colormaps: comboBox.addItem(cm._display_name, name) comboBox.activated[str].connect(self.changeColor) self.colormapComboBox = comboBox # Create contrast_limits slider self.contrastLimitsSlider = QDoubleRangeSlider(Qt.Horizontal, self) self.contrastLimitsSlider.setSingleStep(0.01) self.contrastLimitsSlider.setRange(*self.layer.contrast_limits_range) self.contrastLimitsSlider.setValue(self.layer.contrast_limits) self.contrastLimitsSlider.setToolTip( trans._('Right click for detailed slider popup.')) self.clim_popup = None self.contrastLimitsSlider.mousePressEvent = self._clim_mousepress set_clim = partial(setattr, self.layer, 'contrast_limits') self.contrastLimitsSlider.valueChanged.connect(set_clim) self.contrastLimitsSlider.rangeChanged.connect( lambda *a: setattr(self.layer, 'contrast_limits_range', a)) # gamma slider sld = QSlider(Qt.Horizontal, parent=self) sld.setMinimum(0.2) sld.setMaximum(2) sld.setSingleStep(0.02) sld.setValue(self.layer.gamma) sld.valueChanged.connect(lambda v: setattr(self.layer, 'gamma', v)) self.gammaSlider = sld self.colorbarLabel = QLabel(parent=self) self.colorbarLabel.setObjectName('colorbar') self.colorbarLabel.setToolTip(trans._('Colorbar')) self._on_colormap_change()
class QtPlaneControls(QWidget): """Qt widget encapsulating plane controls for an image layer.""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.grid_layout = LayerListGridLayout(self) self.setLayout(self.grid_layout) self.planeNormalLabel = QLabel(trans._('plane normal:')) self.planeNormalButtons = PlaneNormalButtons(parent=self) self.planeThicknessSlider = QLabeledDoubleSlider(Qt.Horizontal, self) self.planeThicknessSlider.setFocusPolicy(Qt.NoFocus) self.planeThicknessSlider.setMinimum(1) self.planeThicknessSlider.setMaximum(50) self.planeThicknessLabel = QLabel(trans._('plane thickness:')) self.grid_layout.addWidget(self.planeNormalLabel, 1, 0) self.grid_layout.addWidget(self.planeNormalButtons, 1, 1) self.grid_layout.addWidget(self.planeThicknessLabel, 2, 0) self.grid_layout.addWidget(self.planeThicknessSlider, 2, 1)
class QtImageControls(QtBaseImageControls): """Qt view and controls for the napari Image layer. Parameters ---------- layer : napari.layers.Image An instance of a napari Image layer. Attributes ---------- attenuationSlider : qtpy.QtWidgets.QSlider Slider controlling attenuation rate for `attenuated_mip` mode. attenuationLabel : qtpy.QtWidgets.QLabel Label for the attenuation slider widget. grid_layout : qtpy.QtWidgets.QGridLayout Layout of Qt widget controls for the layer. interpComboBox : qtpy.QtWidgets.QComboBox Dropdown menu to select the interpolation mode for image display. interpLabel : qtpy.QtWidgets.QLabel Label for the interpolation dropdown menu. isoThresholdSlider : qtpy.QtWidgets.QSlider Slider controlling the isosurface threshold value for rendering. isoThresholdLabel : qtpy.QtWidgets.QLabel Label for the isosurface threshold slider widget. layer : napari.layers.Image An instance of a napari Image layer. renderComboBox : qtpy.QtWidgets.QComboBox Dropdown menu to select the rendering mode for image display. renderLabel : qtpy.QtWidgets.QLabel Label for the rendering mode dropdown menu. """ layer: 'napari.layers.Image' def __init__(self, layer): super().__init__(layer) self.layer.events.interpolation2d.connect( self._on_interpolation_change) self.layer.events.interpolation3d.connect( self._on_interpolation_change) self.layer.events.rendering.connect(self._on_rendering_change) self.layer.events.iso_threshold.connect(self._on_iso_threshold_change) self.layer.events.attenuation.connect(self._on_attenuation_change) self.layer.events._ndisplay.connect(self._on_ndisplay_change) self.layer.events.depiction.connect(self._on_depiction_change) self.layer.plane.events.thickness.connect( self._on_plane_thickness_change) self.interpComboBox = QComboBox(self) self.interpComboBox.activated[str].connect(self.changeInterpolation) self.interpLabel = QLabel(trans._('interpolation:')) renderComboBox = QComboBox(self) rendering_options = [i.value for i in ImageRendering] renderComboBox.addItems(rendering_options) index = renderComboBox.findText(self.layer.rendering, Qt.MatchFixedString) renderComboBox.setCurrentIndex(index) renderComboBox.activated[str].connect(self.changeRendering) self.renderComboBox = renderComboBox self.renderLabel = QLabel(trans._('rendering:')) self.depictionComboBox = QComboBox(self) depiction_options = [d.value for d in VolumeDepiction] self.depictionComboBox.addItems(depiction_options) index = self.depictionComboBox.findText(self.layer.depiction, Qt.MatchFixedString) self.depictionComboBox.setCurrentIndex(index) self.depictionComboBox.activated[str].connect(self.changeDepiction) self.depictionLabel = QLabel(trans._('depiction:')) # plane controls self.planeNormalButtons = PlaneNormalButtons(self) self.planeNormalLabel = QLabel(trans._('plane normal:')) action_manager.bind_button( 'napari:orient_plane_normal_along_z', self.planeNormalButtons.zButton, ) action_manager.bind_button( 'napari:orient_plane_normal_along_y', self.planeNormalButtons.yButton, ) action_manager.bind_button( 'napari:orient_plane_normal_along_x', self.planeNormalButtons.xButton, ) action_manager.bind_button( 'napari:orient_plane_normal_along_view_direction', self.planeNormalButtons.obliqueButton, ) self.planeThicknessSlider = QLabeledDoubleSlider(Qt.Horizontal, self) self.planeThicknessLabel = QLabel(trans._('plane thickness:')) self.planeThicknessSlider.setFocusPolicy(Qt.NoFocus) self.planeThicknessSlider.setMinimum(1) self.planeThicknessSlider.setMaximum(50) self.planeThicknessSlider.setValue(self.layer.plane.thickness) self.planeThicknessSlider.valueChanged.connect( self.changePlaneThickness) sld = QSlider(Qt.Horizontal, parent=self) sld.setFocusPolicy(Qt.NoFocus) sld.setMinimum(0) sld.setMaximum(100) sld.setSingleStep(1) sld.setValue(int(self.layer.iso_threshold * 100)) sld.valueChanged.connect(self.changeIsoThreshold) self.isoThresholdSlider = sld self.isoThresholdLabel = QLabel(trans._('iso threshold:')) sld = QSlider(Qt.Horizontal, parent=self) sld.setFocusPolicy(Qt.NoFocus) sld.setMinimum(0) sld.setMaximum(100) sld.setSingleStep(1) sld.setValue(int(self.layer.attenuation * 200)) sld.valueChanged.connect(self.changeAttenuation) self.attenuationSlider = sld self.attenuationLabel = QLabel(trans._('attenuation:')) self._on_ndisplay_change() colormap_layout = QHBoxLayout() if hasattr(self.layer, 'rgb') and self.layer.rgb: colormap_layout.addWidget(QLabel("RGB")) self.colormapComboBox.setVisible(False) self.colorbarLabel.setVisible(False) else: colormap_layout.addWidget(self.colorbarLabel) colormap_layout.addWidget(self.colormapComboBox) colormap_layout.addStretch(1) self.layout().addRow(trans._('opacity:'), self.opacitySlider) self.layout().addRow(trans._('contrast limits:'), self.contrastLimitsSlider) self.layout().addRow(trans._('auto-contrast:'), self.autoScaleBar) self.layout().addRow(trans._('gamma:'), self.gammaSlider) self.layout().addRow(trans._('colormap:'), colormap_layout) self.layout().addRow(trans._('blending:'), self.blendComboBox) self.layout().addRow(self.interpLabel, self.interpComboBox) self.layout().addRow(self.depictionLabel, self.depictionComboBox) self.layout().addRow(self.renderLabel, self.renderComboBox) self.layout().addRow(self.isoThresholdLabel, self.isoThresholdSlider) self.layout().addRow(self.attenuationLabel, self.attenuationSlider) self.layout().addRow(self.planeNormalLabel, self.planeNormalButtons) self.layout().addRow(self.planeThicknessLabel, self.planeThicknessSlider) def changeInterpolation(self, text): """Change interpolation mode for image display. Parameters ---------- text : str Interpolation mode used by vispy. Must be one of our supported modes: 'bessel', 'bicubic', 'bilinear', 'blackman', 'catrom', 'gaussian', 'hamming', 'hanning', 'hermite', 'kaiser', 'lanczos', 'mitchell', 'nearest', 'spline16', 'spline36' """ if self.layer._ndisplay == 2: self.layer.interpolation2d = text else: self.layer.interpolation3d = text def changeRendering(self, text): """Change rendering mode for image display. Parameters ---------- text : str Rendering mode used by vispy. Selects a preset rendering mode in vispy that determines how volume is displayed: * translucent: voxel colors are blended along the view ray until the result is opaque. * mip: maximum intensity projection. Cast a ray and display the maximum value that was encountered. * additive: voxel colors are added along the view ray until the result is saturated. * iso: isosurface. Cast a ray until a certain threshold is encountered. At that location, lighning calculations are performed to give the visual appearance of a surface. * attenuated_mip: attenuated maximum intensity projection. Cast a ray and attenuate values based on integral of encountered values, display the maximum value that was encountered after attenuation. This will make nearer objects appear more prominent. """ self.layer.rendering = text self._toggle_rendering_parameter_visbility() def changeDepiction(self, text): self.layer.depiction = text self._toggle_plane_parameter_visibility() def changePlaneThickness(self, value: float): self.layer.plane.thickness = value def changeIsoThreshold(self, value): """Change isosurface threshold on the layer model. Parameters ---------- value : float Threshold for isosurface. """ with self.layer.events.blocker(self._on_iso_threshold_change): self.layer.iso_threshold = value / 100 def _on_iso_threshold_change(self): """Receive layer model isosurface change event and update the slider.""" with self.layer.events.iso_threshold.blocker(): self.isoThresholdSlider.setValue( int(self.layer.iso_threshold * 100)) def changeAttenuation(self, value): """Change attenuation rate for attenuated maximum intensity projection. Parameters ---------- value : Float Attenuation rate for attenuated maximum intensity projection. """ with self.layer.events.blocker(self._on_attenuation_change): self.layer.attenuation = value / 200 def _on_attenuation_change(self): """Receive layer model attenuation change event and update the slider.""" with self.layer.events.attenuation.blocker(): self.attenuationSlider.setValue(int(self.layer.attenuation * 200)) def _on_interpolation_change(self, event): """Receive layer interpolation change event and update dropdown menu. Parameters ---------- event : napari.utils.event.Event The napari event that triggered this method. """ interp_string = event.value.value with self.layer.events.interpolation.blocker( ), self.layer.events.interpolation2d.blocker( ), self.layer.events.interpolation3d.blocker(): if self.interpComboBox.findText(interp_string) == -1: self.interpComboBox.addItem(interp_string) self.interpComboBox.setCurrentText(interp_string) def _on_rendering_change(self): """Receive layer model rendering change event and update dropdown menu.""" with self.layer.events.rendering.blocker(): index = self.renderComboBox.findText(self.layer.rendering, Qt.MatchFixedString) self.renderComboBox.setCurrentIndex(index) self._toggle_rendering_parameter_visbility() def _on_depiction_change(self): """Receive layer model depiction change event and update combobox.""" with self.layer.events.depiction.blocker(): index = self.depictionComboBox.findText(self.layer.depiction, Qt.MatchFixedString) self.depictionComboBox.setCurrentIndex(index) self._toggle_plane_parameter_visibility() def _on_plane_thickness_change(self): with self.layer.plane.events.blocker(): self.planeThicknessSlider.setValue(self.layer.plane.thickness) def _toggle_rendering_parameter_visbility(self): """Hide isosurface rendering parameters if they aren't needed.""" rendering = ImageRendering(self.layer.rendering) if rendering == ImageRendering.ISO: self.isoThresholdSlider.show() self.isoThresholdLabel.show() else: self.isoThresholdSlider.hide() self.isoThresholdLabel.hide() if rendering == ImageRendering.ATTENUATED_MIP: self.attenuationSlider.show() self.attenuationLabel.show() else: self.attenuationSlider.hide() self.attenuationLabel.hide() def _toggle_plane_parameter_visibility(self): """Hide plane rendering controls if they aren't needed.""" depiction = VolumeDepiction(self.layer.depiction) if depiction == VolumeDepiction.VOLUME or self.layer._ndisplay == 2: self.planeNormalButtons.hide() self.planeNormalLabel.hide() self.planeThicknessSlider.hide() self.planeThicknessLabel.hide() if depiction == VolumeDepiction.PLANE and self.layer._ndisplay == 3: self.planeNormalButtons.show() self.planeNormalLabel.show() self.planeThicknessSlider.show() self.planeThicknessLabel.show() def _update_interpolation_combo(self): self.interpComboBox.clear() interp_names = (Interpolation3D.keys() if self.layer._ndisplay == 3 else [i.value for i in Interpolation.view_subset()]) self.interpComboBox.addItems(interp_names) interp = (self.layer.interpolation2d if self.layer._ndisplay == 2 else self.layer.interpolation3d) index = self.interpComboBox.findText(interp, Qt.MatchFixedString) self.interpComboBox.setCurrentIndex(index) def _on_ndisplay_change(self): """Toggle between 2D and 3D visualization modes.""" self._update_interpolation_combo() self._toggle_plane_parameter_visibility() if self.layer._ndisplay == 2: self.isoThresholdSlider.hide() self.isoThresholdLabel.hide() self.attenuationSlider.hide() self.attenuationLabel.hide() self.renderComboBox.hide() self.renderLabel.hide() self.depictionComboBox.hide() self.depictionLabel.hide() else: self.renderComboBox.show() self.renderLabel.show() self._toggle_rendering_parameter_visbility() self.depictionComboBox.show() self.depictionLabel.show()