Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
0a5483c
WIP: code for sub-module and main window UI
ElpadoCan May 28, 2026
3bd23ba
WIP: base UI layout, working on lut items
ElpadoCan May 30, 2026
0fe4e67
WIP: set channels data
ElpadoCan May 31, 2026
57e6e40
WIP: store channels dataclass
ElpadoCan Jun 5, 2026
b9e98b3
WIP: correct multi-channel blending options
ElpadoCan Jun 7, 2026
bd63a26
WIP: add opacity sliders
ElpadoCan Jun 13, 2026
95a4f8b
WIP: add toolbutton, still missing the slot
ElpadoCan Jun 14, 2026
4b566f6
WIP: add channels toolbar button and single channel mode
ElpadoCan Jun 14, 2026
7163cc3
fix: import volume renderer only if GUI libraries installed
ElpadoCan Jun 15, 2026
492b2f9
Merge branch 'renderer_3d_v2' of https://github.com/SchmollerLab/Cell…
ElpadoCan Jun 15, 2026
5b167b1
WIP: add labels ui items
ElpadoCan Jun 30, 2026
4c322b3
WIP: adds labels volume renderer
ElpadoCan Jun 30, 2026
50790cc
WIP: overloaded set_labels
ElpadoCan Jun 30, 2026
b6513cc
WIP: labels node cmap on gradient change
ElpadoCan Jun 30, 2026
34134fb
WIP: selectable object labels
ElpadoCan Jul 1, 2026
bc4f915
feat: 3d viewer API
ElpadoCan Jul 1, 2026
99dfd08
feat: add 3d viewer to gui
ElpadoCan Jul 1, 2026
3bd2acb
fix: set viewer camera range
ElpadoCan Jul 1, 2026
0ca2386
improvement: set and reset view as distinct methods
ElpadoCan Jul 1, 2026
78444af
WIP: add points layer markers
ElpadoCan Jul 2, 2026
b488152
WIP: adding ui items for points layer
ElpadoCan Jul 4, 2026
c1902a3
WIP: adapting edit points layer dialog to vispy markers
ElpadoCan Jul 5, 2026
3b68bff
WIP: adapting edit points layer dialog to vispy markers
ElpadoCan Jul 6, 2026
e0470c0
feat: points layer with editing and removing options
ElpadoCan Jul 6, 2026
bc863c5
feat: hide unselected or focus on selected diplay option
ElpadoCan Jul 6, 2026
0414de6
WIP: add points layer to volume renderer from gui
ElpadoCan Jul 6, 2026
b631b2c
feat: show all radio button to show raw volume
ElpadoCan Jul 7, 2026
d30a553
chore: add multi channel demo with points
ElpadoCan Jul 7, 2026
5296ee3
Merge branch 'main' into renderer_3d_v2
ElpadoCan Jul 7, 2026
32be2c2
fix: install PyOpenGL when running 3d viewer first time
ElpadoCan Jul 7, 2026
147440f
WIP: fixing demo only labels
ElpadoCan Jul 7, 2026
b012b75
fix: check import OpenGL correctly
ElpadoCan Jul 7, 2026
4e76623
fix: reset lab gradient item
ElpadoCan Jul 7, 2026
75a1356
fix: lint error
ElpadoCan Jul 7, 2026
e337e18
fix: view only segm labels in 3d viewer
ElpadoCan Jul 7, 2026
47d57f3
Merge branch 'main' into renderer_3d_v2
ElpadoCan Jul 7, 2026
40eba5a
fix: address copilot comments
ElpadoCan Jul 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ instructions/Screenshots
UserManual/*
!UserManual/Cell-ACDC_User_Manual.pdf

cellacdc/volume_renderer/_demos/data/*
!cellacdc/volume_renderer/_demos/data/.gitkeep

# Ignore models folder but keep the folder with placeholder.txt dummy file
models/*
!models/placeholder.txt
Expand Down
8 changes: 7 additions & 1 deletion cellacdc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -818,4 +818,10 @@ def inner_function(self, *args, **kwargs):
'.tif',
'.npz',
'_symlink.ini'
)
)

if GUI_INSTALLED:
try:
from cellacdc.volume_renderer.canvas import VolumeRendererWindow
except ModuleNotFoundError:
pass
171 changes: 140 additions & 31 deletions cellacdc/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -638,9 +638,21 @@ def show(self, block=False):
self.loop.exec_()

class _PointsLayerAppearanceGroupbox(QGroupBox):
def __init__(self, *args):
sigValueChanged = Signal(object)

def __init__(
self,
*args,
backend: Literal['pyqtgraph', 'vispy']='pyqtgraph',
is_3d=False,
add_opacity_slider=False
):
super().__init__(*args)

self._backend = backend
self._add_opacity_slider = add_opacity_slider
self._is_3d = is_3d

self.setTitle('Points appearance')

layout = widgets.FormLayout()
Expand All @@ -651,12 +663,18 @@ def __init__(self, *args):
<b>Symbol</b> used to draw the points.
""")
symbolInfoTxt = (f'{html_utils.paragraph(symbolInfoTxt)}')
if backend == 'pyqtgraph':
symbolCombobox = widgets.pgScatterSymbolsCombobox()
elif backend == 'vispy':
symbolCombobox = widgets.VisPyMarkersSymbolsCombobox()

self.symbolWidget = widgets.formWidget(
widgets.pgScatterSymbolsCombobox(), addInfoButton=True,
symbolCombobox, addInfoButton=True,
labelTextLeft='Symbol: ', parent=self, infoTxt=symbolInfoTxt,
stretchWidget=False
)
layout.addFormWidget(self.symbolWidget, row=row)
symbolCombobox.currentTextChanged.connect(self.emitSigValueChanged)
'----------------------------------------------------------------------'

'----------------------------------------------------------------------'
Expand All @@ -669,6 +687,7 @@ def __init__(self, *args):
layout.addFormWidget(self.colorWidget, align=Qt.AlignLeft, row=row)
self.colorButton.clicked.disconnect()
self.colorButton.clicked.connect(self.selectColor)
self.colorButton.sigColorChanging.connect(self.emitSigValueChanged)
'----------------------------------------------------------------------'

'----------------------------------------------------------------------'
Expand All @@ -680,24 +699,15 @@ def __init__(self, *args):
labelTextLeft='Size: ', parent=self
)
layout.addFormWidget(self.sizeWidget, row=row)
self.sizeSpinBox.valueChanged.connect(self.emitSigValueChanged)
'----------------------------------------------------------------------'

'----------------------------------------------------------------------'
row += 1
zHeightTooltip = (
'If "Z-depth" is greater than 1, the points will be annotated '
'in all the z-slices in the range `z - (Z-depth/2) < z < z + (Z-depth/2)`\n'
'where `z` is the center z-slice of the added point.'
)
self.zHeightSpinBox = widgets.OddSpinBox()
self.zHeightSpinBox.setValue(1)
self.zHeightSpinBox.setMinimum(1)
self.zHeightWidget = widgets.formWidget(
self.zHeightSpinBox, stretchWidget=True,
labelTextLeft='Z-depth: ', parent=self,
toolTip=zHeightTooltip
)
layout.addFormWidget(self.zHeightWidget, row=row)
self.zHeightSpinBox = widgets.DummyWidget()
if not is_3d:
row += 1
self.addZheightSpinbox(row)
layout.addFormWidget(self.zHeightWidget, row=row)
'----------------------------------------------------------------------'

'----------------------------------------------------------------------'
Expand All @@ -712,15 +722,66 @@ def __init__(self, *args):
)
layout.addFormWidget(self.shortcutWidget, row=row)
'----------------------------------------------------------------------'

'----------------------------------------------------------------------'
self.opacitySlider = widgets.DummyWidget()
if add_opacity_slider:
row += 1
self.opacitySlider = widgets.sliderWithSpinBox(
title_loc='in_line',
isFloat=True,
parent=self,
normalize_factor=20
)
self.opacitySlider.setValue(0.3)
self.opacitySlider.setRange(0.0, 1.0)
self.opacitySlider.setSingleStep(0.05)
self.opacitySlider.setDecimals(2)
self.opacityWidget = widgets.formWidget(
self.opacitySlider, stretchWidget=True,
labelTextLeft='Opacity: ', parent=self
)
layout.addFormWidget(self.opacityWidget, row=row)
self.opacitySlider.valueChanged.connect(self.emitSigValueChanged)
'----------------------------------------------------------------------'

self.setLayout(layout)

def restoreState(self, state):
self.shortcutWidget.widget.setText(state['shortcut'])
self.colorButton.setColor(state['color'])
self.symbolWidget.widget.setCurrentText(state['symbol'])
self.sizeSpinBox.setValue(state['pointSize'])
self.zHeightSpinBox.setValue(state['zHeight'])
def emitSigValueChanged(self, *args):
self.sigValueChanged.emit(self.state())

def addZheightSpinbox(self, row: int):
zHeightTooltip = (
'If "Z-depth" is greater than 1, the points will be annotated '
'in all the z-slices in the range '
'`z - (Z-depth/2) < z < z + (Z-depth/2)`\n'
'where `z` is the center z-slice of the added point.'
)
self.zHeightSpinBox = widgets.OddSpinBox()
self.zHeightSpinBox.setValue(1)
self.zHeightSpinBox.setMinimum(1)
self.zHeightWidget = widgets.formWidget(
self.zHeightSpinBox, stretchWidget=True,
labelTextLeft='Z-depth: ', parent=self,
toolTip=zHeightTooltip
)
self.zHeightSpinBox.valueChanged.connect(self.emitSigValueChanged)

def restoreState(self, state: dict):
key_widget_setter_mapper = {
'shortcut': self.shortcutWidget.widget.setText,
'color': self.colorButton.setColor,
'symbol': self.symbolWidget.widget.setCurrentText,
'pointSize': self.sizeSpinBox.setValue,
'zHeight': self.zHeightSpinBox.setValue,
'opacity': self.opacitySlider.setValue
}
for key, setter in key_widget_setter_mapper.items():
value = state.get(key)
if value is None:
continue

setter(value)

def selectColor(self):
color = self.colorButton.color()
Expand All @@ -741,9 +802,14 @@ def state(self):
'symbol': self.symbolWidget.widget.currentText(),
'color': (r,g,b),
'pointSize': self.sizeSpinBox.value(),
'zHeight': self.zHeightSpinBox.value(),
'shortcut': self.shortcutWidget.widget.text()
}
if not self._is_3d:
_state['zHeight'] = self.zHeightSpinBox.value()

if self._add_opacity_slider:
_state['opacity'] = self.opacitySlider.value()

return _state

class AddPointsLayerDialog(QBaseDialog):
Expand Down Expand Up @@ -1416,19 +1482,38 @@ def showEvent(self, event) -> None:

class EditPointsLayerAppearanceDialog(QBaseDialog):
sigClosed = Signal()
sigValueChanged = Signal(object)

def __init__(self, parent=None):
def __init__(
self,
backend: Literal['pyqtgraph', 'vispy']='pyqtgraph',
is_3d=False,
add_opacity_slider=False,
hide_on_close=False,
title='Points Layer Properties',
parent=None
):
self.cancel = True
super().__init__(parent)

self._parent = parent
self._backend = backend
self._is_3d = is_3d
self._add_opacity_slider = add_opacity_slider
self._hide_on_close = hide_on_close

self.setWindowTitle('Custom annotation')
self.setWindowTitle(title)
self.setWindowFlags(Qt.Window | Qt.WindowStaysOnTopHint)

mainLayout = QVBoxLayout()

self.appearanceGroupbox = _PointsLayerAppearanceGroupbox()
self.appearanceGroupbox = _PointsLayerAppearanceGroupbox(
backend=backend,
is_3d=is_3d,
add_opacity_slider=add_opacity_slider
)

self.appearanceGroupbox.sigValueChanged.connect(self.emitValueChanged)

buttonsLayout = widgets.CancelOkButtonsLayout()

Expand All @@ -1443,28 +1528,52 @@ def __init__(self, parent=None):

self.setFont(font)

def emitValueChanged(self, *args):
self.sigValueChanged.emit(self.appearanceGroupbox.state())

def restoreState(self, state):
self.appearanceGroupbox.restoreState(state)

def closeEvent(self, event):
super().closeEvent(event)
if self._hide_on_close:
event.ignore()
self.hide()
else:
super().closeEvent(event)
self.sigClosed.emit()

def force_close(self):
self._hide_on_close = False
self.close()

def state(self):
_state = self.appearanceGroupbox.state()
return _state

def ok_cb(self):
self.cancel = False
symbol = self.appearanceGroupbox.symbolWidget.widget.currentText()
self.symbol = re.findall(r"\'(.+)\'", symbol)[0]
self.symbol = symbol
if self._backend == 'pyqtgraph':
self.symbol = re.findall(r"\'(.+)\'", symbol)[0]
self.color = self.appearanceGroupbox.colorButton.color()
self.pointSize = self.appearanceGroupbox.sizeSpinBox.value()
self.zHeight = self.appearanceGroupbox.zHeightSpinBox.value()

self.zHeight = None
if not self._is_3d:
self.zHeight = self.appearanceGroupbox.zHeightSpinBox.value()

self.opacity = 1.0
if self._add_opacity_slider:
self.opacity = self.appearanceGroupbox.opacitySlider.value()

shortcutWidget = self.appearanceGroupbox.shortcutWidget
self.shortcut = shortcutWidget.widget.text()
self.keySequence = shortcutWidget.widget.keySequence
self.close()
if self._hide_on_close:
self.hide()
else:
self.close()

class filenameDialog(QDialog):
def __init__(
Expand Down
Loading
Loading